Merge branch 'dev' into searxng

This commit is contained in:
Que Nguyen 2024-06-15 23:44:31 +07:00 committed by GitHub
commit a02ba52de8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 2132 additions and 1090 deletions

View File

@ -37,7 +37,7 @@ Open WebUI is an [extensible](https://github.com/open-webui/pipelines), feature-
- 📚 **Local RAG Integration**: Dive into the future of chat interactions with groundbreaking Retrieval Augmented Generation (RAG) support. This feature seamlessly integrates document interactions into your chat experience. You can load documents directly into the chat or add files to your document library, effortlessly accessing them using the `#` command before a query.
- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, and `Serply` and inject the results directly into your chat experience.
- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, `Serply`, `DuckDuckGo` and `TavilySearch` and inject the results directly into your chat experience.
- 🌐 **Web Browsing Capability**: Seamlessly integrate websites into your chat experience using the `#` command followed by a URL. This feature allows you to incorporate web content directly into your conversations, enhancing the richness and depth of your interactions.

View File

@ -18,6 +18,10 @@ If you're experiencing connection issues, its often due to the WebUI docker c
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
### Error on Slow Reponses for Ollama
Open WebUI has a default timeout of 5 minutes for Ollama to finish generating the response. If needed, this can be adjusted via the environment variable AIOHTTP_CLIENT_TIMEOUT, which sets the timeout in seconds.
### General Connection Errors
**Ensure Ollama Version is Up-to-Date**: Always start by checking that you have the latest version of Ollama. Visit [Ollama's official site](https://ollama.com/) for the latest updates.

View File

@ -46,6 +46,7 @@ from config import (
SRC_LOG_LEVELS,
OLLAMA_BASE_URLS,
ENABLE_OLLAMA_API,
AIOHTTP_CLIENT_TIMEOUT,
ENABLE_MODEL_FILTER,
MODEL_FILTER_LIST,
UPLOAD_DIR,
@ -154,7 +155,9 @@ async def cleanup_response(
async def post_streaming_url(url: str, payload: str):
r = None
try:
session = aiohttp.ClientSession(trust_env=True)
session = aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
)
r = await session.post(url, data=payload)
r.raise_for_status()
@ -751,6 +754,14 @@ async def generate_chat_completion(
if model_info.params.get("num_ctx", None):
payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None)
if model_info.params.get("num_batch", None):
payload["options"]["num_batch"] = model_info.params.get(
"num_batch", None
)
if model_info.params.get("num_keep", None):
payload["options"]["num_keep"] = model_info.params.get("num_keep", None)
if model_info.params.get("repeat_last_n", None):
payload["options"]["repeat_last_n"] = model_info.params.get(
"repeat_last_n", None

View File

@ -73,6 +73,7 @@ from apps.rag.search.serper import search_serper
from apps.rag.search.serpstack import search_serpstack
from apps.rag.search.serply import search_serply
from apps.rag.search.duckduckgo import search_duckduckgo
from apps.rag.search.tavily import search_tavily
from utils.misc import (
calculate_sha256,
@ -120,6 +121,7 @@ from config import (
SERPSTACK_HTTPS,
SERPER_API_KEY,
SERPLY_API_KEY,
TAVILY_API_KEY,
RAG_WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_EMBEDDING_OPENAI_BATCH_SIZE,
@ -174,6 +176,7 @@ app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY
app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS
app.state.config.SERPER_API_KEY = SERPER_API_KEY
app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
@ -402,6 +405,7 @@ async def get_rag_config(user=Depends(get_admin_user)):
"serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"tavily_api_key": app.state.config.TAVILY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
@ -430,6 +434,7 @@ class WebSearchConfig(BaseModel):
serpstack_https: Optional[bool] = None
serper_api_key: Optional[str] = None
serply_api_key: Optional[str] = None
tavily_api_key: Optional[str] = None
result_count: Optional[int] = None
concurrent_requests: Optional[int] = None
@ -481,6 +486,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
app.state.config.SERPSTACK_HTTPS = form_data.web.search.serpstack_https
app.state.config.SERPER_API_KEY = form_data.web.search.serper_api_key
app.state.config.SERPLY_API_KEY = form_data.web.search.serply_api_key
app.state.config.TAVILY_API_KEY = form_data.web.search.tavily_api_key
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
form_data.web.search.concurrent_requests
@ -510,6 +516,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
"serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"tavily_api_key": app.state.config.TAVILY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
@ -758,7 +765,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
- SERPSTACK_API_KEY
- SERPER_API_KEY
- SERPLY_API_KEY
- TAVILY_API_KEY
Args:
query (str): The query to search for
"""
@ -833,6 +840,15 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
raise Exception("No SERPLY_API_KEY found in environment variables")
elif engine == "duckduckgo":
return search_duckduckgo(query, app.state.config.RAG_WEB_SEARCH_RESULT_COUNT, app.state.config.RAG_WEB_SEARCH_WHITE_LIST_DOMAINS)
elif engine == "tavily":
if app.state.config.TAVILY_API_KEY:
return search_tavily(
app.state.config.TAVILY_API_KEY,
query,
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
)
else:
raise Exception("No TAVILY_API_KEY found in environment variables")
else:
raise Exception("No search engine API key found in environment variables")

View File

@ -0,0 +1,39 @@
import logging
import requests
from apps.rag.search.main import SearchResult
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
"""Search using Tavily's Search API and return the results as a list of SearchResult objects.
Args:
api_key (str): A Tavily Search API key
query (str): The query to search for
Returns:
List[SearchResult]: A list of search results
"""
url = "https://api.tavily.com/search"
data = {"query": query, "api_key": api_key}
response = requests.post(url, json=data)
response.raise_for_status()
json_response = response.json()
raw_search_results = json_response.get("results", [])
return [
SearchResult(
link=result["url"],
title=result.get("title", ""),
snippet=result.get("content"),
)
for result in raw_search_results[:count]
]

View File

@ -65,6 +65,20 @@ class MemoriesTable:
else:
return None
def update_memory_by_id(
self,
id: str,
content: str,
) -> Optional[MemoryModel]:
try:
memory = Memory.get(Memory.id == id)
memory.content = content
memory.updated_at = int(time.time())
memory.save()
return MemoryModel(**model_to_dict(memory))
except:
return None
def get_memories(self) -> List[MemoryModel]:
try:
memories = Memory.select()

View File

@ -44,6 +44,10 @@ class AddMemoryForm(BaseModel):
content: str
class MemoryUpdateModel(BaseModel):
content: Optional[str] = None
@router.post("/add", response_model=Optional[MemoryModel])
async def add_memory(
request: Request, form_data: AddMemoryForm, user=Depends(get_verified_user)
@ -62,6 +66,34 @@ async def add_memory(
return memory
@router.post("/{memory_id}/update", response_model=Optional[MemoryModel])
async def update_memory_by_id(
memory_id: str,
request: Request,
form_data: MemoryUpdateModel,
user=Depends(get_verified_user),
):
memory = Memories.update_memory_by_id(memory_id, form_data.content)
if memory is None:
raise HTTPException(status_code=404, detail="Memory not found")
if form_data.content is not None:
memory_embedding = request.app.state.EMBEDDING_FUNCTION(form_data.content)
collection = CHROMA_CLIENT.get_or_create_collection(
name=f"user-memory-{user.id}"
)
collection.upsert(
documents=[form_data.content],
ids=[memory.id],
embeddings=[memory_embedding],
metadatas=[
{"created_at": memory.created_at, "updated_at": memory.updated_at}
],
)
return memory
############################
# QueryMemory
############################

View File

@ -425,6 +425,7 @@ OLLAMA_API_BASE_URL = os.environ.get(
)
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
AIOHTTP_CLIENT_TIMEOUT = int(os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "300"))
K8S_FLAG = os.environ.get("K8S_FLAG", "")
USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
@ -951,6 +952,11 @@ SERPLY_API_KEY = PersistentConfig(
os.getenv("SERPLY_API_KEY", ""),
)
TAVILY_API_KEY = PersistentConfig(
"TAVILY_API_KEY",
"rag.web.search.tavily_api_key",
os.getenv("TAVILY_API_KEY", ""),
)
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
"RAG_WEB_SEARCH_RESULT_COUNT",

View File

@ -494,6 +494,9 @@ def filter_pipeline(payload, user):
if "title" in payload:
del payload["title"]
if "task" in payload:
del payload["task"]
return payload
@ -835,6 +838,71 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user)
"messages": [{"role": "user", "content": content}],
"stream": False,
"max_tokens": 30,
"task": True,
}
print(payload)
try:
payload = filter_pipeline(payload, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if model["owned_by"] == "ollama":
return await generate_ollama_chat_completion(
OpenAIChatCompletionForm(**payload), user=user
)
else:
return await generate_openai_chat_completion(payload, user=user)
@app.post("/api/task/emoji/completions")
async def generate_emoji(form_data: dict, user=Depends(get_verified_user)):
print("generate_emoji")
model_id = form_data["model"]
if model_id not in app.state.MODELS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Model not found",
)
# Check if the user has a custom task model
# If the user has a custom task model, use that model
if app.state.MODELS[model_id]["owned_by"] == "ollama":
if app.state.config.TASK_MODEL:
task_model_id = app.state.config.TASK_MODEL
if task_model_id in app.state.MODELS:
model_id = task_model_id
else:
if app.state.config.TASK_MODEL_EXTERNAL:
task_model_id = app.state.config.TASK_MODEL_EXTERNAL
if task_model_id in app.state.MODELS:
model_id = task_model_id
print(model_id)
model = app.state.MODELS[model_id]
template = '''
Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
Message: """{{prompt}}"""
'''
content = title_generation_template(
template, form_data["prompt"], user.model_dump()
)
payload = {
"model": model_id,
"messages": [{"role": "user", "content": content}],
"stream": False,
"max_tokens": 4,
"chat_id": form_data.get("chat_id", None),
"task": True,
}
print(payload)

View File

@ -28,6 +28,10 @@ math {
@apply rounded-lg;
}
.markdown a {
@apply underline;
}
ol > li {
counter-increment: list-number;
display: block;

View File

@ -205,6 +205,54 @@ export const generateTitle = async (
return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat';
};
export const generateEmoji = async (
token: string = '',
model: string,
prompt: string,
chat_id?: string
) => {
let error = null;
const res = await fetch(`${WEBUI_BASE_URL}/api/task/emoji/completions`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
model: model,
prompt: prompt,
...(chat_id && { chat_id: chat_id })
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
}
return null;
});
if (error) {
throw error;
}
const response = res?.choices[0]?.message?.content.replace(/["']/g, '') ?? null;
if (response) {
if (/\p{Extended_Pictographic}/u.test(response)) {
return response.match(/\p{Extended_Pictographic}/gu)[0];
}
}
return null;
};
export const generateSearchQuery = async (
token: string = '',
model: string,

View File

@ -3,7 +3,7 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
export const getMemories = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories`, {
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/`, {
method: 'GET',
headers: {
Accept: 'application/json',
@ -59,6 +59,37 @@ export const addNewMemory = async (token: string, content: string) => {
return res;
};
export const updateMemoryById = async (token: string, id: string, content: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/${id}/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
content: content
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const queryMemory = async (token: string, content: string) => {
let error = null;

View File

@ -13,6 +13,8 @@
getRAGConfig,
updateRAGConfig
} from '$lib/apis/rag';
import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import { documents, models } from '$lib/stores';
import { onMount, getContext } from 'svelte';
@ -213,6 +215,34 @@
});
</script>
<ResetUploadDirConfirmDialog
bind:show={showResetUploadDirConfirm}
on:confirm={() => {
const res = resetUploadDir(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Success'));
}
}}
/>
<ResetVectorDBConfirmDialog
bind:show={showResetConfirm}
on:confirm={() => {
const res = resetVectorDB(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Success'));
}
}}
/>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
@ -640,199 +670,56 @@
<hr class=" dark:border-gray-850" />
<div>
{#if showResetUploadDirConfirm}
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
<div class="flex items-center space-x-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 14.25a.75.75 0 0 0 0 1.5H15a.75.75 0 0 0 0-1.5H9.75Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
<span>{$i18n.t('Are you sure?')}</span>
</div>
<div class="flex space-x-1.5 items-center">
<button
class="hover:text-white transition"
on:click={() => {
const res = resetUploadDir(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Success'));
}
showResetUploadDirConfirm = false;
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class="hover:text-white transition"
type="button"
on:click={() => {
showResetUploadDirConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<button
class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showResetUploadDirConfirm = true;
}}
type="button"
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 14.25a.75.75 0 0 0 0 1.5H15a.75.75 0 0 0 0-1.5H9.75Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
</div>
{:else}
<button
class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showResetUploadDirConfirm = true;
}}
type="button"
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 14.25a.75.75 0 0 0 0 1.5H15a.75.75 0 0 0 0-1.5H9.75Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Upload Directory')}</div>
</button>
{/if}
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Upload Directory')}</div>
</button>
{#if showResetConfirm}
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
<div class="flex items-center space-x-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
clip-rule="evenodd"
/>
</svg>
<span>{$i18n.t('Are you sure?')}</span>
</div>
<div class="flex space-x-1.5 items-center">
<button
class="hover:text-white transition"
on:click={() => {
const res = resetVectorDB(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Success'));
}
showResetConfirm = false;
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class="hover:text-white transition"
on:click={() => {
showResetConfirm = false;
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<button
class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showResetConfirm = true;
}}
type="button"
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
{:else}
<button
class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showResetConfirm = true;
}}
type="button"
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
</button>
{/if}
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
</button>
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">

View File

@ -1,5 +1,10 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount, getContext } from 'svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
import { splitStream } from '$lib/utils';
import {
createModel,
@ -11,15 +16,11 @@
uploadModel,
getOllamaConfig
} from '$lib/apis/ollama';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { onMount, getContext } from 'svelte';
import { getModels as _getModels } from '$lib/apis';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import { getModels as _getModels } from '$lib/apis';
import ModelDeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
const i18n = getContext('i18n');
@ -29,6 +30,8 @@
let modelUploadInputElement: HTMLInputElement;
let showModelDeleteConfirm = false;
// Models
let ollamaEnabled = null;
@ -549,6 +552,13 @@
});
</script>
<ModelDeleteConfirmDialog
bind:show={showModelDeleteConfirm}
on:confirm={() => {
deleteModelHandler();
}}
/>
<div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
{#if ollamaEnabled}
@ -763,7 +773,7 @@
<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={() => {
deleteModelHandler();
showModelDeleteConfirm = true;
}}
>
<svg

View File

@ -18,7 +18,8 @@
'serpstack',
'serper',
'serply',
'duckduckgo'
'duckduckgo',
'tavily'
];
let youtubeLanguage = 'en';
@ -214,6 +215,24 @@
</div>
</div>
</div>
{:else if webConfig.search.engine === 'tavily'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.search.tavily_api_key}
autocomplete="off"
/>
</div>
</div>
</div>
{/if}
</div>
{/if}

View File

@ -30,6 +30,7 @@
import {
convertMessagesToHistory,
copyToClipboard,
extractSentencesForAudio,
promptTemplate,
splitStream
} from '$lib/utils';
@ -64,6 +65,8 @@
export let chatIdProp = '';
let loaded = false;
const eventTarget = new EventTarget();
let stopResponseFlag = false;
let autoScroll = true;
let processing = '';
@ -108,7 +111,8 @@
$: if (chatIdProp) {
(async () => {
if (await loadChat()) {
console.log(chatIdProp);
if (chatIdProp && (await loadChat())) {
await tick();
loaded = true;
@ -123,7 +127,11 @@
onMount(async () => {
if (!$chatId) {
await initNewChat();
chatId.subscribe(async (value) => {
if (!value) {
await initNewChat();
}
});
} else {
if (!($settings.saveChatHistory ?? true)) {
await goto('/');
@ -300,7 +308,7 @@
// Chat functions
//////////////////////////
const submitPrompt = async (userPrompt, _user = null) => {
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
let _responses = [];
console.log('submitPrompt', $chatId);
@ -344,7 +352,6 @@
parentId: messages.length !== 0 ? messages.at(-1).id : null,
childrenIds: [],
role: 'user',
user: _user ?? undefined,
content: userPrompt,
files: _files.length > 0 ? _files : undefined,
timestamp: Math.floor(Date.now() / 1000), // Unix epoch
@ -362,15 +369,13 @@
// Wait until history/message have been updated
await tick();
// Send prompt
_responses = await sendPrompt(userPrompt, userMessageId);
_responses = await sendPrompt(userPrompt, userMessageId, { newChat: true });
}
return _responses;
};
const sendPrompt = async (prompt, parentId, modelId = null, newChat = true) => {
const sendPrompt = async (prompt, parentId, { modelId = null, newChat = false } = {}) => {
let _responses = [];
// If modelId is provided, use it, else use selected model
@ -490,7 +495,6 @@
responseMessage.userContext = userContext;
const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
if (webSearchEnabled) {
await getWebSearchResults(model.id, parentId, responseMessageId);
}
@ -503,8 +507,6 @@
}
_responses.push(_response);
console.log('chatEventEmitter', chatEventEmitter);
if (chatEventEmitter) clearInterval(chatEventEmitter);
} else {
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
@ -513,88 +515,9 @@
);
await chats.set(await getChatList(localStorage.token));
return _responses;
};
const getWebSearchResults = async (model: string, parentId: string, responseId: string) => {
const responseMessage = history.messages[responseId];
responseMessage.statusHistory = [
{
done: false,
action: 'web_search',
description: $i18n.t('Generating search query')
}
];
messages = messages;
const prompt = history.messages[parentId].content;
let searchQuery = await generateSearchQuery(localStorage.token, model, messages, prompt).catch(
(error) => {
console.log(error);
return prompt;
}
);
if (!searchQuery) {
toast.warning($i18n.t('No search query generated'));
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search query generated'
});
messages = messages;
}
responseMessage.statusHistory.push({
done: false,
action: 'web_search',
description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
});
messages = messages;
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
console.log(error);
toast.error(error);
return null;
});
if (results) {
responseMessage.statusHistory.push({
done: true,
action: 'web_search',
description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
query: searchQuery,
urls: results.filenames
});
if (responseMessage?.files ?? undefined === undefined) {
responseMessage.files = [];
}
responseMessage.files.push({
collection_name: results.collection_name,
name: searchQuery,
type: 'web_search_results',
urls: results.filenames
});
messages = messages;
} else {
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search results found'
});
messages = messages;
}
};
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
let _response = null;
@ -676,6 +599,16 @@
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
);
eventTarget.dispatchEvent(
new CustomEvent('chat:start', {
detail: {
id: responseMessageId
}
})
);
await tick();
const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model.id,
messages: messagesBody,
@ -745,6 +678,23 @@
continue;
} else {
responseMessage.content += data.message.content;
const sentences = extractSentencesForAudio(responseMessage.content);
sentences.pop();
// dispatch only last sentence and make sure it hasn't been dispatched before
if (
sentences.length > 0 &&
sentences[sentences.length - 1] !== responseMessage.lastSentence
) {
responseMessage.lastSentence = sentences[sentences.length - 1];
eventTarget.dispatchEvent(
new CustomEvent('chat', {
detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
})
);
}
messages = messages;
}
} else {
@ -771,21 +721,13 @@
messages = messages;
if ($settings.notificationEnabled && !document.hasFocus()) {
const notification = new Notification(
selectedModelfile
? `${
selectedModelfile.title.charAt(0).toUpperCase() +
selectedModelfile.title.slice(1)
}`
: `${model.id}`,
{
body: responseMessage.content,
icon: selectedModelfile?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`
}
);
const notification = new Notification(`${model.id}`, {
body: responseMessage.content,
icon: `${WEBUI_BASE_URL}/static/favicon.png`
});
}
if ($settings.responseAutoCopy) {
if ($settings?.responseAutoCopy ?? false) {
copyToClipboard(responseMessage.content);
}
@ -847,6 +789,23 @@
stopResponseFlag = false;
await tick();
let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
if (lastSentence) {
eventTarget.dispatchEvent(
new CustomEvent('chat', {
detail: { id: responseMessageId, content: lastSentence }
})
);
}
eventTarget.dispatchEvent(
new CustomEvent('chat:finish', {
detail: {
id: responseMessageId,
content: responseMessage.content
}
})
);
if (autoScroll) {
scrollToBottom();
}
@ -887,6 +846,15 @@
scrollToBottom();
eventTarget.dispatchEvent(
new CustomEvent('chat:start', {
detail: {
id: responseMessageId
}
})
);
await tick();
try {
const [res, controller] = await generateOpenAIChatCompletion(
localStorage.token,
@ -1007,6 +975,23 @@
continue;
} else {
responseMessage.content += value;
const sentences = extractSentencesForAudio(responseMessage.content);
sentences.pop();
// dispatch only last sentence and make sure it hasn't been dispatched before
if (
sentences.length > 0 &&
sentences[sentences.length - 1] !== responseMessage.lastSentence
) {
responseMessage.lastSentence = sentences[sentences.length - 1];
eventTarget.dispatchEvent(
new CustomEvent('chat', {
detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
})
);
}
messages = messages;
}
@ -1057,6 +1042,24 @@
stopResponseFlag = false;
await tick();
let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
if (lastSentence) {
eventTarget.dispatchEvent(
new CustomEvent('chat', {
detail: { id: responseMessageId, content: lastSentence }
})
);
}
eventTarget.dispatchEvent(
new CustomEvent('chat:finish', {
detail: {
id: responseMessageId,
content: responseMessage.content
}
})
);
if (autoScroll) {
scrollToBottom();
}
@ -1123,9 +1126,12 @@
let userPrompt = userMessage.content;
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
await sendPrompt(userPrompt, userMessage.id, undefined, false);
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
await sendPrompt(userPrompt, userMessage.id);
} else {
await sendPrompt(userPrompt, userMessage.id, message.model, false);
// If there are multiple models selected, use the model of the response message for regeneration
// e.g. many model chat
await sendPrompt(userPrompt, userMessage.id, { modelId: message.model });
}
}
};
@ -1191,6 +1197,84 @@
}
};
const getWebSearchResults = async (model: string, parentId: string, responseId: string) => {
const responseMessage = history.messages[responseId];
responseMessage.statusHistory = [
{
done: false,
action: 'web_search',
description: $i18n.t('Generating search query')
}
];
messages = messages;
const prompt = history.messages[parentId].content;
let searchQuery = await generateSearchQuery(localStorage.token, model, messages, prompt).catch(
(error) => {
console.log(error);
return prompt;
}
);
if (!searchQuery) {
toast.warning($i18n.t('No search query generated'));
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search query generated'
});
messages = messages;
}
responseMessage.statusHistory.push({
done: false,
action: 'web_search',
description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
});
messages = messages;
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
console.log(error);
toast.error(error);
return null;
});
if (results) {
responseMessage.statusHistory.push({
done: true,
action: 'web_search',
description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
query: searchQuery,
urls: results.filenames
});
if (responseMessage?.files ?? undefined === undefined) {
responseMessage.files = [];
}
responseMessage.files.push({
collection_name: results.collection_name,
name: searchQuery,
type: 'web_search_results',
urls: results.filenames
});
messages = messages;
} else {
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search results found'
});
messages = messages;
}
};
const getTags = async () => {
return await getTagsById(localStorage.token, $chatId).catch(async (error) => {
return [];
@ -1206,7 +1290,18 @@
</title>
</svelte:head>
<CallOverlay {submitPrompt} bind:files />
<audio id="audioElement" src="" style="display: none;" />
{#if $showCallOverlay}
<CallOverlay
{submitPrompt}
{stopResponse}
bind:files
modelId={selectedModelIds?.at(0) ?? null}
chatId={$chatId}
{eventTarget}
/>
{/if}
{#if !chatIdProp || (loaded && chatIdProp)}
<div

View File

@ -348,7 +348,6 @@
<Models
bind:this={modelsElement}
bind:prompt
bind:user
bind:chatInputPlaceholder
{messages}
on:select={(e) => {
@ -467,7 +466,7 @@
document.getElementById('chat-textarea')?.focus();
if ($settings?.speechAutoSend ?? false) {
submitPrompt(prompt, user);
submitPrompt(prompt);
}
}}
/>
@ -476,7 +475,7 @@
class="w-full flex gap-1.5"
on:submit|preventDefault={() => {
// check if selectedModels support image input
submitPrompt(prompt, user);
submitPrompt(prompt);
}}
>
<div
@ -718,7 +717,7 @@
// Submit the prompt when Enter key is pressed
if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
submitPrompt(prompt, user);
submitPrompt(prompt);
}
}
}}

View File

@ -2,342 +2,48 @@
import { config, settings, showCallOverlay } from '$lib/stores';
import { onMount, tick, getContext } from 'svelte';
import { blobToFile, calculateSHA256, extractSentences, findWordIndices } from '$lib/utils';
import {
blobToFile,
calculateSHA256,
extractSentencesForAudio,
findWordIndices
} from '$lib/utils';
import { generateEmoji } from '$lib/apis';
import { synthesizeOpenAISpeech, transcribeAudio } from '$lib/apis/audio';
import { toast } from 'svelte-sonner';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import VideoInputMenu from './CallOverlay/VideoInputMenu.svelte';
import { get } from 'svelte/store';
const i18n = getContext('i18n');
export let eventTarget: EventTarget;
export let submitPrompt: Function;
export let stopResponse: Function;
export let files;
export let chatId;
export let modelId;
let loading = false;
let confirmed = false;
let interrupted = false;
let emoji = null;
let camera = false;
let cameraStream = null;
let assistantSpeaking = false;
let assistantAudio = {};
let assistantAudioIdx = null;
let chatStreaming = false;
let rmsLevel = 0;
let hasStartedSpeaking = false;
let currentUtterance = null;
let mediaRecorder;
let audioChunks = [];
const MIN_DECIBELS = -45;
const VISUALIZER_BUFFER_LENGTH = 300;
// Function to calculate the RMS level from time domain data
const calculateRMS = (data: Uint8Array) => {
let sumSquares = 0;
for (let i = 0; i < data.length; i++) {
const normalizedValue = (data[i] - 128) / 128; // Normalize the data
sumSquares += normalizedValue * normalizedValue;
}
return Math.sqrt(sumSquares / data.length);
};
const normalizeRMS = (rms) => {
rms = rms * 10;
const exp = 1.5; // Adjust exponent value; values greater than 1 expand larger numbers more and compress smaller numbers more
const scaledRMS = Math.pow(rms, exp);
// Scale between 0.01 (1%) and 1.0 (100%)
return Math.min(1.0, Math.max(0.01, scaledRMS));
};
const analyseAudio = (stream) => {
const audioContext = new AudioContext();
const audioStreamSource = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.minDecibels = MIN_DECIBELS;
audioStreamSource.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const domainData = new Uint8Array(bufferLength);
const timeDomainData = new Uint8Array(analyser.fftSize);
let lastSoundTime = Date.now();
hasStartedSpeaking = false;
const detectSound = () => {
const processFrame = () => {
if (!mediaRecorder || !$showCallOverlay) {
if (mediaRecorder) {
mediaRecorder.stop();
}
return;
}
analyser.getByteTimeDomainData(timeDomainData);
analyser.getByteFrequencyData(domainData);
// Calculate RMS level from time domain data
rmsLevel = calculateRMS(timeDomainData);
// Check if initial speech/noise has started
const hasSound = domainData.some((value) => value > 0);
if (hasSound) {
stopAllAudio();
hasStartedSpeaking = true;
lastSoundTime = Date.now();
}
// Start silence detection only after initial speech/noise has been detected
if (hasStartedSpeaking) {
if (Date.now() - lastSoundTime > 2000) {
confirmed = true;
if (mediaRecorder) {
mediaRecorder.stop();
}
}
}
window.requestAnimationFrame(processFrame);
};
window.requestAnimationFrame(processFrame);
};
detectSound();
};
const stopAllAudio = () => {
if (currentUtterance) {
speechSynthesis.cancel();
currentUtterance = null;
}
if (assistantAudio[assistantAudioIdx]) {
assistantAudio[assistantAudioIdx].pause();
assistantAudio[assistantAudioIdx].currentTime = 0;
}
const audioElement = document.getElementById('audioElement');
audioElement.pause();
audioElement.currentTime = 0;
assistantSpeaking = false;
};
const playAudio = (idx) => {
if ($showCallOverlay) {
return new Promise((res) => {
assistantAudioIdx = idx;
const audioElement = document.getElementById('audioElement');
const audio = assistantAudio[idx];
audioElement.src = audio.src; // Assume `assistantAudio` has objects with a `src` property
audioElement.muted = true;
audioElement
.play()
.then(() => {
audioElement.muted = false;
})
.catch((error) => {
toast.error(error);
});
audioElement.onended = async (e) => {
await new Promise((r) => setTimeout(r, 300));
if (Object.keys(assistantAudio).length - 1 === idx) {
assistantSpeaking = false;
}
res(e);
};
});
} else {
return Promise.resolve();
}
};
const getOpenAISpeech = async (text) => {
const res = await synthesizeOpenAISpeech(
localStorage.token,
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice,
text
).catch((error) => {
toast.error(error);
assistantSpeaking = false;
return null;
});
if (res) {
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const audio = new Audio(blobUrl);
assistantAudio = audio;
}
};
const transcribeHandler = async (audioBlob) => {
// Create a blob from the audio chunks
await tick();
const file = blobToFile(audioBlob, 'recording.wav');
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(error);
return null;
});
if (res) {
console.log(res.text);
if (res.text !== '') {
const _responses = await submitPrompt(res.text);
console.log(_responses);
if (_responses.at(0)) {
const content = _responses[0];
if ((content ?? '').trim() !== '') {
assistantSpeakingHandler(content);
}
}
}
}
};
const assistantSpeakingHandler = async (content) => {
assistantSpeaking = true;
if (($config.audio.tts.engine ?? '') == '') {
let voices = [];
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
if (voices.length > 0) {
clearInterval(getVoicesLoop);
const voice =
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
currentUtterance = new SpeechSynthesisUtterance(content);
if (voice) {
currentUtterance.voice = voice;
}
speechSynthesis.speak(currentUtterance);
}
}, 100);
} else if ($config.audio.tts.engine === 'openai') {
console.log('openai');
const sentences = extractSentences(content).reduce((mergedTexts, currentText) => {
const lastIndex = mergedTexts.length - 1;
if (lastIndex >= 0) {
const previousText = mergedTexts[lastIndex];
const wordCount = previousText.split(/\s+/).length;
if (wordCount < 2) {
mergedTexts[lastIndex] = previousText + ' ' + currentText;
} else {
mergedTexts.push(currentText);
}
} else {
mergedTexts.push(currentText);
}
return mergedTexts;
}, []);
console.log(sentences);
let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
for (const [idx, sentence] of sentences.entries()) {
const res = await synthesizeOpenAISpeech(
localStorage.token,
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice,
sentence
).catch((error) => {
toast.error(error);
assistantSpeaking = false;
return null;
});
if (res) {
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const audio = new Audio(blobUrl);
assistantAudio[idx] = audio;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
}
}
};
const stopRecordingCallback = async () => {
if ($showCallOverlay) {
if (confirmed) {
loading = true;
if (cameraStream) {
const imageUrl = takeScreenshot();
files = [
{
type: 'image',
url: imageUrl
}
];
}
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
await transcribeHandler(audioBlob);
confirmed = false;
loading = false;
}
audioChunks = [];
mediaRecorder = false;
startRecording();
} else {
audioChunks = [];
mediaRecorder = false;
}
};
const startRecording = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onstart = () => {
console.log('Recording started');
audioChunks = [];
analyseAudio(stream);
};
mediaRecorder.ondataavailable = (event) => {
if (hasStartedSpeaking) {
audioChunks.push(event.data);
}
};
mediaRecorder.onstop = async () => {
console.log('Recording stopped');
await stopRecordingCallback();
};
mediaRecorder.start();
};
let videoInputDevices = [];
let selectedVideoInputDeviceId = null;
@ -439,23 +145,453 @@
camera = false;
};
$: if ($showCallOverlay) {
const MIN_DECIBELS = -55;
const VISUALIZER_BUFFER_LENGTH = 300;
const transcribeHandler = async (audioBlob) => {
// Create a blob from the audio chunks
await tick();
const file = blobToFile(audioBlob, 'recording.wav');
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(error);
return null;
});
if (res) {
console.log(res.text);
if (res.text !== '') {
const _responses = await submitPrompt(res.text, { _raw: true });
console.log(_responses);
}
}
};
const stopRecordingCallback = async (_continue = true) => {
if ($showCallOverlay) {
console.log('%c%s', 'color: red; font-size: 20px;', '🚨 stopRecordingCallback 🚨');
// deep copy the audioChunks array
const _audioChunks = audioChunks.slice(0);
audioChunks = [];
mediaRecorder = false;
if (_continue) {
startRecording();
}
if (confirmed) {
loading = true;
emoji = null;
if (cameraStream) {
const imageUrl = takeScreenshot();
files = [
{
type: 'image',
url: imageUrl
}
];
}
const audioBlob = new Blob(_audioChunks, { type: 'audio/wav' });
await transcribeHandler(audioBlob);
confirmed = false;
loading = false;
}
} else {
audioChunks = [];
mediaRecorder = false;
}
};
const startRecording = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onstart = () => {
console.log('Recording started');
audioChunks = [];
analyseAudio(stream);
};
mediaRecorder.ondataavailable = (event) => {
if (hasStartedSpeaking) {
audioChunks.push(event.data);
}
};
mediaRecorder.onstop = (e) => {
console.log('Recording stopped', e);
stopRecordingCallback();
};
mediaRecorder.start();
};
// Function to calculate the RMS level from time domain data
const calculateRMS = (data: Uint8Array) => {
let sumSquares = 0;
for (let i = 0; i < data.length; i++) {
const normalizedValue = (data[i] - 128) / 128; // Normalize the data
sumSquares += normalizedValue * normalizedValue;
}
return Math.sqrt(sumSquares / data.length);
};
const analyseAudio = (stream) => {
const audioContext = new AudioContext();
const audioStreamSource = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.minDecibels = MIN_DECIBELS;
audioStreamSource.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const domainData = new Uint8Array(bufferLength);
const timeDomainData = new Uint8Array(analyser.fftSize);
let lastSoundTime = Date.now();
hasStartedSpeaking = false;
console.log('🔊 Sound detection started', lastSoundTime, hasStartedSpeaking);
const detectSound = () => {
const processFrame = () => {
if (!mediaRecorder || !$showCallOverlay) {
return;
}
analyser.getByteTimeDomainData(timeDomainData);
analyser.getByteFrequencyData(domainData);
// Calculate RMS level from time domain data
rmsLevel = calculateRMS(timeDomainData);
// Check if initial speech/noise has started
const hasSound = domainData.some((value) => value > 0);
if (hasSound) {
// BIG RED TEXT
console.log('%c%s', 'color: red; font-size: 20px;', '🔊 Sound detected');
if (!hasStartedSpeaking) {
hasStartedSpeaking = true;
stopAllAudio();
}
lastSoundTime = Date.now();
}
// Start silence detection only after initial speech/noise has been detected
if (hasStartedSpeaking) {
if (Date.now() - lastSoundTime > 2000) {
confirmed = true;
if (mediaRecorder) {
console.log('%c%s', 'color: red; font-size: 20px;', '🔇 Silence detected');
mediaRecorder.stop();
return;
}
}
}
window.requestAnimationFrame(processFrame);
};
window.requestAnimationFrame(processFrame);
};
detectSound();
};
let finishedMessages = {};
let currentMessageId = null;
let currentUtterance = null;
const speakSpeechSynthesisHandler = (content) => {
if ($showCallOverlay) {
return new Promise((resolve) => {
let voices = [];
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
if (voices.length > 0) {
clearInterval(getVoicesLoop);
const voice =
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
currentUtterance = new SpeechSynthesisUtterance(content);
if (voice) {
currentUtterance.voice = voice;
}
speechSynthesis.speak(currentUtterance);
currentUtterance.onend = async (e) => {
await new Promise((r) => setTimeout(r, 200));
resolve(e);
};
}
}, 100);
});
} else {
return Promise.resolve();
}
};
const playAudio = (audio) => {
if ($showCallOverlay) {
return new Promise((resolve) => {
const audioElement = document.getElementById('audioElement');
if (audioElement) {
audioElement.src = audio.src;
audioElement.muted = true;
audioElement
.play()
.then(() => {
audioElement.muted = false;
})
.catch((error) => {
console.error(error);
});
audioElement.onended = async (e) => {
await new Promise((r) => setTimeout(r, 100));
resolve(e);
};
}
});
} else {
return Promise.resolve();
}
};
const stopAllAudio = async () => {
interrupted = true;
if (chatStreaming) {
stopResponse();
}
if (currentUtterance) {
speechSynthesis.cancel();
currentUtterance = null;
}
const audioElement = document.getElementById('audioElement');
if (audioElement) {
audioElement.muted = true;
audioElement.pause();
audioElement.currentTime = 0;
}
};
let audioAbortController = new AbortController();
// Audio cache map where key is the content and value is the Audio object.
const audioCache = new Map();
const emojiCache = new Map();
const fetchAudio = async (content) => {
if (!audioCache.has(content)) {
try {
// Set the emoji for the content if needed
if ($settings?.showEmojiInCall ?? false) {
const emoji = await generateEmoji(localStorage.token, modelId, content, chatId);
if (emoji) {
emojiCache.set(content, emoji);
}
}
if ($config.audio.tts.engine !== '') {
const res = await synthesizeOpenAISpeech(
localStorage.token,
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice,
content
).catch((error) => {
console.error(error);
return null;
});
if (res) {
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
audioCache.set(content, new Audio(blobUrl));
}
} else {
audioCache.set(content, true);
}
} catch (error) {
console.error('Error synthesizing speech:', error);
}
}
return audioCache.get(content);
};
let messages = {};
const monitorAndPlayAudio = async (id, signal) => {
while (!signal.aborted) {
if (messages[id] && messages[id].length > 0) {
// Retrieve the next content string from the queue
const content = messages[id].shift(); // Dequeues the content for playing
if (audioCache.has(content)) {
// If content is available in the cache, play it
// Set the emoji for the content if available
if (($settings?.showEmojiInCall ?? false) && emojiCache.has(content)) {
emoji = emojiCache.get(content);
} else {
emoji = null;
}
if ($config.audio.tts.engine !== '') {
try {
console.log(
'%c%s',
'color: red; font-size: 20px;',
`Playing audio for content: ${content}`
);
const audio = audioCache.get(content);
await playAudio(audio); // Here ensure that playAudio is indeed correct method to execute
console.log(`Played audio for content: ${content}`);
await new Promise((resolve) => setTimeout(resolve, 200)); // Wait before retrying to reduce tight loop
} catch (error) {
console.error('Error playing audio:', error);
}
} else {
await speakSpeechSynthesisHandler(content);
}
} else {
// If not available in the cache, push it back to the queue and delay
messages[id].unshift(content); // Re-queue the content at the start
console.log(`Audio for "${content}" not yet available in the cache, re-queued...`);
await new Promise((resolve) => setTimeout(resolve, 200)); // Wait before retrying to reduce tight loop
}
} else if (finishedMessages[id] && messages[id] && messages[id].length === 0) {
// If the message is finished and there are no more messages to process, break the loop
break;
} else {
// No messages to process, sleep for a bit
await new Promise((resolve) => setTimeout(resolve, 200));
}
}
console.log(`Audio monitoring and playing stopped for message ID ${id}`);
};
onMount(async () => {
startRecording();
} else {
stopCamera();
}
const chatStartHandler = async (e) => {
const { id } = e.detail;
chatStreaming = true;
if (currentMessageId !== id) {
console.log(`Received chat start event for message ID ${id}`);
currentMessageId = id;
if (audioAbortController) {
audioAbortController.abort();
}
audioAbortController = new AbortController();
// Start monitoring and playing audio for the message ID
monitorAndPlayAudio(id, audioAbortController.signal);
}
};
const chatEventHandler = async (e) => {
const { id, content } = e.detail;
// "id" here is message id
// if "id" is not the same as "currentMessageId" then do not process
// "content" here is a sentence from the assistant,
// there will be many sentences for the same "id"
if (currentMessageId === id) {
console.log(`Received chat event for message ID ${id}: ${content}`);
try {
if (messages[id] === undefined) {
messages[id] = [content];
} else {
messages[id].push(content);
}
console.log(content);
fetchAudio(content);
} catch (error) {
console.error('Failed to fetch or play audio:', error);
}
}
};
const chatFinishHandler = async (e) => {
const { id, content } = e.detail;
// "content" here is the entire message from the assistant
chatStreaming = false;
finishedMessages[id] = true;
};
eventTarget.addEventListener('chat:start', chatStartHandler);
eventTarget.addEventListener('chat', chatEventHandler);
eventTarget.addEventListener('chat:finish', chatFinishHandler);
return async () => {
eventTarget.removeEventListener('chat:start', chatStartHandler);
eventTarget.removeEventListener('chat', chatEventHandler);
eventTarget.removeEventListener('chat:finish', chatFinishHandler);
audioAbortController.abort();
await tick();
await stopAllAudio();
await stopRecordingCallback(false);
await stopCamera();
};
});
</script>
{#if $showCallOverlay}
<audio id="audioElement" src="" style="display: none;" />
<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
<div
class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
>
<div class="max-w-lg w-full h-screen max-h-[100dvh] flex flex-col justify-between p-3 md:p-6">
{#if camera}
<div class="flex justify-center items-center w-full min-h-20">
{#if loading}
<div class="flex justify-center items-center w-full h-20 min-h-20">
{#if emoji}
<div
class=" transition-all rounded-full"
style="font-size:{rmsLevel * 100 > 4
? '4.5'
: rmsLevel * 100 > 2
? '4.25'
: rmsLevel * 100 > 1
? '3.75'
: '3.5'}rem;width: 100%; text-align:center;"
>
{emoji}
</div>
{:else if loading}
<svg
class="size-12 text-gray-900 dark:text-gray-400"
viewBox="0 0 24 24"
@ -509,7 +645,20 @@
<div class="flex justify-center items-center flex-1 h-full w-full max-h-full">
{#if !camera}
{#if loading}
{#if emoji}
<div
class=" transition-all rounded-full"
style="font-size:{rmsLevel * 100 > 4
? '13'
: rmsLevel * 100 > 2
? '12'
: rmsLevel * 100 > 1
? '11.5'
: '11'}rem;width:100%;text-align:center;"
>
{emoji}
</div>
{:else if loading}
<svg
class="size-44 text-gray-900 dark:text-gray-400"
viewBox="0 0 24 24"

View File

@ -79,7 +79,7 @@
history.currentId = userMessageId;
await tick();
await sendPrompt(userPrompt, userMessageId, undefined, false);
await sendPrompt(userPrompt, userMessageId);
};
const updateChatMessages = async () => {

View File

@ -1,11 +1,14 @@
<script lang="ts">
import { WEBUI_BASE_URL } from '$lib/constants';
import { marked } from 'marked';
import { config, user, models as _models } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { blur, fade } from 'svelte/transition';
import Suggestions from '../MessageInput/Suggestions.svelte';
import { sanitizeResponseContent } from '$lib/utils';
const i18n = getContext('i18n');
@ -65,8 +68,12 @@
<div in:fade={{ duration: 200, delay: 200 }}>
{#if models[selectedModelIdx]?.info?.meta?.description ?? null}
<div class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400 line-clamp-3">
{models[selectedModelIdx]?.info?.meta?.description}
<div
class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400 line-clamp-3 markdown"
>
{@html marked.parse(
sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description)
)}
</div>
{#if models[selectedModelIdx]?.info?.meta?.user}
<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { DropdownMenu } from 'bits-ui';
import { marked } from 'marked';
import { flyAndScale } from '$lib/utils/transitions';
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
@ -333,9 +334,12 @@
{#if item.model?.info?.meta?.description}
<Tooltip
content={`${sanitizeResponseContent(
item.model?.info?.meta?.description
).replaceAll('\n', '<br>')}`}
content={`${marked.parse(
sanitizeResponseContent(item.model?.info?.meta?.description).replaceAll(
'\n',
'<br>'
)
)}`}
>
<div class="">
<svg

View File

@ -21,6 +21,8 @@
top_p: null,
tfs_z: null,
num_ctx: null,
num_batch: null,
num_keep: null,
max_tokens: null,
use_mmap: null,
use_mlock: null,
@ -565,6 +567,98 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Batch Size (num_batch)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_batch = (params?.num_batch ?? null) === null ? 512 : null;
}}
>
{#if (params?.num_batch ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.num_batch ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="256"
max="8192"
step="256"
bind:value={params.num_batch}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_batch}
type="number"
class=" bg-transparent text-center w-14"
min="256"
step="256"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
}}
>
{#if (params?.num_keep ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.num_keep ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-1"
max="10240000"
step="1"
bind:value={params.num_keep}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_keep}
type="number"
class=" bg-transparent text-center w-14"
min="-1"
step="1"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div>

View File

@ -55,6 +55,8 @@
stop: null,
tfs_z: null,
num_ctx: null,
num_batch: null,
num_keep: null,
max_tokens: null
};
@ -308,6 +310,8 @@
top_p: params.top_p !== null ? params.top_p : undefined,
tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
num_batch: params.num_batch !== null ? params.num_batch : undefined,
num_keep: params.num_keep !== null ? params.num_keep : undefined,
max_tokens: params.max_tokens !== null ? params.max_tokens : undefined,
use_mmap: params.use_mmap !== null ? params.use_mmap : undefined,
use_mlock: params.use_mlock !== null ? params.use_mlock : undefined,

View File

@ -20,9 +20,12 @@
// Interface
let defaultModelId = '';
let showUsername = false;
let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' = 'LTR';
let showEmojiInCall = false;
const toggleSplitLargeChunks = async () => {
splitLargeChunks = !splitLargeChunks;
saveSettings({ splitLargeChunks: splitLargeChunks });
@ -43,6 +46,11 @@
saveSettings({ showUsername: showUsername });
};
const toggleEmojiInCall = async () => {
showEmojiInCall = !showEmojiInCall;
saveSettings({ showEmojiInCall: showEmojiInCall });
};
const toggleTitleAutoGenerate = async () => {
titleAutoGenerate = !titleAutoGenerate;
saveSettings({
@ -88,8 +96,12 @@
onMount(async () => {
titleAutoGenerate = $settings?.title?.auto ?? true;
responseAutoCopy = $settings.responseAutoCopy ?? false;
showUsername = $settings.showUsername ?? false;
showEmojiInCall = $settings.showEmojiInCall ?? false;
chatBubble = $settings.chatBubble ?? true;
widescreenMode = $settings.widescreenMode ?? false;
splitLargeChunks = $settings.splitLargeChunks ?? false;
@ -192,6 +204,26 @@
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Display Emoji in Call')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
toggleEmojiInCall();
}}
type="button"
>
{#if showEmojiInCall === true}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if}
</button>
</div>
</div>
{#if !$settings.chatBubble}
<div>
<div class=" py-0.5 flex w-full justify-between">

View File

@ -2,13 +2,12 @@
import { createEventDispatcher, getContext } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
import { addNewMemory } from '$lib/apis/memories';
import { addNewMemory, updateMemoryById } from '$lib/apis/memories';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
export let show;
const i18n = getContext('i18n');
let loading = false;
@ -38,7 +37,9 @@
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
<div class=" text-lg font-medium self-center">{$i18n.t('Add Memory')}</div>
<div class=" text-lg font-medium self-center">
{$i18n.t('Add Memory')}
</div>
<button
class="self-center"
on:click={() => {

View File

@ -0,0 +1,136 @@
<script>
import { createEventDispatcher, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { updateMemoryById } from '$lib/apis/memories';
import Modal from '$lib/components/common/Modal.svelte';
const dispatch = createEventDispatcher();
export let show;
export let memory = {};
const i18n = getContext('i18n');
let loading = false;
let content = '';
$: if (show) {
setContent();
}
const setContent = () => {
content = memory.content;
};
const submitHandler = async () => {
loading = true;
const res = await updateMemoryById(localStorage.token, memory.id, content).catch((error) => {
toast.error(error);
return null;
});
if (res) {
console.log(res);
toast.success('Memory updated successfully');
dispatch('save');
show = false;
}
loading = false;
};
</script>
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
<div class=" text-lg font-medium self-center">
{$i18n.t('Edit Memory')}
</div>
<button
class="self-center"
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
<form
class="flex flex-col w-full"
on:submit|preventDefault={() => {
submitHandler();
}}
>
<div class="">
<textarea
bind:value={content}
class=" bg-transparent w-full text-sm resize-none rounded-xl p-3 outline outline-1 outline-gray-100 dark:outline-gray-800"
rows="3"
placeholder={$i18n.t('Enter a detail about yourself for your LLMs to recall')}
/>
<div class="text-xs text-gray-500">
{$i18n.t('Refer to yourself as "User" (e.g., "User is learning Spanish")')}
</div>
</div>
<div class="flex justify-end pt-1 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-3xl flex flex-row space-x-1 items-center {loading
? ' cursor-not-allowed'
: ''}"
type="submit"
disabled={loading}
>
{$i18n.t('Update')}
{#if loading}
<div class="ml-2 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>
{/if}
</button>
</div>
</form>
</div>
</div>
</div>
</Modal>

View File

@ -10,18 +10,24 @@
import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import { error } from '@sveltejs/kit';
import EditMemoryModal from './EditMemoryModal.svelte';
const i18n = getContext('i18n');
export let show = false;
let memories = [];
let loading = true;
let showAddMemoryModal = false;
let showEditMemoryModal = false;
$: if (show) {
let selectedMemory = null;
$: if (show && memories.length === 0 && loading) {
(async () => {
memories = await getMemories(localStorage.token);
loading = false;
})();
}
</script>
@ -62,7 +68,9 @@
>
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th>
<th scope="col" class="px-3 py-2 hidden md:flex">
{$i18n.t('Last Modified')}
</th>
<th scope="col" class="px-3 py-2 text-right" />
</tr>
</thead>
@ -76,11 +84,38 @@
</td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto whitespace-nowrap">
{dayjs(memory.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
{dayjs(memory.updated_at * 1000).format(
$i18n.t('MMMM DD, YYYY hh:mm:ss A')
)}
</div>
</td>
<td class="px-3 py-1">
<div class="flex justify-end w-full">
<Tooltip content="Edit">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={() => {
selectedMemory = memory;
showEditMemoryModal = true;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 s-FoVA_WMOgxUD"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
class="s-FoVA_WMOgxUD"
/></svg
>
</button>
</Tooltip>
<Tooltip content="Delete">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
@ -163,3 +198,11 @@
memories = await getMemories(localStorage.token);
}}
/>
<EditMemoryModal
bind:show={showEditMemoryModal}
memory={selectedMemory}
on:save={async () => {
memories = await getMemories(localStorage.token);
}}
/>

View File

@ -9,6 +9,9 @@
export let title = 'Confirm your action';
export let message = 'This action cannot be undone. Do you wish to continue?';
export let cancelLabel = 'Cancel';
export let confirmLabel = 'Confirm';
export let show = false;
let modalElement = null;
let mounted = false;
@ -70,7 +73,7 @@
}}
type="button"
>
Cancel
{cancelLabel}
</button>
<button
class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
@ -80,7 +83,7 @@
}}
type="button"
>
Confirm
{confirmLabel}
</button>
</div>
</div>

View File

@ -1,5 +1,7 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { marked } from 'marked';
import tippy from 'tippy.js';
export let placement = 'top';

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
import {
user,
@ -11,10 +12,11 @@
mobile,
showArchivedChats
} from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { onMount, getContext, tick } from 'svelte';
const i18n = getContext('i18n');
import { updateUserSettings } from '$lib/apis/users';
import {
deleteChatById,
getChatList,
@ -25,37 +27,25 @@
archiveChatById,
cloneChatById
} from '$lib/apis/chats';
import { toast } from 'svelte-sonner';
import { fade, slide } from 'svelte/transition';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '../common/Tooltip.svelte';
import ChatMenu from './Sidebar/ChatMenu.svelte';
import ShareChatModal from '../chat/ShareChatModal.svelte';
import ArchiveBox from '../icons/ArchiveBox.svelte';
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
import UserMenu from './Sidebar/UserMenu.svelte';
import { updateUserSettings } from '$lib/apis/users';
import ChatItem from './Sidebar/ChatItem.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
const BREAKPOINT = 768;
let show = false;
let navElement;
let title: string = 'UI';
let search = '';
let shareChatId = null;
let shiftKey = false;
let selectedChatId = null;
let deleteChat = null;
let chatDeleteId = null;
let chatTitleEditId = null;
let chatTitle = '';
let showShareChatModal = false;
let showDeleteConfirm = false;
let showDropdown = false;
let isEditing = false;
let filteredChatList = [];
$: filteredChatList = $chats.filter((chat) => {
@ -78,13 +68,6 @@
}
});
mobile;
const onResize = () => {
if ($showSidebar && window.innerWidth < BREAKPOINT) {
showSidebar.set(false);
}
};
onMount(async () => {
mobile.subscribe((e) => {
if ($showSidebar && e) {
@ -125,10 +108,28 @@
checkDirection();
};
const onKeyDown = (e) => {
if (e.key === 'Shift') {
shiftKey = true;
}
};
const onKeyUp = (e) => {
if (e.key === 'Shift') {
shiftKey = false;
}
};
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
window.addEventListener('touchstart', onTouchStart);
window.addEventListener('touchend', onTouchEnd);
return () => {
window.removeEventListener('keydown', onKeyDown);
window.removeEventListener('keyup', onKeyUp);
window.removeEventListener('touchstart', onTouchStart);
window.removeEventListener('touchend', onTouchEnd);
};
@ -149,69 +150,29 @@
await chats.set(enrichedChats);
};
const loadChat = async (id) => {
goto(`/c/${id}`);
};
const editChatTitle = async (id, _title) => {
if (_title === '') {
toast.error($i18n.t('Title cannot be an empty string.'));
} else {
title = _title;
await updateChatById(localStorage.token, id, {
title: _title
});
await chats.set(await getChatList(localStorage.token));
}
};
const deleteChat = async (id) => {
const res = await deleteChatById(localStorage.token, id).catch((error) => {
toast.error(error);
chatDeleteId = null;
return null;
});
if (res) {
if ($chatId === id) {
goto('/');
}
await chats.set(await getChatList(localStorage.token));
}
};
const cloneChatHandler = async (id) => {
const res = await cloneChatById(localStorage.token, id).catch((error) => {
toast.error(error);
return null;
});
if (res) {
goto(`/c/${res.id}`);
await chats.set(await getChatList(localStorage.token));
}
};
const saveSettings = async (updated) => {
await settings.set({ ...$settings, ...updated });
await updateUserSettings(localStorage.token, { ui: $settings });
location.href = '/';
};
const archiveChatHandler = async (id) => {
await archiveChatById(localStorage.token, id);
await chats.set(await getChatList(localStorage.token));
};
const deleteChatHandler = async (id) => {
const res = await deleteChatById(localStorage.token, id).catch((error) => {
toast.error(error);
return null;
});
const focusEdit = async (node: HTMLInputElement) => {
node.focus();
if (res) {
if ($chatId === id) {
await chatId.set('');
await tick();
goto('/');
}
await chats.set(await getChatList(localStorage.token));
}
};
</script>
<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
<ArchivedChatsModal
bind:show={$showArchivedChats}
on:change={async () => {
@ -219,6 +180,18 @@
}}
/>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title="Delete chat?"
on:confirm={() => {
deleteChatHandler(deleteChat.id);
}}
>
<div class=" text-sm text-gray-500">
This will delete <span class=" font-semibold">{deleteChat.title}</span>.
</div>
</DeleteConfirmDialog>
<!-- svelte-ignore a11y-no-static-element-interactions -->
{#if $showSidebar}
@ -252,12 +225,10 @@
draggable="false"
on:click={async () => {
selectedChatId = null;
await goto('/');
const newChatButton = document.getElementById('new-chat-button');
setTimeout(() => {
newChatButton?.click();
if ($mobile) {
showSidebar.set(false);
}
@ -486,215 +457,18 @@
</div>
{/if}
<div class=" w-full pr-2 relative group">
{#if chatTitleEditId === chat.id}
<div
class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
chat.id === chatTitleEditId ||
chat.id === chatDeleteId
? 'bg-gray-200 dark:bg-gray-900'
: chat.id === selectedChatId
? 'bg-gray-100 dark:bg-gray-950'
: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
>
<input
use:focusEdit
bind:value={chatTitle}
class=" bg-transparent w-full outline-none mr-10"
/>
</div>
{:else}
<a
class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
chat.id === chatTitleEditId ||
chat.id === chatDeleteId
? 'bg-gray-200 dark:bg-gray-900'
: chat.id === selectedChatId
? 'bg-gray-100 dark:bg-gray-950'
: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
href="/c/{chat.id}"
on:click={() => {
selectedChatId = chat.id;
if ($mobile) {
showSidebar.set(false);
}
}}
on:dblclick={() => {
chatTitle = chat.title;
chatTitleEditId = chat.id;
}}
draggable="false"
>
<div class=" flex self-center flex-1 w-full">
<div class=" text-left self-center overflow-hidden w-full h-[20px]">
{chat.title}
</div>
</div>
</a>
{/if}
<div
class="
{chat.id === $chatId || chat.id === chatTitleEditId || chat.id === chatDeleteId
? 'from-gray-200 dark:from-gray-900'
: chat.id === selectedChatId
? 'from-gray-100 dark:from-gray-950'
: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
to-transparent"
>
{#if chatTitleEditId === chat.id}
<div class="flex self-center space-x-1.5 z-10">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
editChatTitle(chat.id, chatTitle);
chatTitleEditId = null;
chatTitle = '';
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
chatTitleEditId = null;
chatTitle = '';
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{:else if chatDeleteId === chat.id}
<div class="flex self-center space-x-1.5 z-10">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
deleteChat(chat.id);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
chatDeleteId = null;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{:else}
<div class="flex self-center space-x-1 z-10">
<ChatMenu
chatId={chat.id}
cloneChatHandler={() => {
cloneChatHandler(chat.id);
}}
shareHandler={() => {
shareChatId = selectedChatId;
showShareChatModal = true;
}}
archiveChatHandler={() => {
archiveChatHandler(chat.id);
}}
renameHandler={() => {
chatTitle = chat.title;
chatTitleEditId = chat.id;
}}
deleteHandler={() => {
chatDeleteId = chat.id;
}}
onClose={() => {
selectedChatId = null;
}}
>
<button
aria-label="Chat Menu"
class=" self-center dark:hover:text-white transition"
on:click={() => {
selectedChatId = chat.id;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
</ChatMenu>
{#if chat.id === $chatId}
<button
id="delete-chat-button"
class="hidden"
on:click={() => {
chatDeleteId = chat.id;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
{/if}
</div>
{/if}
</div>
</div>
<ChatItem
{chat}
{shiftKey}
selected={selectedChatId === chat.id}
on:select={() => {
selectedChatId = chat.id;
}}
on:delete={() => {
deleteChat = chat;
showDeleteConfirm = true;
}}
/>
{/each}
</div>
</div>

View File

@ -0,0 +1,281 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { goto, invalidate, invalidateAll } from '$app/navigation';
import { onMount, getContext, createEventDispatcher, tick } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
import {
archiveChatById,
cloneChatById,
deleteChatById,
getChatList,
updateChatById
} from '$lib/apis/chats';
import { chatId, chats, mobile, showSidebar } from '$lib/stores';
import ChatMenu from './ChatMenu.svelte';
import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte';
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
export let chat;
export let selected = false;
export let shiftKey = false;
let mouseOver = false;
let showShareChatModal = false;
let confirmEdit = false;
let chatTitle = chat.title;
const editChatTitle = async (id, _title) => {
if (_title === '') {
toast.error($i18n.t('Title cannot be an empty string.'));
} else {
await updateChatById(localStorage.token, id, {
title: _title
});
await chats.set(await getChatList(localStorage.token));
}
};
const cloneChatHandler = async (id) => {
const res = await cloneChatById(localStorage.token, id).catch((error) => {
toast.error(error);
return null;
});
if (res) {
goto(`/c/${res.id}`);
await chats.set(await getChatList(localStorage.token));
}
};
const archiveChatHandler = async (id) => {
await archiveChatById(localStorage.token, id);
await chats.set(await getChatList(localStorage.token));
};
const focusEdit = async (node: HTMLInputElement) => {
node.focus();
};
</script>
<ShareChatModal bind:show={showShareChatModal} chatId={chat.id} />
<div class=" w-full pr-2 relative group">
{#if confirmEdit}
<div
class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId || confirmEdit
? 'bg-gray-200 dark:bg-gray-900'
: selected
? 'bg-gray-100 dark:bg-gray-950'
: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
>
<input
use:focusEdit
bind:value={chatTitle}
class=" bg-transparent w-full outline-none mr-10"
/>
</div>
{:else}
<a
class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId || confirmEdit
? 'bg-gray-200 dark:bg-gray-900'
: selected
? 'bg-gray-100 dark:bg-gray-950'
: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
href="/c/{chat.id}"
on:click={() => {
dispatch('select');
if ($mobile) {
showSidebar.set(false);
}
}}
on:dblclick={() => {
chatTitle = chat.title;
confirmEdit = true;
}}
on:mouseenter={(e) => {
mouseOver = true;
}}
on:mouseleave={(e) => {
mouseOver = false;
}}
on:focus={(e) => {}}
draggable="false"
>
<div class=" flex self-center flex-1 w-full">
<div class=" text-left self-center overflow-hidden w-full h-[20px]">
{chat.title}
</div>
</div>
</a>
{/if}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="
{chat.id === $chatId || confirmEdit
? 'from-gray-200 dark:from-gray-900'
: selected
? 'from-gray-100 dark:from-gray-950'
: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
to-transparent"
on:mouseenter={(e) => {
mouseOver = true;
}}
on:mouseleave={(e) => {
mouseOver = false;
}}
>
{#if confirmEdit}
<div class="flex self-center space-x-1.5 z-10">
<Tooltip content="Confirm">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
editChatTitle(chat.id, chatTitle);
confirmEdit = false;
chatTitle = '';
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
</Tooltip>
<Tooltip content="Cancel">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
confirmEdit = false;
chatTitle = '';
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</Tooltip>
</div>
{:else if shiftKey && mouseOver}
<div class=" flex items-center self-center space-x-1.5">
<Tooltip content="Archive" className="flex items-center">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
archiveChatHandler(chat.id);
}}
type="button"
>
<ArchiveBox className="size-4 translate-y-[0.5px]" strokeWidth="2" />
</button>
</Tooltip>
<Tooltip content="Delete">
<button
class=" self-center dark:hover:text-white transition"
on:click={() => {
deleteChat(chat.id);
}}
type="button"
>
<GarbageBin strokeWidth="2" />
</button>
</Tooltip>
</div>
{:else}
<div class="flex self-center space-x-1 z-10">
<ChatMenu
chatId={chat.id}
cloneChatHandler={() => {
cloneChatHandler(chat.id);
}}
shareHandler={() => {
showShareChatModal = true;
}}
archiveChatHandler={() => {
archiveChatHandler(chat.id);
}}
renameHandler={() => {
chatTitle = chat.title;
confirmEdit = true;
}}
deleteHandler={() => {
dispatch('delete');
}}
onClose={() => {
selected = false;
}}
>
<button
aria-label="Chat Menu"
class=" self-center dark:hover:text-white transition"
on:click={() => {
dispatch('select');
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
</ChatMenu>
{#if chat.id === $chatId}
<!-- Shortcut support using "delete-chat-button" id -->
<button
id="delete-chat-button"
class="hidden"
on:click={() => {
dispatch('delete');
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
{/if}
</div>
{/if}
</div>
</div>

View File

@ -17,15 +17,19 @@
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
import ModelMenu from './Models/ModelMenu.svelte';
import ModelDeleteConfirmDialog from '../common/ConfirmDialog.svelte';
const i18n = getContext('i18n');
let showModelDeleteConfirm = false;
let localModelfiles = [];
let importFiles;
let modelsImportInputElement: HTMLInputElement;
let _models = [];
let selectedModel = null;
let sortable = null;
let searchValue = '';
@ -199,6 +203,13 @@
</title>
</svelte:head>
<ModelDeleteConfirmDialog
bind:show={showModelDeleteConfirm}
on:confirm={() => {
deleteModelHandler(selectedModel);
}}
/>
<div class=" text-lg font-semibold mb-3">{$i18n.t('Models')}</div>
<div class=" flex w-full space-x-2">
@ -339,7 +350,8 @@
hideModelHandler(model);
}}
deleteHandler={() => {
deleteModelHandler(model);
selectedModel = model;
showModelDeleteConfirm = true;
}}
onClose={() => {}}
>

View File

@ -69,6 +69,7 @@
"Bad Response": "استجابة خطاء",
"Banners": "لافتات",
"Base Model (From)": "النموذج الأساسي (من)",
"Batch Size (num_batch)": "",
"before": "قبل",
"Being lazy": "كون كسول",
"Brave Search API Key": "مفتاح واجهة برمجة تطبيقات البحث الشجاع",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "اكتشاف وتنزيل واستكشاف المطالبات المخصصة",
"Discover, download, and explore model presets": "اكتشاف وتنزيل واستكشاف الإعدادات المسبقة للنموذج",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "اعرض اسم المستخدم بدلاً منك في الدردشة",
"Document": "المستند",
"Document Settings": "أعدادات المستند",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack",
"Enter stop sequence": "أدخل تسلسل التوقف",
"Enter Tavily API Key": "",
"Enter Top K": "أدخل Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)",
@ -492,6 +495,7 @@
"System": "النظام",
"System Prompt": "محادثة النظام",
"Tags": "الوسوم",
"Tavily API Key": "",
"Tell us more:": "أخبرنا المزيد:",
"Temperature": "درجة حرارة",
"Template": "نموذج",
@ -522,6 +526,7 @@
"Today": "اليوم",
"Toggle settings": "فتح وأغلاق الاعدادات",
"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Невалиден отговор от API",
"Banners": "Банери",
"Base Model (From)": "Базов модел (от)",
"Batch Size (num_batch)": "",
"before": "преди",
"Being lazy": "Да бъдеш мързелив",
"Brave Search API Key": "Смел ключ за API за търсене",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Откриване, сваляне и преглед на персонализирани промптове",
"Discover, download, and explore model presets": "Откриване, сваляне и преглед на пресетове на модели",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Показване на потребителското име вместо Вие в чата",
"Document": "Документ",
"Document Settings": "Документ Настройки",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Въведете Serpstack API ключ",
"Enter stop sequence": "Въведете стоп последователност",
"Enter Tavily API Key": "",
"Enter Top K": "Въведете Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Въведете URL (напр. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Въведете URL (напр. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "Система",
"System Prompt": "Системен Промпт",
"Tags": "Тагове",
"Tavily API Key": "",
"Tell us more:": "Повече информация:",
"Temperature": "Температура",
"Template": "Шаблон",
@ -518,6 +522,7 @@
"Today": "днес",
"Toggle settings": "Toggle settings",
"Toggle sidebar": "Toggle sidebar",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "খারাপ প্রতিক্রিয়া",
"Banners": "ব্যানার",
"Base Model (From)": "বেস মডেল (থেকে)",
"Batch Size (num_batch)": "",
"before": "পূর্ববর্তী",
"Being lazy": "অলস হওয়া",
"Brave Search API Key": "সাহসী অনুসন্ধান API কী",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "কাস্টম প্রম্পটগুলো আবিস্কার, ডাউনলোড এবং এক্সপ্লোর করুন",
"Discover, download, and explore model presets": "মডেল প্রিসেটগুলো আবিস্কার, ডাউনলোড এবং এক্সপ্লোর করুন",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "চ্যাটে 'আপনি'-র পরবর্তে ইউজারনেম দেখান",
"Document": "ডকুমেন্ট",
"Document Settings": "ডকুমেন্ট সেটিংসমূহ",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Serpstack API কী লিখুন",
"Enter stop sequence": "স্টপ সিকোয়েন্স লিখুন",
"Enter Tavily API Key": "",
"Enter Top K": "Top K লিখুন",
"Enter URL (e.g. http://127.0.0.1:7860/)": "ইউআরএল দিন (যেমন http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "ইউআরএল দিন (যেমন http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "সিস্টেম",
"System Prompt": "সিস্টেম প্রম্পট",
"Tags": "ট্যাগসমূহ",
"Tavily API Key": "",
"Tell us more:": "আরও বলুন:",
"Temperature": "তাপমাত্রা",
"Template": "টেম্পলেট",
@ -518,6 +522,7 @@
"Today": "আজ",
"Toggle settings": "সেটিংস টোগল",
"Toggle sidebar": "সাইডবার টোগল",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Resposta Erroni",
"Banners": "Banners",
"Base Model (From)": "Model base (des de)",
"Batch Size (num_batch)": "",
"before": "abans",
"Being lazy": "Ser l'estupidez",
"Brave Search API Key": "Clau API Brave Search",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Descobreix, descarrega i explora prompts personalitzats",
"Discover, download, and explore model presets": "Descobreix, descarrega i explora presets de models",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Mostra el nom d'usuari en lloc de 'Tu' al Xat",
"Document": "Document",
"Document Settings": "Configuració de Documents",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Introduïu la clau de l'API Serpstack",
"Enter stop sequence": "Introdueix la seqüència de parada",
"Enter Tavily API Key": "",
"Enter Top K": "Introdueix Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Introdueix l'URL (p. ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Introdueix l'URL (p. ex. http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sistema",
"System Prompt": "Prompt del Sistema",
"Tags": "Etiquetes",
"Tavily API Key": "",
"Tell us more:": "Dóna'ns més informació:",
"Temperature": "Temperatura",
"Template": "Plantilla",
@ -519,6 +523,7 @@
"Today": "Avui",
"Toggle settings": "Commuta configuracions",
"Toggle sidebar": "Commuta barra lateral",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "",
"Being lazy": "",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Pagdiskubre, pag-download ug pagsuhid sa mga naandan nga pag-aghat",
"Discover, download, and explore model presets": "Pagdiskobre, pag-download, ug pagsuhid sa mga preset sa template",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Ipakita ang username imbes nga 'Ikaw' sa Panaghisgutan",
"Document": "Dokumento",
"Document Settings": "Mga Setting sa Dokumento",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "Pagsulod sa katapusan nga han-ay",
"Enter Tavily API Key": "",
"Enter Top K": "Pagsulod sa Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Pagsulod sa URL (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "",
@ -488,6 +491,7 @@
"System": "Sistema",
"System Prompt": "Madasig nga Sistema",
"Tags": "Mga tag",
"Tavily API Key": "",
"Tell us more:": "",
"Temperature": "Temperatura",
"Template": "Modelo",
@ -518,6 +522,7 @@
"Today": "",
"Toggle settings": "I-toggle ang mga setting",
"Toggle sidebar": "I-toggle ang sidebar",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Ibabaw nga P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Schlechte Antwort",
"Banners": "Banner",
"Base Model (From)": "Basismodell (von)",
"Batch Size (num_batch)": "",
"before": "bereits geteilt",
"Being lazy": "Faul sein",
"Brave Search API Key": "API-Schlüssel für die Brave-Suche",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Benutzerdefinierte Prompts entdecken, herunterladen und erkunden",
"Discover, download, and explore model presets": "Modellvorgaben entdecken, herunterladen und erkunden",
"Dismissible": "ausblendbar",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Den Benutzernamen anstelle von 'du' im Chat anzeigen",
"Document": "Dokument",
"Document Settings": "Dokumenteinstellungen",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein",
"Enter stop sequence": "Stop-Sequenz eingeben",
"Enter Tavily API Key": "",
"Enter Top K": "Gib Top K ein",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Gib die URL ein (z.B. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Gib die URL ein (z.B. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "System",
"System Prompt": "System-Prompt",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "Erzähl uns mehr",
"Temperature": "Temperatur",
"Template": "Vorlage",
@ -518,6 +522,7 @@
"Today": "Heute",
"Toggle settings": "Einstellungen umschalten",
"Toggle sidebar": "Seitenleiste umschalten",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "",
"Being lazy": "",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Discover, download, and explore custom prompts",
"Discover, download, and explore model presets": "Discover, download, and explore model presets",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Display username instead of You in Chat",
"Document": "Document",
"Document Settings": "Document Settings",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "Enter stop bark",
"Enter Tavily API Key": "",
"Enter Top K": "Enter Top Wow",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Enter URL (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "",
@ -488,6 +491,7 @@
"System": "System very system",
"System Prompt": "System Prompt much prompt",
"Tags": "Tags very tags",
"Tavily API Key": "",
"Tell us more:": "",
"Temperature": "Temperature very temp",
"Template": "Template much template",
@ -518,6 +522,7 @@
"Today": "",
"Toggle settings": "Toggle settings much toggle",
"Toggle sidebar": "Toggle sidebar much toggle",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K very top",
"Top P": "Top P very top",

View File

@ -69,6 +69,7 @@
"Bad Response": "",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "",
"Being lazy": "",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "",
"Discover, download, and explore model presets": "",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "",
"Document": "",
"Document Settings": "",
@ -176,6 +178,7 @@
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
"Edit": "",
"Edit Doc": "",
"Edit Memory": "",
"Edit User": "",
"Email": "",
"Embedding Batch Size": "",
@ -205,6 +208,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "",
"Enter Tavily API Key": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -280,6 +284,7 @@
"Knowledge": "",
"Language": "",
"Last Active": "",
"Last Modified": "",
"Light": "",
"Listening...": "",
"LLMs can make mistakes. Verify important information.": "",
@ -488,6 +493,7 @@
"System": "",
"System Prompt": "",
"Tags": "",
"Tavily API Key": "",
"Tell us more:": "",
"Temperature": "",
"Template": "",
@ -518,6 +524,7 @@
"Today": "",
"Toggle settings": "",
"Toggle sidebar": "",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "",
"Top P": "",

View File

@ -69,6 +69,7 @@
"Bad Response": "",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "",
"Being lazy": "",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "",
"Discover, download, and explore model presets": "",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "",
"Document": "",
"Document Settings": "",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "",
"Enter Tavily API Key": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -488,6 +491,7 @@
"System": "",
"System Prompt": "",
"Tags": "",
"Tavily API Key": "",
"Tell us more:": "",
"Temperature": "",
"Template": "",
@ -518,6 +522,7 @@
"Today": "",
"Toggle settings": "",
"Toggle sidebar": "",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "",
"Top P": "",

View File

@ -69,6 +69,7 @@
"Bad Response": "Respuesta incorrecta",
"Banners": "Banners",
"Base Model (From)": "Modelo base (desde)",
"Batch Size (num_batch)": "",
"before": "antes",
"Being lazy": "Ser perezoso",
"Brave Search API Key": "Clave de API de Brave Search",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Descubre, descarga, y explora Prompts personalizados",
"Discover, download, and explore model presets": "Descubre, descarga y explora ajustes preestablecidos de modelos",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Mostrar el nombre de usuario en lugar de Usted en el chat",
"Document": "Documento",
"Document Settings": "Configuración del Documento",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Ingrese la clave API de Serpstack",
"Enter stop sequence": "Ingrese la secuencia de parada",
"Enter Tavily API Key": "",
"Enter Top K": "Ingrese el Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Ingrese la URL (p.ej., http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Ingrese la URL (p.ej., http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sistema",
"System Prompt": "Prompt del sistema",
"Tags": "Etiquetas",
"Tavily API Key": "",
"Tell us more:": "Dinos más:",
"Temperature": "Temperatura",
"Template": "Plantilla",
@ -519,6 +523,7 @@
"Today": "Hoy",
"Toggle settings": "Alternar configuración",
"Toggle sidebar": "Alternar barra lateral",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "پاسخ خوب نیست",
"Banners": "بنر",
"Base Model (From)": "مدل پایه (از)",
"Batch Size (num_batch)": "",
"before": "قبل",
"Being lazy": "حالت سازنده",
"Brave Search API Key": "کلید API جستجوی شجاع",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "پرامپت\u200cهای سفارشی را کشف، دانلود و کاوش کنید",
"Discover, download, and explore model presets": "پیش تنظیمات مدل را کشف، دانلود و کاوش کنید",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "نمایش نام کاربری به جای «شما» در چت",
"Document": "سند",
"Document Settings": "تنظیمات سند",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "کلید API Serpstack را وارد کنید",
"Enter stop sequence": "توالی توقف را وارد کنید",
"Enter Tavily API Key": "",
"Enter Top K": "مقدار Top K را وارد کنید",
"Enter URL (e.g. http://127.0.0.1:7860/)": "مقدار URL را وارد کنید (مثال http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "مقدار URL را وارد کنید (مثال http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "سیستم",
"System Prompt": "پرامپت سیستم",
"Tags": "تگ\u200cها",
"Tavily API Key": "",
"Tell us more:": "بیشتر بگویید:",
"Temperature": "دما",
"Template": "الگو",
@ -518,6 +522,7 @@
"Today": "امروز",
"Toggle settings": "نمایش/عدم نمایش تنظیمات",
"Toggle sidebar": "نمایش/عدم نمایش نوار کناری",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Epäkelpo vastaus",
"Banners": "Bannerit",
"Base Model (From)": "Perusmalli (alkaen)",
"Batch Size (num_batch)": "",
"before": "ennen",
"Being lazy": "Oli laiska",
"Brave Search API Key": "Brave Search API -avain",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Löydä ja lataa mukautettuja kehotteita",
"Discover, download, and explore model presets": "Löydä ja lataa mallien esiasetuksia",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Näytä käyttäjänimi keskustelussa",
"Document": "Asiakirja",
"Document Settings": "Asiakirja-asetukset",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Anna Serpstack API -avain",
"Enter stop sequence": "Syötä lopetussekvenssi",
"Enter Tavily API Key": "",
"Enter Top K": "Syötä Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Syötä URL (esim. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Syötä URL (esim. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "Järjestelmä",
"System Prompt": "Järjestelmäkehote",
"Tags": "Tagit",
"Tavily API Key": "",
"Tell us more:": "Kerro lisää:",
"Temperature": "Lämpötila",
"Template": "Malline",
@ -518,6 +522,7 @@
"Today": "Tänään",
"Toggle settings": "Kytke asetukset",
"Toggle sidebar": "Kytke sivupalkki",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Mauvaise réponse",
"Banners": "Bannières",
"Base Model (From)": "Modèle de base (à partir de)",
"Batch Size (num_batch)": "",
"before": "avant",
"Being lazy": "En manque de temps",
"Brave Search API Key": "Clé dAPI de recherche brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Découvrir, télécharger et explorer des prompts personnalisés",
"Discover, download, and explore model presets": "Découvrir, télécharger et explorer des préconfigurations de modèles",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Afficher le nom d'utilisateur au lieu de 'Vous' dans la Discussion",
"Document": "Document",
"Document Settings": "Paramètres du document",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Entrez dans la clé API Serpstack",
"Enter stop sequence": "Entrez la séquence de fin",
"Enter Tavily API Key": "",
"Enter Top K": "Entrez Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (p. ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (p. ex. http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Système",
"System Prompt": "Prompt Système",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "Donnez-nous plus:",
"Temperature": "Température",
"Template": "Modèle",
@ -519,6 +523,7 @@
"Today": "Aujourd'hui",
"Toggle settings": "Basculer les paramètres",
"Toggle sidebar": "Basculer la barre latérale",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Mauvaise Réponse",
"Banners": "Bannières",
"Base Model (From)": "Modèle de Base (De)",
"Batch Size (num_batch)": "",
"before": "avant",
"Being lazy": "Est paresseux",
"Brave Search API Key": "Clé API Brave Search",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Découvrir, télécharger et explorer des prompts personnalisés",
"Discover, download, and explore model presets": "Découvrir, télécharger et explorer des préconfigurations de modèles",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Afficher le nom d'utilisateur au lieu de 'Vous' dans le Chat",
"Document": "Document",
"Document Settings": "Paramètres du document",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Entrez la clé API Serpstack",
"Enter stop sequence": "Entrez la séquence de fin",
"Enter Tavily API Key": "",
"Enter Top K": "Entrez Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (p. ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (p. ex. http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Système",
"System Prompt": "Prompt du Système",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "Dites-nous en plus :",
"Temperature": "Température",
"Template": "Modèle",
@ -519,6 +523,7 @@
"Today": "Aujourd'hui",
"Toggle settings": "Basculer les paramètres",
"Toggle sidebar": "Basculer la barre latérale",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "תגובה שגויה",
"Banners": "באנרים",
"Base Model (From)": "דגם בסיס (מ)",
"Batch Size (num_batch)": "",
"before": "לפני",
"Being lazy": "להיות עצלן",
"Brave Search API Key": "מפתח API של חיפוש אמיץ",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "גלה, הורד, וחקור פקודות מותאמות אישית",
"Discover, download, and explore model presets": "גלה, הורד, וחקור הגדרות מודל מוגדרות מראש",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "הצג את שם המשתמש במקום 'אתה' בצ'אט",
"Document": "מסמך",
"Document Settings": "הגדרות מסמך",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "הזן מפתח API של Serpstack",
"Enter stop sequence": "הזן רצף עצירה",
"Enter Tavily API Key": "",
"Enter Top K": "הזן Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "הזן כתובת URL (למשל http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "הזן כתובת URL (למשל http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "מערכת",
"System Prompt": "תגובת מערכת",
"Tags": "תגיות",
"Tavily API Key": "",
"Tell us more:": "תרשמו יותר:",
"Temperature": "טמפרטורה",
"Template": "תבנית",
@ -519,6 +523,7 @@
"Today": "היום",
"Toggle settings": "החלפת מצב של הגדרות",
"Toggle sidebar": "החלפת מצב של סרגל הצד",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "ख़राब प्रतिक्रिया",
"Banners": "बैनर",
"Base Model (From)": "बेस मॉडल (से)",
"Batch Size (num_batch)": "",
"before": "पहले",
"Being lazy": "आलसी होना",
"Brave Search API Key": "Brave सर्च एपीआई कुंजी",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "कस्टम प्रॉम्प्ट को खोजें, डाउनलोड करें और एक्सप्लोर करें",
"Discover, download, and explore model presets": "मॉडल प्रीसेट खोजें, डाउनलोड करें और एक्सप्लोर करें",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "चैट में 'आप' के स्थान पर उपयोगकर्ता नाम प्रदर्शित करें",
"Document": "दस्तावेज़",
"Document Settings": "दस्तावेज़ सेटिंग्स",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "सर्पस्टैक एपीआई कुंजी दर्ज करें",
"Enter stop sequence": "स्टॉप अनुक्रम दर्ज करें",
"Enter Tavily API Key": "",
"Enter Top K": "शीर्ष K दर्ज करें",
"Enter URL (e.g. http://127.0.0.1:7860/)": "यूआरएल दर्ज करें (उदा. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "यूआरएल दर्ज करें (उदा. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "सिस्टम",
"System Prompt": "सिस्टम प्रॉम्प्ट",
"Tags": "टैग",
"Tavily API Key": "",
"Tell us more:": "हमें और अधिक बताएँ:",
"Temperature": "टेंपेरेचर",
"Template": "टेम्पलेट",
@ -518,6 +522,7 @@
"Today": "आज",
"Toggle settings": "सेटिंग्स टॉगल करें",
"Toggle sidebar": "साइडबार टॉगल करें",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "शीर्ष K",
"Top P": "शीर्ष P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Loš odgovor",
"Banners": "Baneri",
"Base Model (From)": "Osnovni model (Od)",
"Batch Size (num_batch)": "",
"before": "prije",
"Being lazy": "Biti lijen",
"Brave Search API Key": "Brave tražilica - API ključ",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Otkrijte, preuzmite i istražite prilagođene prompte",
"Discover, download, and explore model presets": "Otkrijte, preuzmite i istražite unaprijed postavljene modele",
"Dismissible": "Odbaciti",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Prikaži korisničko ime umjesto Vas u razgovoru",
"Document": "Dokument",
"Document Settings": "Postavke dokumenta",
@ -205,6 +207,7 @@
"Enter Serply API Key": "Unesite Serply API ključ",
"Enter Serpstack API Key": "Unesite Serpstack API ključ",
"Enter stop sequence": "Unesite sekvencu zaustavljanja",
"Enter Tavily API Key": "",
"Enter Top K": "Unesite Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Unesite URL (npr. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Unesite URL (npr. http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sustav",
"System Prompt": "Sistemski prompt",
"Tags": "Oznake",
"Tavily API Key": "",
"Tell us more:": "Recite nam više:",
"Temperature": "Temperatura",
"Template": "Predložak",
@ -519,6 +523,7 @@
"Today": "Danas",
"Toggle settings": "Prebaci postavke",
"Toggle sidebar": "Prebaci bočnu traku",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "Alati",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Risposta non valida",
"Banners": "Banner",
"Base Model (From)": "Modello base (da)",
"Batch Size (num_batch)": "",
"before": "prima",
"Being lazy": "Essere pigri",
"Brave Search API Key": "Chiave API di ricerca Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Scopri, scarica ed esplora prompt personalizzati",
"Discover, download, and explore model presets": "Scopri, scarica ed esplora i preset del modello",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Visualizza il nome utente invece di Tu nella chat",
"Document": "Documento",
"Document Settings": "Impostazioni documento",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Inserisci la chiave API Serpstack",
"Enter stop sequence": "Inserisci la sequenza di arresto",
"Enter Tavily API Key": "",
"Enter Top K": "Inserisci Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sistema",
"System Prompt": "Prompt di sistema",
"Tags": "Tag",
"Tavily API Key": "",
"Tell us more:": "Raccontaci di più:",
"Temperature": "Temperatura",
"Template": "Modello",
@ -519,6 +523,7 @@
"Today": "Oggi",
"Toggle settings": "Attiva/disattiva impostazioni",
"Toggle sidebar": "Attiva/disattiva barra laterale",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "応答が悪い",
"Banners": "バナー",
"Base Model (From)": "ベースモデル(From)",
"Batch Size (num_batch)": "",
"before": "より前",
"Being lazy": "怠惰な",
"Brave Search API Key": "Brave Search APIキー",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "カスタムプロンプトを見つけて、ダウンロードして、探索",
"Discover, download, and explore model presets": "モデルプリセットを見つけて、ダウンロードして、探索",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "チャットで「あなた」の代わりにユーザー名を表示",
"Document": "ドキュメント",
"Document Settings": "ドキュメント設定",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Serpstack APIキーの入力",
"Enter stop sequence": "ストップシーケンスを入力してください",
"Enter Tavily API Key": "",
"Enter Top K": "トップ K を入力してください",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL を入力してください (例: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL を入力してください (例: http://localhost:11434)",
@ -487,6 +490,7 @@
"System": "システム",
"System Prompt": "システムプロンプト",
"Tags": "タグ",
"Tavily API Key": "",
"Tell us more:": "もっと話してください:",
"Temperature": "温度",
"Template": "テンプレート",
@ -517,6 +521,7 @@
"Today": "今日",
"Toggle settings": "設定を切り替え",
"Toggle sidebar": "サイドバーを切り替え",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "トップ K",
"Top P": "トップ P",

View File

@ -69,6 +69,7 @@
"Bad Response": "ხარვეზი",
"Banners": "რეკლამა",
"Base Model (From)": "საბაზო მოდელი (-დან)",
"Batch Size (num_batch)": "",
"before": "ადგილზე",
"Being lazy": "ჩაიტყვევა",
"Brave Search API Key": "Brave Search API გასაღები",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მორგებული მოთხოვნები",
"Discover, download, and explore model presets": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მოდელის წინასწარ პარამეტრები",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "ჩატში აჩვენე მომხმარებლის სახელი თქვენს ნაცვლად",
"Document": "დოკუმენტი",
"Document Settings": "დოკუმენტის პარამეტრები",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "შეიყვანეთ Serpstack API Key",
"Enter stop sequence": "შეიყვანეთ ტოპ თანმიმდევრობა",
"Enter Tavily API Key": "",
"Enter Top K": "შეიყვანეთ Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "შეიყვანეთ მისამართი (მაგალითად http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "სისტემა",
"System Prompt": "სისტემური მოთხოვნა",
"Tags": "ტეგები",
"Tavily API Key": "",
"Tell us more:": "ჩვენთან დავუკავშირდით",
"Temperature": "ტემპერატურა",
"Template": "შაბლონი",
@ -518,6 +522,7 @@
"Today": "დღეს",
"Toggle settings": "პარამეტრების გადართვა",
"Toggle sidebar": "გვერდითი ზოლის გადართვა",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "ტოპ K",
"Top P": "ტოპ P",

View File

@ -69,6 +69,7 @@
"Bad Response": "응답이 좋지 않습니다.",
"Banners": "배너",
"Base Model (From)": "기본 모델(시작)",
"Batch Size (num_batch)": "",
"before": "이전",
"Being lazy": "게으름 피우기",
"Brave Search API Key": "Brave Search API 키",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "사용자 정의 프롬프트 검색, 다운로드 및 탐색",
"Discover, download, and explore model presets": "모델 사전 설정 검색, 다운로드 및 탐색",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "채팅에서 'You' 대신 사용자 이름 표시",
"Document": "문서",
"Document Settings": "문서 설정",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Serpstack API Key 입력",
"Enter stop sequence": "중지 시퀀스 입력",
"Enter Tavily API Key": "",
"Enter Top K": "Top K 입력",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL 입력(예: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL 입력(예: http://localhost:11434)",
@ -487,6 +490,7 @@
"System": "시스템",
"System Prompt": "시스템 프롬프트",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "더 알려주세요:",
"Temperature": "Temperature",
"Template": "Template",
@ -517,6 +521,7 @@
"Today": "오늘",
"Toggle settings": "설정 전환",
"Toggle sidebar": "사이드바 전환",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Neteisingas atsakymas",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "prieš",
"Being lazy": "Būvimas tingiu",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Atrasti ir parsisiųsti užklausas",
"Discover, download, and explore model presets": "Atrasti ir parsisiųsti modelių konfigūracija",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Rodyti naudotojo vardą vietoje žodžio Jūs pokalbyje",
"Document": "Dokumentas",
"Document Settings": "Dokumento nuostatos",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "Įveskite pabaigos sekvenciją",
"Enter Tavily API Key": "",
"Enter Top K": "Įveskite Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Įveskite nuorodą (pvz. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Įveskite nuorododą (pvz. http://localhost:11434",
@ -490,6 +493,7 @@
"System": "Sistema",
"System Prompt": "Sistemos užklausa",
"Tags": "Žymos",
"Tavily API Key": "",
"Tell us more:": "Papasakokite daugiau",
"Temperature": "Temperatūra",
"Template": "Modelis",
@ -520,6 +524,7 @@
"Today": "Šiandien",
"Toggle settings": "Atverti/užverti parametrus",
"Toggle sidebar": "Atverti/užverti šoninį meniu",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Dårlig svar",
"Banners": "Bannere",
"Base Model (From)": "Grunnmodell (Fra)",
"Batch Size (num_batch)": "",
"before": "før",
"Being lazy": "Er lat",
"Brave Search API Key": "Brave Search API-nøkkel",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Oppdag, last ned og utforsk egendefinerte prompts",
"Discover, download, and explore model presets": "Oppdag, last ned og utforsk modellforhåndsinnstillinger",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Vis brukernavnet i stedet for Du i chatten",
"Document": "Dokument",
"Document Settings": "Dokumentinnstillinger",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Skriv inn Serpstack API-nøkkel",
"Enter stop sequence": "Skriv inn stoppsekvens",
"Enter Tavily API Key": "",
"Enter Top K": "Skriv inn Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Skriv inn URL (f.eks. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Skriv inn URL (f.eks. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "System",
"System Prompt": "Systemprompt",
"Tags": "Tagger",
"Tavily API Key": "",
"Tell us more:": "Fortell oss mer:",
"Temperature": "Temperatur",
"Template": "Mal",
@ -518,6 +522,7 @@
"Today": "I dag",
"Toggle settings": "Veksle innstillinger",
"Toggle sidebar": "Veksle sidefelt",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Ongeldig antwoord",
"Banners": "Banners",
"Base Model (From)": "Basismodel (vanaf)",
"Batch Size (num_batch)": "",
"before": "voor",
"Being lazy": "Lustig zijn",
"Brave Search API Key": "Brave Search API-sleutel",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Ontdek, download en verken aangepaste prompts",
"Discover, download, and explore model presets": "Ontdek, download en verken model presets",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Toon de gebruikersnaam in plaats van Jij in de Chat",
"Document": "Document",
"Document Settings": "Document Instellingen",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Voer de Serpstack API-sleutel in",
"Enter stop sequence": "Zet stop sequentie",
"Enter Tavily API Key": "",
"Enter Top K": "Voeg Top K toe",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Zet URL (Bijv. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Zet URL (Bijv. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "Systeem",
"System Prompt": "Systeem Prompt",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "Vertel ons meer:",
"Temperature": "Temperatuur",
"Template": "Template",
@ -518,6 +522,7 @@
"Today": "Vandaag",
"Toggle settings": "Wissel instellingen",
"Toggle sidebar": "Wissel sidebar",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "ਖਰਾਬ ਜਵਾਬ",
"Banners": "ਬੈਨਰ",
"Base Model (From)": "ਬੇਸ ਮਾਡਲ (ਤੋਂ)",
"Batch Size (num_batch)": "",
"before": "ਪਹਿਲਾਂ",
"Being lazy": "ਆਲਸੀ ਹੋਣਾ",
"Brave Search API Key": "ਬਹਾਦਰ ਖੋਜ API ਕੁੰਜੀ",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "ਕਸਟਮ ਪ੍ਰੰਪਟਾਂ ਨੂੰ ਖੋਜੋ, ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਪੜਚੋਲ ਕਰੋ",
"Discover, download, and explore model presets": "ਮਾਡਲ ਪ੍ਰੀਸੈਟਾਂ ਨੂੰ ਖੋਜੋ, ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਪੜਚੋਲ ਕਰੋ",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "ਗੱਲਬਾਤ 'ਚ ਤੁਹਾਡੇ ਸਥਾਨ 'ਤੇ ਉਪਭੋਗਤਾ ਨਾਮ ਦਿਖਾਓ",
"Document": "ਡਾਕੂਮੈਂਟ",
"Document Settings": "ਡਾਕੂਮੈਂਟ ਸੈਟਿੰਗਾਂ",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Serpstack API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ",
"Enter stop sequence": "ਰੋਕਣ ਦਾ ਕ੍ਰਮ ਦਰਜ ਕਰੋ",
"Enter Tavily API Key": "",
"Enter Top K": "ਸਿਖਰ K ਦਰਜ ਕਰੋ",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "ਸਿਸਟਮ",
"System Prompt": "ਸਿਸਟਮ ਪ੍ਰੰਪਟ",
"Tags": "ਟੈਗ",
"Tavily API Key": "",
"Tell us more:": "ਸਾਨੂੰ ਹੋਰ ਦੱਸੋ:",
"Temperature": "ਤਾਪਮਾਨ",
"Template": "ਟੈਮਪਲੇਟ",
@ -518,6 +522,7 @@
"Today": "ਅੱਜ",
"Toggle settings": "ਸੈਟਿੰਗਾਂ ਟੌਗਲ ਕਰੋ",
"Toggle sidebar": "ਸਾਈਡਬਾਰ ਟੌਗਲ ਕਰੋ",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "ਸਿਖਰ K",
"Top P": "ਸਿਖਰ P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Zła odpowiedź",
"Banners": "Banery",
"Base Model (From)": "Model podstawowy (od)",
"Batch Size (num_batch)": "",
"before": "przed",
"Being lazy": "Jest leniwy",
"Brave Search API Key": "Klucz API wyszukiwania Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Odkryj, pobierz i eksploruj niestandardowe prompty",
"Discover, download, and explore model presets": "Odkryj, pobierz i eksploruj ustawienia modeli",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Wyświetl nazwę użytkownika zamiast Ty w czacie",
"Document": "Dokument",
"Document Settings": "Ustawienia dokumentu",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Wprowadź klucz API Serpstack",
"Enter stop sequence": "Wprowadź sekwencję zatrzymania",
"Enter Tavily API Key": "",
"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 URL (e.g. http://localhost:11434)": "Wprowadź adres URL (np. http://localhost:11434/)",
@ -490,6 +493,7 @@
"System": "System",
"System Prompt": "Prompt systemowy",
"Tags": "Tagi",
"Tavily API Key": "",
"Tell us more:": "Powiedz nam więcej",
"Temperature": "Temperatura",
"Template": "Szablon",
@ -520,6 +524,7 @@
"Today": "Dzisiaj",
"Toggle settings": "Przełącz ustawienia",
"Toggle sidebar": "Przełącz panel boczny",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Najlepsze K",
"Top P": "Najlepsze P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Resposta ruim",
"Banners": "Banners",
"Base Model (From)": "Modelo Base (De)",
"Batch Size (num_batch)": "",
"before": "antes",
"Being lazy": "Ser preguiçoso",
"Brave Search API Key": "Chave da API de pesquisa do Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Descubra, baixe e explore prompts personalizados",
"Discover, download, and explore model presets": "Descubra, baixe e explore predefinições de modelo",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Exibir o nome de usuário em vez de Você no Bate-papo",
"Document": "Documento",
"Document Settings": "Configurações de Documento",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Digite a chave da API Serpstack",
"Enter stop sequence": "Digite a sequência de parada",
"Enter Tavily API Key": "",
"Enter Top K": "Digite o Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Digite a URL (por exemplo, http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sistema",
"System Prompt": "Prompt do Sistema",
"Tags": "Tags",
"Tavily API Key": "",
"Tell us more:": "Dê-nos mais:",
"Temperature": "Temperatura",
"Template": "Modelo",
@ -519,6 +523,7 @@
"Today": "Hoje",
"Toggle settings": "Alternar configurações",
"Toggle sidebar": "Alternar barra lateral",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Resposta má",
"Banners": "Estandartes",
"Base Model (From)": "Modelo Base (De)",
"Batch Size (num_batch)": "",
"before": "antes",
"Being lazy": "Ser preguiçoso",
"Brave Search API Key": "Chave da API de Pesquisa Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Descubra, descarregue e explore prompts personalizados",
"Discover, download, and explore model presets": "Descubra, descarregue e explore predefinições de modelo",
"Dismissible": "Dispensável",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Exibir o nome de utilizador em vez de Você na Conversa",
"Document": "Documento",
"Document Settings": "Configurações de Documento",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Escreva a chave da API Serpstack",
"Enter stop sequence": "Escreva a sequência de paragem",
"Enter Tavily API Key": "",
"Enter Top K": "Escreva o Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Escreva o URL (por exemplo, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Escreva o URL (por exemplo, http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Sistema",
"System Prompt": "Prompt do Sistema",
"Tags": "Etiquetas",
"Tavily API Key": "",
"Tell us more:": "Diga-nos mais:",
"Temperature": "Temperatura",
"Template": "Modelo",
@ -519,6 +523,7 @@
"Today": "Hoje",
"Toggle settings": "Alternar configurações",
"Toggle sidebar": "Alternar barra lateral",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Недопустимый ответ",
"Banners": "Баннеры",
"Base Model (From)": "Базовая модель (от)",
"Batch Size (num_batch)": "",
"before": "до",
"Being lazy": "ленивый",
"Brave Search API Key": "Ключ API поиска Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Находите, загружайте и исследуйте настраиваемые промты",
"Discover, download, and explore model presets": "Находите, загружайте и исследуйте предустановки модели",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Отображать имя пользователя вместо 'Вы' в чате",
"Document": "Документ",
"Document Settings": "Настройки документа",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Введите ключ API Serpstack",
"Enter stop sequence": "Введите последовательность остановки",
"Enter Tavily API Key": "",
"Enter Top K": "Введите Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Введите URL-адрес (например, http://localhost:11434)",
@ -490,6 +493,7 @@
"System": "Система",
"System Prompt": "Системный промпт",
"Tags": "Теги",
"Tavily API Key": "",
"Tell us more:": "Пожалуйста, расскажите нам больше:",
"Temperature": "Температура",
"Template": "Шаблон",
@ -520,6 +524,7 @@
"Today": "Сегодня",
"Toggle settings": "Переключить настройки",
"Toggle sidebar": "Переключить боковую панель",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Лош одговор",
"Banners": "Барјаке",
"Base Model (From)": "Основни модел (од)",
"Batch Size (num_batch)": "",
"before": "пре",
"Being lazy": "Бити лењ",
"Brave Search API Key": "Апи кључ за храбру претрагу",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Откријте, преузмите и истражите прилагођене упите",
"Discover, download, and explore model presets": "Откријте, преузмите и истражите образце модела",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Прикажи корисничко име уместо Ти у чату",
"Document": "Документ",
"Document Settings": "Подешавања документа",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Унесите Серпстацк АПИ кључ",
"Enter stop sequence": "Унесите секвенцу заустављања",
"Enter Tavily API Key": "",
"Enter Top K": "Унесите Топ К",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Унесите адресу (нпр. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Унесите адресу (нпр. http://localhost:11434)",
@ -489,6 +492,7 @@
"System": "Систем",
"System Prompt": "Системски упит",
"Tags": "Ознаке",
"Tavily API Key": "",
"Tell us more:": "Реците нам више:",
"Temperature": "Температура",
"Template": "Шаблон",
@ -519,6 +523,7 @@
"Today": "Данас",
"Toggle settings": "Пребаци подешавања",
"Toggle sidebar": "Пребаци бочну траку",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Топ К",
"Top P": "Топ П",

View File

@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' eller '-1' för ingen utgång.",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' eller '-1' för inget utgångsdatum",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(t.ex. `sh webui.sh --api`)",
"(latest)": "(senaste)",
@ -12,15 +12,15 @@
"a user": "en användare",
"About": "Om",
"Account": "Konto",
"Account Activation Pending": "",
"Account Activation Pending": "Kontoaktivering väntar",
"Accurate information": "Exakt information",
"Active Users": "",
"Active Users": "Aktiva användare",
"Add": "Lägg till",
"Add a model id": "Lägga till ett modell-ID",
"Add a short description about what this model does": "Lägg till en kort beskrivning av vad den här modellen gör",
"Add a short title for this prompt": "Lägg till en kort titel för denna prompt",
"Add a short title for this prompt": "Lägg till en kort titel för denna instruktion",
"Add a tag": "Lägg till en tagg",
"Add custom prompt": "Lägg till en anpassad prompt",
"Add custom prompt": "Lägg till en anpassad instruktion",
"Add Docs": "Lägg till dokument",
"Add Files": "Lägg till filer",
"Add Memory": "Lägg till minne",
@ -30,10 +30,10 @@
"Add User": "Lägg till användare",
"Adjusting these settings will apply changes universally to all users.": "Justering av dessa inställningar kommer att tillämpa ändringar universellt för alla användare.",
"admin": "administratör",
"Admin": "",
"Admin": "Admin",
"Admin Panel": "Administrationspanel",
"Admin Settings": "Administratörsinställningar",
"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratörer har tillgång till alla verktyg hela tiden, medan användare behöver verktyg som tilldelas per modell i arbetsytan.",
"Advanced Parameters": "Avancerade parametrar",
"Advanced Params": "Avancerade parametrar",
"all": "alla",
@ -41,7 +41,7 @@
"All Users": "Alla användare",
"Allow": "Tillåt",
"Allow Chat Deletion": "Tillåt chattborttagning",
"Allow non-local voices": "",
"Allow non-local voices": "Tillåt icke-lokala röster",
"alphanumeric characters and hyphens": "alfanumeriska tecken och bindestreck",
"Already have an account?": "Har du redan ett konto?",
"an assistant": "en assistent",
@ -51,7 +51,7 @@
"API Key": "API-nyckel",
"API Key created.": "API-nyckel skapad.",
"API keys": "API-nycklar",
"April": "April",
"April": "april",
"Archive": "Arkiv",
"Archive All Chats": "Arkivera alla chattar",
"Archived Chats": "Arkiverade chattar",
@ -60,7 +60,7 @@
"Attach file": "Bifoga fil",
"Attention to detail": "Detaljerad uppmärksamhet",
"Audio": "Ljud",
"August": "Augusti",
"August": "augusti",
"Auto-playback response": "Automatisk uppspelning",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 bas-URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 bas-URL krävs.",
@ -68,16 +68,17 @@
"Back": "Tillbaka",
"Bad Response": "Felaktig respons",
"Banners": "Banners",
"Base Model (From)": "Basmodell (från)",
"before": "før",
"Base Model (From)": "Basmodell (Från)",
"Batch Size (num_batch)": "Batchstorlek (num_batch)",
"before": "för",
"Being lazy": "Lägg till",
"Brave Search API Key": "API-nyckel för modig sökning",
"Brave Search API Key": "API-nyckel för Brave Search",
"Bypass SSL verification for Websites": "Kringgå SSL-verifiering för webbplatser",
"Call": "",
"Call feature is not supported when using Web STT engine": "",
"Camera": "",
"Call": "Samtal",
"Call feature is not supported when using Web STT engine": "Samtalsfunktionen är inte kompatibel med Web Tal-till-text motor",
"Camera": "Kamera",
"Cancel": "Avbryt",
"Capabilities": "Kapacitet",
"Capabilities": "Kapaciteter",
"Change Password": "Ändra lösenord",
"Chat": "Chatt",
"Chat Bubble UI": "Chatbubblar UI",
@ -93,12 +94,12 @@
"Chunk Params": "Chunk-parametrar",
"Chunk Size": "Chunk-storlek",
"Citation": "Citat",
"Clear memory": "",
"Clear memory": "Rensa minnet",
"Click here for help.": "Klicka här för hjälp.",
"Click here to": "Klicka här för att",
"Click here to select": "Klicka här för att välja",
"Click here to select a csv file.": "Klicka här för att välja en csv-fil.",
"Click here to select a py file.": "",
"Click here to select a py file.": "Klicka här för att välja en python-fil.",
"Click here to select documents.": "Klicka här för att välja dokument.",
"click here.": "klicka här.",
"Click on the user role button to change a user's role.": "Klicka på knappen för användarroll för att ändra en användares roll.",
@ -109,10 +110,10 @@
"ComfyUI Base URL": "ComfyUI Base URL",
"ComfyUI Base URL is required.": "ComfyUI Base URL krävs.",
"Command": "Kommando",
"Concurrent Requests": "Samtidiga begäranden",
"Concurrent Requests": "Parallella anrop",
"Confirm Password": "Bekräfta lösenord",
"Connections": "Anslutningar",
"Contact Admin for WebUI Access": "",
"Contact Admin for WebUI Access": "Kontakta administratören för att få åtkomst till WebUI",
"Content": "Innehåll",
"Context Length": "Kontextlängd",
"Continue Response": "Fortsätt svar",
@ -133,15 +134,15 @@
"Custom": "Anpassad",
"Customize models for a specific purpose": "Anpassa modeller för ett specifikt syfte",
"Dark": "Mörk",
"Dashboard": "",
"Dashboard": "Instrumentpanel",
"Database": "Databas",
"December": "December",
"December": "december",
"Default": "Standard",
"Default (Automatic1111)": "Standard (Automatic1111)",
"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
"Default Model": "Standardmodell",
"Default model updated": "Standardmodell uppdaterad",
"Default Prompt Suggestions": "Standardpromptförslag",
"Default Prompt Suggestions": "Standardinstruktionsförslag",
"Default User Role": "Standardanvändarroll",
"delete": "radera",
"Delete": "Radera",
@ -156,42 +157,43 @@
"Description": "Beskrivning",
"Didn't fully follow instructions": "Följde inte instruktionerna",
"Discover a model": "Upptäck en modell",
"Discover a prompt": "Upptäck en prompt",
"Discover, download, and explore custom prompts": "Upptäck, ladda ner och utforska anpassade prompts",
"Discover a prompt": "Upptäck en instruktion",
"Discover, download, and explore custom prompts": "Upptäck, ladda ner och utforska anpassade instruktioner",
"Discover, download, and explore model presets": "Upptäck, ladda ner och utforska modellförinställningar",
"Dismissible": "",
"Dismissible": "Kan stängas",
"Display Emoji in Call": "Visa Emoji under samtal",
"Display the username instead of You in the Chat": "Visa användarnamnet istället för du i chatten",
"Document": "Dokument",
"Document Settings": "Dokumentinställningar",
"Documentation": "",
"Documentation": "Dokumentation",
"Documents": "Dokument",
"does not make any external connections, and your data stays securely on your locally hosted server.": "gör inga externa anslutningar, och dina data förblir säkra på din lokalt värdade server.",
"Don't Allow": "Tillåt inte",
"Don't have an account?": "Har du inte ett konto?",
"Don't like the style": "Du tycker inte om utseendet",
"Don't have an account?": "Har du inget konto?",
"Don't like the style": "Tycker inte om utseendet",
"Download": "Ladda ner",
"Download canceled": "Nedladdning avbruten",
"Download Database": "Ladda ner databas",
"Drop any files here to add to the conversation": "Släpp filer här för att lägga till i konversationen",
"Drop any files here to add to the conversation": "Släpp filer här för att lägga till i samtalet",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "t.ex. '30s', '10m'. Giltiga tidsenheter är 's', 'm', 'h'.",
"Edit": "Redigera",
"Edit Doc": "Redigera dokument",
"Edit User": "Redigera användare",
"Email": "E-post",
"Embedding Batch Size": "",
"Embedding Model": "Embeddingsmodell",
"Embedding Model Engine": "Embeddingsmodellmotor",
"Embedding model set to \"{{embedding_model}}\"": "Embeddingsmodell inställd på \"{{embedding_model}}\"",
"Embedding Batch Size": "Batchstorlek för inbäddning",
"Embedding Model": "Inbäddningsmodell",
"Embedding Model Engine": "Motor för inbäddningsmodell",
"Embedding model set to \"{{embedding_model}}\"": "Inbäddningsmodell inställd på \"{{embedding_model}}\"",
"Enable Chat History": "Aktivera chatthistorik",
"Enable Community Sharing": "Aktivera community-delning",
"Enable New Sign Ups": "Aktivera nya registreringar",
"Enable Web Search": "Aktivera webbsökning",
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Se till att din CSV-fil innehåller fyra kolumner i denna ordning: Namn, E-post, Lösenord, Roll.",
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Se till att din CSV-fil innehåller fyra kolumner i denna ordning: Name, Email, Password, Role.",
"Enter {{role}} message here": "Skriv {{role}} meddelande här",
"Enter a detail about yourself for your LLMs to recall": "Skriv en detalj om dig själv för att dina LLMs ska komma ihåg",
"Enter Brave Search API Key": "Ange API-nyckel för modig sökning",
"Enter Chunk Overlap": "Ange Chunk-överlappning",
"Enter Chunk Size": "Ange Chunk-storlek",
"Enter Brave Search API Key": "Ange API-nyckel för Brave Search",
"Enter Chunk Overlap": "Ange chunköverlappning",
"Enter Chunk Size": "Ange chunkstorlek",
"Enter Github Raw URL": "Ange Github Raw URL",
"Enter Google PSE API Key": "Ange Google PSE API-nyckel",
"Enter Google PSE Engine Id": "Ange Google PSE Engine Id",
@ -199,12 +201,13 @@
"Enter language codes": "Skriv språkkoder",
"Enter model tag (e.g. {{modelTag}})": "Ange modelltagg (t.ex. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Ange antal steg (t.ex. 50)",
"Enter Score": "Ange poäng",
"Enter Score": "Ange betyg",
"Enter Searxng Query URL": "Ange Searxng Query URL",
"Enter Serper API Key": "Ange Serper API-nyckel",
"Enter Serply API Key": "",
"Enter Serply API Key": "Ange Serply API-nyckel",
"Enter Serpstack API Key": "Ange Serpstack API-nyckel",
"Enter stop sequence": "Ange stoppsekvens",
"Enter Tavily API Key": "",
"Enter Top K": "Ange Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Ange URL (t.ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Ange URL (t.ex. http://localhost:11434)",
@ -216,38 +219,38 @@
"Experimental": "Experimentell",
"Export": "Export",
"Export All Chats (All Users)": "Exportera alla chattar (alla användare)",
"Export chat (.json)": "",
"Export chat (.json)": "Exportera chatt (.json)",
"Export Chats": "Exportera chattar",
"Export Documents Mapping": "Exportera dokumentmappning",
"Export Models": "Exportera modeller",
"Export Prompts": "Exportera prompts",
"Export Tools": "",
"External Models": "",
"Export Prompts": "Exportera instruktioner",
"Export Tools": "Exportera verktyg",
"External Models": "Externa modeller",
"Failed to create API Key.": "Misslyckades med att skapa API-nyckel.",
"Failed to read clipboard contents": "Misslyckades med att läsa urklippsinnehåll",
"Failed to update settings": "",
"February": "Februar",
"Feel free to add specific details": "Förfoga att lägga till specifika detaljer",
"Failed to update settings": "Misslyckades med att uppdatera inställningarna",
"February": "februari",
"Feel free to add specific details": "Tveka inte att lägga till specifika detaljer",
"File Mode": "Fil-läge",
"File not found.": "Fil hittades inte.",
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingeravtrycksmanipulering upptäckt: Kan inte använda initialer som avatar. Återställning till standardprofilbild.",
"Fluidly stream large external response chunks": "Flytande ström stora externa svarsblock",
"Focus chat input": "Fokusera chattindata",
"Fluidly stream large external response chunks": "Strömma flytande stora externa svarschunks",
"Focus chat input": "Fokusera på chattfältet",
"Followed instructions perfectly": "Följde instruktionerna perfekt",
"Format your variables using square brackets like this:": "Formatera dina variabler med hakparenteser så här:",
"Frequency Penalty": "Straff för frekvens",
"General": "Allmän",
"General Settings": "Allmänna inställningar",
"Generate Image": "",
"Generating search query": "Generera sökfråga",
"Generation Info": "Generasjon Info",
"Generate Image": "Generera bild",
"Generating search query": "Genererar sökfråga",
"Generation Info": "Info om generation",
"Good Response": "Bra svar",
"Google PSE API Key": "Google PSE API-nyckel",
"Google PSE Engine Id": "Google PSE Engine Id",
"h:mm a": "h:mm a",
"has no conversations.": "har ingen samtaler.",
"has no conversations.": "har inga samtal.",
"Hello, {{name}}": "Hej, {{name}}",
"Help": "Hjelp",
"Help": "Hjälp",
"Hide": "Dölj",
"How can I help you today?": "Hur kan jag hjälpa dig idag?",
"Hybrid Search": "Hybrid sökning",
@ -258,52 +261,52 @@
"Import Chats": "Importera chattar",
"Import Documents Mapping": "Importera dokumentmappning",
"Import Models": "Importera modeller",
"Import Prompts": "Importera prompts",
"Import Tools": "",
"Include `--api` flag when running stable-diffusion-webui": "Inkludera `--api`-flagga när du kör stabil-diffusion-webui",
"Import Prompts": "Importera instruktioner",
"Import Tools": "Importera verktyg",
"Include `--api` flag when running stable-diffusion-webui": "Inkludera flaggan `--api` när du kör stable-diffusion-webui",
"Info": "Information",
"Input commands": "Indatakommandon",
"Install from Github URL": "Installera från Github-URL",
"Instant Auto-Send After Voice Transcription": "",
"Instant Auto-Send After Voice Transcription": "Skicka automatiskt efter rösttranskribering",
"Interface": "Gränssnitt",
"Invalid Tag": "Ogiltig tagg",
"January": "januar",
"January": "januari",
"join our Discord for help.": "gå med i vår Discord för hjälp.",
"JSON": "JSON",
"JSON Preview": "Förhandsversion av JSON",
"July": "juli",
"June": "juni",
"JWT Expiration": "JWT-utgång",
"JWT Expiration": "JWT-utgångsdatum",
"JWT Token": "JWT-token",
"Keep Alive": "Håll vid liv",
"Keep Alive": "Keep Alive",
"Keyboard shortcuts": "Tangentbordsgenvägar",
"Knowledge": "",
"Knowledge": "Kunskap",
"Language": "Språk",
"Last Active": "Senast aktiv",
"Light": "Ljus",
"Listening...": "",
"LLMs can make mistakes. Verify important information.": "LLM:er kan göra misstag. Verifiera viktig information.",
"Local Models": "",
"Listening...": "Lyssnar...",
"LLMs can make mistakes. Verify important information.": "LLM:er kan göra misstag. Granska viktig information.",
"Local Models": "Lokala modeller",
"LTR": "LTR",
"Made by OpenWebUI Community": "Skapad av OpenWebUI Community",
"Make sure to enclose them with": "Se till att bifoga dem med",
"Manage": "",
"Manage": "Hantera",
"Manage Models": "Hantera modeller",
"Manage Ollama Models": "Hantera Ollama-modeller",
"Manage Pipelines": "Hantera pipelines",
"Manage Pipelines": "Hantera rörledningar",
"March": "mars",
"Max Tokens (num_predict)": "Maximalt antal polletter (num_predict)",
"Max Tokens (num_predict)": "Maximalt antal tokens (num_predict)",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Högst 3 modeller kan laddas ner samtidigt. Vänligen försök igen senare.",
"May": "mai",
"Memories accessible by LLMs will be shown here.": "Minnen som kan komma ihåg av LLM:er kommer att visas här.",
"May": "maj",
"Memories accessible by LLMs will be shown here.": "Minnen som LLM:er kan komma åt visas här.",
"Memory": "Minnen",
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Meddelanden du skickar efter att ha skapat din länk kommer inte att delas. Användare med URL:en kommer att kunna se delad chatt.",
"Minimum Score": "Minimum poäng",
"Minimum Score": "Tröskel",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, ÅÅÅÅ",
"MMMM DD, YYYY HH:mm": "MMMM DD, ÅÅÅÅ HH:mm",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"Model '{{modelName}}' has been successfully downloaded.": "Modellen '{{modelName}}' har laddats ner framgångsrikt.",
"Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' är redan i kö för nedladdning.",
"Model {{modelId}} not found": "Modell {{modelId}} hittades inte",
@ -314,7 +317,7 @@
"Model not selected": "Modell inte vald",
"Model Params": "Modell Params",
"Model Whitelisting": "Modellens vitlista",
"Model(s) Whitelisted": "Modell(er) vitlistade",
"Model(s) Whitelisted": "Vitlistade modeller",
"Modelfile Content": "Modelfilens innehåll",
"Models": "Modeller",
"More": "Mer",
@ -323,24 +326,24 @@
"Name your model": "Namnge din modell",
"New Chat": "Ny chatt",
"New Password": "Nytt lösenord",
"No documents found": "",
"No documents found": "Inga dokument hittades",
"No results found": "Inga resultat hittades",
"No search query generated": "Ingen sökfråga genererad",
"No source available": "Ingen tilgjengelig kilde",
"No source available": "Ingen tillgänglig källa",
"None": "Ingen",
"Not factually correct": "Inte faktiskt korrekt",
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Merk: Hvis du angir en minimumspoengsum, returnerer søket bare dokumenter med en poengsum som er større enn eller lik minimumspoengsummen.",
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Obs: Om du anger en tröskel kommer sökningen endast att returnera dokument med ett betyg som är större än eller lika med tröskeln.",
"Notifications": "Notifikationer",
"November": "november",
"num_thread (Ollama)": "num_thread (Ollama)",
"October": "oktober",
"Off": "Av",
"Okay, Let's Go!": "Okej, nu kör vi!",
"OLED Dark": "OLED mörkt",
"OLED Dark": "Mörk (OLED)",
"Ollama": "Ollama",
"Ollama API": "Ollama API",
"Ollama API disabled": "Ollama API inaktiverat",
"Ollama API is disabled": "",
"Ollama API is disabled": "Ollama API är inaktiverat",
"Ollama Version": "Ollama-version",
"On": "På",
"Only": "Endast",
@ -363,50 +366,50 @@
"PDF document (.pdf)": "PDF-dokument (.pdf)",
"PDF Extract Images (OCR)": "PDF Extrahera bilder (OCR)",
"pending": "väntande",
"Permission denied when accessing media devices": "",
"Permission denied when accessing microphone": "",
"Permission denied when accessing media devices": "Nekad behörighet vid åtkomst till mediaenheter",
"Permission denied when accessing microphone": "Nekad behörighet vid åtkomst till mikrofon",
"Permission denied when accessing microphone: {{error}}": "Tillstånd nekades vid åtkomst till mikrofon: {{error}}",
"Personalization": "Personalisering",
"Pipelines": "Rörledningar",
"Pipelines Valves": "Rörledningar Ventiler",
"Plain text (.txt)": "Rå text (.txt)",
"Pipelines Valves": "Ventiler för rörledningar",
"Plain text (.txt)": "Text (.txt)",
"Playground": "Lekplats",
"Positive attitude": "Positivt humör",
"Positive attitude": "Positivt inställning",
"Previous 30 days": "Föregående 30 dagar",
"Previous 7 days": "Föregående 7 dagar",
"Profile Image": "Profilbild",
"Prompt": "Prompt",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (t.ex. Berätta mig en rolig faktor om Romerska Imperiet)",
"Prompt Content": "Promptinnehåll",
"Prompt suggestions": "Förslag",
"Prompts": "Prompts",
"Pull \"{{searchValue}}\" from Ollama.com": "Dra \"{{searchValue}}\" från Ollama.com",
"Pull a model from Ollama.com": "Dra en modell från Ollama.com",
"Query Params": "Frågeparametrar",
"Prompt": "Instruktion",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Instruktion (t.ex. Berätta en kuriosa om Romerska Imperiet)",
"Prompt Content": "Instruktionens innehåll",
"Prompt suggestions": "Instruktionsförslag",
"Prompts": "Instruktioner",
"Pull \"{{searchValue}}\" from Ollama.com": "Ladda ner \"{{searchValue}}\" från Ollama.com",
"Pull a model from Ollama.com": "Ladda ner en modell från Ollama.com",
"Query Params": "Inställningar för sökfråga",
"RAG Template": "RAG-mall",
"Read Aloud": "Läs igenom",
"Record voice": "Spela in röst",
"Redirecting you to OpenWebUI Community": "Omdirigerar dig till OpenWebUI Community",
"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
"Refused when it shouldn't have": "Avvisades när det inte borde ha",
"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Referera till dig själv som \"Användare\" (t.ex. \"Användaren lär sig spanska\")",
"Refused when it shouldn't have": "Avvisades när det inte borde ha gjort det",
"Regenerate": "Regenerera",
"Release Notes": "Versionsinformation",
"Remove": "Ta bort",
"Remove Model": "Ta bort modell",
"Rename": "Byt namn",
"Repeat Last N": "Upprepa senaste N",
"Request Mode": "Begär läge",
"Request Mode": "Frågeläge",
"Reranking Model": "Reranking modell",
"Reranking model disabled": "Reranking modell inaktiverad",
"Reranking model set to \"{{reranking_model}}\"": "Reranking modell inställd på \"{{reranking_model}}\"",
"Reset Upload Directory": "",
"Reset Upload Directory": "Återställ uppladdningskatalog",
"Reset Vector Storage": "Återställ vektorlager",
"Response AutoCopy to Clipboard": "Svara AutoCopy till urklipp",
"Role": "Roll",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"RTL": "RTL",
"Running": "",
"Running": "Kör",
"Save": "Spara",
"Save & Create": "Spara och skapa",
"Save & Update": "Spara och uppdatera",
@ -419,35 +422,35 @@
"Search Chats": "Sök i chattar",
"Search Documents": "Sök dokument",
"Search Models": "Sök modeller",
"Search Prompts": "Sök promptar",
"Search Query Generation Prompt": "",
"Search Query Generation Prompt Length Threshold": "",
"Search Prompts": "Sök instruktioner",
"Search Query Generation Prompt": "Instruktion för generering av sökfrågor",
"Search Query Generation Prompt Length Threshold": "Tröskelvärde för generering av sökfrågor",
"Search Result Count": "Antal sökresultat",
"Search Tools": "",
"Search Tools": "Sökverktyg",
"Searched {{count}} sites_one": "Sökte på {{count}} sites_one",
"Searched {{count}} sites_other": "Sökte på {{count}} sites_other",
"Searching \"{{searchQuery}}\"": "",
"Searching \"{{searchQuery}}\"": "Söker \"{{searchQuery}}\"",
"Searxng Query URL": "Searxng Query URL",
"See readme.md for instructions": "Se readme.md för instruktioner",
"See what's new": "Se vad som är nytt",
"Seed": "Seed",
"Select a base model": "Välj en basmodell",
"Select a engine": "",
"Select a engine": "Välj en motor",
"Select a mode": "Välj ett läge",
"Select a model": "Välj en modell",
"Select a pipeline": "Välj en pipeline",
"Select a pipeline url": "Välj en pipeline-URL",
"Select a pipeline": "Välj en rörledning",
"Select a pipeline url": "Välj en URL för rörledningen",
"Select an Ollama instance": "Välj en Ollama-instans",
"Select Documents": "",
"Select Documents": "Välj dokument",
"Select model": "Välj en modell",
"Select only one model to call": "",
"Select only one model to call": "Välj endast en modell att ringa",
"Selected model(s) do not support image inputs": "Valda modeller stöder inte bildinmatningar",
"Send": "Skicka",
"Send a Message": "Skicka ett meddelande",
"Send message": "Skicka meddelande",
"September": "september",
"Serper API Key": "Serper API-nyckel",
"Serply API Key": "",
"Serply API Key": "Serply API-nyckel",
"Serpstack API Key": "Serpstack API-nyckel",
"Server connection verified": "Serveranslutning verifierad",
"Set as default": "Ange som standard",
@ -460,15 +463,15 @@
"Set Voice": "Ange röst",
"Settings": "Inställningar",
"Settings saved successfully!": "Inställningar sparades framgångsrikt!",
"Settings updated successfully": "",
"Settings updated successfully": "Inställningar uppdaterades framgångsrikt",
"Share": "Dela",
"Share Chat": "Dela chatt",
"Share to OpenWebUI Community": "Dela till OpenWebUI Community",
"short-summary": "kort sammanfattning",
"Show": "Visa",
"Show Admin Details in Account Pending Overlay": "",
"Show Admin Details in Account Pending Overlay": "Visa administratörsinformation till väntande konton",
"Show shortcuts": "Visa genvägar",
"Showcased creativity": "Visuell kreativitet",
"Showcased creativity": "Visade kreativitet",
"sidebar": "sidofält",
"Sign in": "Logga in",
"Sign Out": "Logga ut",
@ -478,16 +481,17 @@
"Speech recognition error: {{error}}": "Fel vid taligenkänning: {{error}}",
"Speech-to-Text Engine": "Tal-till-text-motor",
"Stop Sequence": "Stoppsekvens",
"STT Model": "",
"STT Settings": "STT-inställningar",
"STT Model": "Tal-till-text-modell",
"STT Settings": "Tal-till-text-inställningar",
"Submit": "Skicka in",
"Subtitle (e.g. about the Roman Empire)": "Undertext (t.ex. om Romerska Imperiet)",
"Success": "Framgång",
"Successfully updated.": "Uppdaterades framgångsrikt.",
"Suggested": "Föreslagen",
"System": "System",
"System Prompt": "Systemprompt",
"System Prompt": "Systeminstruktion",
"Tags": "Taggar",
"Tavily API Key": "",
"Tell us more:": "Berätta mer:",
"Temperature": "Temperatur",
"Template": "Mall",
@ -495,48 +499,49 @@
"Text-to-Speech Engine": "Text-till-tal-motor",
"Tfs Z": "Tfs Z",
"Thanks for your feedback!": "Tack för din feedback!",
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Poäng ska vara ett värde mellan 0,0 (0%) och 1,0 (100%).",
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Betyget ska vara ett värde mellan 0.0 (0%) och 1.0 (100%).",
"Theme": "Tema",
"Thinking...": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Detta säkerställer att dina värdefulla konversationer sparas säkert till din backend-databas. Tack!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Detta säkerställer att dina värdefulla samtal sparas säkert till din backend-databas. Tack!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Detta är en experimentell funktion som kanske inte fungerar som förväntat och som kan komma att ändras när som helst.",
"This setting does not sync across browsers or devices.": "Denna inställning synkroniseras inte mellan webbläsare eller enheter.",
"Thorough explanation": "Djupare förklaring",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Uppdatera flera variabelplatser efter varandra genom att trycka på tabb-tangenten i chattinmatningen efter varje ersättning.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Uppdatera fler variabler genom att trycka på tabb-tangenten i chattinmatningen efter varje ersättning.",
"Title": "Titel",
"Title (e.g. Tell me a fun fact)": "Tittel (f.eks. Fortell meg en fun fact)",
"Title (e.g. Tell me a fun fact)": "Titel (t.ex. Berätta en kuriosa)",
"Title Auto-Generation": "Automatisk generering av titel",
"Title cannot be an empty string.": "Tittel kan ikke være en tom streng.",
"Title Generation Prompt": "Titelgenereringsprompt",
"Title cannot be an empty string.": "Titeln får inte vara en tom sträng.",
"Title Generation Prompt": "Instruktion för titelgenerering",
"to": "till",
"To access the available model names for downloading,": "För att komma åt de tillgängliga modellnamnen för nedladdning,",
"To access the GGUF models available for downloading,": "För att komma åt de GGUF-modeller som finns tillgängliga för nedladdning,",
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
"To add documents here, upload them to the \"Documents\" workspace first.": "",
"To access the GGUF models available for downloading,": "För att komma åt de GGUF-modellerna som finns tillgängliga för nedladdning,",
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "För att få tillgång till WebUI, kontakta administratören. Administratörer kan hantera behörigheter från administrationspanelen.",
"To add documents here, upload them to the \"Documents\" workspace first.": "Om du vill lägga till dokument här ska du först ladda upp dem till arbetsytan \"Dokument\".",
"to chat input.": "till chattinmatning.",
"To select toolkits here, add them to the \"Tools\" workspace first.": "",
"To select toolkits here, add them to the \"Tools\" workspace first.": "Om du vill välja verktygslådor här måste du först lägga till dem i arbetsytan \"Verktyg\".",
"Today": "Idag",
"Toggle settings": "Växla inställningar",
"Toggle sidebar": "Växla sidofält",
"Tools": "",
"Tokens To Keep On Context Refresh (num_keep)": "Tokens att behålla vid kontextuppdatering (num_keep)",
"Tools": "Verktyg",
"Top K": "Topp K",
"Top P": "Topp P",
"Trouble accessing Ollama?": "Problem med att komma åt Ollama?",
"TTS Model": "",
"TTS Settings": "TTS-inställningar",
"TTS Voice": "",
"TTS Model": "Text-till-tal-modell",
"TTS Settings": "Text-till-tal-inställningar",
"TTS Voice": "Text-till-tal-röst",
"Type": "Typ",
"Type Hugging Face Resolve (Download) URL": "Skriv Hugging Face Resolve (nedladdning) URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "Oj då! Det uppstod ett problem med att ansluta till {{provider}}.",
"Uh-oh! There was an issue connecting to {{provider}}.": "Oj då! Det uppstod ett problem med anslutningen till {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Okänd filtyp '{{file_type}}', men accepterar och behandlar som vanlig text",
"Update and Copy Link": "Uppdatera och kopiera länk",
"Update password": "Uppdatera lösenord",
"Upload a GGUF model": "Ladda upp en GGUF-modell",
"Upload Files": "Ladda upp filer",
"Upload Pipeline": "",
"Upload Progress": "Uppladdningsförlopp",
"Upload Pipeline": "Ladda upp rörledning",
"Upload Progress": "Uppladdningsframsteg",
"URL Mode": "URL-läge",
"Use '#' in the prompt input to load and select your documents.": "Använd '#' i promptinmatningen för att ladda och välja dina dokument.",
"Use '#' in the prompt input to load and select your documents.": "Använd '#' i instruktionsinmatningen för att ladda och välja dina dokument.",
"Use Gravatar": "Använd Gravatar",
"Use Initials": "Använd initialer",
"use_mlock (Ollama)": "use_mlock (Ollama)",
@ -552,31 +557,31 @@
"Warning": "Varning",
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Varning: Om du uppdaterar eller ändrar din embedding modell måste du importera alla dokument igen.",
"Web": "Webb",
"Web API": "",
"Web API": "Webb-API",
"Web Loader Settings": "Web Loader-inställningar",
"Web Params": "Web-parametrar",
"Web Search": "Webbsökning",
"Web Search Engine": "Sökmotor på webben",
"Web Search Engine": "Webbsökmotor",
"Webhook URL": "Webhook-URL",
"WebUI Add-ons": "WebUI-tillägg",
"WebUI Settings": "WebUI-inställningar",
"WebUI will make requests to": "WebUI kommer att skicka förfrågningar till",
"Whats New in": "Vad är nytt i",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "När historiken är avstängd visas inte nya chattar i denna webbläsare i din historik på någon av dina enheter.",
"Whisper (Local)": "",
"Widescreen Mode": "",
"Workspace": "arbetsyta",
"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett förslag (t.ex. Vem är du?)",
"Whisper (Local)": "Whisper (lokal)",
"Widescreen Mode": "Bredbildsläge",
"Workspace": "Arbetsyta",
"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett instruktionsförslag (t.ex. Vem är du?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
"Yesterday": "Igenom",
"You": "du",
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"Yesterday": "Igår",
"You": "Dig",
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan anpassa dina interaktioner med stora språkmodeller genom att lägga till minnen via knappen 'Hantera' nedan, så att de blir mer användbara och skräddarsydda för dig.",
"You cannot clone a base model": "Du kan inte klona en basmodell",
"You have no archived conversations.": "Du har inga arkiverade konversationer.",
"You have no archived conversations.": "Du har inga arkiverade samtal.",
"You have shared this chat": "Du har delat denna chatt",
"You're a helpful assistant.": "Du är en hjälpsam assistent.",
"You're now logged in.": "Du är nu inloggad.",
"Your account status is currently pending activation.": "",
"Your account status is currently pending activation.": "Ditt konto väntar på att bli aktiverat",
"Youtube": "Youtube",
"Youtube Loader Settings": "Youtube Loader-inställningar"
}

View File

@ -69,6 +69,7 @@
"Bad Response": "",
"Banners": "",
"Base Model (From)": "",
"Batch Size (num_batch)": "",
"before": "",
"Being lazy": "",
"Brave Search API Key": "",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "",
"Discover, download, and explore model presets": "",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "",
"Document": "",
"Document Settings": "",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "",
"Enter stop sequence": "",
"Enter Tavily API Key": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -488,6 +491,7 @@
"System": "",
"System Prompt": "",
"Tags": "",
"Tavily API Key": "",
"Tell us more:": "",
"Temperature": "",
"Template": "",
@ -518,6 +522,7 @@
"Today": "",
"Toggle settings": "",
"Toggle sidebar": "",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "",
"Top P": "",

View File

@ -69,6 +69,7 @@
"Bad Response": "Kötü Yanıt",
"Banners": "Afişler",
"Base Model (From)": "Temel Model ('den)",
"Batch Size (num_batch)": "",
"before": "önce",
"Being lazy": "Tembelleşiyor",
"Brave Search API Key": "Brave Search API Anahtarı",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Özel promptları keşfedin, indirin ve inceleyin",
"Discover, download, and explore model presets": "Model ön ayarlarını keşfedin, indirin ve inceleyin",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Sohbet'te Siz yerine kullanıcı adını göster",
"Document": "Belge",
"Document Settings": "Belge Ayarları",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Serpstack API Anahtarını Girin",
"Enter stop sequence": "Durdurma dizisini girin",
"Enter Tavily API Key": "",
"Enter Top K": "Top K'yı girin",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL'yi Girin (e.g. http://localhost:11434)",
@ -488,6 +491,7 @@
"System": "Sistem",
"System Prompt": "Sistem Promptu",
"Tags": "Etiketler",
"Tavily API Key": "",
"Tell us more:": "Bize daha fazlasını anlat:",
"Temperature": "Temperature",
"Template": "Şablon",
@ -518,6 +522,7 @@
"Today": "Bugün",
"Toggle settings": "Ayarları Aç/Kapat",
"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Неправильна відповідь",
"Banners": "Прапори",
"Base Model (From)": "Базова модель (від)",
"Batch Size (num_batch)": "",
"before": "до того, як",
"Being lazy": "Не поспішати",
"Brave Search API Key": "Ключ API пошуку Brave",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Знайдіть, завантажте та досліджуйте налаштовані промти",
"Discover, download, and explore model presets": "Знайдіть, завантажте та досліджуйте налаштовані налаштування моделі",
"Dismissible": "Неприйнятно",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Показувати ім'я користувача замість 'Ви' в чаті",
"Document": "Документ",
"Document Settings": "Налаштування документа",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "Введіть ключ API Serpstack",
"Enter stop sequence": "Введіть символ зупинки",
"Enter Tavily API Key": "",
"Enter Top K": "Введіть Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введіть URL-адресу (напр., http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Введіть URL-адресу (напр., http://localhost:11434)",
@ -490,6 +493,7 @@
"System": "Система",
"System Prompt": "Системний промт",
"Tags": "Теги",
"Tavily API Key": "",
"Tell us more:": "Розкажи нам більше:",
"Temperature": "Температура",
"Template": "Шаблон",
@ -520,6 +524,7 @@
"Today": "Сьогодні",
"Toggle settings": "Переключити налаштування",
"Toggle sidebar": "Переключити бокову панель",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -69,6 +69,7 @@
"Bad Response": "Trả lời KHÔNG tốt",
"Banners": "Biểu ngữ",
"Base Model (From)": "Mô hình cơ sở (từ)",
"Batch Size (num_batch)": "",
"before": "trước",
"Being lazy": "Lười biếng",
"Brave Search API Key": "Khóa API tìm kiếm dũng cảm",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "Tìm kiếm, tải về và khám phá thêm các prompt tùy chỉnh",
"Discover, download, and explore model presets": "Tìm kiếm, tải về và khám phá thêm các thiết lập mô hình sẵn",
"Dismissible": "Có thể loại bỏ",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "Hiển thị tên người sử dụng thay vì 'Bạn' trong nội dung chat",
"Document": "Tài liệu",
"Document Settings": "Cấu hình kho tài liệu",
@ -205,6 +207,7 @@
"Enter Serply API Key": "Nhập Serply API Key",
"Enter Serpstack API Key": "Nhập Serpstack API Key",
"Enter stop sequence": "Nhập stop sequence",
"Enter Tavily API Key": "",
"Enter Top K": "Nhập Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Nhập URL (vd: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Nhập URL (vd: http://localhost:11434)",
@ -487,6 +490,7 @@
"System": "Hệ thống",
"System Prompt": "Prompt Hệ thống (System Prompt)",
"Tags": "Thẻ",
"Tavily API Key": "",
"Tell us more:": "Hãy cho chúng tôi hiểu thêm về chất lượng của câu trả lời:",
"Temperature": "Mức độ sáng tạo",
"Template": "Mẫu",
@ -517,6 +521,7 @@
"Today": "Hôm nay",
"Toggle settings": "Bật/tắt cài đặt",
"Toggle sidebar": "Bật/tắt thanh bên",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -33,7 +33,7 @@
"Admin": "管理员联系方式",
"Admin Panel": "管理员面板",
"Admin Settings": "管理员设置",
"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理员拥有所有工具的访问权限;用户则需在工作空间中为每个模型单独分配工具。",
"Advanced Parameters": "高级参数",
"Advanced Params": "高级参数",
"all": "所有",
@ -69,6 +69,7 @@
"Bad Response": "点踩回复",
"Banners": "公告横幅",
"Base Model (From)": "基础模型 (来自)",
"Batch Size (num_batch)": "批大小 (num_batch)",
"before": "对话",
"Being lazy": "懒惰",
"Brave Search API Key": "Brave Search API 密钥",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "发现、下载并探索更多自定义提示词",
"Discover, download, and explore model presets": "发现、下载并探索更多模型预设",
"Dismissible": "是否可关闭",
"Display Emoji in Call": "在通话中显示 Emoji 表情符号",
"Display the username instead of You in the Chat": "在对话中显示用户名而不是“你”",
"Document": "文档",
"Document Settings": "文档设置",
@ -202,9 +204,10 @@
"Enter Score": "输入评分",
"Enter Searxng Query URL": "输入 Searxng 查询地址",
"Enter Serper API Key": "输入 Serper API 密钥",
"Enter Serply API Key": "",
"Enter Serply API Key": "输入 Serply API 密钥",
"Enter Serpstack API Key": "输入 Serpstack API 密钥",
"Enter stop sequence": "输入停止序列 (Stop Sequence)",
"Enter Tavily API Key": "",
"Enter Top K": "输入 Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "输入地址 (例如http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "输入地址 (例如http://localhost:11434)",
@ -424,7 +427,7 @@
"Search Query Generation Prompt Length Threshold": "搜索查询生成提示长度阈值",
"Search Result Count": "搜索结果数量",
"Search Tools": "搜索工具",
"Searched {{count}} sites_other": "检索到 {{count}} 个网站",
"Searched {{count}} sites_other": "搜索到 {{count}} 个结果",
"Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中",
"Searxng Query URL": "Searxng 查询 URL",
"See readme.md for instructions": "查看 readme.md 以获取说明",
@ -487,6 +490,7 @@
"System": "系统",
"System Prompt": "系统提示词",
"Tags": "标签",
"Tavily API Key": "",
"Tell us more:": "请告诉我们更多细节",
"Temperature": "温度 (Temperature)",
"Template": "模板",
@ -513,10 +517,11 @@
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "请联系管理员以访问。管理员可以在后台管理面板中管理用户状态。",
"To add documents here, upload them to the \"Documents\" workspace first.": "要在此处添加文档,请先将它们上传到工作空间中的“文档”内。",
"to chat input.": "到对话输入。",
"To select toolkits here, add them to the \"Tools\" workspace first.": "",
"To select toolkits here, add them to the \"Tools\" workspace first.": "要在这里选择工具包,请先将它们添加到工作空间中的“工具”。",
"Today": "今天",
"Toggle settings": "切换设置",
"Toggle sidebar": "切换侧边栏",
"Tokens To Keep On Context Refresh (num_keep)": "在语境刷新时需保留的 Tokens",
"Tools": "工具",
"Top K": "Top K",
"Top P": "Top P",
@ -555,7 +560,7 @@
"Web Loader Settings": "网页爬取设置",
"Web Params": "网络爬取设置",
"Web Search": "网络搜索",
"Web Search Engine": "Web 搜索引擎",
"Web Search Engine": "网络搜索引擎",
"Webhook URL": "Webhook URL",
"WebUI Add-ons": "WebUI 附加组件",
"WebUI Settings": "WebUI 设置",

View File

@ -69,6 +69,7 @@
"Bad Response": "錯誤回應",
"Banners": "橫幅",
"Base Model (From)": "基本模型(來自)",
"Batch Size (num_batch)": "",
"before": "前",
"Being lazy": "懶人模式",
"Brave Search API Key": "搜尋 API Key",
@ -160,6 +161,7 @@
"Discover, download, and explore custom prompts": "發現、下載並探索他人設置的提示詞",
"Discover, download, and explore model presets": "發現、下載並探索他人設置的模型",
"Dismissible": "",
"Display Emoji in Call": "",
"Display the username instead of You in the Chat": "在聊天中顯示使用者名稱而不是「你」",
"Document": "文件",
"Document Settings": "文件設定",
@ -205,6 +207,7 @@
"Enter Serply API Key": "",
"Enter Serpstack API Key": "輸入 Serpstack API Key",
"Enter stop sequence": "輸入停止序列",
"Enter Tavily API Key": "",
"Enter Top K": "輸入 Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL例如 http://127.0.0.1:7860/",
"Enter URL (e.g. http://localhost:11434)": "輸入 URL例如 http://localhost:11434",
@ -487,6 +490,7 @@
"System": "系統",
"System Prompt": "系統提示詞",
"Tags": "標籤",
"Tavily API Key": "",
"Tell us more:": "告訴我們更多:",
"Temperature": "溫度",
"Template": "模板",
@ -517,6 +521,7 @@
"Today": "今天",
"Toggle settings": "切換設定",
"Toggle sidebar": "切換側邊欄",
"Tokens To Keep On Context Refresh (num_keep)": "",
"Tools": "",
"Top K": "Top K",
"Top P": "Top P",

View File

@ -92,6 +92,8 @@ type Settings = {
top_k?: string;
top_p?: string;
num_ctx?: string;
num_batch?: string;
num_keep?: string;
options?: ModelOptions;
};

View File

@ -436,13 +436,31 @@ export const removeEmojis = (str) => {
export const extractSentences = (text) => {
// Split the paragraph into sentences based on common punctuation marks
const sentences = text.split(/(?<=[.!?])/);
const sentences = text.split(/(?<=[.!?])\s+/);
return sentences
.map((sentence) => removeEmojis(sentence.trim()))
.filter((sentence) => sentence !== '');
};
export const extractSentencesForAudio = (text) => {
return extractSentences(text).reduce((mergedTexts, currentText) => {
const lastIndex = mergedTexts.length - 1;
if (lastIndex >= 0) {
const previousText = mergedTexts[lastIndex];
const wordCount = previousText.split(/\s+/).length;
if (wordCount < 2) {
mergedTexts[lastIndex] = previousText + ' ' + currentText;
} else {
mergedTexts.push(currentText);
}
} else {
mergedTexts.push(currentText);
}
return mergedTexts;
}, []);
};
export const blobToFile = (blob, fileName) => {
// Create a new File object from the Blob
const file = new File([blob], fileName, { type: blob.type });

View File

@ -18,6 +18,7 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
import UserChatsModal from '$lib/components/admin/UserChatsModal.svelte';
import AddUserModal from '$lib/components/admin/AddUserModal.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
const i18n = getContext('i18n');
@ -30,6 +31,7 @@
let page = 1;
let showDeleteConfirmDialog = false;
let showAddUserModal = false;
let showUserChatsModal = false;
@ -77,6 +79,13 @@
});
</script>
<ConfirmDialog
bind:show={showDeleteConfirmDialog}
on:confirm={() => {
deleteUserHandler(selectedUser.id);
}}
/>
{#key selectedUser}
<EditUserModal
bind:show={showEditUserModal}
@ -256,7 +265,8 @@
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={async () => {
deleteUserHandler(user.id);
showDeleteConfirmDialog = true;
selectedUser = user;
}}
>
<svg