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
This commit is contained in:
2026-01-07 20:30:33 +01:00
parent 245526af99
commit c2136fc06a
4 changed files with 145 additions and 13 deletions

View File

@@ -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<StoredDocument[]>([]);
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.
</p>
<p class="mt-2 text-sm text-theme-muted">
<strong class="text-theme-secondary">Note:</strong> Requires an embedding model to be installed
in Ollama (e.g., <code class="rounded bg-theme-tertiary px-1">ollama pull nomic-embed-text</code>).
</p>
{#if !modelsState.hasEmbeddingModel}
<p class="mt-2 text-sm text-amber-400">
<strong>No embedding model found.</strong> Install one to use the knowledge base:
<code class="ml-1 rounded bg-theme-tertiary px-1 text-theme-muted">ollama pull nomic-embed-text</code>
</p>
{:else}
<p class="mt-2 text-sm text-emerald-400">
Embedding model available: {modelsState.embeddingModels[0]?.name}
{#if modelsState.embeddingModels.length > 1}
<span class="text-theme-muted">(+{modelsState.embeddingModels.length - 1} more)</span>
{/if}
</p>
{/if}
</section>
</div>

View File

@@ -75,9 +75,25 @@
<option value={model}>{model}</option>
{/each}
</select>
<p class="mt-2 text-xs text-theme-muted">
Note: The model must be installed in Ollama. Run <code class="bg-theme-tertiary px-1 rounded">ollama pull {settingsState.embeddingModel}</code> if not installed.
</p>
{#if !modelsState.hasEmbeddingModel}
<p class="mt-2 text-xs text-amber-400">
No embedding model installed. Run <code class="bg-theme-tertiary px-1 rounded text-theme-muted">ollama pull {settingsState.embeddingModel}</code> to enable semantic search.
</p>
{:else}
{@const selectedInstalled = modelsState.embeddingModels.some(m => m.name.includes(settingsState.embeddingModel.split(':')[0]))}
{#if !selectedInstalled}
<p class="mt-2 text-xs text-amber-400">
Selected model not installed. Run <code class="bg-theme-tertiary px-1 rounded text-theme-muted">ollama pull {settingsState.embeddingModel}</code> or select an installed model.
</p>
<p class="mt-1 text-xs text-theme-muted">
Installed: {modelsState.embeddingModels.map(m => m.name).join(', ')}
</p>
{:else}
<p class="mt-2 text-xs text-emerald-400">
Model installed and ready.
</p>
{/if}
{/if}
</div>
<!-- Auto-Compact Toggle -->

View File

@@ -23,12 +23,10 @@ export const CAPABILITY_INFO: Record<string, { label: string; icon: string; colo
};
/**
* Middleware models that should NOT appear in the chat model selector
* These are special-purpose models for embeddings, function routing, etc.
* Embedding model patterns for semantic search/RAG
*/
const MIDDLEWARE_MODEL_PATTERNS = [
const EMBEDDING_MODEL_PATTERNS = [
'embeddinggemma',
'functiongemma',
'nomic-embed',
'mxbai-embed',
'all-minilm',
@@ -36,7 +34,16 @@ const MIDDLEWARE_MODEL_PATTERNS = [
'bge-', // BGE embedding models
'e5-', // E5 embedding models
'gte-', // GTE embedding models
'embed' // Generic embed pattern (catches most embedding models)
'embed' // Generic embed pattern
];
/**
* Middleware models that should NOT appear in the chat model selector
* These are special-purpose models for embeddings, function routing, etc.
*/
const MIDDLEWARE_MODEL_PATTERNS = [
...EMBEDDING_MODEL_PATTERNS,
'functiongemma' // Function routing model
];
/** Check if a model is a middleware/utility model (not for direct chat) */
@@ -45,6 +52,12 @@ function isMiddlewareModel(model: OllamaModel): boolean {
return MIDDLEWARE_MODEL_PATTERNS.some((pattern) => 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(() => {

View File

@@ -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
}