model editor improvs

This commit is contained in:
2025-09-22 06:48:49 +02:00
parent 4cba14715e
commit 6cda81088d
3 changed files with 171 additions and 113 deletions

View File

@@ -1,10 +1,11 @@
from nicegui import ui
from nicegui import ui, binding
from niceguiasyncelement import AsyncCard
from pathlib import Path
from utils import ollama
class OllamaDownloaderComponent(AsyncCard):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.is_downloading = False
@@ -19,8 +20,7 @@ class OllamaDownloaderComponent(AsyncCard):
model_input = ui.input(
'Model ID',
placeholder='e.g., TheBloke/Llama-2-7B-GGUF',
value='qwen2.5:0.5b'
placeholder='e.g., qwen2.5:0.5b'
).props('outlined dense').classes('w-full')
with ui.row().classes('items-center gap-2'):
ui.link('Ollama Library', target='https://ollama.com/library/', new_tab=True)
@@ -37,6 +37,9 @@ class OllamaDownloaderComponent(AsyncCard):
).props('color=primary').classes('w-full').bind_enabled_from(self, 'model_id', backward=lambda x: bool(x) and not self.is_downloading)
async def download_model(self, model):
if model.startswith('ollama run '):
model = model[11:]
self.download_btn.set_enabled(False)
try:
async for chunk in ollama.download_model(model):

View File

