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.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.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
### Fixed
......
......@@ -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.
- 🔍 **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.
......
......@@ -69,7 +69,7 @@ from apps.rag.search.main import SearchResult
from apps.rag.search.searxng import search_searxng
from apps.rag.search.serper import search_serper
from apps.rag.search.serpstack import search_serpstack
from apps.rag.search.serply import search_serply
from utils.misc import (
calculate_sha256,
......@@ -115,6 +115,7 @@ from config import (
SERPSTACK_API_KEY,
SERPSTACK_HTTPS,
SERPER_API_KEY,
SERPLY_API_KEY,
RAG_WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_EMBEDDING_OPENAI_BATCH_SIZE,
......@@ -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_HTTPS = SERPSTACK_HTTPS
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_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
......@@ -394,6 +396,7 @@ async def get_rag_config(user=Depends(get_admin_user)):
"serpstack_api_key": app.state.config.SERPSTACK_API_KEY,
"serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
......@@ -421,6 +424,7 @@ class WebSearchConfig(BaseModel):
serpstack_api_key: Optional[str] = None
serpstack_https: Optional[bool] = None
serper_api_key: Optional[str] = None
serply_api_key: Optional[str] = None
result_count: Optional[int] = None
concurrent_requests: Optional[int] = None
......@@ -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_HTTPS = form_data.web.search.serpstack_https
app.state.config.SERPER_API_KEY = form_data.web.search.serper_api_key
app.state.config.SERPLY_API_KEY = form_data.web.search.serply_api_key
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
form_data.web.search.concurrent_requests
......@@ -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_https": app.state.config.SERPSTACK_HTTPS,
"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,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
......@@ -746,6 +752,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
- BRAVE_SEARCH_API_KEY
- SERPSTACK_API_KEY
- SERPER_API_KEY
- SERPLY_API_KEY
Args:
query (str): The query to search for
......@@ -804,6 +811,15 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
)
else:
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:
raise Exception("No search engine API key found in environment variables")
......@@ -811,6 +827,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
@app.post("/web/search")
def store_web_search(form_data: SearchForm, user=Depends(get_current_user)):
try:
logging.info(f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}")
web_results = search_web(
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"
if frontend_favicon.exists():
try:
shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
except PermissionError:
logging.error(f"No write permission to {STATIC_DIR / 'favicon.png'}")
except Exception as e:
logging.error(f"An error occurred: {e}")
else:
logging.warning(f"Frontend favicon not found at {frontend_favicon}")
......@@ -915,6 +916,12 @@ SERPER_API_KEY = PersistentConfig(
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",
......
{
"name": "open-webui",
"version": "0.3.1",
"version": "0.3.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.3.1",
"version": "0.3.2",
"dependencies": {
"@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1",
......
{
"name": "open-webui",
"version": "0.3.1",
"version": "0.3.2",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
......@@ -71,4 +71,4 @@
"tippy.js": "^6.3.7",
"uuid": "^9.0.1"
}
}
\ No newline at end of file
}
......@@ -11,7 +11,7 @@
export let saveHandler: Function;
let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper'];
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper', 'serply'];
let youtubeLanguage = 'en';
let youtubeTranslation = null;
......@@ -188,6 +188,24 @@
</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}
</div>
{/if}
......
......@@ -327,6 +327,9 @@
chatTextAreaElement.style.height = '';
}
const _files = JSON.parse(JSON.stringify(files));
files = [];
prompt = '';
// Create user message
......@@ -338,7 +341,7 @@
role: 'user',
user: _user ?? undefined,
content: userPrompt,
files: files.length > 0 ? files : undefined,
files: _files.length > 0 ? _files : undefined,
timestamp: Math.floor(Date.now() / 1000), // Unix epoch
models: selectedModels.filter((m, mIdx) => selectedModels.indexOf(m) === mIdx)
};
......@@ -355,32 +358,6 @@
// Wait until history/message have been updated
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
_responses = await sendPrompt(userPrompt, userMessageId);
}
......@@ -390,15 +367,78 @@
const sendPrompt = async (prompt, parentId, modelId = null) => {
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));
await Promise.all(
(modelId
? [modelId]
: atSelectedModel !== undefined
? [atSelectedModel.id]
: selectedModels
).map(async (modelId) => {
selectedModelIds.map(async (modelId) => {
console.log('modelId', modelId);
const model = $models.filter((m) => m.id === modelId).at(0);
......@@ -416,33 +456,8 @@
);
}
// Create response message
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
];
}
await tick();
let responseMessageId = responseMessageIds[modelId];
let responseMessage = history.messages[responseMessageId];
let userContext = null;
if ($settings?.memory ?? false) {
......@@ -451,7 +466,6 @@
toast.error(error);
return null;
});
if (res) {
if (res.documents[0].length > 0) {
userContext = res.documents.reduce((acc, doc, index) => {
......@@ -477,7 +491,6 @@
}
let _response = null;
if (model?.owned_by === 'openai') {
_response = await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (model) {
......@@ -502,11 +515,13 @@
const getWebSearchResults = async (model: string, parentId: string, responseId: string) => {
const responseMessage = history.messages[responseId];
responseMessage.status = {
done: false,
action: 'web_search',
description: $i18n.t('Generating search query')
};
responseMessage.statusHistory = [
{
done: false,
action: 'web_search',
description: $i18n.t('Generating search query')
}
];
messages = messages;
const prompt = history.messages[parentId].content;
......@@ -519,19 +534,21 @@
if (!searchQuery) {
toast.warning($i18n.t('No search query generated'));
responseMessage.status = {
...responseMessage.status,
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search query generated'
};
});
messages = messages;
}
responseMessage.status = {
...responseMessage.status,
description: $i18n.t("Searching the web for '{{searchQuery}}'", { searchQuery })
};
responseMessage.statusHistory.push({
done: false,
action: 'web_search',
description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
});
messages = messages;
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
......@@ -542,12 +559,13 @@
});
if (results) {
responseMessage.status = {
...responseMessage.status,
responseMessage.statusHistory.push({
done: true,
action: 'web_search',
description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
query: searchQuery,
urls: results.filenames
};
});
if (responseMessage?.files ?? undefined === undefined) {
responseMessage.files = [];
......@@ -562,12 +580,12 @@
messages = messages;
} else {
responseMessage.status = {
...responseMessage.status,
responseMessage.statusHistory.push({
done: true,
error: true,
action: 'web_search',
description: 'No search results found'
};
});
messages = messages;
}
};
......
......@@ -29,6 +29,7 @@
import InputMenu from './MessageInput/InputMenu.svelte';
import Headphone from '../icons/Headphone.svelte';
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
import { transcribeAudio } from '$lib/apis/audio';
const i18n = getContext('i18n');
......
......@@ -205,7 +205,7 @@
if (_responses.at(0)) {
const content = _responses[0];
if (content) {
if ((content ?? '').trim() !== '') {
assistantSpeakingHandler(content);
}
}
......
......@@ -211,93 +211,98 @@
speaking = null;
speakingIdx = null;
} else {
speaking = true;
if ($config.audio.tts.engine === 'openai') {
loadingSpeech = true;
const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
const lastIndex = mergedTexts.length - 1;
if (lastIndex >= 0) {
const previousText = mergedTexts[lastIndex];
const wordCount = previousText.split(/\s+/).length;
if (wordCount < 2) {
mergedTexts[lastIndex] = previousText + ' ' + currentText;
if ((message?.content ?? '').trim() !== '') {
speaking = true;
if ($config.audio.tts.engine === 'openai') {
loadingSpeech = true;
const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
const lastIndex = mergedTexts.length - 1;
if (lastIndex >= 0) {
const previousText = mergedTexts[lastIndex];
const wordCount = previousText.split(/\s+/).length;
if (wordCount < 2) {
mergedTexts[lastIndex] = previousText + ' ' + currentText;
} else {
mergedTexts.push(currentText);
}
} else {
mergedTexts.push(currentText);
}
} else {
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);
return mergedTexts;
}, []);
const voice =
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
console.log(sentences);
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;
if ($settings.conversationMode) {
document.getElementById('voice-input-button')?.click();
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 =
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) {
speak.voice = voice;
speechSynthesis.speak(speak);
}
speechSynthesis.speak(speak);
}
}, 100);
}, 100);
}
} else {
toast.error('No content to speak');
}
}
};
......@@ -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"
>
<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">
{#if message?.status?.done === false}
{#if status.done === false}
<div class="">
<Spinner className="size-4" />
</div>
{/if}
{#if message?.status?.action === 'web_search' && message?.status?.urls}
<WebSearchResults urls={message?.status?.urls}>
{#if status?.action === 'web_search' && status?.urls}
<WebSearchResults {status}>
<div class="flex flex-col justify-center -space-y-0.5">
<div class="text-base line-clamp-1 text-wrap">
{message.status.description}
{status?.description}
</div>
</div>
</WebSearchResults>
{:else}
<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">
{message.status.description}
{status?.description}
</div>
</div>
{/if}
......
<script lang="ts">
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte';
import { Collapsible } from 'bits-ui';
import { slide } from 'svelte/transition';
export let urls = [];
export let status = { urls: [], query: '' };
let state = false;
</script>
......@@ -27,11 +28,45 @@
class=" text-sm border border-gray-300/30 dark:border-gray-700/50 rounded-xl"
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
href={url}
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"
>
......
......@@ -5,21 +5,23 @@
const i18n = getContext('i18n');
export let admin = false;
export let params = {
// Advanced
seed: 0,
seed: null,
stop: null,
temperature: '',
frequency_penalty: '',
repeat_last_n: '',
mirostat: '',
mirostat_eta: '',
mirostat_tau: '',
top_k: '',
top_p: '',
tfs_z: '',
num_ctx: '',
max_tokens: '',
temperature: null,
frequency_penalty: null,
repeat_last_n: null,
mirostat: null,
mirostat_eta: null,
mirostat_tau: null,
top_k: null,
top_p: null,
tfs_z: null,
num_ctx: null,
max_tokens: null,
use_mmap: null,
use_mlock: null,
num_thread: null,
......@@ -112,10 +114,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
......@@ -123,7 +125,7 @@
</button>
</div>
{#if (params?.temperature ?? '') !== ''}
{#if (params?.temperature ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -143,7 +145,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="0.05"
step="any"
/>
</div>
</div>
......@@ -158,10 +160,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -169,7 +171,7 @@
</button>
</div>
{#if (params?.mirostat ?? '') !== ''}
{#if (params?.mirostat ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -204,10 +206,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -215,7 +217,7 @@
</button>
</div>
{#if (params?.mirostat_eta ?? '') !== ''}
{#if (params?.mirostat_eta ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -235,7 +237,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="0.05"
step="any"
/>
</div>
</div>
......@@ -250,10 +252,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -261,7 +263,7 @@
</button>
</div>
{#if (params?.mirostat_tau ?? '') !== ''}
{#if (params?.mirostat_tau ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -281,7 +283,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="10"
step="0.5"
step="any"
/>
</div>
</div>
......@@ -296,10 +298,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -307,7 +309,7 @@
</button>
</div>
{#if (params?.top_k ?? '') !== ''}
{#if (params?.top_k ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -327,7 +329,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="100"
step="0.5"
step="any"
/>
</div>
</div>
......@@ -342,10 +344,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -353,7 +355,7 @@
</button>
</div>
{#if (params?.top_p ?? '') !== ''}
{#if (params?.top_p ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -373,7 +375,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="0.05"
step="any"
/>
</div>
</div>
......@@ -388,10 +390,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -399,7 +401,7 @@
</button>
</div>
{#if (params?.frequency_penalty ?? '') !== ''}
{#if (params?.frequency_penalty ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -419,7 +421,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="2"
step="0.05"
step="any"
/>
</div>
</div>
......@@ -434,10 +436,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -445,7 +447,7 @@
</button>
</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-1">
<input
......@@ -480,10 +482,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -491,7 +493,7 @@
</button>
</div>
{#if (params?.tfs_z ?? '') !== ''}
{#if (params?.tfs_z ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -511,7 +513,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="2"
step="0.05"
step="any"
/>
</div>
</div>
......@@ -526,10 +528,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -537,7 +539,7 @@
</button>
</div>
{#if (params?.num_ctx ?? '') !== ''}
{#if (params?.num_ctx ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -571,10 +573,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
......@@ -582,7 +584,7 @@
</button>
</div>
{#if (params?.max_tokens ?? '') !== ''}
{#if (params?.max_tokens ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
......@@ -609,122 +611,124 @@
{/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('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
{#if admin}
<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('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
</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('use_mlock (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}}
>
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
<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('use_mlock (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}}
>
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
</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('num_thread (Ollama)')}</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('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
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>
{#if (params?.num_thread ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="1"
max="256"
step="1"
bind:value={params.num_thread}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</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>
{#if (params?.num_thread ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="1"
max="256"
step="1"
bind:value={params.num_thread}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</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 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
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.template = (params?.template ?? null) === null ? '' : null;
}}
>
{#if (params?.template ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{/if}
</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
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.template = (params?.template ?? null) === null ? '' : null;
}}
>
{#if (params?.template ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.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}
/>
{#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}
</div>
{/if}
</div> -->
{/if}
</div>
......@@ -4,7 +4,7 @@
import { getLanguages } from '$lib/i18n';
const dispatch = createEventDispatcher();
import { models, settings, theme } from '$lib/stores';
import { models, settings, theme, user } from '$lib/stores';
const i18n = getContext('i18n');
......@@ -43,19 +43,19 @@
let params = {
// Advanced
seed: 0,
temperature: '',
frequency_penalty: '',
repeat_last_n: '',
mirostat: '',
mirostat_eta: '',
mirostat_tau: '',
top_k: '',
top_p: '',
seed: null,
temperature: null,
frequency_penalty: null,
repeat_last_n: null,
mirostat: null,
mirostat_eta: null,
mirostat_tau: null,
top_k: null,
top_p: null,
stop: null,
tfs_z: '',
num_ctx: '',
max_tokens: ''
tfs_z: null,
num_ctx: null,
max_tokens: null
};
const toggleRequestFormat = async () => {
......@@ -79,12 +79,6 @@
requestFormat = $settings.requestFormat ?? '';
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.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null;
});
......@@ -227,7 +221,7 @@
</div>
{#if showAdvanced}
<AdvancedParams bind:params />
<AdvancedParams admin={$user?.role === 'admin'} bind:params />
<hr class=" dark:border-gray-850" />
<div class=" py-1 w-full justify-between">
......@@ -300,20 +294,23 @@
saveSettings({
system: system !== '' ? system : undefined,
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,
temperature: params.temperature !== '' ? params.temperature : undefined,
temperature: params.temperature !== null ? params.temperature : undefined,
frequency_penalty:
params.frequency_penalty !== '' ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== '' ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined,
mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined,
top_k: params.top_k !== '' ? params.top_k : undefined,
top_p: params.top_p !== '' ? params.top_p : undefined,
tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined,
max_tokens: params.max_tokens !== '' ? params.max_tokens : undefined
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== null ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
top_k: params.top_k !== null ? params.top_k : undefined,
top_p: params.top_p !== null ? params.top_p : undefined,
tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
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
});
......
......@@ -11,7 +11,7 @@
export let saveHandler: Function;
let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper'];
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper', 'serply'];
let youtubeLanguage = 'en';
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 @@
</div>
<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 class="flex flex-col">
......@@ -97,7 +97,7 @@
<Selector bind:knowledge>
<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"
type="button">Select Documents</button
type="button">{$i18n.t('Select Documents')}</button
>
</Selector>
</div>
......
......@@ -201,6 +201,7 @@
"Enter Score": "أدخل النتيجة",
"Enter Searxng Query URL": "أدخل عنوان URL لاستعلام Searxng",
"Enter Serper API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serper",
"Enter Serply API Key": "",
"Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack",
"Enter stop sequence": "أدخل تسلسل التوقف",
"Enter Top K": "أدخل Top K",
......@@ -425,7 +426,7 @@
"Searched {{count}} sites_few": "تم البحث في {{count}} sites_few",
"Searched {{count}} sites_many": "تم البحث في {{count}} sites_many",
"Searched {{count}} sites_other": "تم البحث في {{count}} sites_other",
"Searching the web for '{{searchQuery}}'": "البحث في الويب عن \"{{searchQuery}}\"",
"Searching \"{{searchQuery}}\"": "",
"Searxng Query URL": "عنوان URL لاستعلام Searxng",
"See readme.md for instructions": "readme.md للحصول على التعليمات",
"See what's new": "ما الجديد",
......@@ -437,6 +438,7 @@
"Select a pipeline": "حدد مسارا",
"Select a pipeline url": "حدد عنوان URL لخط الأنابيب",
"Select an Ollama instance": "أختار سيرفر ",
"Select Documents": "",
"Select model": " أختار موديل",
"Select only one model to call": "",
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
......@@ -445,6 +447,7 @@
"Send message": "يُرجى إدخال طلبك هنا.",
"September": "سبتمبر",
"Serper API Key": "مفتاح واجهة برمجة تطبيقات سيربر",
"Serply API Key": "",
"Serpstack API Key": "مفتاح واجهة برمجة تطبيقات Serpstack",
"Server connection verified": "تم التحقق من اتصال الخادم",
"Set as default": "الافتراضي",
......@@ -509,6 +512,7 @@
"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
"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 add documents here, upload them to the \"Documents\" workspace first.": "",
"to chat input.": "الى كتابة المحادثه",
"Today": "اليوم",
"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