mirror of
https://github.com/open-webui/open-webui
synced 2024-11-16 21:42:58 +00:00
refac: ollama connections
This commit is contained in:
parent
f8f36f298a
commit
607a8b2109
@ -17,10 +17,14 @@ from open_webui.config import (
|
|||||||
ENABLE_OLLAMA_API,
|
ENABLE_OLLAMA_API,
|
||||||
MODEL_FILTER_LIST,
|
MODEL_FILTER_LIST,
|
||||||
OLLAMA_BASE_URLS,
|
OLLAMA_BASE_URLS,
|
||||||
|
OLLAMA_API_CONFIGS,
|
||||||
UPLOAD_DIR,
|
UPLOAD_DIR,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
)
|
)
|
||||||
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
from open_webui.env import (
|
||||||
|
AIOHTTP_CLIENT_TIMEOUT,
|
||||||
|
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
@ -67,6 +71,8 @@ app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
|
|||||||
|
|
||||||
app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
|
app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
|
||||||
app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
|
app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
|
||||||
|
app.state.config.OLLAMA_API_CONFIGS = OLLAMA_API_CONFIGS
|
||||||
|
|
||||||
app.state.MODELS = {}
|
app.state.MODELS = {}
|
||||||
|
|
||||||
|
|
||||||
@ -92,17 +98,64 @@ async def get_status():
|
|||||||
return {"status": True}
|
return {"status": True}
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionVerificationForm(BaseModel):
|
||||||
|
url: str
|
||||||
|
key: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/verify")
|
||||||
|
async def verify_connection(
|
||||||
|
form_data: ConnectionVerificationForm, user=Depends(get_admin_user)
|
||||||
|
):
|
||||||
|
url = form_data.url
|
||||||
|
key = form_data.key
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
if key:
|
||||||
|
headers["Authorization"] = f"Bearer {key}"
|
||||||
|
|
||||||
|
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
|
||||||
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||||
|
try:
|
||||||
|
async with session.get(f"{url}/api/version", headers=headers) as r:
|
||||||
|
if r.status != 200:
|
||||||
|
# Extract response error details if available
|
||||||
|
error_detail = f"HTTP Error: {r.status}"
|
||||||
|
res = await r.json()
|
||||||
|
if "error" in res:
|
||||||
|
error_detail = f"External Error: {res['error']}"
|
||||||
|
raise Exception(error_detail)
|
||||||
|
|
||||||
|
response_data = await r.json()
|
||||||
|
return response_data
|
||||||
|
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
# ClientError covers all aiohttp requests issues
|
||||||
|
log.exception(f"Client error: {str(e)}")
|
||||||
|
# Handle aiohttp-specific connection issues, timeout etc.
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail="Open WebUI: Server Connection Error"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Unexpected error: {e}")
|
||||||
|
# Generic error handler in case parsing JSON or other steps fail
|
||||||
|
error_detail = f"Unexpected error: {str(e)}"
|
||||||
|
raise HTTPException(status_code=500, detail=error_detail)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/config")
|
@app.get("/config")
|
||||||
async def get_config(user=Depends(get_admin_user)):
|
async def get_config(user=Depends(get_admin_user)):
|
||||||
return {
|
return {
|
||||||
"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API,
|
"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API,
|
||||||
"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS,
|
"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS,
|
||||||
|
"OLLAMA_API_CONFIGS": app.state.config.OLLAMA_API_CONFIGS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class OllamaConfigForm(BaseModel):
|
class OllamaConfigForm(BaseModel):
|
||||||
ENABLE_OLLAMA_API: Optional[bool] = None
|
ENABLE_OLLAMA_API: Optional[bool] = None
|
||||||
OLLAMA_BASE_URLS: list[str]
|
OLLAMA_BASE_URLS: list[str]
|
||||||
|
OLLAMA_API_CONFIGS: dict
|
||||||
|
|
||||||
|
|
||||||
@app.post("/config/update")
|
@app.post("/config/update")
|
||||||
@ -110,17 +163,27 @@ async def update_config(form_data: OllamaConfigForm, user=Depends(get_admin_user
|
|||||||
app.state.config.ENABLE_OLLAMA_API = form_data.ENABLE_OLLAMA_API
|
app.state.config.ENABLE_OLLAMA_API = form_data.ENABLE_OLLAMA_API
|
||||||
app.state.config.OLLAMA_BASE_URLS = form_data.OLLAMA_BASE_URLS
|
app.state.config.OLLAMA_BASE_URLS = form_data.OLLAMA_BASE_URLS
|
||||||
|
|
||||||
|
app.state.config.OLLAMA_API_CONFIGS = form_data.OLLAMA_API_CONFIGS
|
||||||
|
|
||||||
|
# Remove any extra configs
|
||||||
|
config_urls = app.state.config.OLLAMA_API_CONFIGS.keys()
|
||||||
|
for url in list(app.state.config.OLLAMA_BASE_URLS):
|
||||||
|
if url not in config_urls:
|
||||||
|
app.state.config.OLLAMA_API_CONFIGS.pop(url, None)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API,
|
"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API,
|
||||||
"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS,
|
"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS,
|
||||||
|
"OLLAMA_API_CONFIGS": app.state.config.OLLAMA_API_CONFIGS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def fetch_url(url):
|
async def aiohttp_get(url, key=None):
|
||||||
timeout = aiohttp.ClientTimeout(total=3)
|
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
|
||||||
try:
|
try:
|
||||||
|
headers = {"Authorization": f"Bearer {key}"} if key else {}
|
||||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(url, headers=headers) as response:
|
||||||
return await response.json()
|
return await response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle connection error here
|
# Handle connection error here
|
||||||
@ -204,13 +267,42 @@ def merge_models_lists(model_lists):
|
|||||||
|
|
||||||
async def get_all_models():
|
async def get_all_models():
|
||||||
log.info("get_all_models()")
|
log.info("get_all_models()")
|
||||||
|
|
||||||
if app.state.config.ENABLE_OLLAMA_API:
|
if app.state.config.ENABLE_OLLAMA_API:
|
||||||
tasks = [
|
tasks = []
|
||||||
fetch_url(f"{url}/api/tags") for url in app.state.config.OLLAMA_BASE_URLS
|
for idx, url in enumerate(app.state.config.OLLAMA_BASE_URLS):
|
||||||
]
|
if url not in app.state.config.OLLAMA_API_CONFIGS:
|
||||||
|
tasks.append(aiohttp_get(f"{url}/api/tags"))
|
||||||
|
else:
|
||||||
|
api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
|
||||||
|
enable = api_config.get("enable", True)
|
||||||
|
|
||||||
|
if enable:
|
||||||
|
tasks.append(aiohttp_get(f"{url}/api/tags"))
|
||||||
|
else:
|
||||||
|
tasks.append(None)
|
||||||
|
|
||||||
responses = await asyncio.gather(*tasks)
|
responses = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
for idx, response in enumerate(responses):
|
||||||
|
if response:
|
||||||
|
url = app.state.config.OLLAMA_BASE_URLS[idx]
|
||||||
|
api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
|
||||||
|
|
||||||
|
prefix_id = api_config.get("prefix_id", None)
|
||||||
|
model_ids = api_config.get("model_ids", [])
|
||||||
|
|
||||||
|
if len(model_ids) != 0:
|
||||||
|
response["models"] = list(
|
||||||
|
filter(
|
||||||
|
lambda model: model["model"] in model_ids,
|
||||||
|
response["models"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix_id:
|
||||||
|
for model in response["models"]:
|
||||||
|
model["model"] = f"{prefix_id}.{model['model']}"
|
||||||
|
|
||||||
models = {
|
models = {
|
||||||
"models": merge_models_lists(
|
"models": merge_models_lists(
|
||||||
map(
|
map(
|
||||||
@ -279,7 +371,7 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
|
|||||||
if url_idx is None:
|
if url_idx is None:
|
||||||
# returns lowest version
|
# returns lowest version
|
||||||
tasks = [
|
tasks = [
|
||||||
fetch_url(f"{url}/api/version")
|
aiohttp_get(f"{url}/api/version")
|
||||||
for url in app.state.config.OLLAMA_BASE_URLS
|
for url in app.state.config.OLLAMA_BASE_URLS
|
||||||
]
|
]
|
||||||
responses = await asyncio.gather(*tasks)
|
responses = await asyncio.gather(*tasks)
|
||||||
@ -718,6 +810,10 @@ async def generate_completion(
|
|||||||
)
|
)
|
||||||
|
|
||||||
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
|
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
|
||||||
|
api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
|
||||||
|
prefix_id = api_config.get("prefix_id", None)
|
||||||
|
if prefix_id:
|
||||||
|
form_data.model = form_data.model.replace(f"{prefix_id}.", "")
|
||||||
log.info(f"url: {url}")
|
log.info(f"url: {url}")
|
||||||
|
|
||||||
return await post_streaming_url(
|
return await post_streaming_url(
|
||||||
@ -799,6 +895,11 @@ async def generate_chat_completion(
|
|||||||
log.info(f"url: {url}")
|
log.info(f"url: {url}")
|
||||||
log.debug(f"generate_chat_completion() - 2.payload = {payload}")
|
log.debug(f"generate_chat_completion() - 2.payload = {payload}")
|
||||||
|
|
||||||
|
api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
|
||||||
|
prefix_id = api_config.get("prefix_id", None)
|
||||||
|
if prefix_id:
|
||||||
|
payload["model"] = payload["model"].replace(f"{prefix_id}.", "")
|
||||||
|
|
||||||
return await post_streaming_url(
|
return await post_streaming_url(
|
||||||
f"{url}/api/chat",
|
f"{url}/api/chat",
|
||||||
json.dumps(payload),
|
json.dumps(payload),
|
||||||
@ -874,6 +975,11 @@ async def generate_openai_chat_completion(
|
|||||||
url = get_ollama_url(url_idx, payload["model"])
|
url = get_ollama_url(url_idx, payload["model"])
|
||||||
log.info(f"url: {url}")
|
log.info(f"url: {url}")
|
||||||
|
|
||||||
|
api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
|
||||||
|
prefix_id = api_config.get("prefix_id", None)
|
||||||
|
if prefix_id:
|
||||||
|
payload["model"] = payload["model"].replace(f"{prefix_id}.", "")
|
||||||
|
|
||||||
return await post_streaming_url(
|
return await post_streaming_url(
|
||||||
f"{url}/v1/chat/completions",
|
f"{url}/v1/chat/completions",
|
||||||
json.dumps(payload),
|
json.dumps(payload),
|
||||||
|
@ -206,10 +206,10 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
|||||||
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.OPENAI_NOT_FOUND)
|
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.OPENAI_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
async def aiohttp_get(url, key):
|
async def aiohttp_get(url, key=None):
|
||||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
|
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {key}"}
|
headers = {"Authorization": f"Bearer {key}"} if key else {}
|
||||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||||
async with session.get(url, headers=headers) as response:
|
async with session.get(url, headers=headers) as response:
|
||||||
return await response.json()
|
return await response.json()
|
||||||
|
@ -1,5 +1,44 @@
|
|||||||
import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
|
|
||||||
|
export const verifyOllamaConnection = async (
|
||||||
|
token: string = '',
|
||||||
|
url: string = '',
|
||||||
|
key: string = ''
|
||||||
|
) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`${OLLAMA_API_BASE_URL}/verify`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error = `Ollama: ${err?.error?.message ?? 'Network Problem'}`;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const getOllamaConfig = async (token: string = '') => {
|
export const getOllamaConfig = async (token: string = '') => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
@ -13,10 +13,11 @@
|
|||||||
import Switch from '$lib/components/common/Switch.svelte';
|
import Switch from '$lib/components/common/Switch.svelte';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
|
import Plus from '$lib/components/icons/Plus.svelte';
|
||||||
|
|
||||||
import OpenAIConnection from './Connections/OpenAIConnection.svelte';
|
import OpenAIConnection from './Connections/OpenAIConnection.svelte';
|
||||||
import OpenAIConnectionModal from './Connections/OpenAIConnectionModal.svelte';
|
import AddConnectionModal from './Connections/AddConnectionModal.svelte';
|
||||||
import Plus from '$lib/components/icons/Plus.svelte';
|
import OllamaConnection from './Connections/OllamaConnection.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@ -38,10 +39,14 @@
|
|||||||
|
|
||||||
let pipelineUrls = {};
|
let pipelineUrls = {};
|
||||||
let showAddOpenAIConnectionModal = false;
|
let showAddOpenAIConnectionModal = false;
|
||||||
|
let showAddOllamaConnectionModal = false;
|
||||||
|
|
||||||
const updateOpenAIHandler = async () => {
|
const updateOpenAIHandler = async () => {
|
||||||
if (ENABLE_OPENAI_API !== null) {
|
if (ENABLE_OPENAI_API !== null) {
|
||||||
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
|
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
|
||||||
|
(url, urlIdx) => OPENAI_API_BASE_URLS.indexOf(url) === urlIdx && url !== ''
|
||||||
|
).map((url) => url.replace(/\/$/, ''));
|
||||||
|
|
||||||
// Check if API KEYS length is same than API URLS length
|
// Check if API KEYS length is same than API URLS length
|
||||||
if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
|
if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
|
||||||
// if there are more keys than urls, remove the extra keys
|
// if there are more keys than urls, remove the extra keys
|
||||||
@ -76,9 +81,10 @@
|
|||||||
|
|
||||||
const updateOllamaHandler = async () => {
|
const updateOllamaHandler = async () => {
|
||||||
if (ENABLE_OLLAMA_API !== null) {
|
if (ENABLE_OLLAMA_API !== null) {
|
||||||
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '').map((url) =>
|
// Remove duplicate URLs
|
||||||
url.replace(/\/$/, '')
|
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter(
|
||||||
);
|
(url, urlIdx) => OLLAMA_BASE_URLS.indexOf(url) === urlIdx && url !== ''
|
||||||
|
).map((url) => url.replace(/\/$/, ''));
|
||||||
|
|
||||||
console.log(OLLAMA_BASE_URLS);
|
console.log(OLLAMA_BASE_URLS);
|
||||||
|
|
||||||
@ -110,6 +116,13 @@
|
|||||||
await updateOpenAIHandler();
|
await updateOpenAIHandler();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addOllamaConnectionHandler = async (connection) => {
|
||||||
|
OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, connection.url];
|
||||||
|
OLLAMA_API_CONFIGS[connection.url] = connection.config;
|
||||||
|
|
||||||
|
await updateOllamaHandler();
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.role === 'admin') {
|
if ($user.role === 'admin') {
|
||||||
let ollamaConfig = {};
|
let ollamaConfig = {};
|
||||||
@ -160,11 +173,17 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<OpenAIConnectionModal
|
<AddConnectionModal
|
||||||
bind:show={showAddOpenAIConnectionModal}
|
bind:show={showAddOpenAIConnectionModal}
|
||||||
onSubmit={addOpenAIConnectionHandler}
|
onSubmit={addOpenAIConnectionHandler}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AddConnectionModal
|
||||||
|
ollama
|
||||||
|
bind:show={showAddOllamaConnectionModal}
|
||||||
|
onSubmit={addOllamaConnectionHandler}
|
||||||
|
/>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="flex flex-col h-full justify-between text-sm"
|
class="flex flex-col h-full justify-between text-sm"
|
||||||
on:submit|preventDefault={() => {
|
on:submit|preventDefault={() => {
|
||||||
@ -219,7 +238,7 @@
|
|||||||
pipeline={pipelineUrls[url] ? true : false}
|
pipeline={pipelineUrls[url] ? true : false}
|
||||||
bind:url
|
bind:url
|
||||||
bind:key={OPENAI_API_KEYS[idx]}
|
bind:key={OPENAI_API_KEYS[idx]}
|
||||||
bind:config={OPENAI_API_CONFIGS[OPENAI_API_BASE_URLS[idx]]}
|
bind:config={OPENAI_API_CONFIGS[url]}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
updateOpenAIHandler();
|
updateOpenAIHandler();
|
||||||
}}
|
}}
|
||||||
@ -247,11 +266,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
bind:state={ENABLE_OLLAMA_API}
|
bind:state={ENABLE_OLLAMA_API}
|
||||||
on:change={async () => {
|
on:change={async () => {
|
||||||
updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API);
|
updateOllamaHandler();
|
||||||
|
|
||||||
if (OLLAMA_BASE_URLS.length === 0) {
|
|
||||||
OLLAMA_BASE_URLS = [''];
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -261,85 +276,35 @@
|
|||||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="flex justify-between items-center mb-1.5">
|
<div class="flex justify-between items-center">
|
||||||
<div class="font-medium">{$i18n.t('Manage Ollama API Connections')}</div>
|
<div class="font-medium">{$i18n.t('Manage Ollama API Connections')}</div>
|
||||||
|
|
||||||
<button
|
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||||
class="px-1"
|
<button
|
||||||
on:click={() => {
|
class="px-1"
|
||||||
OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
|
on:click={() => {
|
||||||
}}
|
showAddOllamaConnectionModal = true;
|
||||||
type="button"
|
}}
|
||||||
>
|
type="button"
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
>
|
||||||
<path
|
<Plus />
|
||||||
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
|
</button>
|
||||||
/>
|
</Tooltip>
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full gap-1.5">
|
<div class="flex w-full gap-1.5">
|
||||||
<div class="flex-1 flex flex-col gap-2">
|
<div class="flex-1 flex flex-col gap-1.5 mt-1.5">
|
||||||
{#each OLLAMA_BASE_URLS as url, idx}
|
{#each OLLAMA_BASE_URLS as url, idx}
|
||||||
<div class="flex gap-1.5">
|
<OllamaConnection
|
||||||
<input
|
bind:url
|
||||||
class="w-full text-sm bg-transparent outline-none"
|
bind:config={OLLAMA_API_CONFIGS[url]}
|
||||||
placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
|
onSubmit={() => {
|
||||||
bind:value={url}
|
updateOllamaHandler();
|
||||||
/>
|
}}
|
||||||
|
onDelete={() => {
|
||||||
<div class="flex gap-1">
|
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
|
||||||
<Tooltip content="Verify" className="self-start">
|
}}
|
||||||
<button
|
/>
|
||||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
|
||||||
on:click={() => {
|
|
||||||
verifyOllamaHandler(idx);
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Remove')} className="self-start">
|
|
||||||
<button
|
|
||||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
|
||||||
on:click={() => {
|
|
||||||
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter(
|
|
||||||
(url, urlIdx) => idx !== urlIdx
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { models } from '$lib/stores';
|
import { models } from '$lib/stores';
|
||||||
import { verifyOpenAIConnection } from '$lib/apis/openai';
|
import { verifyOpenAIConnection } from '$lib/apis/openai';
|
||||||
|
import { verifyOllamaConnection } from '$lib/apis/ollama';
|
||||||
|
|
||||||
import Modal from '$lib/components/common/Modal.svelte';
|
import Modal from '$lib/components/common/Modal.svelte';
|
||||||
import Plus from '$lib/components/icons/Plus.svelte';
|
import Plus from '$lib/components/icons/Plus.svelte';
|
||||||
@ -19,6 +20,7 @@
|
|||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let edit = false;
|
export let edit = false;
|
||||||
|
export let ollama = false;
|
||||||
|
|
||||||
export let connection = null;
|
export let connection = null;
|
||||||
|
|
||||||
@ -33,6 +35,16 @@
|
|||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
|
const verifyOllamaHandler = async () => {
|
||||||
|
const res = await verifyOllamaConnection(localStorage.token, url, key).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
toast.success($i18n.t('Server connection verified'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const verifyOpenAIHandler = async () => {
|
const verifyOpenAIHandler = async () => {
|
||||||
const res = await verifyOpenAIConnection(localStorage.token, url, key).catch((error) => {
|
const res = await verifyOpenAIConnection(localStorage.token, url, key).catch((error) => {
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
@ -43,6 +55,14 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifyHandler = () => {
|
||||||
|
if (ollama) {
|
||||||
|
verifyOllamaHandler();
|
||||||
|
} else {
|
||||||
|
verifyOpenAIHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addModelHandler = () => {
|
const addModelHandler = () => {
|
||||||
if (modelId) {
|
if (modelId) {
|
||||||
modelIds = [...modelIds, modelId];
|
modelIds = [...modelIds, modelId];
|
||||||
@ -53,7 +73,7 @@
|
|||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
if (!url || !key) {
|
if (!ollama && (!url || !key)) {
|
||||||
loading = false;
|
loading = false;
|
||||||
toast.error('URL and Key are required');
|
toast.error('URL and Key are required');
|
||||||
return;
|
return;
|
||||||
@ -159,7 +179,7 @@
|
|||||||
<button
|
<button
|
||||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
verifyOpenAIHandler();
|
verifyHandler();
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -194,7 +214,7 @@
|
|||||||
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||||
bind:value={key}
|
bind:value={key}
|
||||||
placeholder={$i18n.t('API Key')}
|
placeholder={$i18n.t('API Key')}
|
||||||
required
|
required={!ollama}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -249,9 +269,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
||||||
{$i18n.t('Leave empty to include all models from "{{URL}}/models" endpoint', {
|
{#if ollama}
|
||||||
URL: url
|
{$i18n.t('Leave empty to include all models from "{{URL}}/api/tags" endpoint', {
|
||||||
})}
|
URL: url
|
||||||
|
})}
|
||||||
|
{:else}
|
||||||
|
{$i18n.t('Leave empty to include all models from "{{URL}}/models" endpoint', {
|
||||||
|
URL: url
|
||||||
|
})}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
@ -0,0 +1,70 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext, tick } from 'svelte';
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
|
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||||
|
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||||
|
import AddConnectionModal from './AddConnectionModal.svelte';
|
||||||
|
|
||||||
|
export let onDelete = () => {};
|
||||||
|
export let onSubmit = () => {};
|
||||||
|
|
||||||
|
export let url = '';
|
||||||
|
export let config = {};
|
||||||
|
|
||||||
|
let showConfigModal = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AddConnectionModal
|
||||||
|
ollama
|
||||||
|
edit
|
||||||
|
bind:show={showConfigModal}
|
||||||
|
connection={{
|
||||||
|
url,
|
||||||
|
key: config?.key ?? '',
|
||||||
|
config: config
|
||||||
|
}}
|
||||||
|
{onDelete}
|
||||||
|
onSubmit={(connection) => {
|
||||||
|
url = connection.url;
|
||||||
|
config = { ...connection.config, key: connection.key };
|
||||||
|
onSubmit(connection);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex gap-1.5">
|
||||||
|
<Tooltip
|
||||||
|
className="w-full relative"
|
||||||
|
content={$i18n.t(`WebUI will make requests to "{{url}}/api/chat"`, {
|
||||||
|
url
|
||||||
|
})}
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
{#if !(config?.enable ?? true)}
|
||||||
|
<div
|
||||||
|
class="absolute top-0 bottom-0 left-0 right-0 opacity-60 bg-white dark:bg-gray-900 z-10"
|
||||||
|
></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="w-full text-sm bg-transparent outline-none"
|
||||||
|
placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
|
||||||
|
bind:value={url}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<Tooltip content={$i18n.t('Configure')} className="self-start">
|
||||||
|
<button
|
||||||
|
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
||||||
|
on:click={() => {
|
||||||
|
showConfigModal = true;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Cog6 />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -5,7 +5,7 @@
|
|||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||||
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||||
import OpenAIConnectionModal from './OpenAIConnectionModal.svelte';
|
import AddConnectionModal from './AddConnectionModal.svelte';
|
||||||
import { connect } from 'socket.io-client';
|
import { connect } from 'socket.io-client';
|
||||||
|
|
||||||
export let onDelete = () => {};
|
export let onDelete = () => {};
|
||||||
@ -20,7 +20,7 @@
|
|||||||
let showConfigModal = false;
|
let showConfigModal = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<OpenAIConnectionModal
|
<AddConnectionModal
|
||||||
edit
|
edit
|
||||||
bind:show={showConfigModal}
|
bind:show={showConfigModal}
|
||||||
connection={{
|
connection={{
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
let ollamaEnabled = null;
|
let ollamaEnabled = null;
|
||||||
|
|
||||||
let OLLAMA_URLS = [];
|
let OLLAMA_BASE_URLS = [];
|
||||||
let selectedOllamaUrlIdx: number | null = null;
|
let selectedOllamaUrlIdx: number | null = null;
|
||||||
|
|
||||||
let updateModelId = null;
|
let updateModelId = null;
|
||||||
@ -532,21 +532,13 @@
|
|||||||
if (ollamaConfig.ENABLE_OLLAMA_API) {
|
if (ollamaConfig.ENABLE_OLLAMA_API) {
|
||||||
ollamaEnabled = true;
|
ollamaEnabled = true;
|
||||||
|
|
||||||
await Promise.all([
|
OLLAMA_BASE_URLS = ollamaConfig.OLLAMA_BASE_URLS;
|
||||||
(async () => {
|
|
||||||
OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
|
|
||||||
toast.error(error);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (OLLAMA_URLS.length > 0) {
|
if (OLLAMA_BASE_URLS.length > 0) {
|
||||||
selectedOllamaUrlIdx = 0;
|
selectedOllamaUrlIdx = 0;
|
||||||
}
|
}
|
||||||
})(),
|
|
||||||
(async () => {
|
ollamaVersion = true;
|
||||||
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
|
|
||||||
})()
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
ollamaEnabled = false;
|
ollamaEnabled = false;
|
||||||
toast.error($i18n.t('Ollama API is disabled'));
|
toast.error($i18n.t('Ollama API is disabled'));
|
||||||
@ -568,7 +560,7 @@
|
|||||||
<div class="space-y-2 pr-1.5">
|
<div class="space-y-2 pr-1.5">
|
||||||
<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
|
<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
|
||||||
|
|
||||||
{#if OLLAMA_URLS.length > 0}
|
{#if OLLAMA_BASE_URLS.length > 0}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="flex-1 pb-1">
|
<div class="flex-1 pb-1">
|
||||||
<select
|
<select
|
||||||
@ -576,7 +568,7 @@
|
|||||||
bind:value={selectedOllamaUrlIdx}
|
bind:value={selectedOllamaUrlIdx}
|
||||||
placeholder={$i18n.t('Select an Ollama instance')}
|
placeholder={$i18n.t('Select an Ollama instance')}
|
||||||
>
|
>
|
||||||
{#each OLLAMA_URLS as url, idx}
|
{#each OLLAMA_BASE_URLS as url, idx}
|
||||||
<option value={idx} class="bg-gray-50 dark:bg-gray-700">{url}</option>
|
<option value={idx} class="bg-gray-50 dark:bg-gray-700">{url}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
Loading…
Reference in New Issue
Block a user