enh: ollama loaded model display

This commit is contained in:
Timothy Jaeryang Baek 2025-05-23 19:13:18 +04:00
parent a50a8e2ef9
commit 0e6f09a0a9
2 changed files with 74 additions and 32 deletions

View File

@ -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": []}

View File

@ -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 () => {