From c2136fc06a2acfddca41409c4875dfd22149f5b6 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Wed, 7 Jan 2026 20:30:33 +0100 Subject: [PATCH] feat: add release notes to install script and smart embedding model detection Install script improvements: - Show release notes after --update completes - Detect installed version from backend/cmd/server/main.go - Fetch releases from GitHub API and display changes between versions - Graceful fallback when jq not installed (shows link only) Embedding model detection: - Add EMBEDDING_MODEL_PATTERNS for detecting embedding models - Add embeddingModels and hasEmbeddingModel derived properties - KnowledgeTab shows embedding model status conditionally - MemoryTab shows model installation status with three states --- .../components/settings/KnowledgeTab.svelte | 19 +++-- .../lib/components/settings/MemoryTab.svelte | 22 ++++- frontend/src/lib/stores/models.svelte.ts | 33 ++++++-- install.sh | 84 +++++++++++++++++++ 4 files changed, 145 insertions(+), 13 deletions(-) diff --git a/frontend/src/lib/components/settings/KnowledgeTab.svelte b/frontend/src/lib/components/settings/KnowledgeTab.svelte index 7f984de..151ae2b 100644 --- a/frontend/src/lib/components/settings/KnowledgeTab.svelte +++ b/frontend/src/lib/components/settings/KnowledgeTab.svelte @@ -13,7 +13,7 @@ DEFAULT_EMBEDDING_MODEL } from '$lib/memory'; import type { StoredDocument } from '$lib/storage/db'; - import { toastState } from '$lib/stores'; + import { toastState, modelsState } from '$lib/stores'; let documents = $state([]); let stats = $state({ documentCount: 0, chunkCount: 0, totalTokens: 0 }); @@ -258,9 +258,18 @@ Documents are split into chunks and converted to embeddings. When you ask a question, relevant chunks are found by similarity search and included in the AI's context.

-

- Note: Requires an embedding model to be installed - in Ollama (e.g., ollama pull nomic-embed-text). -

