mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
enh: ollama loaded model display
This commit is contained in:
parent
a50a8e2ef9
commit
0e6f09a0a9
@ -9,6 +9,8 @@ import os
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@ -389,6 +391,19 @@ async def get_all_models(request: Request, user: UserModel = None):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loaded_models = await get_ollama_loaded_models(request, user=user)
|
||||||
|
expires_map = {
|
||||||
|
m["name"]: m["expires_at"]
|
||||||
|
for m in loaded_models["models"]
|
||||||
|
if "expires_at" in m
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in models["models"]:
|
||||||
|
if m["name"] in expires_map:
|
||||||
|
# Parse ISO8601 datetime with offset, get unix timestamp as int
|
||||||
|
dt = datetime.fromisoformat(expires_map[m["name"]])
|
||||||
|
m["expires_at"] = int(dt.timestamp())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
models = {"models": []}
|
models = {"models": []}
|
||||||
|
|
||||||
@ -470,7 +485,7 @@ async def get_ollama_tags(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/ps")
|
@router.get("/api/ps")
|
||||||
async def get_ollama_loaded_models(request: Request, user=Depends(get_verified_user)):
|
async def get_ollama_loaded_models(request: Request, user=Depends(get_admin_user)):
|
||||||
"""
|
"""
|
||||||
List models that are currently loaded into Ollama memory, and which node they are loaded on.
|
List models that are currently loaded into Ollama memory, and which node they are loaded on.
|
||||||
"""
|
"""
|
||||||
@ -525,10 +540,6 @@ async def get_ollama_loaded_models(request: Request, user=Depends(get_verified_u
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
|
|
||||||
models["models"] = await get_filtered_models(models, user)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
models = {"models": []}
|
models = {"models": []}
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@
|
|||||||
import Switch from '$lib/components/common/Switch.svelte';
|
import Switch from '$lib/components/common/Switch.svelte';
|
||||||
import ChatBubbleOval from '$lib/components/icons/ChatBubbleOval.svelte';
|
import ChatBubbleOval from '$lib/components/icons/ChatBubbleOval.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import dayjs from '$lib/dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@ -326,8 +329,17 @@
|
|||||||
aria-label={placeholder}
|
aria-label={placeholder}
|
||||||
id="model-selector-{id}-button"
|
id="model-selector-{id}-button"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="flex w-full text-left px-0.5 outline-hidden bg-transparent truncate {triggerClassName} justify-between font-medium placeholder-gray-400 focus:outline-hidden"
|
class="flex w-full text-left px-0.5 outline-hidden bg-transparent truncate {triggerClassName} justify-between font-medium placeholder-gray-400 focus:outline-hidden"
|
||||||
|
on:mouseenter={async () => {
|
||||||
|
models.set(
|
||||||
|
await getModels(
|
||||||
|
localStorage.token,
|
||||||
|
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{#if selectedModel}
|
{#if selectedModel}
|
||||||
{selectedModel.label}
|
{selectedModel.label}
|
||||||
@ -335,7 +347,7 @@
|
|||||||
{placeholder}
|
{placeholder}
|
||||||
{/if}
|
{/if}
|
||||||
<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
|
<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
|
||||||
</div>
|
</button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
@ -510,38 +522,57 @@
|
|||||||
<div class="line-clamp-1">
|
<div class="line-clamp-1">
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if item.model.owned_by === 'ollama' && (item.model.ollama?.details?.parameter_size ?? '') !== ''}
|
|
||||||
<div class="flex ml-1 items-center translate-y-[0.5px]">
|
|
||||||
<Tooltip
|
|
||||||
content={`${
|
|
||||||
item.model.ollama?.details?.quantization_level
|
|
||||||
? item.model.ollama?.details?.quantization_level + ' '
|
|
||||||
: ''
|
|
||||||
}${
|
|
||||||
item.model.ollama?.size
|
|
||||||
? `(${(item.model.ollama?.size / 1024 ** 3).toFixed(1)}GB)`
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
className="self-end"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class=" text-xs font-medium text-gray-600 dark:text-gray-400 line-clamp-1"
|
|
||||||
>{item.model.ollama?.details?.parameter_size ?? ''}</span
|
|
||||||
>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if item.model.owned_by === 'ollama'}
|
||||||
|
{#if (item.model.ollama?.details?.parameter_size ?? '') !== ''}
|
||||||
|
<div class="flex items-center translate-y-[0.5px]">
|
||||||
|
<Tooltip
|
||||||
|
content={`${
|
||||||
|
item.model.ollama?.details?.quantization_level
|
||||||
|
? item.model.ollama?.details?.quantization_level + ' '
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
item.model.ollama?.size
|
||||||
|
? `(${(item.model.ollama?.size / 1024 ** 3).toFixed(1)}GB)`
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
className="self-end"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class=" text-xs font-medium text-gray-600 dark:text-gray-400 line-clamp-1"
|
||||||
|
>{item.model.ollama?.details?.parameter_size ?? ''}</span
|
||||||
|
>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if item.model.ollama?.expires_at && new Date(item.model.ollama?.expires_at * 1000) > new Date()}
|
||||||
|
<div class="flex items-center translate-y-[0.5px] px-0.5">
|
||||||
|
<Tooltip
|
||||||
|
content={`${dayjs(item.model.ollama?.expires_at * 1000).fromNow()}`}
|
||||||
|
className="self-end"
|
||||||
|
>
|
||||||
|
<div class=" flex items-center">
|
||||||
|
<span class="relative flex size-2">
|
||||||
|
<span
|
||||||
|
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
|
||||||
|
/>
|
||||||
|
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- {JSON.stringify(item.info)} -->
|
<!-- {JSON.stringify(item.info)} -->
|
||||||
|
|
||||||
{#if item.model?.direct}
|
{#if item.model?.direct}
|
||||||
<Tooltip content={`${'Direct'}`}>
|
<Tooltip content={`${$i18n.t('Direct')}`}>
|
||||||
<div class="translate-y-[1px]">
|
<div class="translate-y-[1px]">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -558,7 +589,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else if item.model.connection_type === 'external'}
|
{:else if item.model.connection_type === 'external'}
|
||||||
<Tooltip content={`${'External'}`}>
|
<Tooltip content={`${$i18n.t('External')}`}>
|
||||||
<div class="translate-y-[1px]">
|
<div class="translate-y-[1px]">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -746,7 +777,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showTemporaryChatControl}
|
{#if showTemporaryChatControl}
|
||||||
<div class="flex items-center mx-2 mb-2">
|
<div class="flex items-center mx-2 mt-1 mb-2">
|
||||||
<button
|
<button
|
||||||
class="flex justify-between w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 px-3 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted"
|
class="flex justify-between w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 px-3 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user