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 @@
{model}
{/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
}