+ {#if !modelsState.hasEmbeddingModel} +

+ No embedding model found. Install one to use the knowledge base: + ollama pull nomic-embed-text +

+ {:else} +

+ Embedding model available: {modelsState.embeddingModels[0]?.name} + {#if modelsState.embeddingModels.length > 1} + (+{modelsState.embeddingModels.length - 1} more) + {/if} +

+ {/if} diff --git a/frontend/src/lib/components/settings/MemoryTab.svelte b/frontend/src/lib/components/settings/MemoryTab.svelte index 87f875e..607273f 100644 --- a/frontend/src/lib/components/settings/MemoryTab.svelte +++ b/frontend/src/lib/components/settings/MemoryTab.svelte @@ -75,9 +75,25 @@ {/each} -

- Note: The model must be installed in Ollama. Run ollama pull {settingsState.embeddingModel} if not installed. -

+ {#if !modelsState.hasEmbeddingModel} +

+ No embedding model installed. Run ollama pull {settingsState.embeddingModel} to enable semantic search. +

+ {:else} + {@const selectedInstalled = modelsState.embeddingModels.some(m => m.name.includes(settingsState.embeddingModel.split(':')[0]))} + {#if !selectedInstalled} +

+ Selected model not installed. Run ollama pull {settingsState.embeddingModel} or select an installed model. +

+

+ Installed: {modelsState.embeddingModels.map(m => m.name).join(', ')} +

+ {:else} +

+ Model installed and ready. +

+ {/if} + {/if} diff --git a/frontend/src/lib/stores/models.svelte.ts b/frontend/src/lib/stores/models.svelte.ts index 6fcdeea..33ccbb3 100644 --- a/frontend/src/lib/stores/models.svelte.ts +++ b/frontend/src/lib/stores/models.svelte.ts @@ -23,12 +23,10 @@ export const CAPABILITY_INFO: Record name.includes(pattern)); } +/** Check if a model is an embedding model */ +function isEmbeddingModel(model: OllamaModel): boolean { + const name = model.name.toLowerCase(); + return EMBEDDING_MODEL_PATTERNS.some((pattern) => name.includes(pattern)); +} + /** Check if a model supports vision */ function isVisionModel(model: OllamaModel): boolean { const name = model.name.toLowerCase(); @@ -139,6 +152,16 @@ export class ModelsState { return this.available.filter(isVisionModel); }); + // Derived: Embedding models available for RAG/semantic search + embeddingModels = $derived.by(() => { + return this.available.filter(isEmbeddingModel); + }); + + // Derived: Check if any embedding model is available + hasEmbeddingModel = $derived.by(() => { + return this.embeddingModels.length > 0; + }); + // Derived: Check if selected model supports vision // Uses capabilities cache first (from Ollama API), falls back to pattern matching selectedSupportsVision = $derived.by(() => { diff --git a/install.sh b/install.sh index 54595fc..3272bca 100755 --- a/install.sh +++ b/install.sh @@ -103,6 +103,80 @@ prompt_yes_no() { [[ "$response" =~ ^[Yy]$ ]] } +# ============================================================================= +# Version & Release Notes +# ============================================================================= + +GITHUB_RELEASES_URL="https://api.github.com/repos/VikingOwl91/vessel/releases" +GITHUB_RELEASES_PAGE="https://github.com/VikingOwl91/vessel/releases" + +get_installed_version() { + if [[ -f "backend/cmd/server/main.go" ]]; then + grep -oP 'Version\s*=\s*"\K[^"]+' backend/cmd/server/main.go 2>/dev/null || echo "unknown" + else + echo "unknown" + fi +} + +version_gt() { + # Returns 0 (true) if $1 > $2 using version sort + [[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" != "$1" ]] +} + +show_release_notes() { + local old_version="$1" + local new_version="$2" + + # Skip if versions are the same or unknown + if [[ "$old_version" == "$new_version" ]] || [[ "$old_version" == "unknown" ]]; then + return + fi + + # Check if jq is available + if ! check_command jq; then + echo "" + echo -e "${CYAN}📋 What's New:${NC}" + echo -e " View release notes at: ${CYAN}${GITHUB_RELEASES_PAGE}${NC}" + echo "" + return + fi + + # Fetch releases from GitHub API + local releases + releases=$(curl -s --connect-timeout 5 "$GITHUB_RELEASES_URL" 2>/dev/null) || { + return + } + + # Check if we got valid JSON + if ! echo "$releases" | jq -e '.' &>/dev/null; then + return + fi + + # Filter releases between old and new version, format output + local notes + notes=$(echo "$releases" | jq -r --arg old "$old_version" --arg new "$new_version" ' + .[] | + select(.draft == false and .prerelease == false) | + (.tag_name | ltrimstr("v")) as $ver | + select( + ($ver != $old) and + ([$ver, $old] | sort_by(split(".") | map(tonumber? // 0)) | .[0] == $old) and + ([$ver, $new] | sort_by(split(".") | map(tonumber? // 0)) | .[1] == $new or $ver == $new) + ) | + "───────────────────────────────────────────────────────────\n" + + "📦 " + .tag_name + " - " + (.name // "Release") + "\n" + + "🔗 " + .html_url + "\n\n" + + ((.body // "No release notes") | split("\n")[0:5] | join("\n")) + ' 2>/dev/null) + + if [[ -n "$notes" ]]; then + echo "" + echo -e "${CYAN}📋 What's New (${old_version} → ${new_version}):${NC}" + echo -e "$notes" + echo "" + fi +} + # ============================================================================= # Prerequisite Checks # ============================================================================= @@ -366,6 +440,10 @@ do_update() { fatal "Vessel installation not found" fi + # Capture current version before updating + local old_version + old_version=$(get_installed_version) + info "Pulling latest changes..." git pull @@ -375,6 +453,12 @@ do_update() { success "Vessel has been updated" wait_for_health + + # Get new version and show release notes + local new_version + new_version=$(get_installed_version) + show_release_notes "$old_version" "$new_version" + print_success exit 0 }