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:
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
84
install.sh
84
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user