@@ -3,9 +3,13 @@ from niceguiasyncelement import AsyncCard
from pathlib import Path
from utils import ollama
from typing import Optional
from pprint import pprint
class OllamaModelCreationComponent(AsyncCard):
class OllamaModelEditComponent(AsyncCard):
model_info: dict
model_name_original: str
model_name = binding.BindableProperty()
model_from = binding.BindableProperty()
system_message = binding.BindableProperty()
@@ -68,127 +72,133 @@ class OllamaModelCreationComponent(AsyncCard):
self.use_seed = False
self.use_stop = False
async def build(self, existing_model_name: Optional[str] = None) -> None:
async def build(self, model_name: str, model_info: dict) -> None:
self.classes('w-full')
self.model_name_original = model_name
# Load existing model data if provided
if existing_model_name:
await self.load_existing_model(existing_model_name)
model_parameters = ollama.model_parameters(model_info)
print(model_parameters)
# pprint(model_info)
# Always load the existing model data
self.model_name = model_name
# await self.load_existing_model(model_name)
with self:
with ui.column().classes('w-full gap-4'):
title = 'Edit Model' if existing_model_name else 'Create Model'
ui.label(title).classes('text-xl font-bold')
with ui.column().classes('w-full gap-2'):
ui.label('Edit Model').classes('text-xl font-bold')
# Basic fields
model_name_default = existing_model_name if existing_model_name else 'my-custom-model:latest'
base_model_default = '' if existing_model_name else 'llama3.2:3b'
ui.input('Model Name', value=model_name_default).props('outlined dense').classes('w-full').bind_value(self, 'model_name')
ui.input('Base Model', value=base_model_default).props('outlined dense').classes('w-full').bind_value(self, 'model_from')
# Basic fields - pre-filled with existing model data
ui.input('Model Name', value=model_name, on_change=lambda e: self._on_model_name_change(e.value)).props('outlined dense').classes('w-full').bind_value(self, 'model_name').tooltip('Keep the same name to update the existing model, or change it to create a new model')
# ui.label().props('outlined dense').classes('w-full').bind_text_from(self, 'model_from')
ui.input('From', value=model_info['details']['parent_model']).props('outlined dense').classes('w-full').bind_value(self, 'model_from').set_enabled(False)
# System message field (commonly used)
ui.textarea('System Message', placeholder='You are a helpful assistant...').classes('w-full').props('autogrow outlined').bind_value(self, 'system_message')
ui.textarea('System Message', placeholder='You are a helpful assistant...', value=model_info['system'] if 'system' in model_info else '').classes('w-full').props('autogrow outlined').bind_value(self, 'system_message')
# Common Parameters section
ui.label('Common Parameters').classes('text-md font-medium mt-2 mb-3')
# Temperature (always visible)
with ui.row().classes('items-center gap-3 w-full mb-3'):
ui.switch().bind_value(self, 'use_temperature')
ui.label('Temperature').classes('min-w-fit')
ui.slider(min=0.0, max=2.0, step=0.1).classes('flex-1').bind_value(self, 'temperature').bind_enabled_from(self, 'use_temperature')
ui.label().bind_text_from(self, 'temperature', backward=lambda x: f'{x:.1f}').classes('text-xs text-gray-500 min-w-fit')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('The temperature of the model. Higher values (e.g., 1.2) make output more creative, lower values (e.g., 0.5) more focused. Default: 0.8')
# Temperature
self.build_float_component(label='Temperature', switch_binding='use_temperature', value_binding='temperature',
value_min=0.0, value_max=2.0, value_step=0.1,
value=model_parameters['temperature'] if 'temperature' in model_parameters else None,
value_default=0.8,
info='The temperature of the model. Higher values (e.g., 1.2) make output more creative, lower values (e.g., 0.5) more focused. Default: 0.8',
backward=lambda x: f'{x:.1f}')
# Context Length (always visible)
with ui.row().classes('items-center gap-3 w-full mb-3'):
ui.switch().bind_value(self, 'use_num_ctx')
ui.label('Context Length').classes('min-w-fit')
ui.number(value=4096, min=1, max=32768).classes('flex-1').bind_value(self, 'num_ctx').bind_enabled_from(self, 'use_num_ctx')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Size of the context window used to generate the next token. Default: 4096')
model_context_length: Optional[int] = None # the context length the model supports
for model_info_key, model_info_value in model_info['model_info'].items():
if model_info_key.endswith('context_length'):
model_context_length = model_info_value
self.build_float_component('Context Length', 'use_num_ctx', 'num_ctx', 512, model_context_length, 1,
model_parameters['num_ctx'] if 'num_ctx' in model_parameters else None,
4096,
'Size of the context window used to generate the next token. Default: 4096')
# Max Tokens (always visible)
with ui.row().classes('items-center gap-3 w-full mb-4'):
ui.switch().bind_value(self, 'use_num_predict')
# TODO MISSING ?????
with ui.row().classes('items-center gap-2 w-full mb-1'):
ui.switch().bind_value(self, 'use_num_predict').props('dense')
ui.label('Max Tokens').classes('min-w-fit')
ui.number(value=-1, min=-1, max=4096).classes('flex-1').bind_value(self, 'num_predict').bind_enabled_from(self, 'use_num_predict')
ui.number(value=-1, min=-1, max=4096).classes('flex-1').props('dense').bind_value(self, 'num_predict').bind_enabled_from(self, 'use_num_predict')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Maximum number of tokens to predict. -1 for infinite generation. Default: -1')
# Advanced Parameters section
ui.label('Advanced Parameters').classes('text-md font-medium mt-2 mb-3')
# Generation Parameters
with ui.expansion('Generation', icon='tune').classes('w-full mb-2'):
with ui.column().classes('w-full gap-3 pt-2'):
with ui.expansion('Generation', icon='tune', group='creation_group').classes('w-full mb-2'):
with ui.column().classes('w-full gap-1 pt-2'):
# Top K
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_top_k')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_top_k').props('dense')
ui.label('Top K').classes('min-w-fit')
ui.number(value=40, min=1, max=200).classes('flex-1').bind_value(self, 'top_k').bind_enabled_from(self, 'use_top_k')
ui.number(value=40, min=1, max=200).classes('flex-1').props('dense').bind_value(self, 'top_k').bind_enabled_from(self, 'use_top_k')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Reduces probability of generating nonsense. Higher values (e.g., 100) give more diverse answers, lower values (e.g., 10) are more conservative. Default: 40')
# Top P
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_top_p')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_top_p').props('dense')
ui.label('Top P').classes('min-w-fit')
ui.slider(min=0.0, max=1.0, step=0.05).classes('flex-1').bind_value(self, 'top_p').bind_enabled_from(self, 'use_top_p')
ui.slider(min=0.0, max=1.0, step=0.05).classes('flex-1').props('dense').bind_value(self, 'top_p').bind_enabled_from(self, 'use_top_p')
ui.label().bind_text_from(self, 'top_p', backward=lambda x: f'{x:.2f}').classes('text-xs text-gray-500 min-w-fit')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Works with top-k. Higher values (e.g., 0.95) lead to more diverse text, lower values (e.g., 0.5) generate more focused text. Default: 0.9')
# Min P
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_min_p')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_min_p').props('dense')
ui.label('Min P').classes('min-w-fit')
ui.slider(min=0.0, max=1.0, step=0.01).classes('flex-1').bind_value(self, 'min_p').bind_enabled_from(self, 'use_min_p')
ui.slider(min=0.0, max=1.0, step=0.01).classes('flex-1').props('dense').bind_value(self, 'min_p').bind_enabled_from(self, 'use_min_p')
ui.label().bind_text_from(self, 'min_p', backward=lambda x: f'{x:.2f}').classes('text-xs text-gray-500 min-w-fit')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Alternative to top_p. Minimum probability for a token relative to the most likely token. Default: 0.0')
# Repetition Parameters
with ui.expansion('Repetition Control', icon='repeat').classes('w-full mb-2'):
with ui.column().classes('w-full gap-3 pt-2'):
with ui.expansion('Repetition Control', icon='repeat', group='creation_group').classes('w-full mb-2'):
with ui.column().classes('w-full gap-1 pt-2'):
# Repeat Last N
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_repeat_last_n')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_repeat_last_n').props('dense')
ui.label('Repeat Last N').classes('min-w-fit')
ui.number(value=64, min=-1, max=512).classes('flex-1').bind_value(self, 'repeat_last_n').bind_enabled_from(self, 'use_repeat_last_n')
ui.number(value=64, min=-1, max=512).classes('flex-1').props('dense').bind_value(self, 'repeat_last_n').bind_enabled_from(self, 'use_repeat_last_n')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('How far back the model looks to prevent repetition. 0=disabled, -1=num_ctx. Default: 64')
# Repeat Penalty
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_repeat_penalty')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_repeat_penalty').props('dense')
ui.label('Repeat Penalty').classes('min-w-fit')
ui.slider(min=0.5, max=2.0, step=0.1).classes('flex-1').bind_value(self, 'repeat_penalty').bind_enabled_from(self, 'use_repeat_penalty')
ui.label().bind_text_from(self, 'repeat_penalty', backward=lambda x: f'{x:.1f}').classes('text-xs text-gray-500 min-w-fit')
ui.label().bind_text_from(self, 'repeat_penalty', backward=lambda x: f'{x:.1f}').classes('text-xs text-gray-500 min-w-fit').props('dense')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('How strongly to penalize repetitions. Higher values (e.g., 1.5) penalize more, lower values (e.g., 0.9) are more lenient. Default: 1.1')
# Control Parameters
with ui.expansion('Control', icon='settings').classes('w-full mb-2'):
with ui.column().classes('w-full gap-3 pt-2'):
with ui.expansion('Control', icon='settings', group='creation_group').classes('w-full mb-2'):
with ui.column().classes('w-full gap-1 pt-2'):
# Seed
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_seed')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_seed').props('dense')
ui.label('Seed').classes('min-w-fit')
ui.number(value=0, min=0, max=999999).classes('flex-1').bind_value(self, 'seed').bind_enabled_from(self, 'use_seed')
ui.number(value=0, min=0, max=999999).classes('flex-1').props('dense').bind_value(self, 'seed').bind_enabled_from(self, 'use_seed')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Random number seed for generation. Same seed produces same output for same prompt. Default: 0')
# Stop Sequences
with ui.row().classes('items-center gap-3 w-full'):
ui.switch().bind_value(self, 'use_stop')
with ui.row().classes('items-center gap-2 w-full'):
ui.switch().bind_value(self, 'use_stop').props('dense')
ui.label('Stop Sequence').classes('min-w-fit')
ui.input(placeholder='AI assistant:').classes('flex-1').bind_value(self, 'stop').bind_enabled_from(self, 'use_stop')
ui.input(placeholder='AI assistant:').classes('flex-1').props('dense').bind_value(self, 'stop').bind_enabled_from(self, 'use_stop')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip('Text pattern where the model stops generating. Default: none')
# Advanced section (collapsible)
with ui.expansion('Advanced Settings', icon='settings').classes('w-full').bind_value(self, 'show_advanced'):
with ui.expansion('Advanced Settings', icon='settings', group='creation_group').classes('w-full').bind_value(self, 'show_advanced'):
with ui.column().classes('w-full gap-4 pt-2'):
# Quantization
ui.select(['q4_K_M', 'q4_K_S', 'q8_0'],
label='Quantization', clearable=True).props('outlined dense').classes('w-full').bind_value(self, 'quantize')
label='Quantization', clearable=True).props('outlined dense').classes('w-full').props('dense').bind_value(self, 'quantize')
# Template field
ui.textarea('Template',
placeholder='{{ if .System }}<|im_start|>system\n{{ .System }}<|im_end|>\n{{ end }}...').classes('w-full').props('autogrow outlined').bind_value(self, 'template')
template_placeholder = '{{ if .System }}<|im_start|>system\n{{ .System }}<|im_end|>\n{{ end }}...'
ui.textarea('Template', placeholder=template_placeholder, value=model_info['template']).classes('w-full').props('autogrow outlined').props('dense').bind_value(self, 'template')
# Status and progress
with ui.row().classes('items-center gap-2'):
@@ -196,15 +206,36 @@ class OllamaModelCreationComponent(AsyncCard):
self.status_label = ui.label().bind_text_from(self, 'download_status')
ui.linear_progress(value=0, show_value=False).props('buffer=0.0 animation-speed=0').bind_value_from(self, 'download_progress')
# Create button
button_text = 'Update Model' if existing_model_name else 'Create Model'
self.create_btn = ui.button(button_text, icon='add', on_click=self.create_model).props('color=primary').classes('w-full').bind_enabled_from(self, 'model_name', backward=lambda x: bool(x) and not self.is_downloading)
# Save button
self.create_btn = ui.button('Save Model', icon='save', on_click=self.create_model).props('color=primary').classes('w-full').bind_enabled_from(self, 'model_name', backward=lambda x: bool(x) and not self.is_downloading)
def build_float_component(self, label, switch_binding,
value_binding, value_min, value_max, value_step, value, value_default,
info, backward=None
):
with ui.row().classes('items-center gap-2 w-full mb-1'):
ui.switch().bind_value(self, switch_binding).props('dense').set_value(value is not None)
ui.label(label).classes('min-w-fit')
ui.slider(min=value_min, max=value_max, step=value_step, value=value if value else value_default).classes('flex-1').props('dense').bind_value(self, value_binding).bind_enabled_from(self, switch_binding)
# TODO backward
ui.label().bind_text_from(self, value_binding, backward=backward).classes('text-xs text-gray-500 min-w-fit')
ui.icon('info', size='sm').classes('text-gray-500 cursor-help').tooltip(info)
async def _on_model_name_change(self, value):
return
if self.model_name != self.model_name_original:
self.model_from = self.model_name_original
else:
self.model_from = ''
if len(self.model_info['details']['parent_model']) > 0:
self.model_from = self.model_info['details']['parent_model']
async def load_existing_model(self, model_name):
"""Load existing model data and populate form fields"""
try:
model_info = await ollama.model_info(model_name)
modelfile = model_info.get('modelfile', '')
self.model_info = await ollama.model_info(model_name)
modelfile = self.model_info.get('modelfile', '')
await self._on_model_name_change(model_name)
# Parse the modelfile content to extract settings
for line in modelfile.split('\n'):
@@ -212,8 +243,6 @@ class OllamaModelCreationComponent(AsyncCard):
if not line:
continue
if line.startswith('FROM '):
self.model_from = line[5:].strip()
elif line.startswith('SYSTEM '):
# Extract system message (remove quotes)
system_msg = line[7:].strip()
@@ -343,7 +372,7 @@ class OllamaModelCreationComponent(AsyncCard):
# Success
self.download_status = 'success'
self.download_progress = 1.0
ui.notify(f'Model "{self.model_name}" created successfully!', type='positive')
ui.notify(f'Model "{self.model_name}" saved successfully!', type='positive')
except Exception as e:
self.download_status = f'Error: {str(e)}'

