Commit 30d10480 authored by Jannik Streidl's avatar Jannik Streidl
Browse files

resolved the conflicts 😍

parents 142f82ba 5e168e6f
...@@ -29,6 +29,9 @@ jobs: ...@@ -29,6 +29,9 @@ jobs:
- name: Format Frontend - name: Format Frontend
run: npm run format run: npm run format
- name: Run i18next
run: npm run i18n:parse
- name: Check for Changes After Format - name: Check for Changes After Format
run: git diff --exit-code run: git diff --exit-code
......
...@@ -53,3 +53,134 @@ jobs: ...@@ -53,3 +53,134 @@ jobs:
name: compose-logs name: compose-logs
path: compose-logs.txt path: compose-logs.txt
if-no-files-found: ignore if-no-files-found: ignore
migration_test:
name: Run Migration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
# mysql:
# image: mysql
# env:
# MYSQL_ROOT_PASSWORD: mysql
# MYSQL_DATABASE: mysql
# options: >-
# --health-cmd "mysqladmin ping -h localhost"
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# ports:
# - 3306:3306
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Set up uv
uses: yezz123/setup-uv@v4
with:
uv-venv: venv
- name: Activate virtualenv
run: |
. venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
- name: Install dependencies
run: |
uv pip install -r backend/requirements.txt
- name: Test backend with SQLite
id: sqlite
env:
WEBUI_SECRET_KEY: secret-key
GLOBAL_LOG_LEVEL: debug
run: |
cd backend
uvicorn main:app --port "8080" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 20 seconds for the server to start
for i in {1..20}; do
curl -s http://localhost:8080/api/config > /dev/null && break
sleep 1
if [ $i -eq 20 ]; then
echo "Server failed to start"
kill -9 $UVICORN_PID
exit 1
fi
done
# Check that the server is still running after 5 seconds
sleep 5
if ! kill -0 $UVICORN_PID; then
echo "Server has stopped"
exit 1
fi
- name: Test backend with Postgres
if: success() || steps.sqlite.conclusion == 'failure'
env:
WEBUI_SECRET_KEY: secret-key
GLOBAL_LOG_LEVEL: debug
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
run: |
cd backend
uvicorn main:app --port "8081" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 20 seconds for the server to start
for i in {1..20}; do
curl -s http://localhost:8081/api/config > /dev/null && break
sleep 1
if [ $i -eq 20 ]; then
echo "Server failed to start"
kill -9 $UVICORN_PID
exit 1
fi
done
# Check that the server is still running after 5 seconds
sleep 5
if ! kill -0 $UVICORN_PID; then
echo "Server has stopped"
exit 1
fi
# - name: Test backend with MySQL
# if: success() || steps.sqlite.conclusion == 'failure' || steps.postgres.conclusion == 'failure'
# env:
# WEBUI_SECRET_KEY: secret-key
# GLOBAL_LOG_LEVEL: debug
# DATABASE_URL: mysql://root:mysql@localhost:3306/mysql
# run: |
# cd backend
# uvicorn main:app --port "8083" --forwarded-allow-ips '*' &
# UVICORN_PID=$!
# # Wait up to 20 seconds for the server to start
# for i in {1..20}; do
# curl -s http://localhost:8083/api/config > /dev/null && break
# sleep 1
# if [ $i -eq 20 ]; then
# echo "Server failed to start"
# kill -9 $UVICORN_PID
# exit 1
# fi
# done
# # Check that the server is still running after 5 seconds
# sleep 5
# if ! kill -0 $UVICORN_PID; then
# echo "Server has stopped"
# exit 1
# fi
...@@ -171,6 +171,7 @@ async def fetch_url(url, key): ...@@ -171,6 +171,7 @@ async def fetch_url(url, key):
def merge_models_lists(model_lists): def merge_models_lists(model_lists):
log.info(f"merge_models_lists {model_lists}")
merged_list = [] merged_list = []
for idx, models in enumerate(model_lists): for idx, models in enumerate(model_lists):
...@@ -199,14 +200,16 @@ async def get_all_models(): ...@@ -199,14 +200,16 @@ async def get_all_models():
] ]
responses = await asyncio.gather(*tasks) responses = await asyncio.gather(*tasks)
log.info(f"get_all_models:responses() {responses}")
models = { models = {
"data": merge_models_lists( "data": merge_models_lists(
list( list(
map( map(
lambda response: ( lambda response: (
response["data"] response["data"]
if response and "data" in response if (response and "data" in response)
else None else (response if isinstance(response, list) else None)
), ),
responses, responses,
) )
......
...@@ -31,6 +31,11 @@ from langchain_community.document_loaders import ( ...@@ -31,6 +31,11 @@ from langchain_community.document_loaders import (
) )
from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.text_splitter import RecursiveCharacterTextSplitter
import validators
import urllib.parse
import socket
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
import mimetypes import mimetypes
...@@ -84,6 +89,7 @@ from config import ( ...@@ -84,6 +89,7 @@ from config import (
CHUNK_SIZE, CHUNK_SIZE,
CHUNK_OVERLAP, CHUNK_OVERLAP,
RAG_TEMPLATE, RAG_TEMPLATE,
ENABLE_LOCAL_WEB_FETCH,
) )
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
...@@ -391,16 +397,16 @@ def query_doc_handler( ...@@ -391,16 +397,16 @@ def query_doc_handler(
return query_doc_with_hybrid_search( return query_doc_with_hybrid_search(
collection_name=form_data.collection_name, collection_name=form_data.collection_name,
query=form_data.query, query=form_data.query,
embeddings_function=app.state.EMBEDDING_FUNCTION, embedding_function=app.state.EMBEDDING_FUNCTION,
reranking_function=app.state.sentence_transformer_rf,
k=form_data.k if form_data.k else app.state.TOP_K, k=form_data.k if form_data.k else app.state.TOP_K,
reranking_function=app.state.sentence_transformer_rf,
r=form_data.r if form_data.r else app.state.RELEVANCE_THRESHOLD, r=form_data.r if form_data.r else app.state.RELEVANCE_THRESHOLD,
) )
else: else:
return query_doc( return query_doc(
collection_name=form_data.collection_name, collection_name=form_data.collection_name,
query=form_data.query, query=form_data.query,
embeddings_function=app.state.EMBEDDING_FUNCTION, embedding_function=app.state.EMBEDDING_FUNCTION,
k=form_data.k if form_data.k else app.state.TOP_K, k=form_data.k if form_data.k else app.state.TOP_K,
) )
except Exception as e: except Exception as e:
...@@ -429,16 +435,16 @@ def query_collection_handler( ...@@ -429,16 +435,16 @@ def query_collection_handler(
return query_collection_with_hybrid_search( return query_collection_with_hybrid_search(
collection_names=form_data.collection_names, collection_names=form_data.collection_names,
query=form_data.query, query=form_data.query,
embeddings_function=app.state.EMBEDDING_FUNCTION, embedding_function=app.state.EMBEDDING_FUNCTION,
reranking_function=app.state.sentence_transformer_rf,
k=form_data.k if form_data.k else app.state.TOP_K, k=form_data.k if form_data.k else app.state.TOP_K,
reranking_function=app.state.sentence_transformer_rf,
r=form_data.r if form_data.r else app.state.RELEVANCE_THRESHOLD, r=form_data.r if form_data.r else app.state.RELEVANCE_THRESHOLD,
) )
else: else:
return query_collection( return query_collection(
collection_names=form_data.collection_names, collection_names=form_data.collection_names,
query=form_data.query, query=form_data.query,
embeddings_function=app.state.EMBEDDING_FUNCTION, embedding_function=app.state.EMBEDDING_FUNCTION,
k=form_data.k if form_data.k else app.state.TOP_K, k=form_data.k if form_data.k else app.state.TOP_K,
) )
...@@ -454,7 +460,7 @@ def query_collection_handler( ...@@ -454,7 +460,7 @@ def query_collection_handler(
def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): def store_web(form_data: StoreWebForm, user=Depends(get_current_user)):
# "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm" # "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm"
try: try:
loader = WebBaseLoader(form_data.url) loader = get_web_loader(form_data.url)
data = loader.load() data = loader.load()
collection_name = form_data.collection_name collection_name = form_data.collection_name
...@@ -475,6 +481,37 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): ...@@ -475,6 +481,37 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)):
) )
def get_web_loader(url: str):
# Check if the URL is valid
if isinstance(validators.url(url), validators.ValidationError):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
if not ENABLE_LOCAL_WEB_FETCH:
# Local web fetch is disabled, filter out any URLs that resolve to private IP addresses
parsed_url = urllib.parse.urlparse(url)
# Get IPv4 and IPv6 addresses
ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)
# Check if any of the resolved addresses are private
# This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader
for ip in ipv4_addresses:
if validators.ipv4(ip, private=True):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
for ip in ipv6_addresses:
if validators.ipv6(ip, private=True):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
return WebBaseLoader(url)
def resolve_hostname(hostname):
# Get address information
addr_info = socket.getaddrinfo(hostname, None)
# Extract IP addresses from address information
ipv4_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET]
ipv6_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET6]
return ipv4_addresses, ipv6_addresses
def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool: def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool:
text_splitter = RecursiveCharacterTextSplitter( text_splitter = RecursiveCharacterTextSplitter(
......
...@@ -35,6 +35,7 @@ def query_doc( ...@@ -35,6 +35,7 @@ def query_doc(
try: try:
collection = CHROMA_CLIENT.get_collection(name=collection_name) collection = CHROMA_CLIENT.get_collection(name=collection_name)
query_embeddings = embedding_function(query) query_embeddings = embedding_function(query)
result = collection.query( result = collection.query(
query_embeddings=[query_embeddings], query_embeddings=[query_embeddings],
n_results=k, n_results=k,
...@@ -76,9 +77,9 @@ def query_doc_with_hybrid_search( ...@@ -76,9 +77,9 @@ def query_doc_with_hybrid_search(
compressor = RerankCompressor( compressor = RerankCompressor(
embedding_function=embedding_function, embedding_function=embedding_function,
top_n=k,
reranking_function=reranking_function, reranking_function=reranking_function,
r_score=r, r_score=r,
top_n=k,
) )
compression_retriever = ContextualCompressionRetriever( compression_retriever = ContextualCompressionRetriever(
...@@ -91,6 +92,7 @@ def query_doc_with_hybrid_search( ...@@ -91,6 +92,7 @@ def query_doc_with_hybrid_search(
"documents": [[d.page_content for d in result]], "documents": [[d.page_content for d in result]],
"metadatas": [[d.metadata for d in result]], "metadatas": [[d.metadata for d in result]],
} }
log.info(f"query_doc_with_hybrid_search:result {result}") log.info(f"query_doc_with_hybrid_search:result {result}")
return result return result
except Exception as e: except Exception as e:
...@@ -167,7 +169,6 @@ def query_collection_with_hybrid_search( ...@@ -167,7 +169,6 @@ def query_collection_with_hybrid_search(
reranking_function, reranking_function,
r: float, r: float,
): ):
results = [] results = []
for collection_name in collection_names: for collection_name in collection_names:
try: try:
...@@ -182,7 +183,6 @@ def query_collection_with_hybrid_search( ...@@ -182,7 +183,6 @@ def query_collection_with_hybrid_search(
results.append(result) results.append(result)
except: except:
pass pass
return merge_and_sort_query_results(results, k=k, reverse=True) return merge_and_sort_query_results(results, k=k, reverse=True)
...@@ -443,13 +443,15 @@ class ChromaRetriever(BaseRetriever): ...@@ -443,13 +443,15 @@ class ChromaRetriever(BaseRetriever):
metadatas = results["metadatas"][0] metadatas = results["metadatas"][0]
documents = results["documents"][0] documents = results["documents"][0]
return [ results = []
Document( for idx in range(len(ids)):
metadata=metadatas[idx], results.append(
page_content=documents[idx], Document(
metadata=metadatas[idx],
page_content=documents[idx],
)
) )
for idx in range(len(ids)) return results
]
import operator import operator
...@@ -465,9 +467,9 @@ from sentence_transformers import util ...@@ -465,9 +467,9 @@ from sentence_transformers import util
class RerankCompressor(BaseDocumentCompressor): class RerankCompressor(BaseDocumentCompressor):
embedding_function: Any embedding_function: Any
top_n: int
reranking_function: Any reranking_function: Any
r_score: float r_score: float
top_n: int
class Config: class Config:
extra = Extra.forbid extra = Extra.forbid
...@@ -479,7 +481,9 @@ class RerankCompressor(BaseDocumentCompressor): ...@@ -479,7 +481,9 @@ class RerankCompressor(BaseDocumentCompressor):
query: str, query: str,
callbacks: Optional[Callbacks] = None, callbacks: Optional[Callbacks] = None,
) -> Sequence[Document]: ) -> Sequence[Document]:
if self.reranking_function: reranking = self.reranking_function is not None
if reranking:
scores = self.reranking_function.predict( scores = self.reranking_function.predict(
[(query, doc.page_content) for doc in documents] [(query, doc.page_content) for doc in documents]
) )
...@@ -496,9 +500,7 @@ class RerankCompressor(BaseDocumentCompressor): ...@@ -496,9 +500,7 @@ class RerankCompressor(BaseDocumentCompressor):
(d, s) for d, s in docs_with_scores if s >= self.r_score (d, s) for d, s in docs_with_scores if s >= self.r_score
] ]
reverse = self.reranking_function is not None result = sorted(docs_with_scores, key=operator.itemgetter(1), reverse=True)
result = sorted(docs_with_scores, key=operator.itemgetter(1), reverse=reverse)
final_results = [] final_results = []
for doc, doc_score in result[: self.top_n]: for doc, doc_score in result[: self.top_n]:
metadata = doc.metadata metadata = doc.metadata
......
...@@ -168,7 +168,11 @@ except: ...@@ -168,7 +168,11 @@ except:
STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve()) STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve())
shutil.copyfile(f"{FRONTEND_BUILD_DIR}/favicon.png", f"{STATIC_DIR}/favicon.png") frontend_favicon = f"{FRONTEND_BUILD_DIR}/favicon.png"
if os.path.exists(frontend_favicon):
shutil.copyfile(frontend_favicon, f"{STATIC_DIR}/favicon.png")
else:
logging.warning(f"Frontend favicon not found at {frontend_favicon}")
#################################### ####################################
# CUSTOM_NAME # CUSTOM_NAME
...@@ -516,6 +520,8 @@ RAG_TEMPLATE = os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE) ...@@ -516,6 +520,8 @@ RAG_TEMPLATE = os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE)
RAG_OPENAI_API_BASE_URL = os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL) RAG_OPENAI_API_BASE_URL = os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL)
RAG_OPENAI_API_KEY = os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY) RAG_OPENAI_API_KEY = os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY)
ENABLE_LOCAL_WEB_FETCH = os.getenv("ENABLE_LOCAL_WEB_FETCH", "False").lower() == "true"
#################################### ####################################
# Transcribe # Transcribe
#################################### ####################################
......
...@@ -71,3 +71,7 @@ class ERROR_MESSAGES(str, Enum): ...@@ -71,3 +71,7 @@ class ERROR_MESSAGES(str, Enum):
EMPTY_CONTENT = "The content provided is empty. Please ensure that there is text or data present before proceeding." EMPTY_CONTENT = "The content provided is empty. Please ensure that there is text or data present before proceeding."
DB_NOT_SQLITE = "This feature is only available when running with SQLite databases." DB_NOT_SQLITE = "This feature is only available when running with SQLite databases."
INVALID_URL = (
"Oops! The URL you provided is invalid. Please double-check and try again."
)
File mode changed from 100644 to 100755
...@@ -318,11 +318,16 @@ async def get_manifest_json(): ...@@ -318,11 +318,16 @@ async def get_manifest_json():
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache") app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
app.mount( if os.path.exists(FRONTEND_BUILD_DIR):
"/", app.mount(
SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True), "/",
name="spa-static-files", SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
) name="spa-static-files",
)
else:
log.warning(
f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only."
)
@app.on_event("shutdown") @app.on_event("shutdown")
......
...@@ -19,8 +19,8 @@ psycopg2-binary ...@@ -19,8 +19,8 @@ psycopg2-binary
pymysql pymysql
bcrypt bcrypt
litellm==1.35.17 litellm==1.35.28
litellm[proxy]==1.35.17 litellm[proxy]==1.35.28
boto3 boto3
...@@ -43,6 +43,7 @@ pandas ...@@ -43,6 +43,7 @@ pandas
openpyxl openpyxl
pyxlsb pyxlsb
xlrd xlrd
validators
opencv-python-headless opencv-python-headless
rapidocr-onnxruntime rapidocr-onnxruntime
......
...@@ -73,7 +73,11 @@ async function* streamLargeDeltasAsRandomChunks( ...@@ -73,7 +73,11 @@ async function* streamLargeDeltasAsRandomChunks(
const chunkSize = Math.min(Math.floor(Math.random() * 3) + 1, content.length); const chunkSize = Math.min(Math.floor(Math.random() * 3) + 1, content.length);
const chunk = content.slice(0, chunkSize); const chunk = content.slice(0, chunkSize);
yield { done: false, value: chunk }; yield { done: false, value: chunk };
await sleep(5); // Do not sleep if the tab is hidden
// Timers are throttled to 1s in hidden tabs
if (document?.visibilityState !== 'hidden') {
await sleep(5);
}
content = content.slice(chunkSize); content = content.slice(chunkSize);
} }
} }
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
> >
<tr> <tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th> <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('Created at')} </th>
<th scope="col" class="px-3 py-2 text-right" /> <th scope="col" class="px-3 py-2 text-right" />
</tr> </tr>
</thead> </thead>
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
<td class="px-3 py-1 text-right"> <td class="px-3 py-1 text-right">
<div class="flex justify-end w-full"> <div class="flex justify-end w-full">
<Tooltip content="Delete Chat"> <Tooltip content={$i18n.t('Delete Chat')}>
<button <button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" 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 () => { on:click={async () => {
...@@ -133,7 +133,10 @@ ...@@ -133,7 +133,10 @@
{/each} --> {/each} -->
</div> </div>
{:else} {:else}
<div class="text-left text-sm w-full mb-8">{user.name} has no conversations.</div> <div class="text-left text-sm w-full mb-8">
{user.name}
{$i18n.t('has no conversations.')}
</div>
{/if} {/if}
</div> </div>
</div> </div>
......
...@@ -38,8 +38,8 @@ ...@@ -38,8 +38,8 @@
/> />
{:else} {:else}
<img <img
src={models.length === 1 src={$i18n.language === 'dg-DG'
? `${WEBUI_BASE_URL}/static/favicon.png` ? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`} : `${WEBUI_BASE_URL}/static/favicon.png`}
class=" size-12 rounded-full border-[1px] border-gray-200 dark:border-none" class=" size-12 rounded-full border-[1px] border-gray-200 dark:border-none"
alt="logo" alt="logo"
......
...@@ -325,7 +325,9 @@ ...@@ -325,7 +325,9 @@
{#key message.id} {#key message.id}
<div class=" flex w-full message-{message.id}" id="message-{message.id}"> <div class=" flex w-full message-{message.id}" id="message-{message.id}">
<ProfileImage <ProfileImage
src={modelfiles[message.model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`} src={modelfiles[message.model]?.imageUrl ?? $i18n.language === 'dg-DG'
? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`}
/> />
<div class="w-full overflow-hidden"> <div class="w-full overflow-hidden">
...@@ -377,7 +379,7 @@ ...@@ -377,7 +379,7 @@
<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium"> <div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
<button <button
class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg-lg" class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={() => { on:click={() => {
editMessageConfirmHandler(); editMessageConfirmHandler();
}} }}
...@@ -492,7 +494,7 @@ ...@@ -492,7 +494,7 @@
{/if} {/if}
{#if !readOnly} {#if !readOnly}
<Tooltip content="Edit" placement="bottom"> <Tooltip content={$i18n.t('Edit')} placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -519,7 +521,7 @@ ...@@ -519,7 +521,7 @@
</Tooltip> </Tooltip>
{/if} {/if}
<Tooltip content="Copy" placement="bottom"> <Tooltip content={$i18n.t('Copy')} placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -546,7 +548,7 @@ ...@@ -546,7 +548,7 @@
</Tooltip> </Tooltip>
{#if !readOnly} {#if !readOnly}
<Tooltip content="Good Response" placement="bottom"> <Tooltip content={$i18n.t('Good Response')} placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -581,7 +583,7 @@ ...@@ -581,7 +583,7 @@
</button> </button>
</Tooltip> </Tooltip>
<Tooltip content="Bad Response" placement="bottom"> <Tooltip content={$i18n.t('Bad Response')} placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -616,7 +618,7 @@ ...@@ -616,7 +618,7 @@
</Tooltip> </Tooltip>
{/if} {/if}
<Tooltip content="Read Aloud" placement="bottom"> <Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
<button <button
id="speak-button-{message.id}" id="speak-button-{message.id}"
class="{isLastMessage class="{isLastMessage
...@@ -765,7 +767,7 @@ ...@@ -765,7 +767,7 @@
{/if} {/if}
{#if message.info} {#if message.info}
<Tooltip content="Generation Info" placement="bottom"> <Tooltip content={$i18n.t('Generation Info')} placement="bottom">
<button <button
class=" {isLastMessage class=" {isLastMessage
? 'visible' ? 'visible'
...@@ -794,7 +796,7 @@ ...@@ -794,7 +796,7 @@
{/if} {/if}
{#if isLastMessage && !readOnly} {#if isLastMessage && !readOnly}
<Tooltip content="Continue Response" placement="bottom"> <Tooltip content={$i18n.t('Continue Response')} placement="bottom">
<button <button
type="button" type="button"
class="{isLastMessage class="{isLastMessage
...@@ -826,7 +828,7 @@ ...@@ -826,7 +828,7 @@
</button> </button>
</Tooltip> </Tooltip>
<Tooltip content="Regenerate" placement="bottom"> <Tooltip content={$i18n.t('Regenerate')} placement="bottom">
<button <button
type="button" type="button"
class="{isLastMessage class="{isLastMessage
......
...@@ -193,7 +193,7 @@ ...@@ -193,7 +193,7 @@
<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium"> <div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
<button <button
id="save-edit-message-button" id="save-edit-message-button"
class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg-lg" class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={() => { on:click={() => {
editMessageConfirmHandler(); editMessageConfirmHandler();
}} }}
...@@ -266,7 +266,7 @@ ...@@ -266,7 +266,7 @@
{/if} {/if}
{#if !readOnly} {#if !readOnly}
<Tooltip content="Edit" placement="bottom"> <Tooltip content={$i18n.t('Edit')} placement="bottom">
<button <button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button" class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
on:click={() => { on:click={() => {
...@@ -291,7 +291,7 @@ ...@@ -291,7 +291,7 @@
</Tooltip> </Tooltip>
{/if} {/if}
<Tooltip content="Copy" placement="bottom"> <Tooltip content={$i18n.t('Copy')} placement="bottom">
<button <button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
...@@ -316,7 +316,7 @@ ...@@ -316,7 +316,7 @@
</Tooltip> </Tooltip>
{#if !isFirstMessage && !readOnly} {#if !isFirstMessage && !readOnly}
<Tooltip content="Delete" placement="bottom"> <Tooltip content={$i18n.t('Delete')} placement="bottom">
<button <button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition" class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
export let value = ''; export let value = '';
export let placeholder = 'Select a model'; export let placeholder = 'Select a model';
export let searchEnabled = true; export let searchEnabled = true;
export let searchPlaceholder = $i18n.t(`Search a model`); export let searchPlaceholder = $i18n.t('Search a model');
export let items = [{ value: 'mango', label: 'Mango' }]; export let items = [{ value: 'mango', label: 'Mango' }];
......
...@@ -492,8 +492,8 @@ ...@@ -492,8 +492,8 @@
<input <input
id="steps-range" id="steps-range"
type="range" type="range"
min="1" min="-1"
max="16000" max="10240000"
step="1" step="1"
bind:value={options.num_ctx} bind:value={options.num_ctx}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
...@@ -504,9 +504,8 @@ ...@@ -504,9 +504,8 @@
bind:value={options.num_ctx} bind:value={options.num_ctx}
type="number" type="number"
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="1" min="-1"
max="16000" step="10"
step="1"
/> />
</div> </div>
</div> </div>
......
...@@ -106,6 +106,7 @@ ...@@ -106,6 +106,7 @@
responseAutoCopy = settings.responseAutoCopy ?? false; responseAutoCopy = settings.responseAutoCopy ?? false;
showUsername = settings.showUsername ?? false; showUsername = settings.showUsername ?? false;
fullScreenMode = settings.fullScreenMode ?? false; fullScreenMode = settings.fullScreenMode ?? false;
splitLargeChunks = settings.splitLargeChunks ?? false;
}); });
</script> </script>
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
bind:this={modalElement} bind:this={modalElement}
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain" class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }} in:fade={{ duration: 10 }}
on:click={() => { on:mousedown={() => {
show = false; show = false;
}} }}
> >
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
size size
)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl" )} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl"
in:flyAndScale in:flyAndScale
on:click={(e) => { on:mousedown={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
> >
......
<script lang="ts"> <script lang="ts">
import TagInput from './Tags/TagInput.svelte'; import TagInput from './Tags/TagInput.svelte';
import TagList from './Tags/TagList.svelte'; import TagList from './Tags/TagList.svelte';
import { getContext } from 'svelte';
const i18n = getContext('i18n');
export let tags = []; export let tags = [];
...@@ -17,7 +20,7 @@ ...@@ -17,7 +20,7 @@
/> />
<TagInput <TagInput
label={tags.length == 0 ? 'Add Tags' : ''} label={tags.length == 0 ? $i18n.t('Add Tags') : ''}
on:add={(e) => { on:add={(e) => {
addTag(e.detail); addTag(e.detail);
}} }}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment