UI touchups

This commit is contained in:
Jody Doolittle 2025-07-09 23:19:21 -07:00
parent 368c11d846
commit 372e478636
4 changed files with 215 additions and 13 deletions

View file

@ -0,0 +1,134 @@
<script lang="ts">
import * as Icons from "@lucide/svelte"
import { Modal } from "@skeletonlabs/skeleton-svelte"
interface Props {
open: boolean
modelName: string
onclose: () => void
onconfirm: (modelName: string) => void
}
let { open = $bindable(), modelName, onclose, onconfirm }: Props = $props()
let inputValue = $state(modelName)
let isLoading = $state(false)
// Function to clean the model name
function cleanModelName(input: string): string {
let cleaned = input.trim()
// Remove "ollama pull " prefix if present
if (cleaned.toLowerCase().startsWith("ollama pull ")) {
cleaned = cleaned.substring(12).trim()
}
// Remove "ollama run " prefix if present
if (cleaned.toLowerCase().startsWith("ollama run ")) {
cleaned = cleaned.substring(11).trim()
}
return cleaned
}
function handleConfirm() {
const cleanedName = cleanModelName(inputValue)
if (cleanedName) {
isLoading = true
onconfirm(cleanedName)
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") {
handleConfirm()
}
}
// Reset input when modal opens
$effect(() => {
if (open) {
inputValue = modelName
isLoading = false
}
})
</script>
<Modal
{open}
onOpenChange={(e) => {
if (!e.open) {
onclose()
}
}}
contentBase="card bg-surface-100-900 p-4 space-y-4 shadow-xl w-[30em] max-w-dvw-lg border border-surface-300-700"
backdropClasses="backdrop-blur-sm"
>
{#snippet content()}
<div class="mb-6">
<div class="mb-2 flex items-center gap-3">
<div class="bg-primary-500/10 rounded-full p-2">
<Icons.Download size={20} class="text-primary-500" />
</div>
<h3 class="text-foreground text-lg font-bold">Install Model</h3>
</div>
<p class="text-muted-foreground text-sm">
Enter the model name to download. You can use formats like "ollama pull model", "ollama run model", or just "model".
</p>
</div>
<div class="mb-6 space-y-3">
<div>
<label
class="text-foreground mb-1 block text-sm font-medium"
for="modelNameInput"
>
Model Name
</label>
<input
id="modelNameInput"
type="text"
bind:value={inputValue}
onkeydown={handleKeydown}
placeholder="e.g., llama2, ollama pull llama2, ollama run llama2"
class="input w-full"
disabled={isLoading}
/>
</div>
{#if inputValue.trim()}
<div class="bg-surface-100-900 rounded border p-3">
<div class="text-muted-foreground text-xs font-medium mb-1">
Will install:
</div>
<div class="text-foreground font-mono text-sm">
{cleanModelName(inputValue)}
</div>
</div>
{/if}
</div>
<div class="flex gap-3">
<button
class="btn btn-secondary flex-1"
onclick={onclose}
disabled={isLoading}
>
Cancel
</button>
<button
class="btn btn-primary flex-1"
onclick={handleConfirm}
disabled={isLoading || !inputValue.trim()}
>
{#if isLoading}
<Icons.Loader2 size={14} class="animate-spin" />
Installing...
{:else}
<Icons.Download size={14} />
Install
{/if}
</button>
</div>
{/snippet}
</Modal>

View file

@ -3,6 +3,7 @@
import { toaster } from "$lib/client/utils/toaster"
import * as skio from "sveltekit-io"
import { onMount, onDestroy, getContext } from "svelte"
import OllamaIcon from "../icons/OllamaIcon.svelte"
interface OllamaModel {
name: string
@ -21,10 +22,8 @@
let currentVersion = $state("")
let updateAvailable = $state(false)
let latestVersion = $state("")
let isConnected = $state(true)
let isCheckingUpdates = $state(false)
let isSavingBaseUrl = $state(false)
let installedModels: OllamaModel[] = $state([])
let showDeleteModal = $state(false)
let modelToDelete: OllamaModel | null = $state(null)
let baseUrlField = $state("")
@ -158,6 +157,32 @@
<div class="space-y-6 p-4">
<!-- Version Information -->
<div class="text-center mt-8">
<OllamaIcon class="text-muted-foreground mx-auto mb-4 h-16 w-16" />
<span class="h5">Ollama</span>
<!-- Links to documentation and GitHub -->
<div class="flex items-center justify-center gap-4 mb-6">
<a
href="https://ollama.ai/docs"
target="_blank"
rel="noopener noreferrer"
class="text-muted-foreground hover:text-primary-500 transition-colors text-xs flex items-center gap-1"
>
<Icons.BookOpen class="h-3 w-3" />
Documentation
</a>
<div class="text-muted-foreground"></div>
<a
href="https://github.com/ollama/ollama"
target="_blank"
rel="noopener noreferrer"
class="text-muted-foreground hover:text-primary-500 transition-colors text-xs flex items-center gap-1"
>
<Icons.Github class="h-3 w-3" />
GitHub
</a>
</div>
</div>
<div class="card bg-surface-100-800 flex flex-col gap-4 p-4">
<div>
<label class="block text-sm font-medium" for="baseUrl">

View file

@ -9,6 +9,7 @@
import OllamaDownloadsTab from "../ollamaManager/OllamaDownloadsTab.svelte"
import * as skio from "sveltekit-io"
import { toaster } from "$lib/client/utils/toaster"
import OllamaIcon from "../icons/OllamaIcon.svelte"
interface Props {
onclose?: () => Promise<boolean> | undefined
@ -128,54 +129,78 @@
</div>
{:else if !isConnected}
<!-- Connection setup -->
<div class="flex flex-1 items-center justify-center p-4">
<div class="w-full max-w-md space-y-4">
<div class="flex items-center justify-center p-4 mt-10">
<div class="w-full max-w-md space-y-6">
<div class="text-center">
<Icons.Server
class="text-muted-foreground mx-auto mb-4 h-12 w-12"
/>
<OllamaIcon class="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 class="text-foreground mb-2 text-lg font-semibold">
Connect to Ollama
</h3>
<p class="text-muted-foreground mb-4 text-sm">
Enter your Ollama server URL to get started.
Connect to your Ollama server to manage AI models locally.
</p>
</div>
<!-- Don't have Ollama installed? -->
<div class="bg-surface-200 dark:bg-surface-800 rounded-lg border p-4">
<div class="flex items-start gap-3">
<Icons.Download class="text-primary-500 mt-0.5 h-5 w-5 flex-shrink-0" />
<div class="flex-1">
<h4 class="text-foreground mb-1 text-sm font-medium">
Don't have Ollama installed?
</h4>
<p class="text-muted-foreground mb-3 text-xs">
Download and install Ollama to run AI models locally on your machine. It's easy!
</p>
<a
href="https://ollama.ai/download"
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm preset-filled-primary-500 inline-flex items-center gap-2"
>
<Icons.ExternalLink class="h-4 w-4" />
Download Ollama
</a>
</div>
</div>
</div>
<!-- Connection form -->
<div class="space-y-3">
<div>
<label
class="text-foreground mb-1 block text-sm font-medium"
for="ollamaBaseUrl"
>
Ollama Base URL
Ollama Server URL
</label>
<input
id="ollamaBaseUrl"
type="text"
bind:value={baseUrlField}
placeholder="http://localhost:11434/"
placeholder="http://localhost:11434"
class="input w-full"
/>
</div>
<div class="flex gap-2">
<button
class="btn btn-primary flex-1"
class="btn preset-filled-primary-500 flex-1"
onclick={handleSaveBaseUrl}
disabled={isSavingBaseUrl}
>
{#if isSavingBaseUrl}
<Icons.Loader2 class="h-4 w-4 animate-spin" />
Saving...
Connecting...
{:else}
<Icons.Save class="h-4 w-4" />
Save & Connect
{/if}
</button>
<button
class="btn btn-secondary"
class="btn preset-filled-secondary-500"
onclick={checkConnection}
title="Test connection to Ollama server"
>
<Icons.RefreshCw class="h-4 w-4" />
Test

View file

@ -133,6 +133,24 @@
<Icons.GitBranch size={16} />
<span>Repository</span>
</a>
<a
href="https://github.com/doolijb/serene-pub/wiki"
target="_blank"
rel="noopener noreferrer"
class="btn preset-filled-surface-500"
>
<Icons.BookOpen size={16} />
<span>Wiki</span>
</a>
<a
href="https://discord.gg/3kUx3MDcSa"
target="_blank"
rel="noopener noreferrer"
class="btn preset-filled-tertiary-500"
>
<Icons.MessageSquare size={16} />
<span>Discord</span>
</a>
<a
href="https://github.com/doolijb/serene-pub/issues"
target="_blank"