View File

@@ -4,7 +4,7 @@ from typing import Literal, List, Dict, Optional
from pprint import pprint
from niceguiasyncelement import AsyncColumn
from components.ollama_downloader import OllamaDownloaderComponent
from components.ollama_model_creation import OllamaModelCreationComponent
from components.ollama_model_edit import OllamaModelEditComponent
from components.ollama_quick_test import ModelQuickTestComponent
from components.model_info import ModelInfoComponent
@@ -48,7 +48,6 @@ class OllamaManagerPage(AsyncColumn):
with ui.row().classes('w-full items-center mb-4'):
ui.label('Installed Models').classes('text-h6 font-bold')
ui.space()
ui.button('Create New Model', icon='create', on_click=self._create_model_dialog).props('color=primary')
ui.button('Pull New Model', icon='download', on_click=self._download_model_dialog).props('color=primary')
with ui.column().classes('w-full gap-2'):
@@ -57,7 +56,7 @@ class OllamaManagerPage(AsyncColumn):
async def _create_model_dialog(self):
with ui.dialog() as dialog:
await OllamaModelCreationComponent.create()
await OllamaModelEditComponent.create()
await dialog
self.models_container.refresh()
@@ -97,10 +96,10 @@ class OllamaManagerPage(AsyncColumn):
async def _create_model_item(self, model):
base_model: Optional[Literal['ollama', 'huggingface']] = None
base_model: Optional[Literal['ollama', 'hugging-face']] = None
if len(model['details']['parent_model']) == 0:
if model['name'].startswith('hf.co/'):
base_model = 'huggingface'
base_model = 'hugging-face'
else:
base_model = 'ollama'
@@ -115,36 +114,69 @@ class OllamaManagerPage(AsyncColumn):
parameters = ollama.model_parameters(model) # if no num_ctx is in parameters, we have a default of 4096
with ui.card().classes('w-full'):
with ui.row().classes('w-full items-center'):
with ui.column().classes('flex-grow gap-1'):
# Model name
with ui.row().classes('items-center'):
with ui.row().classes('w-full flex items-center'):
with ui.column().classes('grow gap-1'):
with ui.row().classes('items-center w-full'):
ui.label(model['name']).classes('font-bold text-h6')
if base_model == 'ollama':
ui.html(f'<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ollama-dark.svg" style="width: 20px; height: 20px; object-fit: contain;" />')
if base_model == 'huggingface':
ui.html(f'<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/hugging-face.svg" style="width: 20px; height: 20px; object-fit: contain;" />')
# Details row with chips
with ui.row().classes('gap-2 flex-wrap'):
# Size chip
if base_model:
base_icon = 'ollama-dark' if base_model == 'ollama' else 'hugging-face'
ui.html(f'<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/{base_icon}.svg" style="width: 16px; height: 16px; object-fit: contain;" />')
ui.space()
# Capabilities icons
if 'vision' in capabilities:
ui.icon('visibility', size='xs').classes('text-green-500').tooltip('Vision capabilities')
if 'tools' in capabilities:
ui.icon('build', size='xs').classes('text-indigo-500').tooltip('Tool/Function calling')
if 'completion' in capabilities:
ui.icon('edit_note', size='xs').classes('text-yellow-500').tooltip('Code completion')
if 'embedding' in capabilities:
ui.icon('vector', size='xs').classes('text-pink-500').tooltip('Embedding generation')
if not base_model:
ui.label(model['details']['parent_model']).classes('text-xs text-grey-5 ml-2')
# Details row with icon rows
with ui.row().classes('gap-3 flex-wrap items-center'):
# Size
with ui.row().classes('gap-1 items-center'):
ui.icon('storage', size='xs').classes('text-cyan-500')
size_gb = model['size'] / (1024**3)
ui.chip(f"{size_gb:.2f} GB", icon='storage').props('outline dense color=cyan')
ui.label(f"{size_gb:.2f} GB").classes('text-xs')
# Quantization chip
ui.chip(model['details']['quantization_level'], icon='memory').props('outline dense color=orange')
# Quantization
with ui.row().classes('gap-1 items-center'):
ui.icon('memory', size='xs').classes('text-orange-500')
ui.label(model['details']['quantization_level']).classes('text-xs')
# Parameter size chip
# Parameter size
if model['details'].get('parameter_size'):
ui.chip(model['details']['parameter_size'], icon='tune').props('outline dense color=purple')
with ui.row().classes('gap-1 items-center'):
ui.icon('tune', size='xs').classes('text-purple-500')
ui.label(model['details']['parameter_size']).classes('text-xs')
# Format chip
# Context Length (what Ollama uses)
with ui.row().classes('gap-1 items-center'):
ui.icon('width_normal', size='xs').classes('text-blue-500')
ollama_ctx = parameters.get('num_ctx', 4096)
ctx_label = f"{ollama_ctx:,}"
if model_context_length and model_context_length != ollama_ctx:
ctx_label += f" / {model_context_length:,}"
ui.label(ctx_label).classes('text-xs').tooltip(f'Context: {ollama_ctx:,} tokens (model supports {model_context_length:,} max)' if model_context_length else f'Context: {ollama_ctx:,} tokens')
# Key parameters (if customized)
if parameters.get('temperature') and parameters['temperature'] != 0.8:
with ui.row().classes('gap-1 items-center'):
ui.icon('thermostat', size='xs').classes('text-red-500')
ui.label(f"{parameters['temperature']:.1f}").classes('text-xs').tooltip(f'Temperature: {parameters["temperature"]}')
if parameters.get('top_p'):
with ui.row().classes('gap-1 items-center'):
ui.icon('percent', size='xs').classes('text-teal-500')
ui.label(f"{parameters['top_p']:.2f}").classes('text-xs').tooltip(f'Top-p: {parameters["top_p"]}')
# Format
if model['details'].get('format'):
ui.chip(model['details']['format'].upper(), icon='description').props('outline dense color=green')
# Family chip
if model['details'].get('family'):
ui.chip(model['details']['family'], icon='category').props('outline dense color=blue')
with ui.row().classes('gap-1 items-center'):
ui.icon('description', size='xs').classes('text-gray-500')
ui.label(model['details']['format'].upper()).classes('text-xs')
# Modified timestamp
if model.get('modified_at'):
@@ -167,8 +199,6 @@ class OllamaManagerPage(AsyncColumn):
except:
pass
ui.space()
with ui.row().classes('gap-2'):
ui.button(icon='chat', on_click=lambda m=model['name']: self._test_model_dialog(m)).props('round flat').tooltip('Test Model')
ui.button(icon='edit', on_click=lambda m=model['name']: self._model_edit_model_dialog(m)).props('round flat').tooltip('Model Info')
@@ -176,18 +206,14 @@ class OllamaManagerPage(AsyncColumn):
ui.button(icon='delete', on_click=lambda m=model['name']: self._delete_model(m)).props('round flat color=negative').tooltip('Delete Model')
async def _model_edit_model_dialog(self, model_name):
model_info = await ollama.model_info(model_name)
with ui.dialog() as dialog:
await OllamaModelCreationComponent.create(model_name)
await dialog
await OllamaModelEditComponent.create(model_name, model_info)
result = await dialog
if result:
self.models_container.refresh() # type: ignore
async def _print_model_info(self, model_name):
result = await ollama.model_info(model_name)
for key, value in result.items():
print(key)
print(result['modelfile'])
async def _model_information_dialog(self, model_name):
model_info = await ollama.model_info(model_name)