refac: web/rag config

This commit is contained in:
Timothy Jaeryang Baek 2025-04-12 16:33:36 -07:00
parent c3497da5dd
commit 48a23ce3fe
11 changed files with 1367 additions and 1530 deletions

View File

@ -201,7 +201,10 @@ def save_config(config):
T = TypeVar("T") T = TypeVar("T")
ENABLE_PERSISTENT_CONFIG = os.environ.get("ENABLE_PERSISTENT_CONFIG", "True").lower() == "true" ENABLE_PERSISTENT_CONFIG = (
os.environ.get("ENABLE_PERSISTENT_CONFIG", "True").lower() == "true"
)
class PersistentConfig(Generic[T]): class PersistentConfig(Generic[T]):
def __init__(self, env_name: str, config_path: str, env_value: T): def __init__(self, env_name: str, config_path: str, env_value: T):
@ -612,10 +615,16 @@ def load_oauth_providers():
"scope": OAUTH_SCOPES.value, "scope": OAUTH_SCOPES.value,
} }
if OAUTH_CODE_CHALLENGE_METHOD.value and OAUTH_CODE_CHALLENGE_METHOD.value == "S256": if (
OAUTH_CODE_CHALLENGE_METHOD.value
and OAUTH_CODE_CHALLENGE_METHOD.value == "S256"
):
client_kwargs["code_challenge_method"] = "S256" client_kwargs["code_challenge_method"] = "S256"
elif OAUTH_CODE_CHALLENGE_METHOD.value: elif OAUTH_CODE_CHALLENGE_METHOD.value:
raise Exception('Code challenge methods other than "%s" not supported. Given: "%s"' % ("S256", OAUTH_CODE_CHALLENGE_METHOD.value)) raise Exception(
'Code challenge methods other than "%s" not supported. Given: "%s"'
% ("S256", OAUTH_CODE_CHALLENGE_METHOD.value)
)
client.register( client.register(
name="oidc", name="oidc",
@ -1820,12 +1829,6 @@ RAG_FILE_MAX_SIZE = PersistentConfig(
), ),
) )
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
"ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
"rag.enable_web_loader_ssl_verification",
os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
)
RAG_EMBEDDING_ENGINE = PersistentConfig( RAG_EMBEDDING_ENGINE = PersistentConfig(
"RAG_EMBEDDING_ENGINE", "RAG_EMBEDDING_ENGINE",
"rag.embedding_engine", "rag.embedding_engine",
@ -1990,16 +1993,20 @@ YOUTUBE_LOADER_PROXY_URL = PersistentConfig(
) )
ENABLE_RAG_WEB_SEARCH = PersistentConfig( ####################################
"ENABLE_RAG_WEB_SEARCH", # Web Search (RAG)
####################################
ENABLE_WEB_SEARCH = PersistentConfig(
"ENABLE_WEB_SEARCH",
"rag.web.search.enable", "rag.web.search.enable",
os.getenv("ENABLE_RAG_WEB_SEARCH", "False").lower() == "true", os.getenv("ENABLE_WEB_SEARCH", "False").lower() == "true",
) )
RAG_WEB_SEARCH_ENGINE = PersistentConfig( WEB_SEARCH_ENGINE = PersistentConfig(
"RAG_WEB_SEARCH_ENGINE", "WEB_SEARCH_ENGINE",
"rag.web.search.engine", "rag.web.search.engine",
os.getenv("RAG_WEB_SEARCH_ENGINE", ""), os.getenv("WEB_SEARCH_ENGINE", ""),
) )
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = PersistentConfig( BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
@ -2008,10 +2015,18 @@ BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
os.getenv("BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL", "False").lower() == "true", os.getenv("BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL", "False").lower() == "true",
) )
WEB_SEARCH_RESULT_COUNT = PersistentConfig(
"WEB_SEARCH_RESULT_COUNT",
"rag.web.search.result_count",
int(os.getenv("WEB_SEARCH_RESULT_COUNT", "3")),
)
# You can provide a list of your own websites to filter after performing a web search. # You can provide a list of your own websites to filter after performing a web search.
# This ensures the highest level of safety and reliability of the information sources. # This ensures the highest level of safety and reliability of the information sources.
RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig( WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig(
"RAG_WEB_SEARCH_DOMAIN_FILTER_LIST", "WEB_SEARCH_DOMAIN_FILTER_LIST",
"rag.web.search.domain.filter_list", "rag.web.search.domain.filter_list",
[ [
# "wikipedia.com", # "wikipedia.com",
@ -2020,6 +2035,30 @@ RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig(
], ],
) )
WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig(
"WEB_SEARCH_CONCURRENT_REQUESTS",
"rag.web.search.concurrent_requests",
int(os.getenv("WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
)
WEB_LOADER_ENGINE = PersistentConfig(
"WEB_LOADER_ENGINE",
"rag.web.loader.engine",
os.environ.get("WEB_LOADER_ENGINE", ""),
)
ENABLE_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
"ENABLE_WEB_LOADER_SSL_VERIFICATION",
"rag.web.loader.ssl_verification",
os.environ.get("ENABLE_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
)
WEB_SEARCH_TRUST_ENV = PersistentConfig(
"WEB_SEARCH_TRUST_ENV",
"rag.web.search.trust_env",
os.getenv("WEB_SEARCH_TRUST_ENV", "False").lower() == "true",
)
SEARXNG_QUERY_URL = PersistentConfig( SEARXNG_QUERY_URL = PersistentConfig(
"SEARXNG_QUERY_URL", "SEARXNG_QUERY_URL",
@ -2155,34 +2194,22 @@ SOUGOU_API_SK = PersistentConfig(
os.getenv("SOUGOU_API_SK", ""), os.getenv("SOUGOU_API_SK", ""),
) )
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig( TAVILY_API_KEY = PersistentConfig(
"RAG_WEB_SEARCH_RESULT_COUNT", "TAVILY_API_KEY",
"rag.web.search.result_count", "rag.web.search.tavily_api_key",
int(os.getenv("RAG_WEB_SEARCH_RESULT_COUNT", "3")), os.getenv("TAVILY_API_KEY", ""),
) )
RAG_WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig( TAVILY_EXTRACT_DEPTH = PersistentConfig(
"RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "TAVILY_EXTRACT_DEPTH",
"rag.web.search.concurrent_requests", "rag.web.search.tavily_extract_depth",
int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")), os.getenv("TAVILY_EXTRACT_DEPTH", "basic"),
) )
RAG_WEB_LOADER_ENGINE = PersistentConfig( PLAYWRIGHT_WS_URL = PersistentConfig(
"RAG_WEB_LOADER_ENGINE", "PLAYWRIGHT_WS_URL",
"rag.web.loader.engine", "rag.web.loader.PLAYWRIGHT_WS_URL",
os.environ.get("RAG_WEB_LOADER_ENGINE", "safe_web"), os.environ.get("PLAYWRIGHT_WS_URL", ""),
)
RAG_WEB_SEARCH_TRUST_ENV = PersistentConfig(
"RAG_WEB_SEARCH_TRUST_ENV",
"rag.web.search.trust_env",
os.getenv("RAG_WEB_SEARCH_TRUST_ENV", "False").lower() == "true",
)
PLAYWRIGHT_WS_URI = PersistentConfig(
"PLAYWRIGHT_WS_URI",
"rag.web.loader.playwright_ws_uri",
os.environ.get("PLAYWRIGHT_WS_URI", ""),
) )
PLAYWRIGHT_TIMEOUT = PersistentConfig( PLAYWRIGHT_TIMEOUT = PersistentConfig(
@ -2203,17 +2230,6 @@ FIRECRAWL_API_BASE_URL = PersistentConfig(
os.environ.get("FIRECRAWL_API_BASE_URL", "https://api.firecrawl.dev"), os.environ.get("FIRECRAWL_API_BASE_URL", "https://api.firecrawl.dev"),
) )
TAVILY_API_KEY = PersistentConfig(
"TAVILY_API_KEY",
"rag.web.loader.tavily_api_key",
os.getenv("TAVILY_API_KEY", ""),
)
TAVILY_EXTRACT_DEPTH = PersistentConfig(
"TAVILY_EXTRACT_DEPTH",
"rag.web.loader.tavily_extract_depth",
os.getenv("TAVILY_EXTRACT_DEPTH", "basic"),
)
#################################### ####################################
# Images # Images

View File

@ -160,11 +160,11 @@ from open_webui.config import (
AUDIO_TTS_VOICE, AUDIO_TTS_VOICE,
AUDIO_TTS_AZURE_SPEECH_REGION, AUDIO_TTS_AZURE_SPEECH_REGION,
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT, AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
PLAYWRIGHT_WS_URI, PLAYWRIGHT_WS_URL,
PLAYWRIGHT_TIMEOUT, PLAYWRIGHT_TIMEOUT,
FIRECRAWL_API_BASE_URL, FIRECRAWL_API_BASE_URL,
FIRECRAWL_API_KEY, FIRECRAWL_API_KEY,
RAG_WEB_LOADER_ENGINE, WEB_LOADER_ENGINE,
WHISPER_MODEL, WHISPER_MODEL,
DEEPGRAM_API_KEY, DEEPGRAM_API_KEY,
WHISPER_MODEL_AUTO_UPDATE, WHISPER_MODEL_AUTO_UPDATE,
@ -205,12 +205,13 @@ from open_webui.config import (
YOUTUBE_LOADER_LANGUAGE, YOUTUBE_LOADER_LANGUAGE,
YOUTUBE_LOADER_PROXY_URL, YOUTUBE_LOADER_PROXY_URL,
# Retrieval (Web Search) # Retrieval (Web Search)
RAG_WEB_SEARCH_ENGINE, ENABLE_WEB_SEARCH,
WEB_SEARCH_ENGINE,
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL, BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
RAG_WEB_SEARCH_RESULT_COUNT, WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS, WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_WEB_SEARCH_TRUST_ENV, WEB_SEARCH_TRUST_ENV,
RAG_WEB_SEARCH_DOMAIN_FILTER_LIST, WEB_SEARCH_DOMAIN_FILTER_LIST,
JINA_API_KEY, JINA_API_KEY,
SEARCHAPI_API_KEY, SEARCHAPI_API_KEY,
SEARCHAPI_ENGINE, SEARCHAPI_ENGINE,
@ -240,8 +241,7 @@ from open_webui.config import (
ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_ID,
ENABLE_RAG_HYBRID_SEARCH, ENABLE_RAG_HYBRID_SEARCH,
ENABLE_RAG_LOCAL_WEB_FETCH, ENABLE_RAG_LOCAL_WEB_FETCH,
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION, ENABLE_WEB_LOADER_SSL_VERIFICATION,
ENABLE_RAG_WEB_SEARCH,
ENABLE_GOOGLE_DRIVE_INTEGRATION, ENABLE_GOOGLE_DRIVE_INTEGRATION,
ENABLE_ONEDRIVE_INTEGRATION, ENABLE_ONEDRIVE_INTEGRATION,
UPLOAD_DIR, UPLOAD_DIR,
@ -594,9 +594,7 @@ app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = BYPASS_EMBEDDING_AND_RETRIEVAL app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = BYPASS_EMBEDDING_AND_RETRIEVAL
app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = ( app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION = ENABLE_WEB_LOADER_SSL_VERIFICATION
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
)
app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE
app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
@ -629,12 +627,16 @@ app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
app.state.config.YOUTUBE_LOADER_PROXY_URL = YOUTUBE_LOADER_PROXY_URL app.state.config.YOUTUBE_LOADER_PROXY_URL = YOUTUBE_LOADER_PROXY_URL
app.state.config.ENABLE_RAG_WEB_SEARCH = ENABLE_RAG_WEB_SEARCH app.state.config.ENABLE_WEB_SEARCH = ENABLE_WEB_SEARCH
app.state.config.RAG_WEB_SEARCH_ENGINE = RAG_WEB_SEARCH_ENGINE app.state.config.WEB_SEARCH_ENGINE = WEB_SEARCH_ENGINE
app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST = WEB_SEARCH_DOMAIN_FILTER_LIST
app.state.config.WEB_SEARCH_RESULT_COUNT = WEB_SEARCH_RESULT_COUNT
app.state.config.WEB_SEARCH_CONCURRENT_REQUESTS = WEB_SEARCH_CONCURRENT_REQUESTS
app.state.config.WEB_LOADER_ENGINE = WEB_LOADER_ENGINE
app.state.config.WEB_SEARCH_TRUST_ENV = WEB_SEARCH_TRUST_ENV
app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = ( app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
) )
app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = RAG_WEB_SEARCH_DOMAIN_FILTER_LIST
app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION
app.state.config.ENABLE_ONEDRIVE_INTEGRATION = ENABLE_ONEDRIVE_INTEGRATION app.state.config.ENABLE_ONEDRIVE_INTEGRATION = ENABLE_ONEDRIVE_INTEGRATION
@ -662,11 +664,8 @@ app.state.config.PERPLEXITY_API_KEY = PERPLEXITY_API_KEY
app.state.config.SOUGOU_API_SID = SOUGOU_API_SID app.state.config.SOUGOU_API_SID = SOUGOU_API_SID
app.state.config.SOUGOU_API_SK = SOUGOU_API_SK app.state.config.SOUGOU_API_SK = SOUGOU_API_SK
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.PLAYWRIGHT_WS_URL = PLAYWRIGHT_WS_URL
app.state.config.RAG_WEB_LOADER_ENGINE = RAG_WEB_LOADER_ENGINE
app.state.config.RAG_WEB_SEARCH_TRUST_ENV = RAG_WEB_SEARCH_TRUST_ENV
app.state.config.PLAYWRIGHT_WS_URI = PLAYWRIGHT_WS_URI
app.state.config.PLAYWRIGHT_TIMEOUT = PLAYWRIGHT_TIMEOUT app.state.config.PLAYWRIGHT_TIMEOUT = PLAYWRIGHT_TIMEOUT
app.state.config.FIRECRAWL_API_BASE_URL = FIRECRAWL_API_BASE_URL app.state.config.FIRECRAWL_API_BASE_URL = FIRECRAWL_API_BASE_URL
app.state.config.FIRECRAWL_API_KEY = FIRECRAWL_API_KEY app.state.config.FIRECRAWL_API_KEY = FIRECRAWL_API_KEY
@ -1261,7 +1260,7 @@ async def get_app_config(request: Request):
{ {
"enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS, "enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS,
"enable_channels": app.state.config.ENABLE_CHANNELS, "enable_channels": app.state.config.ENABLE_CHANNELS,
"enable_web_search": app.state.config.ENABLE_RAG_WEB_SEARCH, "enable_web_search": app.state.config.ENABLE_WEB_SEARCH,
"enable_code_execution": app.state.config.ENABLE_CODE_EXECUTION, "enable_code_execution": app.state.config.ENABLE_CODE_EXECUTION,
"enable_code_interpreter": app.state.config.ENABLE_CODE_INTERPRETER, "enable_code_interpreter": app.state.config.ENABLE_CODE_INTERPRETER,
"enable_image_generation": app.state.config.ENABLE_IMAGE_GENERATION, "enable_image_generation": app.state.config.ENABLE_IMAGE_GENERATION,

View File

@ -28,9 +28,9 @@ from open_webui.retrieval.loaders.tavily import TavilyLoader
from open_webui.constants import ERROR_MESSAGES from open_webui.constants import ERROR_MESSAGES
from open_webui.config import ( from open_webui.config import (
ENABLE_RAG_LOCAL_WEB_FETCH, ENABLE_RAG_LOCAL_WEB_FETCH,
PLAYWRIGHT_WS_URI, PLAYWRIGHT_WS_URL,
PLAYWRIGHT_TIMEOUT, PLAYWRIGHT_TIMEOUT,
RAG_WEB_LOADER_ENGINE, WEB_LOADER_ENGINE,
FIRECRAWL_API_BASE_URL, FIRECRAWL_API_BASE_URL,
FIRECRAWL_API_KEY, FIRECRAWL_API_KEY,
TAVILY_API_KEY, TAVILY_API_KEY,
@ -584,13 +584,6 @@ class SafeWebBaseLoader(WebBaseLoader):
return [document async for document in self.alazy_load()] return [document async for document in self.alazy_load()]
RAG_WEB_LOADER_ENGINES = defaultdict(lambda: SafeWebBaseLoader)
RAG_WEB_LOADER_ENGINES["playwright"] = SafePlaywrightURLLoader
RAG_WEB_LOADER_ENGINES["safe_web"] = SafeWebBaseLoader
RAG_WEB_LOADER_ENGINES["firecrawl"] = SafeFireCrawlLoader
RAG_WEB_LOADER_ENGINES["tavily"] = SafeTavilyLoader
def get_web_loader( def get_web_loader(
urls: Union[str, Sequence[str]], urls: Union[str, Sequence[str]],
verify_ssl: bool = True, verify_ssl: bool = True,
@ -608,27 +601,36 @@ def get_web_loader(
"trust_env": trust_env, "trust_env": trust_env,
} }
if RAG_WEB_LOADER_ENGINE.value == "playwright": if WEB_LOADER_ENGINE.value == "" or WEB_LOADER_ENGINE.value == "safe_web":
WebLoaderClass = SafeWebBaseLoader
if WEB_LOADER_ENGINE.value == "playwright":
WebLoaderClass = SafePlaywrightURLLoader
web_loader_args["playwright_timeout"] = PLAYWRIGHT_TIMEOUT.value * 1000 web_loader_args["playwright_timeout"] = PLAYWRIGHT_TIMEOUT.value * 1000
if PLAYWRIGHT_WS_URI.value: if PLAYWRIGHT_WS_URL.value:
web_loader_args["playwright_ws_url"] = PLAYWRIGHT_WS_URI.value web_loader_args["playwright_ws_url"] = PLAYWRIGHT_WS_URL.value
if RAG_WEB_LOADER_ENGINE.value == "firecrawl": if WEB_LOADER_ENGINE.value == "firecrawl":
WebLoaderClass = SafeFireCrawlLoader
web_loader_args["api_key"] = FIRECRAWL_API_KEY.value web_loader_args["api_key"] = FIRECRAWL_API_KEY.value
web_loader_args["api_url"] = FIRECRAWL_API_BASE_URL.value web_loader_args["api_url"] = FIRECRAWL_API_BASE_URL.value
if RAG_WEB_LOADER_ENGINE.value == "tavily": if WEB_LOADER_ENGINE.value == "tavily":
WebLoaderClass = SafeTavilyLoader
web_loader_args["api_key"] = TAVILY_API_KEY.value web_loader_args["api_key"] = TAVILY_API_KEY.value
web_loader_args["extract_depth"] = TAVILY_EXTRACT_DEPTH.value web_loader_args["extract_depth"] = TAVILY_EXTRACT_DEPTH.value
# Create the appropriate WebLoader based on the configuration if WebLoaderClass:
WebLoaderClass = RAG_WEB_LOADER_ENGINES[RAG_WEB_LOADER_ENGINE.value]
web_loader = WebLoaderClass(**web_loader_args) web_loader = WebLoaderClass(**web_loader_args)
log.debug( log.debug(
"Using RAG_WEB_LOADER_ENGINE %s for %s URLs", "Using WEB_LOADER_ENGINE %s for %s URLs",
web_loader.__class__.__name__, web_loader.__class__.__name__,
len(safe_urls), len(safe_urls),
) )
return web_loader return web_loader
else:
raise ValueError(
f"Invalid WEB_LOADER_ENGINE: {WEB_LOADER_ENGINE.value}. "
"Please set it to 'safe_web', 'playwright', 'firecrawl', or 'tavily'."
)

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,8 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "$SCRIPT_DIR" || exit cd "$SCRIPT_DIR" || exit
# Add conditional Playwright browser installation # Add conditional Playwright browser installation
if [[ "${RAG_WEB_LOADER_ENGINE,,}" == "playwright" ]]; then if [[ "${WEB_LOADER_ENGINE,,}" == "playwright" ]]; then
if [[ -z "${PLAYWRIGHT_WS_URI}" ]]; then if [[ -z "${PLAYWRIGHT_WS_URL}" ]]; then
echo "Installing Playwright browsers..." echo "Installing Playwright browsers..."
playwright install chromium playwright install chromium
playwright install-deps chromium playwright install-deps chromium

View File

@ -7,8 +7,8 @@ SET "SCRIPT_DIR=%~dp0"
cd /d "%SCRIPT_DIR%" || exit /b cd /d "%SCRIPT_DIR%" || exit /b
:: Add conditional Playwright browser installation :: Add conditional Playwright browser installation
IF /I "%RAG_WEB_LOADER_ENGINE%" == "playwright" ( IF /I "%WEB_LOADER_ENGINE%" == "playwright" (
IF "%PLAYWRIGHT_WS_URI%" == "" ( IF "%PLAYWRIGHT_WS_URL%" == "" (
echo Installing Playwright browsers... echo Installing Playwright browsers...
playwright install chromium playwright install chromium
playwright install-deps chromium playwright install-deps chromium

View File

@ -6,5 +6,5 @@ services:
open-webui: open-webui:
environment: environment:
- 'RAG_WEB_LOADER_ENGINE=playwright' - 'WEB_LOADER_ENGINE=playwright'
- 'PLAYWRIGHT_WS_URI=ws://playwright:3000' - 'PLAYWRIGHT_WS_URL=ws://playwright:3000'

View File

@ -50,9 +50,9 @@ type YoutubeConfigForm = {
}; };
type RAGConfigForm = { type RAGConfigForm = {
pdf_extract_images?: boolean; PDF_EXTRACT_IMAGES?: boolean;
enable_google_drive_integration?: boolean; ENABLE_GOOGLE_DRIVE_INTEGRATION?: boolean;
enable_onedrive_integration?: boolean; ENABLE_ONEDRIVE_INTEGRATION?: boolean;
chunk?: ChunkConfigForm; chunk?: ChunkConfigForm;
content_extraction?: ContentExtractConfigForm; content_extraction?: ContentExtractConfigForm;
web_loader_ssl_verification?: boolean; web_loader_ssl_verification?: boolean;
@ -89,33 +89,6 @@ export const updateRAGConfig = async (token: string, payload: RAGConfigForm) =>
return res; return res;
}; };
export const getRAGTemplate = async (token: string) => {
let error = null;
const res = await fetch(`${RETRIEVAL_API_BASE_URL}/template`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res?.template ?? '';
};
export const getQuerySettings = async (token: string) => { export const getQuerySettings = async (token: string) => {
let error = null; let error = null;

View File

@ -27,6 +27,7 @@
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte'; import Switch from '$lib/components/common/Switch.svelte';
import Textarea from '$lib/components/common/Textarea.svelte'; import Textarea from '$lib/components/common/Textarea.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
@ -42,31 +43,6 @@
let embeddingBatchSize = 1; let embeddingBatchSize = 1;
let rerankingModel = ''; let rerankingModel = '';
let fileMaxSize = null;
let fileMaxCount = null;
let contentExtractionEngine = 'default';
let tikaServerUrl = '';
let showTikaServerUrl = false;
let doclingServerUrl = '';
let showDoclingServerUrl = false;
let documentIntelligenceEndpoint = '';
let documentIntelligenceKey = '';
let showDocumentIntelligenceConfig = false;
let mistralApiKey = '';
let showMistralOcrConfig = false;
let textSplitter = '';
let chunkSize = 0;
let chunkOverlap = 0;
let pdfExtractImages = true;
let RAG_FULL_CONTEXT = false;
let BYPASS_EMBEDDING_AND_RETRIEVAL = false;
let enableGoogleDriveIntegration = false;
let enableOneDriveIntegration = false;
let OpenAIUrl = ''; let OpenAIUrl = '';
let OpenAIKey = ''; let OpenAIKey = '';
@ -81,6 +57,8 @@
hybrid: false hybrid: false
}; };
let RAGConfig = null;
const embeddingModelUpdateHandler = async () => { const embeddingModelUpdateHandler = async () => {
if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) { if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
toast.error( toast.error(
@ -175,65 +153,40 @@
}; };
const submitHandler = async () => { const submitHandler = async () => {
if (contentExtractionEngine === 'tika' && tikaServerUrl === '') { if (RAGConfig.CONTENT_EXTRACTION_ENGINE === 'tika' && RAGConfig.TIKA_SERVER_URL === '') {
toast.error($i18n.t('Tika Server URL required.')); toast.error($i18n.t('Tika Server URL required.'));
return; return;
} }
if (contentExtractionEngine === 'docling' && doclingServerUrl === '') { if (RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling' && RAGConfig.DOCLING_SERVER_URL === '') {
toast.error($i18n.t('Docling Server URL required.')); toast.error($i18n.t('Docling Server URL required.'));
return; return;
} }
if ( if (
contentExtractionEngine === 'document_intelligence' && RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence' &&
(documentIntelligenceEndpoint === '' || documentIntelligenceKey === '') (RAGConfig.DOCUMENT_INTELLIGENCE_ENDPOINT === '' ||
RAGConfig.DOCUMENT_INTELLIGENCE_KEY === '')
) { ) {
toast.error($i18n.t('Document Intelligence endpoint and key required.')); toast.error($i18n.t('Document Intelligence endpoint and key required.'));
return; return;
} }
if (contentExtractionEngine === 'mistral_ocr' && mistralApiKey === '') { if (
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'mistral_ocr' &&
RAGConfig.MISTRAL_OCR_API_KEY === ''
) {
toast.error($i18n.t('Mistral OCR API Key required.')); toast.error($i18n.t('Mistral OCR API Key required.'));
return; return;
} }
if (!BYPASS_EMBEDDING_AND_RETRIEVAL) { if (!RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL) {
await embeddingModelUpdateHandler(); await embeddingModelUpdateHandler();
if (querySettings.hybrid) { if (RAGConfig.ENABLE_RAG_HYBRID_SEARCH) {
await rerankingModelUpdateHandler(); await rerankingModelUpdateHandler();
} }
} }
const res = await updateRAGConfig(localStorage.token, { const res = await updateRAGConfig(localStorage.token, RAGConfig);
pdf_extract_images: pdfExtractImages,
enable_google_drive_integration: enableGoogleDriveIntegration,
enable_onedrive_integration: enableOneDriveIntegration,
file: {
max_size: fileMaxSize === '' ? null : fileMaxSize,
max_count: fileMaxCount === '' ? null : fileMaxCount
},
RAG_FULL_CONTEXT: RAG_FULL_CONTEXT,
BYPASS_EMBEDDING_AND_RETRIEVAL: BYPASS_EMBEDDING_AND_RETRIEVAL,
chunk: {
text_splitter: textSplitter,
chunk_overlap: chunkOverlap,
chunk_size: chunkSize
},
content_extraction: {
engine: contentExtractionEngine,
tika_server_url: tikaServerUrl,
docling_server_url: doclingServerUrl,
document_intelligence_config: {
key: documentIntelligenceKey,
endpoint: documentIntelligenceEndpoint
},
mistral_ocr_config: {
api_key: mistralApiKey
}
}
});
await updateQuerySettings(localStorage.token, querySettings);
dispatch('save'); dispatch('save');
}; };
@ -261,46 +214,11 @@
} }
}; };
const toggleHybridSearch = async () => {
querySettings = await updateQuerySettings(localStorage.token, querySettings);
};
onMount(async () => { onMount(async () => {
await setEmbeddingConfig(); await setEmbeddingConfig();
await setRerankingConfig(); await setRerankingConfig();
querySettings = await getQuerySettings(localStorage.token); RAGConfig = await getRAGConfig(localStorage.token);
const res = await getRAGConfig(localStorage.token);
if (res) {
pdfExtractImages = res.pdf_extract_images;
textSplitter = res.chunk.text_splitter;
chunkSize = res.chunk.chunk_size;
chunkOverlap = res.chunk.chunk_overlap;
RAG_FULL_CONTEXT = res.RAG_FULL_CONTEXT;
BYPASS_EMBEDDING_AND_RETRIEVAL = res.BYPASS_EMBEDDING_AND_RETRIEVAL;
contentExtractionEngine = res.content_extraction.engine;
tikaServerUrl = res.content_extraction.tika_server_url;
doclingServerUrl = res.content_extraction.docling_server_url;
showTikaServerUrl = contentExtractionEngine === 'tika';
showDoclingServerUrl = contentExtractionEngine === 'docling';
documentIntelligenceEndpoint = res.content_extraction.document_intelligence_config.endpoint;
documentIntelligenceKey = res.content_extraction.document_intelligence_config.key;
showDocumentIntelligenceConfig = contentExtractionEngine === 'document_intelligence';
mistralApiKey = res.content_extraction.mistral_ocr_config.api_key;
showMistralOcrConfig = contentExtractionEngine === 'mistral_ocr';
fileMaxSize = res?.file.max_size ?? '';
fileMaxCount = res?.file.max_count ?? '';
enableGoogleDriveIntegration = res.enable_google_drive_integration;
enableOneDriveIntegration = res.enable_onedrive_integration;
}
}); });
</script> </script>
@ -332,7 +250,6 @@
}} }}
/> />
<ReindexKnowledgeFilesConfirmDialog <ReindexKnowledgeFilesConfirmDialog
bind:show={showReindexConfirm} bind:show={showReindexConfirm}
on:confirm={async () => { on:confirm={async () => {
@ -353,6 +270,7 @@
submitHandler(); submitHandler();
}} }}
> >
{#if RAGConfig}
<div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full pr-1.5"> <div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full pr-1.5">
<div class=""> <div class="">
<div class="mb-3"> <div class="mb-3">
@ -368,7 +286,7 @@
<div class=""> <div class="">
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={contentExtractionEngine} bind:value={RAGConfig.CONTENT_EXTRACTION_ENGINE}
> >
<option value="">{$i18n.t('Default')}</option> <option value="">{$i18n.t('Default')}</option>
<option value="tika">{$i18n.t('Tika')}</option> <option value="tika">{$i18n.t('Tika')}</option>
@ -378,57 +296,58 @@
</select> </select>
</div> </div>
</div> </div>
{#if contentExtractionEngine === 'tika'}
{#if RAGConfig.CONTENT_EXTRACTION_ENGINE === ''}
<div class="flex w-full mt-1">
<div class="flex-1 flex justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('PDF Extract Images (OCR)')}
</div>
<div class="flex items-center relative">
<Switch bind:state={RAGConfig.PDF_EXTRACT_IMAGES} />
</div>
</div>
</div>
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'tika'}
<div class="flex w-full mt-1"> <div class="flex w-full mt-1">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Enter Tika Server URL')} placeholder={$i18n.t('Enter Tika Server URL')}
bind:value={tikaServerUrl} bind:value={RAGConfig.TIKA_SERVER_URL}
/> />
</div> </div>
</div> </div>
{:else if contentExtractionEngine === 'docling'} {:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling'}
<div class="flex w-full mt-1"> <div class="flex w-full mt-1">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Enter Docling Server URL')} placeholder={$i18n.t('Enter Docling Server URL')}
bind:value={doclingServerUrl} bind:value={RAGConfig.DOCLING_SERVER_URL}
/> />
</div> </div>
{:else if contentExtractionEngine === 'document_intelligence'} {:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
<div class="my-0.5 flex gap-2 pr-2"> <div class="my-0.5 flex gap-2 pr-2">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Enter Document Intelligence Endpoint')} placeholder={$i18n.t('Enter Document Intelligence Endpoint')}
bind:value={documentIntelligenceEndpoint} bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_ENDPOINT}
/> />
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Document Intelligence Key')} placeholder={$i18n.t('Enter Document Intelligence Key')}
bind:value={documentIntelligenceKey} bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_KEY}
/> />
</div> </div>
{:else if contentExtractionEngine === 'mistral_ocr'} {:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'mistral_ocr'}
<div class="my-0.5 flex gap-2 pr-2"> <div class="my-0.5 flex gap-2 pr-2">
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Mistral API Key')} placeholder={$i18n.t('Enter Mistral API Key')}
bind:value={mistralApiKey} bind:value={RAGConfig.MISTRAL_OCR_API_KEY}
/> />
</div> </div>
{/if} {/if}
</div> </div>
{#if contentExtractionEngine === ''}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('PDF Extract Images (OCR)')}
</div>
<div class="flex items-center relative">
<Switch bind:state={pdfExtractImages} />
</div>
</div>
{/if}
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start"> <Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
@ -437,7 +356,7 @@
</div> </div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Tooltip <Tooltip
content={BYPASS_EMBEDDING_AND_RETRIEVAL content={RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL
? $i18n.t( ? $i18n.t(
'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.' 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
) )
@ -445,18 +364,18 @@
'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.' 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
)} )}
> >
<Switch bind:state={BYPASS_EMBEDDING_AND_RETRIEVAL} /> <Switch bind:state={RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL} />
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
{#if !BYPASS_EMBEDDING_AND_RETRIEVAL} {#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={textSplitter} bind:value={RAGConfig.TEXT_SPLITTER}
> >
<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option> <option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
<option value="token">{$i18n.t('Token')} ({$i18n.t('Tiktoken')})</option> <option value="token">{$i18n.t('Token')} ({$i18n.t('Tiktoken')})</option>
@ -475,7 +394,7 @@
class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Enter Chunk Size')} placeholder={$i18n.t('Enter Chunk Size')}
bind:value={chunkSize} bind:value={RAGConfig.CHUNK_SIZE}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
@ -492,7 +411,7 @@
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Enter Chunk Overlap')} placeholder={$i18n.t('Enter Chunk Overlap')}
bind:value={chunkOverlap} bind:value={RAGConfig.CHUNK_OVERLAP}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
@ -503,7 +422,7 @@
{/if} {/if}
</div> </div>
{#if !BYPASS_EMBEDDING_AND_RETRIEVAL} {#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<div class="mb-3"> <div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div> <div class=" mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div>
@ -660,7 +579,9 @@
{#if embeddingEngine === 'ollama' || embeddingEngine === 'openai'} {#if embeddingEngine === 'ollama' || embeddingEngine === 'openai'}
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Batch Size')}</div> <div class=" self-center text-xs font-medium">
{$i18n.t('Embedding Batch Size')}
</div>
<div class=""> <div class="">
<input <input
@ -685,7 +606,7 @@
<div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Tooltip <Tooltip
content={RAG_FULL_CONTEXT content={RAGConfig.RAG_FULL_CONTEXT
? $i18n.t( ? $i18n.t(
'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.' 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
) )
@ -693,25 +614,25 @@
'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.' 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
)} )}
> >
<Switch bind:state={RAG_FULL_CONTEXT} /> <Switch bind:state={RAGConfig.RAG_FULL_CONTEXT} />
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
{#if !RAG_FULL_CONTEXT} {#if !RAGConfig.RAG_FULL_CONTEXT}
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Switch <Switch
bind:state={querySettings.hybrid} bind:state={RAGConfig.ENABLE_RAG_HYBRID_SEARCH}
on:change={() => { on:change={() => {
toggleHybridSearch(); submitHandler();
}} }}
/> />
</div> </div>
</div> </div>
{#if querySettings.hybrid === true} {#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
<div class=" mb-2.5 flex flex-col w-full"> <div class=" mb-2.5 flex flex-col w-full">
<div class=" mb-1 text-xs font-medium">{$i18n.t('Reranking Model')}</div> <div class=" mb-1 text-xs font-medium">{$i18n.t('Reranking Model')}</div>
@ -791,14 +712,14 @@
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Enter Top K')} placeholder={$i18n.t('Enter Top K')}
bind:value={querySettings.k} bind:value={RAGConfig.TOP_K}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
</div> </div>
</div> </div>
{#if querySettings.hybrid === true} {#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
<div class="mb-2.5 flex w-full justify-between"> <div class="mb-2.5 flex w-full justify-between">
<div class="self-center text-xs font-medium">{$i18n.t('Top K Reranker')}</div> <div class="self-center text-xs font-medium">{$i18n.t('Top K Reranker')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
@ -806,7 +727,7 @@
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Enter Top K Reranker')} placeholder={$i18n.t('Enter Top K Reranker')}
bind:value={querySettings.k_reranker} bind:value={RAGConfig.TOP_K_RERANKER}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
@ -814,17 +735,19 @@
</div> </div>
{/if} {/if}
{#if querySettings.hybrid === true} {#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
<div class=" mb-2.5 flex flex-col w-full justify-between"> <div class=" mb-2.5 flex flex-col 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('Minimum Score')}</div> <div class=" self-center text-xs font-medium">
{$i18n.t('Relevance Threshold')}
</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<input <input
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
step="0.01" step="0.01"
placeholder={$i18n.t('Enter Score')} placeholder={$i18n.t('Enter Score')}
bind:value={querySettings.r} bind:value={RAGConfig.RELEVANCE_THRESHOLD}
autocomplete="off" autocomplete="off"
min="0.0" min="0.0"
title={$i18n.t( title={$i18n.t(
@ -846,12 +769,14 @@
<div class=" mb-1 text-xs font-medium">{$i18n.t('RAG Template')}</div> <div class=" mb-1 text-xs font-medium">{$i18n.t('RAG Template')}</div>
<div class="flex w-full items-center relative"> <div class="flex w-full items-center relative">
<Tooltip <Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')} content={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)}
placement="top-start" placement="top-start"
className="w-full" className="w-full"
> >
<Textarea <Textarea
bind:value={querySettings.template} bind:value={RAGConfig.RAG_TEMPLATE}
placeholder={$i18n.t( placeholder={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt' 'Leave empty to use the default prompt, or enter a custom prompt'
)} )}
@ -880,7 +805,7 @@
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Leave empty for unlimited')} placeholder={$i18n.t('Leave empty for unlimited')}
bind:value={fileMaxSize} bind:value={RAGConfig.FILE_MAX_SIZE}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
@ -901,7 +826,7 @@
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="number" type="number"
placeholder={$i18n.t('Leave empty for unlimited')} placeholder={$i18n.t('Leave empty for unlimited')}
bind:value={fileMaxCount} bind:value={RAGConfig.FILE_MAX_COUNT}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
@ -918,14 +843,14 @@
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Google Drive')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Google Drive')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Switch bind:state={enableGoogleDriveIntegration} /> <Switch bind:state={RAGConfig.ENABLE_GOOGLE_DRIVE_INTEGRATION} />
</div> </div>
</div> </div>
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('OneDrive')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('OneDrive')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Switch bind:state={enableOneDriveIntegration} /> <Switch bind:state={RAGConfig.ENABLE_ONEDRIVE_INTEGRATION} />
</div> </div>
</div> </div>
</div> </div>
@ -990,4 +915,9 @@
{$i18n.t('Save')} {$i18n.t('Save')}
</button> </button>
</div> </div>
{:else}
<div class="flex items-center justify-center h-full">
<Spinner />
</div>
{/if}
</form> </form>

View File

@ -12,12 +12,6 @@
export let saveHandler: Function; export let saveHandler: Function;
let webConfig = null;
let bypass_ssl_verification = null;
let tavily_api_key = null;
let youtube_language = null;
let webSearchEngines = [ let webSearchEngines = [
'searxng', 'searxng',
'google_pse', 'google_pse',
@ -38,33 +32,25 @@
'perplexity', 'perplexity',
'sougou' 'sougou'
]; ];
let webLoaderEngines = ['safe_web', 'playwright', 'firecrawl', 'tavily']; let webLoaderEngines = ['playwright', 'firecrawl', 'tavily'];
let webConfig = null;
const submitHandler = async () => { const submitHandler = async () => {
// Convert domain filter string to array before sending // Convert domain filter string to array before sending
if (webConfig.search.domain_filter_list) { if (webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST) {
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.split(',')
.split(',')
.map((domain) => domain.trim()) .map((domain) => domain.trim())
.filter((domain) => domain.length > 0); .filter((domain) => domain.length > 0);
} else { } else {
webConfig.search.domain_filter_list = []; webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = [];
} }
// Set the enable_ssl_verification flag based on the switch state
webConfig.loader.enable_ssl_verification = !bypass_ssl_verification;
// Set shared tavily_api_key
webConfig.search.tavily_api_key = tavily_api_key;
webConfig.loader.tavily_api_key = tavily_api_key;
webConfig.loader.youtube.language = youtube_language.split(',').map((lang) => lang.trim());
const res = await updateRAGConfig(localStorage.token, { const res = await updateRAGConfig(localStorage.token, {
web: webConfig web: webConfig
}); });
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', '); webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST = webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.join(', ');
youtube_language = webConfig.loader.youtube.language.join(', ');
}; };
onMount(async () => { onMount(async () => {
@ -72,13 +58,14 @@
if (res) { if (res) {
webConfig = res.web; webConfig = res.web;
// Convert array back to comma-separated string for display // Convert array back to comma-separated string for display
if (webConfig?.search?.domain_filter_list) { if (webConfig?.WEB_SEARCH_DOMAIN_FILTER_LIST) {
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', '); webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST =
webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST.join(', ');
} }
bypass_ssl_verification = !webConfig.loader.enable_ssl_verification;
tavily_api_key = webConfig.search.tavily_api_key || webConfig.loader.tavily_api_key; webConfig.YOUTUBE_LOADER_LANGUAGE = webConfig.YOUTUBE_LOADER_LANGUAGE.join(',');
youtube_language = webConfig.loader.youtube.language.join(', ');
} }
}); });
</script> </script>
@ -100,10 +87,10 @@
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{$i18n.t('Enable Web Search')} {$i18n.t('Web Search')}
</div> </div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<Switch bind:state={webConfig.ENABLE_RAG_WEB_SEARCH} /> <Switch bind:state={webConfig.ENABLE_WEB_SEARCH} />
</div> </div>
</div> </div>
@ -114,7 +101,7 @@
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={webConfig.search.engine} bind:value={webConfig.WEB_SEARCH_ENGINE}
placeholder={$i18n.t('Select a engine')} placeholder={$i18n.t('Select a engine')}
required required
> >
@ -126,8 +113,8 @@
</div> </div>
</div> </div>
{#if webConfig.search.engine !== ''} {#if webConfig.WEB_SEARCH_ENGINE !== ''}
{#if webConfig.search.engine === 'searxng'} {#if webConfig.WEB_SEARCH_ENGINE === 'searxng'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -140,14 +127,14 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Searxng Query URL')} placeholder={$i18n.t('Enter Searxng Query URL')}
bind:value={webConfig.search.searxng_query_url} bind:value={webConfig.SEARXNG_QUERY_URL}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'google_pse'} {:else if webConfig.WEB_SEARCH_ENGINE === 'google_pse'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -156,7 +143,7 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Google PSE API Key')} placeholder={$i18n.t('Enter Google PSE API Key')}
bind:value={webConfig.search.google_pse_api_key} bind:value={webConfig.GOOGLE_PSE_API_KEY}
/> />
</div> </div>
<div class="mt-1.5"> <div class="mt-1.5">
@ -170,14 +157,14 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Google PSE Engine Id')} placeholder={$i18n.t('Enter Google PSE Engine Id')}
bind:value={webConfig.search.google_pse_engine_id} bind:value={webConfig.GOOGLE_PSE_ENGINE_ID}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'brave'} {:else if webConfig.WEB_SEARCH_ENGINE === 'brave'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -186,11 +173,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Brave Search API Key')} placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.search.brave_search_api_key} bind:value={webConfig.BRAVE_SEARCH_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'kagi'} {:else if webConfig.WEB_SEARCH_ENGINE === 'kagi'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -199,11 +186,12 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Kagi Search API Key')} placeholder={$i18n.t('Enter Kagi Search API Key')}
bind:value={webConfig.search.kagi_search_api_key} bind:value={webConfig.KAGI_SEARCH_API_KEY}
/> />
</div> </div>
.
</div> </div>
{:else if webConfig.search.engine === 'mojeek'} {:else if webConfig.WEB_SEARCH_ENGINE === 'mojeek'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -212,11 +200,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Mojeek Search API Key')} placeholder={$i18n.t('Enter Mojeek Search API Key')}
bind:value={webConfig.search.mojeek_search_api_key} bind:value={webConfig.MOJEEK_SEARCH_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'bocha'} {:else if webConfig.WEB_SEARCH_ENGINE === 'bocha'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -225,11 +213,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Bocha Search API Key')} placeholder={$i18n.t('Enter Bocha Search API Key')}
bind:value={webConfig.search.bocha_search_api_key} bind:value={webConfig.BOCHA_SEARCH_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'serpstack'} {:else if webConfig.WEB_SEARCH_ENGINE === 'serpstack'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -238,11 +226,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Serpstack API Key')} placeholder={$i18n.t('Enter Serpstack API Key')}
bind:value={webConfig.search.serpstack_api_key} bind:value={webConfig.SERPSTACK_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'serper'} {:else if webConfig.WEB_SEARCH_ENGINE === 'serper'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -251,11 +239,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Serper API Key')} placeholder={$i18n.t('Enter Serper API Key')}
bind:value={webConfig.search.serper_api_key} bind:value={webConfig.SERPER_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'serply'} {:else if webConfig.WEB_SEARCH_ENGINE === 'serply'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -264,11 +252,24 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Serply API Key')} placeholder={$i18n.t('Enter Serply API Key')}
bind:value={webConfig.search.serply_api_key} bind:value={webConfig.SERPLY_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'searchapi'} {:else if webConfig.WEB_SEARCH_ENGINE === 'tavily'}
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.TAVILY_API_KEY}
/>
</div>
</div>
{:else if webConfig.WEB_SEARCH_ENGINE === 'searchapi'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -277,7 +278,7 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter SearchApi API Key')} placeholder={$i18n.t('Enter SearchApi API Key')}
bind:value={webConfig.search.searchapi_api_key} bind:value={webConfig.SEARCHAPI_API_KEY}
/> />
</div> </div>
<div class="mt-1.5"> <div class="mt-1.5">
@ -291,14 +292,14 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter SearchApi Engine')} placeholder={$i18n.t('Enter SearchApi Engine')}
bind:value={webConfig.search.searchapi_engine} bind:value={webConfig.SEARCHAPI_ENGINE}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'serpapi'} {:else if webConfig.WEB_SEARCH_ENGINE === 'serpapi'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -307,7 +308,7 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter SerpApi API Key')} placeholder={$i18n.t('Enter SerpApi API Key')}
bind:value={webConfig.search.serpapi_api_key} bind:value={webConfig.SERPAPI_API_KEY}
/> />
</div> </div>
<div class="mt-1.5"> <div class="mt-1.5">
@ -321,27 +322,14 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter SerpApi Engine')} placeholder={$i18n.t('Enter SerpApi Engine')}
bind:value={webConfig.search.serpapi_engine} bind:value={webConfig.SERPAPI_ENGINE}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'tavily'} {:else if webConfig.WEB_SEARCH_ENGINE === 'jina'}
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={tavily_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'jina'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -350,35 +338,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Jina API Key')} placeholder={$i18n.t('Enter Jina API Key')}
bind:value={webConfig.search.jina_api_key} bind:value={webConfig.JINA_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'exa'} {:else if webConfig.WEB_SEARCH_ENGINE === 'bing'}
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Exa API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.search.exa_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'perplexity'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Perplexity API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Perplexity API Key')}
bind:value={webConfig.search.perplexity_api_key}
/>
</div>
{:else if webConfig.search.engine === 'bing'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -391,7 +355,7 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')} placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
bind:value={webConfig.search.bing_search_v7_endpoint} bind:value={webConfig.BING_SEARCH_V7_ENDPOINT}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@ -405,11 +369,35 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')} placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
bind:value={webConfig.search.bing_search_v7_subscription_key} bind:value={webConfig.BING_SEARCH_V7_SUBSCRIPTION_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.search.engine === 'sougou'} {:else if webConfig.WEB_SEARCH_ENGINE === 'exa'}
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Exa API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.EXA_API_KEY}
/>
</div>
</div>
{:else if webConfig.WEB_SEARCH_ENGINE === 'perplexity'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Perplexity API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Perplexity API Key')}
bind:value={webConfig.PERPLEXITY_API_KEY}
/>
</div>
{:else if webConfig.WEB_SEARCH_ENGINE === 'sougou'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -418,7 +406,7 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Sougou Search API sID')} placeholder={$i18n.t('Enter Sougou Search API sID')}
bind:value={webConfig.search.sougou_api_sid} bind:value={webConfig.SOUGOU_API_SID}
/> />
</div> </div>
</div> </div>
@ -430,13 +418,14 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Sougou Search API SK')} placeholder={$i18n.t('Enter Sougou Search API SK')}
bind:value={webConfig.search.sougou_api_sk} bind:value={webConfig.SOUGOU_API_SK}
/> />
</div> </div>
</div> </div>
{/if} {/if}
{/if} {/if}
{#if webConfig.ENABLE_WEB_SEARCH}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div class="flex gap-2"> <div class="flex gap-2">
<div class="w-full"> <div class="w-full">
@ -447,7 +436,7 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Search Result Count')} placeholder={$i18n.t('Search Result Count')}
bind:value={webConfig.search.result_count} bind:value={webConfig.WEB_SEARCH_RESULT_COUNT}
required required
/> />
</div> </div>
@ -460,7 +449,7 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Concurrent Requests')} placeholder={$i18n.t('Concurrent Requests')}
bind:value={webConfig.search.concurrent_requests} bind:value={webConfig.WEB_SEARCH_CONCURRENT_REQUESTS}
required required
/> />
</div> </div>
@ -477,10 +466,51 @@
placeholder={$i18n.t( placeholder={$i18n.t(
'Enter domains separated by commas (e.g., example.com,site.org)' 'Enter domains separated by commas (e.g., example.com,site.org)'
)} )}
bind:value={webConfig.search.domain_filter_list} bind:value={webConfig.WEB_SEARCH_DOMAIN_FILTER_LIST}
/> />
</div> </div>
{/if}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
{$i18n.t('Bypass Embedding and Retrieval')}
</Tooltip>
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
? $i18n.t(
'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
)
: $i18n.t(
'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
)}
>
<Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
</Tooltip>
</div>
</div>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Trust Proxy Environment')}
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.WEB_SEARCH_TRUST_ENV
? $i18n.t(
'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.'
)
: $i18n.t('Use no proxy to fetch page contents.')}
>
<Switch bind:state={webConfig.WEB_SEARCH_TRUST_ENV} />
</Tooltip>
</div>
</div>
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div> <div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" /> <hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -492,11 +522,11 @@
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right" class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={webConfig.loader.engine} bind:value={webConfig.WEB_LOADER_ENGINE}
placeholder={$i18n.t('Select a engine')} placeholder={$i18n.t('Select a engine')}
required required
> >
<option disabled selected value="">{$i18n.t('Select a engine')}</option> <option value="">{$i18n.t('Default')}</option>
{#each webLoaderEngines as engine} {#each webLoaderEngines as engine}
<option value={engine}>{engine}</option> <option value={engine}>{engine}</option>
{/each} {/each}
@ -504,8 +534,16 @@
</div> </div>
</div> </div>
{#if webConfig.loader.engine !== ''} {#if webConfig.WEB_LOADER_ENGINE === '' || webConfig.WEB_LOADER_ENGINE === 'safe_web'}
{#if webConfig.loader.engine === 'playwright'} <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Verify SSL Certificate')}
</div>
<div class="flex items-center relative">
<Switch bind:state={webConfig.ENABLE_WEB_LOADER_SSL_VERIFICATION} />
</div>
</div>
{:else if webConfig.WEB_LOADER_ENGINE === 'playwright'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -518,7 +556,7 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Playwright WebSocket URL')} placeholder={$i18n.t('Enter Playwright WebSocket URL')}
bind:value={webConfig.loader.playwright_ws_uri} bind:value={webConfig.PLAYWRIGHT_WS_URL}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@ -534,15 +572,15 @@
<div class="flex-1"> <div class="flex-1">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Playwright Timeout (ms)')} placeholder={$i18n.t('Enter Playwright Timeout')}
bind:value={webConfig.loader.playwright_timeout} bind:value={webConfig.PLAYWRIGHT_TIMEOUT}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{:else if webConfig.loader.engine === 'firecrawl'} {:else if webConfig.WEB_LOADER_ENGINE === 'firecrawl'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -555,7 +593,7 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Firecrawl API Base URL')} placeholder={$i18n.t('Enter Firecrawl API Base URL')}
bind:value={webConfig.loader.firecrawl_api_base_url} bind:value={webConfig.FIRECRAWL_API_BASE_URL}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@ -569,11 +607,11 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Firecrawl API Key')} placeholder={$i18n.t('Enter Firecrawl API Key')}
bind:value={webConfig.loader.firecrawl_api_key} bind:value={webConfig.FIRECRAWL_API_KEY}
/> />
</div> </div>
</div> </div>
{:else if webConfig.loader.engine === 'tavily'} {:else if webConfig.WEB_LOADER_ENGINE === 'tavily'}
<div class="mb-2.5 flex w-full flex-col"> <div class="mb-2.5 flex w-full flex-col">
<div> <div>
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
@ -586,14 +624,14 @@
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter Tavily Extract Depth')} placeholder={$i18n.t('Enter Tavily Extract Depth')}
bind:value={webConfig.loader.tavily_extract_depth} bind:value={webConfig.TAVILY_EXTRACT_DEPTH}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
</div> </div>
{#if webConfig.search.engine !== 'tavily'} {#if webConfig.WEB_SEARCH_ENGINE !== 'tavily'}
<div class="mt-2"> <div class="mt-2">
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')} {$i18n.t('Tavily API Key')}
@ -601,15 +639,12 @@
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter Tavily API Key')} placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={tavily_api_key} bind:value={webConfig.TAVILY_API_KEY}
/> />
</div> </div>
{/if} {/if}
</div> </div>
{/if} {/if}
{/if}
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between"> <div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
@ -620,7 +655,7 @@
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter language codes')} placeholder={$i18n.t('Enter language codes')}
bind:value={youtube_language} bind:value={webConfig.YOUTUBE_LOADER_LANGUAGE}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@ -632,63 +667,14 @@
</div> </div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="text" type="text"
placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')} placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
bind:value={webConfig.loader.youtube.proxy_url} bind:value={webConfig.YOUTUBE_LOADER_PROXY_URL}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> </div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Bypass SSL verification for Websites')}
</div>
<div class="flex items-center relative">
<Switch bind:state={bypass_ssl_verification} />
</div>
</div>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Trust Proxy Environment')}
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.loader.trust_env
? $i18n.t(
'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.'
)
: $i18n.t('Use no proxy to fetch page contents.')}
>
<Switch bind:state={webConfig.loader.trust_env} />
</Tooltip>
</div>
</div>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
{$i18n.t('Bypass Embedding and Retrieval')}
</Tooltip>
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.loader.bypass_embedding_and_retrieval
? $i18n.t(
'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
)
: $i18n.t(
'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'
)}
>
<Switch bind:state={webConfig.loader.bypass_embedding_and_retrieval} />
</Tooltip>
</div>
</div>
</div> </div>
</div> </div>
{/if} {/if}

View File

@ -1,24 +0,0 @@
import { getRAGTemplate } from '$lib/apis/retrieval';
export const RAGTemplate = async (token: string, context: string, query: string) => {
let template = await getRAGTemplate(token).catch(() => {
return `Use the following context as your learned knowledge, inside <context></context> XML tags.
<context>
[context]
</context>
When answer to user:
- If you don't know, just say that you don't know.
- If you don't know when you are not sure, ask for clarification.
Avoid mentioning that you obtained the information from the context.
And answer according to the language of the user's question.
Given the context information, answer the query.
Query: [query]`;
});
template = template.replace(/\[context\]/g, context);
template = template.replace(/\[query\]/g, query);
return template;
};