Unverified Commit 3933db2c authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #2992 from open-webui/dev

0.3.2
parents 75d455ac dc8f0987
...@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.2] - 2024-06-10
### Added
- **🔍 Web Search Query Status**: The web search query will now persist in the results section to aid in easier debugging and tracking of search queries.
- **🌐 New Web Search Provider**: We have added Serply as a new option for web search providers, giving you more choices for your search needs.
- **🌏 Improved Translations**: We've enhanced translations for Chinese and Portuguese.
### Fixed
- **🎤 Audio File Upload Issue**: The bug that prevented audio files from being uploaded in chat input has been fixed, ensuring smooth communication.
- **💬 Message Input Handling**: Improved the handling of message inputs by instantly clearing images and text after sending, along with immediate visual indications when a response message is loading, enhancing user feedback.
- **⚙️ Parameter Registration and Validation**: Fixed the issue where parameters were not registering in certain cases and addressed the problem where users were unable to save due to invalid input errors.
## [0.3.1] - 2024-06-09 ## [0.3.1] - 2024-06-09
### Fixed ### Fixed
......
...@@ -33,7 +33,7 @@ Open WebUI is an [extensible](https://github.com/open-webui/pipelines), feature- ...@@ -33,7 +33,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. - 📚 **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`, and `serper`, 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`, and `Serply` 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. - 🌐 **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.
......
...@@ -69,7 +69,7 @@ from apps.rag.search.main import SearchResult ...@@ -69,7 +69,7 @@ from apps.rag.search.main import SearchResult
from apps.rag.search.searxng import search_searxng from apps.rag.search.searxng import search_searxng
from apps.rag.search.serper import search_serper from apps.rag.search.serper import search_serper
from apps.rag.search.serpstack import search_serpstack from apps.rag.search.serpstack import search_serpstack
from apps.rag.search.serply import search_serply
from utils.misc import ( from utils.misc import (
calculate_sha256, calculate_sha256,
...@@ -115,6 +115,7 @@ from config import ( ...@@ -115,6 +115,7 @@ from config import (
SERPSTACK_API_KEY, SERPSTACK_API_KEY,
SERPSTACK_HTTPS, SERPSTACK_HTTPS,
SERPER_API_KEY, SERPER_API_KEY,
SERPLY_API_KEY,
RAG_WEB_SEARCH_RESULT_COUNT, RAG_WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS, RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_EMBEDDING_OPENAI_BATCH_SIZE, RAG_EMBEDDING_OPENAI_BATCH_SIZE,
...@@ -167,6 +168,7 @@ app.state.config.BRAVE_SEARCH_API_KEY = BRAVE_SEARCH_API_KEY ...@@ -167,6 +168,7 @@ app.state.config.BRAVE_SEARCH_API_KEY = BRAVE_SEARCH_API_KEY
app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY
app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS
app.state.config.SERPER_API_KEY = SERPER_API_KEY app.state.config.SERPER_API_KEY = SERPER_API_KEY
app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT 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 app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
...@@ -394,6 +396,7 @@ async def get_rag_config(user=Depends(get_admin_user)): ...@@ -394,6 +396,7 @@ async def get_rag_config(user=Depends(get_admin_user)):
"serpstack_api_key": app.state.config.SERPSTACK_API_KEY, "serpstack_api_key": app.state.config.SERPSTACK_API_KEY,
"serpstack_https": app.state.config.SERPSTACK_HTTPS, "serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY, "serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT, "result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS, "concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
}, },
...@@ -421,6 +424,7 @@ class WebSearchConfig(BaseModel): ...@@ -421,6 +424,7 @@ class WebSearchConfig(BaseModel):
serpstack_api_key: Optional[str] = None serpstack_api_key: Optional[str] = None
serpstack_https: Optional[bool] = None serpstack_https: Optional[bool] = None
serper_api_key: Optional[str] = None serper_api_key: Optional[str] = None
serply_api_key: Optional[str] = None
result_count: Optional[int] = None result_count: Optional[int] = None
concurrent_requests: Optional[int] = None concurrent_requests: Optional[int] = None
...@@ -471,6 +475,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ ...@@ -471,6 +475,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
app.state.config.SERPSTACK_API_KEY = form_data.web.search.serpstack_api_key app.state.config.SERPSTACK_API_KEY = form_data.web.search.serpstack_api_key
app.state.config.SERPSTACK_HTTPS = form_data.web.search.serpstack_https 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.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.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = ( app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
form_data.web.search.concurrent_requests form_data.web.search.concurrent_requests
...@@ -499,6 +504,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ ...@@ -499,6 +504,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
"serpstack_api_key": app.state.config.SERPSTACK_API_KEY, "serpstack_api_key": app.state.config.SERPSTACK_API_KEY,
"serpstack_https": app.state.config.SERPSTACK_HTTPS, "serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY, "serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT, "result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS, "concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
}, },
...@@ -746,6 +752,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]: ...@@ -746,6 +752,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
- BRAVE_SEARCH_API_KEY - BRAVE_SEARCH_API_KEY
- SERPSTACK_API_KEY - SERPSTACK_API_KEY
- SERPER_API_KEY - SERPER_API_KEY
- SERPLY_API_KEY
Args: Args:
query (str): The query to search for query (str): The query to search for
...@@ -804,6 +811,15 @@ def search_web(engine: str, query: str) -> list[SearchResult]: ...@@ -804,6 +811,15 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
) )
else: else:
raise Exception("No SERPER_API_KEY found in environment variables") raise Exception("No SERPER_API_KEY found in environment variables")
elif engine == "serply":
if app.state.config.SERPLY_API_KEY:
return search_serply(
app.state.config.SERPLY_API_KEY,
query,
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
)
else:
raise Exception("No SERPLY_API_KEY found in environment variables")
else: else:
raise Exception("No search engine API key found in environment variables") raise Exception("No search engine API key found in environment variables")
...@@ -811,6 +827,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]: ...@@ -811,6 +827,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
@app.post("/web/search") @app.post("/web/search")
def store_web_search(form_data: SearchForm, user=Depends(get_current_user)): def store_web_search(form_data: SearchForm, user=Depends(get_current_user)):
try: try:
logging.info(f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}")
web_results = search_web( web_results = search_web(
app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query
) )
......
import json
import logging
import requests
from urllib.parse import urlencode
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_serply(
api_key: str,
query: str,
count: int,
hl: str = "us",
limit: int = 10,
device_type: str = "desktop",
proxy_location: str = "US"
) -> list[SearchResult]:
"""Search using serper.dev's API and return the results as a list of SearchResult objects.
Args:
api_key (str): A serply.io API key
query (str): The query to search for
hl (str): Host Language code to display results in (reference https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages)
limit (int): The maximum number of results to return [10-100, defaults to 10]
"""
log.info("Searching with Serply")
url = "https://api.serply.io/v1/search/"
query_payload = {
"q": query,
"language": "en",
"num": limit,
"gl": proxy_location.upper(),
"hl": hl.lower()
}
url = f"{url}{urlencode(query_payload)}"
headers = {
"X-API-KEY": api_key,
"X-User-Agent": device_type,
"User-Agent": "open-webui",
"X-Proxy-Location": proxy_location
}
response = requests.request("GET", url, headers=headers)
response.raise_for_status()
json_response = response.json()
log.info(f"results from serply search: {json_response}")
results = sorted(
json_response.get("results", []), key=lambda x: x.get("realPosition", 0)
)
return [
SearchResult(
link=result["link"],
title=result.get("title"),
snippet=result.get("description"),
)
for result in results[:count]
]
{
"ads": [],
"ads_count": 0,
"answers": [],
"results": [
{
"title": "Apple",
"link": "https://www.apple.com/",
"description": "Discover the innovative world of Apple and shop everything iPhone, iPad, Apple Watch, Mac, and Apple TV, plus explore accessories, entertainment, ...",
"additional_links": [
{
"text": "AppleApplehttps://www.apple.com",
"href": "https://www.apple.com/"
}
],
"cite": {},
"subdomains": [
{
"title": "Support",
"link": "https://support.apple.com/",
"description": "SupportContact - iPhone Support - Billing and Subscriptions - Apple Repair"
},
{
"title": "Store",
"link": "https://www.apple.com/store",
"description": "StoreShop iPhone - Shop iPad - App Store - Shop Mac - ..."
},
{
"title": "Mac",
"link": "https://www.apple.com/mac/",
"description": "MacMacBook Air - MacBook Pro - iMac - Compare Mac models - Mac mini"
},
{
"title": "iPad",
"link": "https://www.apple.com/ipad/",
"description": "iPadShop iPad - iPad Pro - iPad Air - Compare iPad models - ..."
},
{
"title": "Watch",
"link": "https://www.apple.com/watch/",
"description": "WatchShop Apple Watch - Series 9 - SE - Ultra 2 - Nike - Hermès - ..."
}
],
"realPosition": 1
},
{
"title": "Apple",
"link": "https://www.apple.com/",
"description": "Discover the innovative world of Apple and shop everything iPhone, iPad, Apple Watch, Mac, and Apple TV, plus explore accessories, entertainment, ...",
"additional_links": [
{
"text": "AppleApplehttps://www.apple.com",
"href": "https://www.apple.com/"
}
],
"cite": {},
"realPosition": 2
},
{
"title": "Apple Inc.",
"link": "https://en.wikipedia.org/wiki/Apple_Inc.",
"description": "Apple Inc. (formerly Apple Computer, Inc.) is an American multinational corporation and technology company headquartered in Cupertino, California, ...",
"additional_links": [
{
"text": "Apple Inc.Wikipediahttps://en.wikipedia.org › wiki › Apple_Inc",
"href": "https://en.wikipedia.org/wiki/Apple_Inc."
},
{
"text": "",
"href": "https://en.wikipedia.org/wiki/Apple_Inc."
},
{
"text": "History",
"href": "https://en.wikipedia.org/wiki/History_of_Apple_Inc."
},
{
"text": "List of Apple products",
"href": "https://en.wikipedia.org/wiki/List_of_Apple_products"
},
{
"text": "Litigation involving Apple Inc.",
"href": "https://en.wikipedia.org/wiki/Litigation_involving_Apple_Inc."
},
{
"text": "Apple Park",
"href": "https://en.wikipedia.org/wiki/Apple_Park"
}
],
"cite": {
"domain": "https://en.wikipedia.org › wiki › Apple_Inc",
"span": " › wiki › Apple_Inc"
},
"realPosition": 3
},
{
"title": "Apple Inc. (AAPL) Company Profile & Facts",
"link": "https://finance.yahoo.com/quote/AAPL/profile/",
"description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line ...",
"additional_links": [
{
"text": "Apple Inc. (AAPL) Company Profile & FactsYahoo Financehttps://finance.yahoo.com › quote › AAPL › profile",
"href": "https://finance.yahoo.com/quote/AAPL/profile/"
}
],
"cite": {
"domain": "https://finance.yahoo.com › quote › AAPL › profile",
"span": " › quote › AAPL › profile"
},
"realPosition": 4
},
{
"title": "Apple Inc - Company Profile and News",
"link": "https://www.bloomberg.com/profile/company/AAPL:US",
"description": "Apple Inc. Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables and accessories, and sells a variety of related ...",
"additional_links": [
{
"text": "Apple Inc - Company Profile and NewsBloomberghttps://www.bloomberg.com › company › AAPL:US",
"href": "https://www.bloomberg.com/profile/company/AAPL:US"
},
{
"text": "",
"href": "https://www.bloomberg.com/profile/company/AAPL:US"
}
],
"cite": {
"domain": "https://www.bloomberg.com › company › AAPL:US",
"span": " › company › AAPL:US"
},
"realPosition": 5
},
{
"title": "Apple Inc. | History, Products, Headquarters, & Facts",
"link": "https://www.britannica.com/money/Apple-Inc",
"description": "May 22, 2024 — Apple Inc. is an American multinational technology company that revolutionized the technology sector through its innovation of computer ...",
"additional_links": [
{
"text": "Apple Inc. | History, Products, Headquarters, & FactsBritannicahttps://www.britannica.com › money › Apple-Inc",
"href": "https://www.britannica.com/money/Apple-Inc"
},
{
"text": "",
"href": "https://www.britannica.com/money/Apple-Inc"
}
],
"cite": {
"domain": "https://www.britannica.com › money › Apple-Inc",
"span": " › money › Apple-Inc"
},
"realPosition": 6
}
],
"shopping_ads": [],
"places": [
{
"title": "Apple Inc."
},
{
"title": "Apple Inc"
},
{
"title": "Apple Inc"
}
],
"related_searches": {
"images": [],
"text": [
{
"title": "apple inc full form",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Inc+full+form&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhPEAE"
},
{
"title": "apple company history",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+company+history&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhOEAE"
},
{
"title": "apple store",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Store&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhQEAE"
},
{
"title": "apple id",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+id&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhSEAE"
},
{
"title": "apple inc industry",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Inc+industry&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhREAE"
},
{
"title": "apple login",
"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+login&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhTEAE"
}
]
},
"image_results": [],
"carousel": [],
"total": 2450000000,
"knowledge_graph": "",
"related_questions": [
"What does the Apple Inc do?",
"Why did Apple change to Apple Inc?",
"Who owns Apple Inc.?",
"What is Apple Inc best known for?"
],
"carousel_count": 0,
"ts": 2.491065263748169,
"device_type": null
}
...@@ -308,8 +308,9 @@ frontend_favicon = FRONTEND_BUILD_DIR / "favicon.png" ...@@ -308,8 +308,9 @@ frontend_favicon = FRONTEND_BUILD_DIR / "favicon.png"
if frontend_favicon.exists(): if frontend_favicon.exists():
try: try:
shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png") shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
except PermissionError: except Exception as e:
logging.error(f"No write permission to {STATIC_DIR / 'favicon.png'}") logging.error(f"An error occurred: {e}")
else: else:
logging.warning(f"Frontend favicon not found at {frontend_favicon}") logging.warning(f"Frontend favicon not found at {frontend_favicon}")
...@@ -915,6 +916,12 @@ SERPER_API_KEY = PersistentConfig( ...@@ -915,6 +916,12 @@ SERPER_API_KEY = PersistentConfig(
os.getenv("SERPER_API_KEY", ""), os.getenv("SERPER_API_KEY", ""),
) )
SERPLY_API_KEY = PersistentConfig(
"SERPLY_API_KEY",
"rag.web.search.serply_api_key",
os.getenv("SERPLY_API_KEY", ""),
)
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig( RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
"RAG_WEB_SEARCH_RESULT_COUNT", "RAG_WEB_SEARCH_RESULT_COUNT",
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.1", "version": "0.3.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.3.1", "version": "0.3.2",
"dependencies": { "dependencies": {
"@pyscript/core": "^0.4.32", "@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^1.3.1",
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.1", "version": "0.3.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
...@@ -71,4 +71,4 @@ ...@@ -71,4 +71,4 @@
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
} }
} }
\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
export let saveHandler: Function; export let saveHandler: Function;
let webConfig = null; let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper']; let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper', 'serply'];
let youtubeLanguage = 'en'; let youtubeLanguage = 'en';
let youtubeTranslation = null; let youtubeTranslation = null;
...@@ -188,6 +188,24 @@ ...@@ -188,6 +188,24 @@
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'serply'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serply 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 Serply API Key')}
bind:value={webConfig.search.serply_api_key}
autocomplete="off"
/>
</div>
</div>
</div>
{/if} {/if}
</div> </div>
{/if} {/if}
......
...@@ -327,6 +327,9 @@ ...@@ -327,6 +327,9 @@
chatTextAreaElement.style.height = ''; chatTextAreaElement.style.height = '';
} }
const _files = JSON.parse(JSON.stringify(files));
files = [];
prompt = ''; prompt = '';
// Create user message // Create user message
...@@ -338,7 +341,7 @@ ...@@ -338,7 +341,7 @@
role: 'user', role: 'user',
user: _user ?? undefined, user: _user ?? undefined,
content: userPrompt, content: userPrompt,
files: files.length > 0 ? files : undefined, files: _files.length > 0 ? _files : undefined,
timestamp: Math.floor(Date.now() / 1000), // Unix epoch timestamp: Math.floor(Date.now() / 1000), // Unix epoch
models: selectedModels.filter((m, mIdx) => selectedModels.indexOf(m) === mIdx) models: selectedModels.filter((m, mIdx) => selectedModels.indexOf(m) === mIdx)
}; };
...@@ -355,32 +358,6 @@ ...@@ -355,32 +358,6 @@
// Wait until history/message have been updated // Wait until history/message have been updated
await tick(); await tick();
// Create new chat if only one message in messages
if (messages.length == 1) {
if ($settings.saveChatHistory ?? true) {
chat = await createNewChat(localStorage.token, {
id: $chatId,
title: $i18n.t('New Chat'),
models: selectedModels,
system: $settings.system ?? undefined,
options: {
...($settings.params ?? {})
},
messages: messages,
history: history,
tags: [],
timestamp: Date.now()
});
await chats.set(await getChatList(localStorage.token));
await chatId.set(chat.id);
} else {
await chatId.set('local');
}
await tick();
}
files = [];
// Send prompt // Send prompt
_responses = await sendPrompt(userPrompt, userMessageId); _responses = await sendPrompt(userPrompt, userMessageId);
} }
...@@ -390,15 +367,78 @@ ...@@ -390,15 +367,78 @@
const sendPrompt = async (prompt, parentId, modelId = null) => { const sendPrompt = async (prompt, parentId, modelId = null) => {
let _responses = []; let _responses = [];
// If modelId is provided, use it, else use selected model
let selectedModelIds = modelId
? [modelId]
: atSelectedModel !== undefined
? [atSelectedModel.id]
: selectedModels;
// Create response messages for each selected model
const responseMessageIds = {};
for (const modelId of selectedModelIds) {
const model = $models.filter((m) => m.id === modelId).at(0);
if (model) {
let responseMessageId = uuidv4();
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model.id,
modelName: model.name ?? model.id,
userContext: null,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
responseMessageIds[modelId] = responseMessageId;
}
}
await tick();
// Create new chat if only one message in messages
if (messages.length == 2) {
if ($settings.saveChatHistory ?? true) {
chat = await createNewChat(localStorage.token, {
id: $chatId,
title: $i18n.t('New Chat'),
models: selectedModels,
system: $settings.system ?? undefined,
options: {
...($settings.params ?? {})
},
messages: messages,
history: history,
tags: [],
timestamp: Date.now()
});
await chats.set(await getChatList(localStorage.token));
await chatId.set(chat.id);
} else {
await chatId.set('local');
}
await tick();
}
const _chatId = JSON.parse(JSON.stringify($chatId)); const _chatId = JSON.parse(JSON.stringify($chatId));
await Promise.all( await Promise.all(
(modelId selectedModelIds.map(async (modelId) => {
? [modelId]
: atSelectedModel !== undefined
? [atSelectedModel.id]
: selectedModels
).map(async (modelId) => {
console.log('modelId', modelId); console.log('modelId', modelId);
const model = $models.filter((m) => m.id === modelId).at(0); const model = $models.filter((m) => m.id === modelId).at(0);
...@@ -416,33 +456,8 @@ ...@@ -416,33 +456,8 @@
); );
} }
// Create response message let responseMessageId = responseMessageIds[modelId];
let responseMessageId = uuidv4(); let responseMessage = history.messages[responseMessageId];
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model.id,
modelName: model.name ?? model.id,
userContext: null,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
await tick();
let userContext = null; let userContext = null;
if ($settings?.memory ?? false) { if ($settings?.memory ?? false) {
...@@ -451,7 +466,6 @@ ...@@ -451,7 +466,6 @@
toast.error(error); toast.error(error);
return null; return null;
}); });
if (res) { if (res) {
if (res.documents[0].length > 0) { if (res.documents[0].length > 0) {
userContext = res.documents.reduce((acc, doc, index) => { userContext = res.documents.reduce((acc, doc, index) => {
...@@ -477,7 +491,6 @@ ...@@ -477,7 +491,6 @@
} }
let _response = null; let _response = null;
if (model?.owned_by === 'openai') { if (model?.owned_by === 'openai') {
_response = await sendPromptOpenAI(model, prompt, responseMessageId, _chatId); _response = await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (model) { } else if (model) {
...@@ -502,11 +515,13 @@ ...@@ -502,11 +515,13 @@
const getWebSearchResults = async (model: string, parentId: string, responseId: string) => { const getWebSearchResults = async (model: string, parentId: string, responseId: string) => {
const responseMessage = history.messages[responseId]; const responseMessage = history.messages[responseId];
responseMessage.status = { responseMessage.statusHistory = [
done: false, {
action: 'web_search', done: false,
description: $i18n.t('Generating search query') action: 'web_search',
}; description: $i18n.t('Generating search query')
}
];
messages = messages; messages = messages;
const prompt = history.messages[parentId].content; const prompt = history.messages[parentId].content;
...@@ -519,19 +534,21 @@ ...@@ -519,19 +534,21 @@
if (!searchQuery) { if (!searchQuery) {
toast.warning($i18n.t('No search query generated')); toast.warning($i18n.t('No search query generated'));
responseMessage.status = { responseMessage.statusHistory.push({
...responseMessage.status,
done: true, done: true,
error: true, error: true,
action: 'web_search',
description: 'No search query generated' description: 'No search query generated'
}; });
messages = messages; messages = messages;
} }
responseMessage.status = { responseMessage.statusHistory.push({
...responseMessage.status, done: false,
description: $i18n.t("Searching the web for '{{searchQuery}}'", { searchQuery }) action: 'web_search',
}; description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
});
messages = messages; messages = messages;
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => { const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
...@@ -542,12 +559,13 @@ ...@@ -542,12 +559,13 @@
}); });
if (results) { if (results) {
responseMessage.status = { responseMessage.statusHistory.push({
...responseMessage.status,
done: true, done: true,
action: 'web_search',
description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }), description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
query: searchQuery,
urls: results.filenames urls: results.filenames
}; });
if (responseMessage?.files ?? undefined === undefined) { if (responseMessage?.files ?? undefined === undefined) {
responseMessage.files = []; responseMessage.files = [];
...@@ -562,12 +580,12 @@ ...@@ -562,12 +580,12 @@
messages = messages; messages = messages;
} else { } else {
responseMessage.status = { responseMessage.statusHistory.push({
...responseMessage.status,
done: true, done: true,
error: true, error: true,
action: 'web_search',
description: 'No search results found' description: 'No search results found'
}; });
messages = messages; messages = messages;
} }
}; };
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
import InputMenu from './MessageInput/InputMenu.svelte'; import InputMenu from './MessageInput/InputMenu.svelte';
import Headphone from '../icons/Headphone.svelte'; import Headphone from '../icons/Headphone.svelte';
import VoiceRecording from './MessageInput/VoiceRecording.svelte'; import VoiceRecording from './MessageInput/VoiceRecording.svelte';
import { transcribeAudio } from '$lib/apis/audio';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
......
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
if (_responses.at(0)) { if (_responses.at(0)) {
const content = _responses[0]; const content = _responses[0];
if (content) { if ((content ?? '').trim() !== '') {
assistantSpeakingHandler(content); assistantSpeakingHandler(content);
} }
} }
......
...@@ -211,93 +211,98 @@ ...@@ -211,93 +211,98 @@
speaking = null; speaking = null;
speakingIdx = null; speakingIdx = null;
} else { } else {
speaking = true; if ((message?.content ?? '').trim() !== '') {
speaking = true;
if ($config.audio.tts.engine === 'openai') {
loadingSpeech = true; if ($config.audio.tts.engine === 'openai') {
loadingSpeech = true;
const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
const lastIndex = mergedTexts.length - 1; const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
if (lastIndex >= 0) { const lastIndex = mergedTexts.length - 1;
const previousText = mergedTexts[lastIndex]; if (lastIndex >= 0) {
const wordCount = previousText.split(/\s+/).length; const previousText = mergedTexts[lastIndex];
if (wordCount < 2) { const wordCount = previousText.split(/\s+/).length;
mergedTexts[lastIndex] = previousText + ' ' + currentText; if (wordCount < 2) {
mergedTexts[lastIndex] = previousText + ' ' + currentText;
} else {
mergedTexts.push(currentText);
}
} else { } else {
mergedTexts.push(currentText); mergedTexts.push(currentText);
} }
} else { return mergedTexts;
mergedTexts.push(currentText); }, []);
}
return mergedTexts;
}, []);
console.log(sentences);
sentencesAudio = sentences.reduce((a, e, i, arr) => {
a[i] = null;
return a;
}, {});
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);
speaking = null;
loadingSpeech = false;
return null;
});
if (res) {
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const audio = new Audio(blobUrl);
sentencesAudio[idx] = audio;
loadingSpeech = false;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
}
} else {
let voices = [];
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
if (voices.length > 0) {
clearInterval(getVoicesLoop);
const voice = console.log(sentences);
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
console.log(voice); sentencesAudio = sentences.reduce((a, e, i, arr) => {
a[i] = null;
return a;
}, {});
const speak = new SpeechSynthesisUtterance(message.content); let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
console.log(speak); 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);
speak.onend = () => {
speaking = null; speaking = null;
if ($settings.conversationMode) { loadingSpeech = false;
document.getElementById('voice-input-button')?.click();
return null;
});
if (res) {
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const audio = new Audio(blobUrl);
sentencesAudio[idx] = audio;
loadingSpeech = false;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
}
} else {
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;
console.log(voice);
const speak = new SpeechSynthesisUtterance(message.content);
console.log(speak);
speak.onend = () => {
speaking = null;
if ($settings.conversationMode) {
document.getElementById('voice-input-button')?.click();
}
};
if (voice) {
speak.voice = voice;
} }
};
if (voice) { speechSynthesis.speak(speak);
speak.voice = voice;
} }
}, 100);
speechSynthesis.speak(speak); }
} } else {
}, 100); toast.error('No content to speak');
} }
} }
}; };
...@@ -415,26 +420,29 @@ ...@@ -415,26 +420,29 @@
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-headings:-mb-4 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-ol:p-0 prose-li:-mb-4 whitespace-pre-line" class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-headings:-mb-4 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-ol:p-0 prose-li:-mb-4 whitespace-pre-line"
> >
<div> <div>
{#if message?.status} {#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0}
{@const status = (
message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]
).at(-1)}
<div class="flex items-center gap-2 pt-1 pb-1"> <div class="flex items-center gap-2 pt-1 pb-1">
{#if message?.status?.done === false} {#if status.done === false}
<div class=""> <div class="">
<Spinner className="size-4" /> <Spinner className="size-4" />
</div> </div>
{/if} {/if}
{#if message?.status?.action === 'web_search' && message?.status?.urls} {#if status?.action === 'web_search' && status?.urls}
<WebSearchResults urls={message?.status?.urls}> <WebSearchResults {status}>
<div class="flex flex-col justify-center -space-y-0.5"> <div class="flex flex-col justify-center -space-y-0.5">
<div class="text-base line-clamp-1 text-wrap"> <div class="text-base line-clamp-1 text-wrap">
{message.status.description} {status?.description}
</div> </div>
</div> </div>
</WebSearchResults> </WebSearchResults>
{:else} {:else}
<div class="flex flex-col justify-center -space-y-0.5"> <div class="flex flex-col justify-center -space-y-0.5">
<div class=" text-gray-500 dark:text-gray-500 text-base line-clamp-1 text-wrap"> <div class=" text-gray-500 dark:text-gray-500 text-base line-clamp-1 text-wrap">
{message.status.description} {status?.description}
</div> </div>
</div> </div>
{/if} {/if}
......
<script lang="ts"> <script lang="ts">
import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import ChevronUp from '$lib/components/icons/ChevronUp.svelte'; import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte';
import { Collapsible } from 'bits-ui'; import { Collapsible } from 'bits-ui';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
export let urls = []; export let status = { urls: [], query: '' };
let state = false; let state = false;
</script> </script>
...@@ -27,11 +28,45 @@ ...@@ -27,11 +28,45 @@
class=" text-sm border border-gray-300/30 dark:border-gray-700/50 rounded-xl" class=" text-sm border border-gray-300/30 dark:border-gray-700/50 rounded-xl"
transition={slide} transition={slide}
> >
{#each urls as url, urlIdx} {#if status?.query}
<a
href="https://www.google.com/search?q={status.query}"
target="_blank"
class="flex w-full items-center p-3 px-4 border-b border-gray-300/30 dark:border-gray-700/50 group/item justify-between font-normal text-gray-800 dark:text-gray-300 no-underline"
>
<div class="flex gap-2 items-center">
<MagnifyingGlass />
<div class=" line-clamp-1">
{status.query}
</div>
</div>
<div
class=" ml-1 text-white dark:text-gray-900 group-hover/item:text-gray-600 dark:group-hover/item:text-white transition"
>
<!-- -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
</a>
{/if}
{#each status.urls as url, urlIdx}
<a <a
href={url} href={url}
target="_blank" target="_blank"
class="flex w-full items-center p-3 px-4 {urlIdx === urls.length - 1 class="flex w-full items-center p-3 px-4 {urlIdx === status.urls.length - 1
? '' ? ''
: 'border-b border-gray-300/30 dark:border-gray-700/50'} group/item justify-between font-normal text-gray-800 dark:text-gray-300" : 'border-b border-gray-300/30 dark:border-gray-700/50'} group/item justify-between font-normal text-gray-800 dark:text-gray-300"
> >
......
...@@ -5,21 +5,23 @@ ...@@ -5,21 +5,23 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
export let admin = false;
export let params = { export let params = {
// Advanced // Advanced
seed: 0, seed: null,
stop: null, stop: null,
temperature: '', temperature: null,
frequency_penalty: '', frequency_penalty: null,
repeat_last_n: '', repeat_last_n: null,
mirostat: '', mirostat: null,
mirostat_eta: '', mirostat_eta: null,
mirostat_tau: '', mirostat_tau: null,
top_k: '', top_k: null,
top_p: '', top_p: null,
tfs_z: '', tfs_z: null,
num_ctx: '', num_ctx: null,
max_tokens: '', max_tokens: null,
use_mmap: null, use_mmap: null,
use_mlock: null, use_mlock: null,
num_thread: null, num_thread: null,
...@@ -112,10 +114,10 @@ ...@@ -112,10 +114,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.temperature = (params?.temperature ?? '') === '' ? 0.8 : ''; params.temperature = (params?.temperature ?? null) === null ? 0.8 : null;
}} }}
> >
{#if (params?.temperature ?? '') === ''} {#if (params?.temperature ?? null) === null}
<span class="ml-2 self-center"> {$i18n.t('Default')} </span> <span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else} {:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span> <span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
...@@ -123,7 +125,7 @@ ...@@ -123,7 +125,7 @@
</button> </button>
</div> </div>
{#if (params?.temperature ?? '') !== ''} {#if (params?.temperature ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -143,7 +145,7 @@ ...@@ -143,7 +145,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="1" max="1"
step="0.05" step="any"
/> />
</div> </div>
</div> </div>
...@@ -158,10 +160,10 @@ ...@@ -158,10 +160,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.mirostat = (params?.mirostat ?? '') === '' ? 0 : ''; params.mirostat = (params?.mirostat ?? null) === null ? 0 : null;
}} }}
> >
{#if (params?.mirostat ?? '') === ''} {#if (params?.mirostat ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -169,7 +171,7 @@ ...@@ -169,7 +171,7 @@
</button> </button>
</div> </div>
{#if (params?.mirostat ?? '') !== ''} {#if (params?.mirostat ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -204,10 +206,10 @@ ...@@ -204,10 +206,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.mirostat_eta = (params?.mirostat_eta ?? '') === '' ? 0.1 : ''; params.mirostat_eta = (params?.mirostat_eta ?? null) === null ? 0.1 : null;
}} }}
> >
{#if (params?.mirostat_eta ?? '') === ''} {#if (params?.mirostat_eta ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -215,7 +217,7 @@ ...@@ -215,7 +217,7 @@
</button> </button>
</div> </div>
{#if (params?.mirostat_eta ?? '') !== ''} {#if (params?.mirostat_eta ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -235,7 +237,7 @@ ...@@ -235,7 +237,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="1" max="1"
step="0.05" step="any"
/> />
</div> </div>
</div> </div>
...@@ -250,10 +252,10 @@ ...@@ -250,10 +252,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.mirostat_tau = (params?.mirostat_tau ?? '') === '' ? 5.0 : ''; params.mirostat_tau = (params?.mirostat_tau ?? null) === null ? 5.0 : null;
}} }}
> >
{#if (params?.mirostat_tau ?? '') === ''} {#if (params?.mirostat_tau ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -261,7 +263,7 @@ ...@@ -261,7 +263,7 @@
</button> </button>
</div> </div>
{#if (params?.mirostat_tau ?? '') !== ''} {#if (params?.mirostat_tau ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -281,7 +283,7 @@ ...@@ -281,7 +283,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="10" max="10"
step="0.5" step="any"
/> />
</div> </div>
</div> </div>
...@@ -296,10 +298,10 @@ ...@@ -296,10 +298,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.top_k = (params?.top_k ?? '') === '' ? 40 : ''; params.top_k = (params?.top_k ?? null) === null ? 40 : null;
}} }}
> >
{#if (params?.top_k ?? '') === ''} {#if (params?.top_k ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -307,7 +309,7 @@ ...@@ -307,7 +309,7 @@
</button> </button>
</div> </div>
{#if (params?.top_k ?? '') !== ''} {#if (params?.top_k ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -327,7 +329,7 @@ ...@@ -327,7 +329,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="100" max="100"
step="0.5" step="any"
/> />
</div> </div>
</div> </div>
...@@ -342,10 +344,10 @@ ...@@ -342,10 +344,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.top_p = (params?.top_p ?? '') === '' ? 0.9 : ''; params.top_p = (params?.top_p ?? null) === null ? 0.9 : null;
}} }}
> >
{#if (params?.top_p ?? '') === ''} {#if (params?.top_p ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -353,7 +355,7 @@ ...@@ -353,7 +355,7 @@
</button> </button>
</div> </div>
{#if (params?.top_p ?? '') !== ''} {#if (params?.top_p ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -373,7 +375,7 @@ ...@@ -373,7 +375,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="1" max="1"
step="0.05" step="any"
/> />
</div> </div>
</div> </div>
...@@ -388,10 +390,10 @@ ...@@ -388,10 +390,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.frequency_penalty = (params?.frequency_penalty ?? '') === '' ? 1.1 : ''; params.frequency_penalty = (params?.frequency_penalty ?? null) === null ? 1.1 : null;
}} }}
> >
{#if (params?.frequency_penalty ?? '') === ''} {#if (params?.frequency_penalty ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -399,7 +401,7 @@ ...@@ -399,7 +401,7 @@
</button> </button>
</div> </div>
{#if (params?.frequency_penalty ?? '') !== ''} {#if (params?.frequency_penalty ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -419,7 +421,7 @@ ...@@ -419,7 +421,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="2" max="2"
step="0.05" step="any"
/> />
</div> </div>
</div> </div>
...@@ -434,10 +436,10 @@ ...@@ -434,10 +436,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.repeat_last_n = (params?.repeat_last_n ?? '') === '' ? 64 : ''; params.repeat_last_n = (params?.repeat_last_n ?? null) === null ? 64 : null;
}} }}
> >
{#if (params?.repeat_last_n ?? '') === ''} {#if (params?.repeat_last_n ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -445,7 +447,7 @@ ...@@ -445,7 +447,7 @@
</button> </button>
</div> </div>
{#if (params?.repeat_last_n ?? '') !== ''} {#if (params?.repeat_last_n ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -480,10 +482,10 @@ ...@@ -480,10 +482,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.tfs_z = (params?.tfs_z ?? '') === '' ? 1 : ''; params.tfs_z = (params?.tfs_z ?? null) === null ? 1 : null;
}} }}
> >
{#if (params?.tfs_z ?? '') === ''} {#if (params?.tfs_z ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -491,7 +493,7 @@ ...@@ -491,7 +493,7 @@
</button> </button>
</div> </div>
{#if (params?.tfs_z ?? '') !== ''} {#if (params?.tfs_z ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -511,7 +513,7 @@ ...@@ -511,7 +513,7 @@
class=" bg-transparent text-center w-14" class=" bg-transparent text-center w-14"
min="0" min="0"
max="2" max="2"
step="0.05" step="any"
/> />
</div> </div>
</div> </div>
...@@ -526,10 +528,10 @@ ...@@ -526,10 +528,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.num_ctx = (params?.num_ctx ?? '') === '' ? 2048 : ''; params.num_ctx = (params?.num_ctx ?? null) === null ? 2048 : null;
}} }}
> >
{#if (params?.num_ctx ?? '') === ''} {#if (params?.num_ctx ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -537,7 +539,7 @@ ...@@ -537,7 +539,7 @@
</button> </button>
</div> </div>
{#if (params?.num_ctx ?? '') !== ''} {#if (params?.num_ctx ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -571,10 +573,10 @@ ...@@ -571,10 +573,10 @@
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.max_tokens = (params?.max_tokens ?? '') === '' ? 128 : ''; params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
}} }}
> >
{#if (params?.max_tokens ?? '') === ''} {#if (params?.max_tokens ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...@@ -582,7 +584,7 @@ ...@@ -582,7 +584,7 @@
</button> </button>
</div> </div>
{#if (params?.max_tokens ?? '') !== ''} {#if (params?.max_tokens ?? null) !== null}
<div class="flex mt-0.5 space-x-2"> <div class="flex mt-0.5 space-x-2">
<div class=" flex-1"> <div class=" flex-1">
<input <input
...@@ -609,122 +611,124 @@ ...@@ -609,122 +611,124 @@
{/if} {/if}
</div> </div>
<div class=" py-0.5 w-full justify-between"> {#if admin}
<div class="flex w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition" <button
type="button" class="p-1 px-3 text-xs flex rounded transition"
on:click={() => { type="button"
params.use_mmap = (params?.use_mmap ?? null) === null ? true : null; on:click={() => {
}} params.use_mmap = (params?.use_mmap ?? null) === null ? true : null;
> }}
{#if (params?.use_mmap ?? null) === null} >
<span class="ml-2 self-center">{$i18n.t('Default')}</span> {#if (params?.use_mmap ?? null) === null}
{:else} <span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('On')}</span> {:else}
{/if} <span class="ml-2 self-center">{$i18n.t('On')}</span>
</button> {/if}
</button>
</div>
</div> </div>
</div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
type="button" type="button"
on:click={() => { on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null; params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}} }}
> >
{#if (params?.use_mlock ?? null) === null} {#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if} {/if}
</button> </button>
</div>
</div> </div>
</div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_thread = (params?.num_thread ?? null) === null ? 2 : null;
}}
>
{#if (params?.num_thread ?? 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>
<button {#if (params?.num_thread ?? null) !== null}
class="p-1 px-3 text-xs flex rounded transition" <div class="flex mt-0.5 space-x-2">
type="button" <div class=" flex-1">
on:click={() => { <input
params.num_thread = (params?.num_thread ?? null) === null ? 2 : null; id="steps-range"
}} type="range"
> min="1"
{#if (params?.num_thread ?? null) === null} max="256"
<span class="ml-2 self-center">{$i18n.t('Default')}</span> step="1"
{:else} bind:value={params.num_thread}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span> class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
{/if} />
</button> </div>
<div class="">
<input
bind:value={params.num_thread}
type="number"
class=" bg-transparent text-center w-14"
min="1"
max="256"
step="1"
/>
</div>
</div>
{/if}
</div> </div>
{#if (params?.num_thread ?? null) !== null} <!-- <div class=" py-0.5 w-full justify-between">
<div class="flex mt-0.5 space-x-2"> <div class="flex w-full justify-between">
<div class=" flex-1"> <div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
<input
id="steps-range" <button
type="range" class="p-1 px-3 text-xs flex rounded transition"
min="1" type="button"
max="256" on:click={() => {
step="1" params.template = (params?.template ?? null) === null ? '' : null;
bind:value={params.num_thread} }}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" >
/> {#if (params?.template ?? null) === null}
</div> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
<div class=""> {:else}
<input <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
bind:value={params.num_thread} {/if}
type="number" </button>
class=" bg-transparent text-center w-14"
min="1"
max="256"
step="1"
/>
</div>
</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('Template')}</div>
<button {#if (params?.template ?? null) !== null}
class="p-1 px-3 text-xs flex rounded transition" <div class="flex mt-0.5 space-x-2">
type="button" <div class=" flex-1">
on:click={() => { <textarea
params.template = (params?.template ?? null) === null ? '' : null; class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
}} placeholder="Write your model template content here"
> rows="4"
{#if (params?.template ?? null) === null} bind:value={params.template}
<span class="ml-2 self-center">{$i18n.t('Default')}</span> />
{:else} </div>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.template ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
placeholder="Write your model template content here"
rows="4"
bind:value={params.template}
/>
</div> </div>
</div> {/if}
{/if} </div> -->
</div> {/if}
</div> </div>
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import { getLanguages } from '$lib/i18n'; import { getLanguages } from '$lib/i18n';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { models, settings, theme } from '$lib/stores'; import { models, settings, theme, user } from '$lib/stores';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -43,19 +43,19 @@ ...@@ -43,19 +43,19 @@
let params = { let params = {
// Advanced // Advanced
seed: 0, seed: null,
temperature: '', temperature: null,
frequency_penalty: '', frequency_penalty: null,
repeat_last_n: '', repeat_last_n: null,
mirostat: '', mirostat: null,
mirostat_eta: '', mirostat_eta: null,
mirostat_tau: '', mirostat_tau: null,
top_k: '', top_k: null,
top_p: '', top_p: null,
stop: null, stop: null,
tfs_z: '', tfs_z: null,
num_ctx: '', num_ctx: null,
max_tokens: '' max_tokens: null
}; };
const toggleRequestFormat = async () => { const toggleRequestFormat = async () => {
...@@ -79,12 +79,6 @@ ...@@ -79,12 +79,6 @@
requestFormat = $settings.requestFormat ?? ''; requestFormat = $settings.requestFormat ?? '';
keepAlive = $settings.keepAlive ?? null; keepAlive = $settings.keepAlive ?? null;
params.seed = $settings.seed ?? 0;
params.temperature = $settings.temperature ?? '';
params.frequency_penalty = $settings.frequency_penalty ?? '';
params.top_k = $settings.top_k ?? '';
params.top_p = $settings.top_p ?? '';
params.num_ctx = $settings.num_ctx ?? '';
params = { ...params, ...$settings.params }; params = { ...params, ...$settings.params };
params.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null; params.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null;
}); });
...@@ -227,7 +221,7 @@ ...@@ -227,7 +221,7 @@
</div> </div>
{#if showAdvanced} {#if showAdvanced}
<AdvancedParams bind:params /> <AdvancedParams admin={$user?.role === 'admin'} bind:params />
<hr class=" dark:border-gray-850" /> <hr class=" dark:border-gray-850" />
<div class=" py-1 w-full justify-between"> <div class=" py-1 w-full justify-between">
...@@ -300,20 +294,23 @@ ...@@ -300,20 +294,23 @@
saveSettings({ saveSettings({
system: system !== '' ? system : undefined, system: system !== '' ? system : undefined,
params: { params: {
seed: (params.seed !== 0 ? params.seed : undefined) ?? undefined, seed: (params.seed !== null ? params.seed : undefined) ?? undefined,
stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined, stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
temperature: params.temperature !== '' ? params.temperature : undefined, temperature: params.temperature !== null ? params.temperature : undefined,
frequency_penalty: frequency_penalty:
params.frequency_penalty !== '' ? params.frequency_penalty : undefined, params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined, repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== '' ? params.mirostat : undefined, mirostat: params.mirostat !== null ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined, mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined, mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
top_k: params.top_k !== '' ? params.top_k : undefined, top_k: params.top_k !== null ? params.top_k : undefined,
top_p: params.top_p !== '' ? params.top_p : undefined, top_p: params.top_p !== null ? params.top_p : undefined,
tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined, tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined, num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
max_tokens: params.max_tokens !== '' ? params.max_tokens : 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,
num_thread: params.num_thread !== null ? params.num_thread : undefined
}, },
keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
}); });
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
export let saveHandler: Function; export let saveHandler: Function;
let webConfig = null; let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper']; let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper', 'serply'];
let youtubeLanguage = 'en'; let youtubeLanguage = 'en';
let youtubeTranslation = null; let youtubeTranslation = null;
......
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '2';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
/>
</svg>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
</div> </div>
<div class=" text-xs dark:text-gray-500"> <div class=" text-xs dark:text-gray-500">
To add documents here, upload them to the "Documents" workspace first. {$i18n.t('To add documents here, upload them to the "Documents" workspace first.')}
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
<Selector bind:knowledge> <Selector bind:knowledge>
<button <button
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl" class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
type="button">Select Documents</button type="button">{$i18n.t('Select Documents')}</button
> >
</Selector> </Selector>
</div> </div>
......
...@@ -201,6 +201,7 @@ ...@@ -201,6 +201,7 @@
"Enter Score": "أدخل النتيجة", "Enter Score": "أدخل النتيجة",
"Enter Searxng Query URL": "أدخل عنوان URL لاستعلام Searxng", "Enter Searxng Query URL": "أدخل عنوان URL لاستعلام Searxng",
"Enter Serper API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serper", "Enter Serper API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serper",
"Enter Serply API Key": "",
"Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack", "Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack",
"Enter stop sequence": "أدخل تسلسل التوقف", "Enter stop sequence": "أدخل تسلسل التوقف",
"Enter Top K": "أدخل Top K", "Enter Top K": "أدخل Top K",
...@@ -425,7 +426,7 @@ ...@@ -425,7 +426,7 @@
"Searched {{count}} sites_few": "تم البحث في {{count}} sites_few", "Searched {{count}} sites_few": "تم البحث في {{count}} sites_few",
"Searched {{count}} sites_many": "تم البحث في {{count}} sites_many", "Searched {{count}} sites_many": "تم البحث في {{count}} sites_many",
"Searched {{count}} sites_other": "تم البحث في {{count}} sites_other", "Searched {{count}} sites_other": "تم البحث في {{count}} sites_other",
"Searching the web for '{{searchQuery}}'": "البحث في الويب عن \"{{searchQuery}}\"", "Searching \"{{searchQuery}}\"": "",
"Searxng Query URL": "عنوان URL لاستعلام Searxng", "Searxng Query URL": "عنوان URL لاستعلام Searxng",
"See readme.md for instructions": "readme.md للحصول على التعليمات", "See readme.md for instructions": "readme.md للحصول على التعليمات",
"See what's new": "ما الجديد", "See what's new": "ما الجديد",
...@@ -437,6 +438,7 @@ ...@@ -437,6 +438,7 @@
"Select a pipeline": "حدد مسارا", "Select a pipeline": "حدد مسارا",
"Select a pipeline url": "حدد عنوان URL لخط الأنابيب", "Select a pipeline url": "حدد عنوان URL لخط الأنابيب",
"Select an Ollama instance": "أختار سيرفر ", "Select an Ollama instance": "أختار سيرفر ",
"Select Documents": "",
"Select model": " أختار موديل", "Select model": " أختار موديل",
"Select only one model to call": "", "Select only one model to call": "",
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور", "Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
...@@ -445,6 +447,7 @@ ...@@ -445,6 +447,7 @@
"Send message": "يُرجى إدخال طلبك هنا.", "Send message": "يُرجى إدخال طلبك هنا.",
"September": "سبتمبر", "September": "سبتمبر",
"Serper API Key": "مفتاح واجهة برمجة تطبيقات سيربر", "Serper API Key": "مفتاح واجهة برمجة تطبيقات سيربر",
"Serply API Key": "",
"Serpstack API Key": "مفتاح واجهة برمجة تطبيقات Serpstack", "Serpstack API Key": "مفتاح واجهة برمجة تطبيقات Serpstack",
"Server connection verified": "تم التحقق من اتصال الخادم", "Server connection verified": "تم التحقق من اتصال الخادم",
"Set as default": "الافتراضي", "Set as default": "الافتراضي",
...@@ -509,6 +512,7 @@ ...@@ -509,6 +512,7 @@
"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،", "To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،", "To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "", "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 chat input.": "الى كتابة المحادثه",
"Today": "اليوم", "Today": "اليوم",
"Toggle settings": "فتح وأغلاق الاعدادات", "Toggle settings": "فتح وأغلاق الاعدادات",
......
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