From 626ff57e15c3b40c4f481f6d3cbefa38d122675e Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Sun, 18 Aug 2024 16:33:54 -0400 Subject: [PATCH 01/58] settings: convert each section to individual components --- src/lib/components/FieldHelp.svelte | 9 + src/lib/components/P.svelte | 13 ++ src/routes/settings/+page.svelte | 266 +------------------------- src/routes/settings/DangerZone.svelte | 30 +++ src/routes/settings/Ollama.svelte | 195 +++++++++++++++++++ src/routes/settings/Version.svelte | 17 ++ 6 files changed, 271 insertions(+), 259 deletions(-) create mode 100644 src/lib/components/FieldHelp.svelte create mode 100644 src/lib/components/P.svelte create mode 100644 src/routes/settings/DangerZone.svelte create mode 100644 src/routes/settings/Ollama.svelte create mode 100644 src/routes/settings/Version.svelte diff --git a/src/lib/components/FieldHelp.svelte b/src/lib/components/FieldHelp.svelte new file mode 100644 index 00000000..8e1f9150 --- /dev/null +++ b/src/lib/components/FieldHelp.svelte @@ -0,0 +1,9 @@ +
+ +
+ + diff --git a/src/lib/components/P.svelte b/src/lib/components/P.svelte new file mode 100644 index 00000000..380e15ff --- /dev/null +++ b/src/lib/components/P.svelte @@ -0,0 +1,13 @@ +

+ +

+ + diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 8d52d778..c99c8ded 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -1,239 +1,17 @@ +
-
-

Ollama

- - - - {serverStatus} - - - - - {#if ollamaURL && serverStatus === 'disconnected'} -
-

- Needs to allow connections from - {ollamaURL.origin} - in - OLLAMA_ORIGINS, - . Also check no browser extensions are blocking the connection. -

- {#if ollamaURL.protocol === 'https:'} -

- If trying to connect to an Ollama server that is not available on - localhost or 127.0.0.1 you - will need to - - create a tunnel - - to your server or - - allow mixed content - in this browser's site settings. -

- {/if} -
- {/if} -
-
- - - - - - - - -
-

- Browse the list of available models in - Ollama's library -

-
-
-
-
- -
-

Danger zone

- - - -
- -
-

- Version - -

-
+ + +
@@ -246,34 +24,4 @@ .settings__container { @apply my-auto flex flex-col gap-y-4; } - - .about, - .version { - @apply container mx-auto flex max-w-[80ch] flex-col gap-y-2 p-4; - @apply lg:p-6; - } - - .version { - @apply last:py-0 last:text-muted; - } - - .code { - @apply rounded-md p-1 text-active; - } - - .p { - @apply text-sm; - - strong { - @apply font-medium leading-none; - } - } - - .field-help { - @apply my-2 flex flex-col gap-y-3 px-0.5 text-muted; - } - - a { - @apply text-link; - } diff --git a/src/routes/settings/DangerZone.svelte b/src/routes/settings/DangerZone.svelte new file mode 100644 index 00000000..25694da4 --- /dev/null +++ b/src/routes/settings/DangerZone.svelte @@ -0,0 +1,30 @@ + + +
+

Danger zone

+ + + +
diff --git a/src/routes/settings/Ollama.svelte b/src/routes/settings/Ollama.svelte new file mode 100644 index 00000000..5d2c1b6d --- /dev/null +++ b/src/routes/settings/Ollama.svelte @@ -0,0 +1,195 @@ + + +
+

Ollama

+ + + + {serverStatus} + + + + + {#if ollamaURL && serverStatus === 'disconnected'} + +

+ Needs to allow connections from + {ollamaURL.origin} + in + OLLAMA_ORIGINS, + . Also check no browser extensions are blocking the connection. +

+ {#if ollamaURL.protocol === 'https:'} +

+ If trying to connect to an Ollama server that is not available on + localhost or 127.0.0.1 you will + need to + + create a tunnel + + to your server or + in this browser's site settings. +

+ {/if} +
+ {/if} +
+
+ + + + + + + + + +

+ Browse the list of available models in + +

+
+
+
+
diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte new file mode 100644 index 00000000..014dc9ad --- /dev/null +++ b/src/routes/settings/Version.svelte @@ -0,0 +1,17 @@ + + +
+

+ Version + +

+
From 014d454d9dc80785b12d94a7c090b9d2438be0fc Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Sun, 18 Aug 2024 16:38:06 -0400 Subject: [PATCH 02/58] fix styling for external links --- src/app.pcss | 4 ---- src/lib/components/Button.svelte | 6 +++++- src/routes/settings/Ollama.svelte | 11 +++++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app.pcss b/src/app.pcss index 8eee666d..04ec977f 100644 --- a/src/app.pcss +++ b/src/app.pcss @@ -53,7 +53,3 @@ --color-positive-muted: 120 100% 10%; } } - -a:not(.button)[target='_blank']:after { - content: ' ↗'; -} diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 96b7c04e..e00a0776 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -54,7 +54,11 @@ } &--link { - @apply text-link rounded-none; + @apply text-link inline rounded-none; + } + + &--link[target='_blank']:after { + content: ' ↗'; } &--icon { diff --git a/src/routes/settings/Ollama.svelte b/src/routes/settings/Ollama.svelte index 5d2c1b6d..4d863073 100644 --- a/src/routes/settings/Ollama.svelte +++ b/src/routes/settings/Ollama.svelte @@ -1,6 +1,11 @@

- Version -

+ {#if isCheckingForUpdates} +

+ Couldn't check for updates automatically. + +

+ {:else} +
+ + {#if isError} +

+ Couldn't check for updates automatically. + +

+ {:else if isCurrentVersionLatest} +

+ You are on the latest version. + +

+ {:else if !isCurrentVersionLatest && latestVersion} +

+ A new version is available. + {#if isDesktop} + + {:else if isDocker} + + {:else} + + {/if} +

+ {/if} +
+
+ {/if}
diff --git a/src/routes/settings/metadata/+server.ts b/src/routes/settings/metadata/+server.ts new file mode 100644 index 00000000..dd17d2f1 --- /dev/null +++ b/src/routes/settings/metadata/+server.ts @@ -0,0 +1,12 @@ +import { json } from '@sveltejs/kit'; +import { version } from '$app/environment'; +import { env } from '$env/dynamic/public'; + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ request }) { + return json({ + version, + isDesktop: env.PUBLIC_ADAPTER === 'electron-node', + isDocker: env.PUBLIC_ADAPTER === 'docker-node' + }); +} diff --git a/svelte.config.js b/svelte.config.js index 3f352a76..2383919b 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -23,7 +23,7 @@ const config = { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: ['docker-node', 'electron-node'].includes(process.env.ADAPTER) + adapter: ['docker-node', 'electron-node'].includes(process.env.PUBLIC_ADAPTER) ? adapterNode(adapterConfig) : adapterCloudflare(adapterConfig), version: { From 02229eac53562d55568d93b8f8a6947ac5c3610f Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Sun, 18 Aug 2024 21:52:30 -0400 Subject: [PATCH 04/58] use ollama-js types --- package-lock.json | 10 ++++------ package.json | 1 + src/lib/store.ts | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8a2377d..87a8b450 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "prettier-plugin-tailwindcss": "^0.5.9", + "semver": "^7.6.3", "svelte": "^4.2.7", "svelte-check": "^3.6.0", "svelte-sonner": "^0.3.27", @@ -8076,13 +8077,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, diff --git a/package.json b/package.json index 54200bbe..a33452f7 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "prettier-plugin-tailwindcss": "^0.5.9", + "semver": "^7.6.3", "svelte": "^4.2.7", "svelte-check": "^3.6.0", "svelte-sonner": "^0.3.27", diff --git a/src/lib/store.ts b/src/lib/store.ts index 8c16d461..b981c801 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,7 +1,7 @@ +import type { ModelResponse } from 'ollama/browser'; import { browser } from '$app/environment'; import { writable } from 'svelte/store'; import type { Session } from '$lib/sessions'; -import type { OllamaModel } from './ollama'; import type { Knowledge } from './knowledge'; function createLocalStorageStore(key: string, initialValue: T | null = null) { @@ -43,7 +43,7 @@ export const LOCAL_STORAGE_PREFIX = 'hollama'; export interface Settings { ollamaServer: string | null; ollamaModel: string | null; - ollamaModels: OllamaModel[]; + ollamaModels: ModelResponse[]; userTheme?: 'light' | 'dark'; } From 33dc3571c723828649796948a019a89382d61aed Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Sun, 18 Aug 2024 22:25:41 -0400 Subject: [PATCH 05/58] improve UI for update check --- src/lib/components/Badge.svelte | 2 +- src/lib/components/Button.svelte | 2 +- src/lib/store.ts | 1 + .../{settings => api}/metadata/+server.ts | 4 +- src/routes/settings/Ollama.svelte | 2 +- src/routes/settings/Version.svelte | 93 +++++++++++++------ 6 files changed, 69 insertions(+), 35 deletions(-) rename src/routes/{settings => api}/metadata/+server.ts (83%) diff --git a/src/lib/components/Badge.svelte b/src/lib/components/Badge.svelte index 0b2aebc3..07a1c82d 100644 --- a/src/lib/components/Badge.svelte +++ b/src/lib/components/Badge.svelte @@ -17,7 +17,7 @@ diff --git a/src/routes/settings/Ollama.svelte b/src/routes/settings/Ollama.svelte index 4d522cf0..03f97615 100644 --- a/src/routes/settings/Ollama.svelte +++ b/src/routes/settings/Ollama.svelte @@ -141,8 +141,9 @@ target="_blank" > see docs - . Also check no browser extensions are blocking the connection. +

+

Also check no browser extensions are blocking the connection.

{#if ollamaURL.protocol === 'https:'}

If trying to connect to an Ollama server that is not available on diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index 5c19593a..39c1408d 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -19,11 +19,12 @@ const ONE_WEEK_IN_SECONDS = 604800; const isDesktop = env.PUBLIC_ADAPTER === 'electron-node'; - const isDocker = env.PUBLIC_ADAPTER !== 'docker-node'; + const isDocker = env.PUBLIC_ADAPTER === 'docker-node'; let latestVersion: string | null = null; let isCurrentVersionLatest = false; let isCheckingForUpdates = false; + let isError = false; async function checkForUpdates(isUserInitiated = false) { const oneWeekAgoInSeconds = getUnixTime(new Date()) - ONE_WEEK_IN_SECONDS; @@ -33,28 +34,35 @@ if (!isUserInitiated && $settingsStore.lastUpdateCheck > oneWeekAgoInSeconds) return; isCheckingForUpdates = true; - toast.loading('Checking for updates'); - try { - const githubRelease = await (await fetch(GITHUB_RELEASES_API)).json(); - latestVersion = githubRelease[0]?.tag_name; + // First we get the latest version from the GitHub releases API + const githubServerResponse = await fetch(GITHUB_RELEASES_API); + if (githubServerResponse.ok) { + const response = await githubServerResponse.json(); + latestVersion = response[0]?.tag_name; + } + + // If we didn't get a response from the GitHub releases API, we check if the + // current Hollama server has already been updated so we can prompt the user + // to refresh the page. + if (!latestVersion) { + const hollamaServerResponse = await fetch(`http://localhost:5173/api/metadata`); - if (!latestVersion) { - const thisHollamaServer = await (await fetch(`http://localhost:5173/api/metadata`)).json(); - latestVersion = thisHollamaServer?.currentVersion; + if (hollamaServerResponse.ok) { + const response = await hollamaServerResponse.json(); + latestVersion = response?.currentVersion; + } else { + isError = true; } - } catch (error) { - console.error(error); } + isCheckingForUpdates = false; + $settingsStore.lastUpdateCheck = getUnixTime(new Date()); + if (!latestVersion) return; isCurrentVersionLatest = semver.lt(latestVersion, version.replace('-dev', '')); - $settingsStore.lastUpdateCheck = getUnixTime(new Date()); - isCheckingForUpdates = false; } - $: console.log('isCurrentVersionLatest', isCurrentVersionLatest); - onMount(() => { checkForUpdates(); }); @@ -63,42 +71,76 @@

Current version

- {#if !isCheckingForUpdates} -
+
+
- 0.10.2 + {version} - +
+ - {#if isCurrentVersionLatest} -

- You are on the latest version. - -

- {:else if !isCurrentVersionLatest && latestVersion} -

- A new version is available: {latestVersion}. - {#if isDesktop} - - {:else if isDocker} - - {:else} - - {/if} -

+

+ {:else if latestVersion} +

+ A newer version {latestVersion} is available. + {#if isDesktop} + + {:else if isDocker} + + {:else} + + {/if} +

+ {/if} + + {#if $settingsStore?.lastUpdateCheck && !latestVersion} +

+ Last update check + + {new Date($settingsStore.lastUpdateCheck * 1000).toLocaleString()} + +

+ {/if} {:else} +

+ Checking for updates... +

+ {/if} + + {#if isError}

Couldn't check for updates automatically.

{/if}
- {/if} +
+ + From 5fbe10015dc061ab4c6bbd22589d3058ab2827e9 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Mon, 19 Aug 2024 10:30:09 -0400 Subject: [PATCH 09/58] add padding to button "links" --- src/lib/components/Button.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 186f0d58..3d36b380 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -54,7 +54,7 @@ } &--link { - @apply text-link inline rounded-none; + @apply text-link inline rounded-none px-1; } &--link[target='_blank']:after { From bdb7691e0b30e808b53db2cdcc478a71ff7db0b3 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Mon, 19 Aug 2024 10:52:12 -0400 Subject: [PATCH 10/58] add `isDocker` `isDesktop` flags to `$settingsStore` --- src/lib/store.ts | 2 + src/routes/+layout.svelte | 19 +++-- src/routes/settings/Version.svelte | 116 ++++++++++++++--------------- 3 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/lib/store.ts b/src/lib/store.ts index 789a9c37..6ef7afa6 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -45,6 +45,8 @@ export interface Settings { ollamaModel: string | null; ollamaModels: ModelResponse[]; userTheme?: 'light' | 'dark'; + isDocker?: boolean; + isDesktop?: boolean; lastUpdateCheck?: number; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e0dc60e2..2190e3a4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,13 +1,14 @@ From 3034f07c6e8aa000b3315569ba5a75d63e26baf1 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Mon, 19 Aug 2024 10:58:06 -0400 Subject: [PATCH 12/58] set environment from data in fetch request this is so that we can mock it in tests --- src/routes/+layout.svelte | 8 +------- src/routes/settings/Version.svelte | 2 ++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 2190e3a4..d82b9bb1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -21,13 +21,7 @@ $: theme = $settingsStore?.userTheme; onMount(() => { - if (!$settingsStore) return; - - // TODO: fetch this from the server `/api/metadata` - $settingsStore.isDocker = env.PUBLIC_ADAPTER === 'docker-node'; - $settingsStore.isDesktop = env.PUBLIC_ADAPTER === 'electron-node'; - - if (!browser || theme) return; + if (!$settingsStore || !browser || theme) return; $settingsStore.userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index ab8fe11d..7b0a3b3a 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -43,6 +43,8 @@ if (hollamaServerResponse.ok) { const response = await hollamaServerResponse.json(); latestVersion = response?.currentVersion; + $settingsStore.isDesktop = response?.isDesktop; + $settingsStore.isDocker = response?.isDocker; if (latestVersion) { canRefreshToUpdate = semver.lt(version.replace('-dev', ''), latestVersion); From 78070bb6b389ddd812c16ec8423ad752b1abb481 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Mon, 19 Aug 2024 10:58:28 -0400 Subject: [PATCH 13/58] only check GH API version is the server isn't already updated --- src/routes/settings/Version.svelte | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index 7b0a3b3a..61c6b2cf 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -54,12 +54,14 @@ } // Then we get the latest version from the GitHub releases API - const githubServerResponse = await fetch(GITHUB_RELEASES_API); - if (githubServerResponse.ok) { - const response = await githubServerResponse.json(); - latestVersion = response[0]?.tag_name; - } else { - isError = true; + if (!canRefreshToUpdate) { + const githubServerResponse = await fetch(GITHUB_RELEASES_API); + if (githubServerResponse.ok) { + const response = await githubServerResponse.json(); + latestVersion = response[0]?.tag_name; + } else { + isError = true; + } } isCheckingForUpdates = false; From 2ab412c5227fe878bf4678c3eb231be32669160c Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Mon, 19 Aug 2024 10:58:40 -0400 Subject: [PATCH 14/58] Update Badge.svelte --- src/lib/components/Badge.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/Badge.svelte b/src/lib/components/Badge.svelte index 60c8615b..f65ba85b 100644 --- a/src/lib/components/Badge.svelte +++ b/src/lib/components/Badge.svelte @@ -17,7 +17,7 @@ From 5b30f6ef55aa7ddc61edc19305c386cd0677b27d Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Sun, 25 Aug 2024 14:59:50 -0400 Subject: [PATCH 18/58] add autoCheckForUpdates flag in $settingsStore --- src/lib/store.ts | 12 +++-- src/lib/utils.ts | 76 ++++++++++++++++++++++++++- src/routes/+layout.svelte | 13 ++++- src/routes/api/metadata/+server.ts | 10 +++- src/routes/settings/Version.svelte | 82 ++++++++---------------------- 5 files changed, 121 insertions(+), 72 deletions(-) diff --git a/src/lib/store.ts b/src/lib/store.ts index db29dcbb..4e957ace 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -4,6 +4,7 @@ import { writable } from 'svelte/store'; import type { Session } from '$lib/sessions'; import type { Knowledge } from './knowledge'; +// TODO: ADD A WAY TO SET DEFAULT VALUES function createLocalStorageStore(key: string, initialValue: T | null = null) { const localStorageValue: string | null = browser ? window.localStorage.getItem(key) : null; let value: T | null = initialValue; @@ -44,11 +45,12 @@ export interface Settings { ollamaServer: string | null; ollamaModel: string | null; ollamaModels: ModelResponse[]; - userTheme?: 'light' | 'dark'; - isDocker?: boolean; - isDesktop?: boolean; - shouldAutoCheckForUpdates?: boolean; - lastUpdateCheck?: number; + isDocker: boolean; + isDesktop: boolean; + currentVersion: string; + lastUpdateCheck: number | null; + autoCheckForUpdates: boolean; + userTheme: 'light' | 'dark'; } export enum StorageKey { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8f1268b7..1ddc5933 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,9 @@ -import { formatDistanceToNow } from 'date-fns'; +import semver from 'semver'; +import { formatDistanceToNow, getUnixTime } from 'date-fns'; +import { get } from 'svelte/store'; +import { version } from '$app/environment'; +import { settingsStore } from '$lib/store'; +import type { HollamaServerMetadata } from '../routes/api/metadata/+server'; export function generateStorageId() { return Math.random().toString(36).substring(2, 8); // E.g. `z7avx9` @@ -11,3 +16,72 @@ export function getUpdatedAtDate() { export function formatTimestampToNow(timestamp: string) { return formatDistanceToNow(new Date(timestamp), { addSuffix: true }); } + +const HOLLAMA_SERVER_METADATA_ENDPOINT = '/api/metadata'; + +// Updates the $settingsStore with the latest metadata from the server +export async function getHollamaServerMetadata() { + let settings = get(settingsStore); + if (!settings) throw new Error('Settings not found'); + + // HACK: We don't HAVE TO fetch the metadata from a server endpoint, + // we only do it this way because it allows us to mock the response in tests. + const hollamaServerResponse = await fetch(HOLLAMA_SERVER_METADATA_ENDPOINT); + if (hollamaServerResponse.ok) { + const response = await hollamaServerResponse.json() as HollamaServerMetadata; + settings = { ...settings, ...response }; + } + + settingsStore.set(settings); +} + +const GITHUB_RELEASES_API = 'https://api.github.com/repos/fmaclen/hollama/releases'; +const ONE_WEEK_IN_SECONDS = 604800; +const DEVELOPMENT_VERSION_SUFFIX = '-dev'; +export interface UpdateStatus { + canRefreshToUpdate: boolean; + isCurrentVersionLatest: boolean; + latestVersion: string; +} + +export async function checkForUpdates(isUserInitiated = false): Promise { + let settings = get(settingsStore); + if (!settings) return; + + if (!(settings.autoCheckForUpdates === false)) settings.autoCheckForUpdates = true; + + // If the user hasn't initiated the check, we check if the last update check was made more than a week ago. + const oneWeekAgoInSeconds = getUnixTime(new Date()) - ONE_WEEK_IN_SECONDS; + if (!settings.lastUpdateCheck) settings.lastUpdateCheck = oneWeekAgoInSeconds - 1; + if (!isUserInitiated && settings.lastUpdateCheck > oneWeekAgoInSeconds) return; + + // The server may have been already updated, so we need to fetch the metadata again. + await getHollamaServerMetadata(); + settings = get(settingsStore); + if (!settings?.currentVersion) throw new Error('Settings not found'); + + const updateStatus = { + canRefreshToUpdate: semver.lt(version.replace(DEVELOPMENT_VERSION_SUFFIX, ''), settings.currentVersion), + isCurrentVersionLatest: true, + latestVersion: settings.currentVersion, + } + + if (updateStatus.canRefreshToUpdate) { + updateStatus.isCurrentVersionLatest = false + + } else { + const githubServerResponse = await fetch(GITHUB_RELEASES_API); + if (!githubServerResponse.ok) return; + + const response = await githubServerResponse.json(); + const latestVersion = response[0]?.tag_name; + if (!latestVersion) return; + + updateStatus.latestVersion = latestVersion; + updateStatus.isCurrentVersionLatest = semver.lt(latestVersion, settings.currentVersion.replace(DEVELOPMENT_VERSION_SUFFIX, '')) + } + + settings.lastUpdateCheck = getUnixTime(new Date()); + debugger; + return updateStatus; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d82b9bb1..d98f129f 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,7 +8,9 @@ import { page } from '$app/stores'; import { settingsStore } from '$lib/store'; + import { getHollamaServerMetadata } from '$lib/utils'; import '../app.pcss'; + import { onNavigate } from '$app/navigation'; $: pathname = $page.url.pathname; const SITEMAP = [ @@ -20,8 +22,15 @@ $: theme = $settingsStore?.userTheme; - onMount(() => { - if (!$settingsStore || !browser || theme) return; + onNavigate(() => { + console.log('should check for updates'); + }); + + onMount(async () => { + if (!$settingsStore) return; + await getHollamaServerMetadata(); + + if (!browser || theme) return; $settingsStore.userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; diff --git a/src/routes/api/metadata/+server.ts b/src/routes/api/metadata/+server.ts index e4d0bd85..461a8c91 100644 --- a/src/routes/api/metadata/+server.ts +++ b/src/routes/api/metadata/+server.ts @@ -2,11 +2,17 @@ import { json } from '@sveltejs/kit'; import { version } from '$app/environment'; import { env } from '$env/dynamic/public'; +export interface HollamaServerMetadata { + currentVersion: string; + isDocker: boolean; + isDesktop: boolean; +} + /** @type {import('./$types').RequestHandler} */ export async function GET() { return json({ currentVersion: version, isDesktop: env.PUBLIC_ADAPTER === 'electron-node', - isDocker: env.PUBLIC_ADAPTER === 'docker-node' - }); + isDocker: env.PUBLIC_ADAPTER === 'docker-node', + } as HollamaServerMetadata); } diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index 67a8d193..a52a2fd7 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -1,7 +1,5 @@ @@ -85,16 +42,17 @@ {/if} + @@ -103,21 +61,21 @@ {#if isCheckingForUpdates}

Checking for updates...

- {:else if isCurrentVersionLatest} + {:else if updateStatus?.isCurrentVersionLatest}

You are on the latest version.

- {:else if latestVersion} + {:else if updateStatus?.latestVersion}

- A newer version is available {latestVersion} - {#if canRefreshToUpdate} + A newer version is available {updateStatus?.latestVersion} + {#if updateStatus?.canRefreshToUpdate} {:else if $settingsStore?.isDocker} {:else} {/if}

- {:else if isError} + {:else if couldntCheckForUpdates}

Couldn't check for updates automatically.

{/if} - {#if !isCheckingForUpdates && !latestVersion && $settingsStore?.lastUpdateCheck} + {#if !isCheckingForUpdates && !updateStatus?.latestVersion && $settingsStore?.lastUpdateCheck}

Last update check From 4f6e2e709e385285b000c00ec3dc16f6f4e5b1f6 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Tue, 27 Aug 2024 19:48:26 -0400 Subject: [PATCH 19/58] remove debuggers --- src/lib/utils.ts | 1 - src/routes/+layout.svelte | 11 +++-------- src/routes/settings/Version.svelte | 7 ------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1ddc5933..44e98568 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -82,6 +82,5 @@ export async function checkForUpdates(isUserInitiated = false): Promise { - console.log('should check for updates'); + if (!$settingsStore || !($settingsStore.autoCheckForUpdates === false)) checkForUpdates(); }); onMount(async () => { @@ -63,7 +63,7 @@ {#each SITEMAP as [href, text]} - + {#if href === '/knowledge'} {:else if href === '/sessions'} @@ -114,11 +114,6 @@ @apply lg:max-h-10 lg:min-w-10; } - .layout__homepage { - @apply col-start-3 row-start-1 flex items-center; - @apply lg:py-4; - } - .layout__button, .layout__a { @apply flex w-auto flex-grow flex-col items-center gap-x-2 gap-y-0.5 py-3 text-xs font-medium text-muted transition-colors duration-150; diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index a52a2fd7..b7ef5d5d 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -1,5 +1,4 @@

From 03489e2e6a3139a2ba02d3ece8ea756d2d7f50fa Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Tue, 27 Aug 2024 21:22:39 -0400 Subject: [PATCH 20/58] prevent character shifting --- src/lib/components/Badge.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/components/Badge.svelte b/src/lib/components/Badge.svelte index f65ba85b..f4b41356 100644 --- a/src/lib/components/Badge.svelte +++ b/src/lib/components/Badge.svelte @@ -18,6 +18,7 @@ diff --git a/tailwind.config.js b/tailwind.config.js index 291d5cc4..9249023d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -62,6 +62,9 @@ const config = { }, '.text-link': { '@apply underline underline-offset-4 hover:text-accent': {} + }, + '.badge': { + '@apply inline-flex max-w-max items-center rounded-md px-2 py-0.5 font-mono text-xs': {} } }); } From c98d501280424745a9294a250fd35ec44fa1e8c3 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Thu, 29 Aug 2024 21:55:17 -0400 Subject: [PATCH 48/58] add i18n keys for strings in `` --- src/lib/i18n.ts | 17 ++++++++++++++--- src/routes/+error.svelte | 8 ++++---- src/routes/settings/Ollama.svelte | 5 ----- src/routes/settings/Version.svelte | 29 +++++++++++++++++------------ tests/version.test.ts | 4 ++-- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index c5254ea7..c9f500f3 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -26,6 +26,17 @@ i18next.init({ pullingModel: 'Pulling model', pullModelPlaceholder: 'Model tag (e.g. llama3.1)', modelWasDownloaded: '{{model}} was downloaded', + automatcallyCheckForUpdates: 'Automatically check for updates', + checkNow: 'Check now', + checkingForUpdates: 'Checking for updates...', + couldntCheckForUpdates: 'Couldn\'t check for updates automatically', + isCurrentVersionLatest: 'You are on the latest version', + isLatestVersion: 'A newer version is available', + refreshToUpdate: 'Refresh to update', + howToUpdateDocker: 'How to update Docker container?', + goToDownloads: 'Go to downloads', + goToReleases: 'Go to releases', + releaseHistory: 'Release history', sessionsPage: { new: 'New session', empty: 'No sessions', @@ -80,9 +91,9 @@ i18next.init({ }, errors: { genericError: 'Sorry, something went wrong.\n```\n${{error}}\n```', - somethingWentWrong: 'Sorry, something went wrong.', - notFound: 'The page you are looking for does not exist.', - internalServerError: 'There was an internal server error. Please try again later.', + somethingWentWrong: 'Sorry, something went wrong', + notFound: 'The page you are looking for does not exist', + internalServerError: 'There was an internal server error, please try again later', cantConnectToOllamaServer: "Can't connect to Ollama server", couldntConnectToOllamaServer: "Couldn't connect to Ollama server", ollamaConnectionError: "Couldn't connect to Ollama. Is the [server running](/settings)?" diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 8ae10214..0013872e 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -7,11 +7,11 @@ {#if $page.status === 404} Error {$page.status} - — {$i18n.t('errors.notFound')} + — {$i18n.t('errors.notFound')} + {:else if $page.status !== 200} Error {$page.status} - — {$i18n.t('errors.internalServerError')} + — {$i18n.t('errors.internalServerError')} + {/if} diff --git a/src/routes/settings/Ollama.svelte b/src/routes/settings/Ollama.svelte index 56d7e3b6..79907c50 100644 --- a/src/routes/settings/Ollama.svelte +++ b/src/routes/settings/Ollama.svelte @@ -204,8 +204,3 @@
- - diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index b7518332..fd49906e 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -1,6 +1,7 @@
diff --git a/tests/version.test.ts b/tests/version.test.ts index 5582835a..6d19be8f 100644 --- a/tests/version.test.ts +++ b/tests/version.test.ts @@ -145,20 +145,20 @@ test('update check on navigation when auto-update is enabled', async ({ page }) await expect(autoUpdateCheckbox).not.toBeVisible(); const settingsLink = page.locator('.layout__a', { hasText: 'Settings' }); - await expect(settingsLink).not.toHaveClass(/ layout__a--badge/); + await expect(settingsLink).not.toHaveClass(/ layout__a--notification/); await page.locator('.layout__a', { hasText: 'Motd' }).click(); await expect(autoUpdateCheckbox).not.toBeVisible(); - await expect(settingsLink).toHaveClass(/ layout__a--badge/); + await expect(settingsLink).toHaveClass(/ layout__a--notification/); await expect(page.getByText('A newer version is available')).not.toBeVisible(); localStorageValue = await page.evaluate(() => window.localStorage.getItem('hollama-settings')); expect(localStorageValue).not.toContain('"lastUpdateCheck":null'); await settingsLink.click(); - expect(autoUpdateCheckbox).toBeVisible(); - await expect(settingsLink).not.toHaveClass(/ layout__a--badge/); await expect(page.getByText('A newer version is available')).toBeVisible(); + await expect(settingsLink).not.toHaveClass(/ layout__a--notification/); + await expect(autoUpdateCheckbox).toBeVisible(); localStorageValue = await page.evaluate(() => window.localStorage.getItem('hollama-settings')); }); @@ -185,10 +185,10 @@ test('no update check on navigation when auto-update is disabled', async ({ page expect(localStorageValue).toContain('"lastUpdateCheck":null'); const settingsLink = page.locator('.layout__a', { hasText: 'Settings' }); - await expect(settingsLink).not.toHaveClass(/ layout__a--badge/); + await expect(settingsLink).not.toHaveClass(/ layout__a--notification/); await page.locator('.layout__a', { hasText: 'Knowledge' }).click(); - await expect(settingsLink).not.toHaveClass(/ layout__a--badge/); + await expect(settingsLink).not.toHaveClass(/ layout__a--notification/); await settingsLink.click(); await expect(page.getByLabel('Automatically check for updates')).not.toBeChecked(); From 411e19b85ddc99f92ee3965abc02a9c2f386d8f5 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 09:45:17 -0400 Subject: [PATCH 52/58] use more descriptive variable names --- src/lib/store.ts | 6 +++--- src/lib/updates.ts | 25 +++++++++++-------------- src/routes/api/metadata/+server.ts | 4 ++-- src/routes/settings/Version.svelte | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/lib/store.ts b/src/lib/store.ts index cf45f0d7..93cc812d 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -4,7 +4,7 @@ import { writable } from 'svelte/store'; import type { Session } from '$lib/sessions'; import type { Knowledge } from './knowledge'; import { env } from '$env/dynamic/public'; -import type { HollamaServerMetadata } from '../routes/api/metadata/+server'; +import type { HollamaMetadata } from '../routes/api/metadata/+server'; function createLocalStorageStore(key: string, defaultValue: T) { const initialValue: T = browser @@ -55,7 +55,7 @@ export interface Settings { lastUpdateCheck: number | null; autoCheckForUpdates: boolean; userTheme: 'light' | 'dark'; - hollamaServerMetadata: HollamaServerMetadata; + hollamaMetadata: HollamaMetadata; } const defaultSettings: Settings = { @@ -66,7 +66,7 @@ const defaultSettings: Settings = { lastUpdateCheck: null, autoCheckForUpdates: false, userTheme: 'light', - hollamaServerMetadata: { + hollamaMetadata: { currentVersion: version, isDesktop: env.PUBLIC_ADAPTER === 'electron-node', isDocker: env.PUBLIC_ADAPTER === 'docker-node' diff --git a/src/lib/updates.ts b/src/lib/updates.ts index 97d9df47..7e542078 100644 --- a/src/lib/updates.ts +++ b/src/lib/updates.ts @@ -5,7 +5,7 @@ import { get, writable } from 'svelte/store'; import { version } from '$app/environment'; import { settingsStore } from '$lib/store'; import { GITHUB_RELEASES_API } from './github'; -import type { HollamaServerMetadata } from '../routes/api/metadata/+server'; +import type { HollamaMetadata } from '../routes/api/metadata/+server'; const HOLLAMA_SERVER_METADATA_ENDPOINT = '/api/metadata'; const ONE_WEEK_IN_SECONDS = 604800; @@ -42,24 +42,21 @@ export async function checkForUpdates(isUserInitiated = false): Promise { updateStatus.isCheckingForUpdates = true; // The server may have been already updated, so we fetch the latest metadata - let hollamaServerResponse: Response; + let hollamaMetadata: Response; try { - hollamaServerResponse = await fetch(HOLLAMA_SERVER_METADATA_ENDPOINT); - const response = (await hollamaServerResponse.json()) as HollamaServerMetadata; - settings.hollamaServerMetadata = response; + hollamaMetadata = await fetch(HOLLAMA_SERVER_METADATA_ENDPOINT); + const response = (await hollamaMetadata.json()) as HollamaMetadata; + settings.hollamaMetadata = response; } catch (_) { console.error('Failed to fetch Hollama server metadata'); updateStatus.couldntCheckForUpdates = true; } // Determine if the server has been updated, and if so, which version is the latest - updateStatus.canRefreshToUpdate = semver.lt( - version, - settings.hollamaServerMetadata.currentVersion - ); + updateStatus.canRefreshToUpdate = semver.lt(version, settings.hollamaMetadata.currentVersion); updateStatus.isCurrentVersionLatest = !updateStatus.canRefreshToUpdate; - updateStatus.latestVersion = settings.hollamaServerMetadata.currentVersion; + updateStatus.latestVersion = settings.hollamaMetadata.currentVersion; updateStatus.showSidebarNotification = !updateStatus.isCurrentVersionLatest; if (updateStatus.canRefreshToUpdate) { @@ -68,11 +65,11 @@ export async function checkForUpdates(isUserInitiated = false): Promise { updateStatus.isCheckingForUpdates = false; } else { // The server hasn't been updated, so we check if Github has a newer version - let githubServerResponse: Response; + let githubReleases: Response; try { - githubServerResponse = await fetch(GITHUB_RELEASES_API); - const response = await githubServerResponse.json(); + githubReleases = await fetch(GITHUB_RELEASES_API); + const response = await githubReleases.json(); if (response[0]?.tag_name && response[0].tag_name !== '') updateStatus.latestVersion = response[0].tag_name; } catch (_) { @@ -82,7 +79,7 @@ export async function checkForUpdates(isUserInitiated = false): Promise { updateStatus.isCurrentVersionLatest = semver.lt( updateStatus.latestVersion, - settings.hollamaServerMetadata.currentVersion + settings.hollamaMetadata.currentVersion ); updateStatus.showSidebarNotification = !updateStatus.isCurrentVersionLatest; updateStatus.isCheckingForUpdates = false; diff --git a/src/routes/api/metadata/+server.ts b/src/routes/api/metadata/+server.ts index b58b1855..0619b2bd 100644 --- a/src/routes/api/metadata/+server.ts +++ b/src/routes/api/metadata/+server.ts @@ -2,7 +2,7 @@ import { json } from '@sveltejs/kit'; import { version } from '$app/environment'; import { env } from '$env/dynamic/public'; -export interface HollamaServerMetadata { +export interface HollamaMetadata { currentVersion: string; isDocker: boolean; isDesktop: boolean; @@ -14,5 +14,5 @@ export async function GET() { currentVersion: version, isDesktop: env.PUBLIC_ADAPTER === 'electron-node', isDocker: env.PUBLIC_ADAPTER === 'docker-node' - } as HollamaServerMetadata); + } as HollamaMetadata); } diff --git a/src/routes/settings/Version.svelte b/src/routes/settings/Version.svelte index 5dd9ebca..9697e710 100644 --- a/src/routes/settings/Version.svelte +++ b/src/routes/settings/Version.svelte @@ -65,7 +65,7 @@ - {:else if $settingsStore.hollamaServerMetadata.isDocker} + {:else if $settingsStore.hollamaMetadata.isDocker}
diff --git a/tests/settings.test.ts b/tests/settings.test.ts index bf5d4223..10e2edae 100644 --- a/tests/settings.test.ts +++ b/tests/settings.test.ts @@ -54,7 +54,7 @@ test('handles server status updates correctly', async ({ page }) => { await expect(page.getByText('disconnected')).toHaveClass(/badge--warning/); }); -test('settings can be deleted', async ({ page }) => { +test('deletes all settings and resets to default values', async ({ page }) => { await page.goto('/'); const modelSelect = page.getByLabel('Available models'); await expect(modelSelect).toHaveValue(''); @@ -74,7 +74,7 @@ test('settings can be deleted', async ({ page }) => { // Click the delete button page.on('dialog', (dialog) => dialog.accept('Are you sure you want to delete server settings?')); - await page.getByText('Delete server settings').click(); + await page.getByText('Delete all settings').click(); // Wait for page reload await page.waitForFunction(() => { From e83a75faca73640d85b60511ca75dd8116a3be11 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 10:16:16 -0400 Subject: [PATCH 54/58] better test names --- tests/version.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/version.test.ts b/tests/version.test.ts index 6d19be8f..06c957ab 100644 --- a/tests/version.test.ts +++ b/tests/version.test.ts @@ -14,7 +14,7 @@ test.beforeEach(async ({ page }) => { await page.route(GITHUB_RELEASES_API, (route) => route.fulfill()); }); -test('manual update check works regardless of auto-update setting', async ({ page }) => { +test('performs manual update check regardless of auto-update preference', async ({ page }) => { await page.route(GITHUB_RELEASES_API, (route) => route.fulfill({ json: [{ tag_name: MOCK_NEWER_VERSION }] @@ -34,7 +34,7 @@ test('manual update check works regardless of auto-update setting', async ({ pag await expect(page.getByText(`A newer version is available ${MOCK_NEWER_VERSION}`)).toBeVisible(); }); -test('displays appropriate message when on latest version', async ({ page }) => { +test('shows "up-to-date" message when on latest version', async ({ page }) => { await page.route(GITHUB_RELEASES_API, (route) => route.fulfill({ json: [{ tag_name: currentVersion }] }) ); @@ -45,7 +45,7 @@ test('displays appropriate message when on latest version', async ({ page }) => await expect(page.getByText('You are on the latest version')).toBeVisible(); }); -test('handles Docker environment correctly', async ({ page }) => { +test('displays Docker-specific update instructions in Docker environment', async ({ page }) => { await page.route('**/api/metadata', (route) => route.fulfill({ json: { @@ -66,7 +66,7 @@ test('handles Docker environment correctly', async ({ page }) => { await expect(page.getByText('How to update Docker container?')).toBeVisible(); }); -test('handles Desktop environment correctly', async ({ page }) => { +test('shows download link for updates in Desktop environment', async ({ page }) => { await page.route('**/api/metadata', (route) => route.fulfill({ json: { @@ -87,7 +87,7 @@ test('handles Desktop environment correctly', async ({ page }) => { await expect(page.getByText('Go to downloads')).toBeVisible(); }); -test('displays error message when unable to check for updates', async ({ page }) => { +test('shows error message when update check fails', async ({ page }) => { await page.route(GITHUB_RELEASES_API, (route) => route.abort('failed')); await page.goto('/settings'); const checkNowButton = page.getByRole('button', { name: 'Check now' }); @@ -96,7 +96,7 @@ test('displays error message when unable to check for updates', async ({ page }) await expect(page.getByRole('link', { name: 'Go to releases' })).toBeVisible(); }); -test('update check on navigation when auto-update is enabled', async ({ page }) => { +test('performs automatic update check on navigation when enabled', async ({ page }) => { await page.route('**/api/metadata', (route) => route.fulfill({ json: { @@ -162,7 +162,7 @@ test('update check on navigation when auto-update is enabled', async ({ page }) localStorageValue = await page.evaluate(() => window.localStorage.getItem('hollama-settings')); }); -test('no update check on navigation when auto-update is disabled', async ({ page }) => { +test('skips automatic update check on navigation when disabled', async ({ page }) => { await page.route('**/api/metadata', (route) => route.fulfill({ json: { From 1c7666c54cc6e86a719fab8d47d0e97d9cd0d45b Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 11:09:52 -0400 Subject: [PATCH 55/58] more predictable version comparison logic --- src/lib/updates.ts | 35 +++++++++++++++++++++++++---------- tests/version.test.ts | 5 +++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/lib/updates.ts b/src/lib/updates.ts index 7e542078..076a5099 100644 --- a/src/lib/updates.ts +++ b/src/lib/updates.ts @@ -7,7 +7,8 @@ import { settingsStore } from '$lib/store'; import { GITHUB_RELEASES_API } from './github'; import type { HollamaMetadata } from '../routes/api/metadata/+server'; -const HOLLAMA_SERVER_METADATA_ENDPOINT = '/api/metadata'; +const HOLLAMA_DEV_VERSION_SUFFIX = '-dev'; +const HOLLAMA_METADATA_ENDPOINT = '/api/metadata'; const ONE_WEEK_IN_SECONDS = 604800; export interface UpdateStatus { @@ -21,19 +22,30 @@ export interface UpdateStatus { export const updateStatusStore = writable({ canRefreshToUpdate: false, - isCurrentVersionLatest: true, + isCurrentVersionLatest: false, isCheckingForUpdates: false, showSidebarNotification: false, couldntCheckForUpdates: false, - latestVersion: version // Default to the current version + latestVersion: '' }); +// In development and test environments we append a '-dev' suffix to the version +// to indicate that it's a development version. This function strips the suffix +// so it can be compared using `semver` +function isCurrentVersionLatest(currentVersion: string, latestVersion: string): boolean { + return currentVersion === latestVersion || + semver.gt( + currentVersion.replace(HOLLAMA_DEV_VERSION_SUFFIX, ''), + latestVersion.replace(HOLLAMA_DEV_VERSION_SUFFIX, '') + ); +} + export async function checkForUpdates(isUserInitiated = false): Promise { const settings = get(settingsStore); if (!(settings.autoCheckForUpdates === false)) settings.autoCheckForUpdates = true; // If the user hasn't initiated the check we check if the last update check - // was made more than a week ago. + // was made more than a week ago const oneWeekAgoInSeconds = getUnixTime(new Date()) - ONE_WEEK_IN_SECONDS; if (!settings.lastUpdateCheck) settings.lastUpdateCheck = oneWeekAgoInSeconds - 1; if (!isUserInitiated && settings.lastUpdateCheck > oneWeekAgoInSeconds) return; @@ -45,7 +57,7 @@ export async function checkForUpdates(isUserInitiated = false): Promise { let hollamaMetadata: Response; try { - hollamaMetadata = await fetch(HOLLAMA_SERVER_METADATA_ENDPOINT); + hollamaMetadata = await fetch(HOLLAMA_METADATA_ENDPOINT); const response = (await hollamaMetadata.json()) as HollamaMetadata; settings.hollamaMetadata = response; } catch (_) { @@ -54,9 +66,12 @@ export async function checkForUpdates(isUserInitiated = false): Promise { } // Determine if the server has been updated, and if so, which version is the latest - updateStatus.canRefreshToUpdate = semver.lt(version, settings.hollamaMetadata.currentVersion); - updateStatus.isCurrentVersionLatest = !updateStatus.canRefreshToUpdate; updateStatus.latestVersion = settings.hollamaMetadata.currentVersion; + updateStatus.isCurrentVersionLatest = isCurrentVersionLatest( + version, + updateStatus.latestVersion + ); + updateStatus.canRefreshToUpdate = !updateStatus.isCurrentVersionLatest; updateStatus.showSidebarNotification = !updateStatus.isCurrentVersionLatest; if (updateStatus.canRefreshToUpdate) { @@ -77,9 +92,9 @@ export async function checkForUpdates(isUserInitiated = false): Promise { updateStatus.couldntCheckForUpdates = true; } - updateStatus.isCurrentVersionLatest = semver.lt( - updateStatus.latestVersion, - settings.hollamaMetadata.currentVersion + updateStatus.isCurrentVersionLatest = isCurrentVersionLatest( + settings.hollamaMetadata.currentVersion, + updateStatus.latestVersion ); updateStatus.showSidebarNotification = !updateStatus.isCurrentVersionLatest; updateStatus.isCheckingForUpdates = false; diff --git a/tests/version.test.ts b/tests/version.test.ts index 06c957ab..ff315dc7 100644 --- a/tests/version.test.ts +++ b/tests/version.test.ts @@ -40,8 +40,9 @@ test('shows "up-to-date" message when on latest version', async ({ page }) => { ); await page.goto('/settings'); - const checkNowButton = page.getByRole('button', { name: 'Check now' }); - await checkNowButton.click(); + await expect(page.getByText('You are on the latest version')).not.toBeVisible(); + + await page.getByRole('button', { name: 'Check now' }).click(); await expect(page.getByText('You are on the latest version')).toBeVisible(); }); From f9e3ebba3a2d1256d8a0bd2b6023eeb8fe1a1dda Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 11:12:00 -0400 Subject: [PATCH 56/58] apply formatting --- src/lib/updates.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/updates.ts b/src/lib/updates.ts index 076a5099..44bd40a3 100644 --- a/src/lib/updates.ts +++ b/src/lib/updates.ts @@ -33,11 +33,13 @@ export const updateStatusStore = writable({ // to indicate that it's a development version. This function strips the suffix // so it can be compared using `semver` function isCurrentVersionLatest(currentVersion: string, latestVersion: string): boolean { - return currentVersion === latestVersion || + return ( + currentVersion === latestVersion || semver.gt( currentVersion.replace(HOLLAMA_DEV_VERSION_SUFFIX, ''), latestVersion.replace(HOLLAMA_DEV_VERSION_SUFFIX, '') - ); + ) + ); } export async function checkForUpdates(isUserInitiated = false): Promise { @@ -67,10 +69,7 @@ export async function checkForUpdates(isUserInitiated = false): Promise { // Determine if the server has been updated, and if so, which version is the latest updateStatus.latestVersion = settings.hollamaMetadata.currentVersion; - updateStatus.isCurrentVersionLatest = isCurrentVersionLatest( - version, - updateStatus.latestVersion - ); + updateStatus.isCurrentVersionLatest = isCurrentVersionLatest(version, updateStatus.latestVersion); updateStatus.canRefreshToUpdate = !updateStatus.isCurrentVersionLatest; updateStatus.showSidebarNotification = !updateStatus.isCurrentVersionLatest; From f3f4f882d2ff6ff30b9ab51c87ad4feac39e0ee4 Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 11:15:52 -0400 Subject: [PATCH 57/58] use more consistent variable names --- src/lib/updates.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/updates.ts b/src/lib/updates.ts index 44bd40a3..54efae69 100644 --- a/src/lib/updates.ts +++ b/src/lib/updates.ts @@ -60,8 +60,8 @@ export async function checkForUpdates(isUserInitiated = false): Promise { try { hollamaMetadata = await fetch(HOLLAMA_METADATA_ENDPOINT); - const response = (await hollamaMetadata.json()) as HollamaMetadata; - settings.hollamaMetadata = response; + const metadata = (await hollamaMetadata.json()) as HollamaMetadata; + settings.hollamaMetadata = metadata; } catch (_) { console.error('Failed to fetch Hollama server metadata'); updateStatus.couldntCheckForUpdates = true; @@ -83,9 +83,9 @@ export async function checkForUpdates(isUserInitiated = false): Promise { try { githubReleases = await fetch(GITHUB_RELEASES_API); - const response = await githubReleases.json(); - if (response[0]?.tag_name && response[0].tag_name !== '') - updateStatus.latestVersion = response[0].tag_name; + const releases = await githubReleases.json(); + if (releases[0]?.tag_name && releases[0].tag_name !== '') + updateStatus.latestVersion = releases[0].tag_name; } catch (_) { console.error('Failed to fetch GitHub releases'); updateStatus.couldntCheckForUpdates = true; From 56215d628a5529b7330b4c6fb9fbdc4a3743002b Mon Sep 17 00:00:00 2001 From: Fernando Maclen Date: Fri, 30 Aug 2024 11:29:00 -0400 Subject: [PATCH 58/58] remove unused async --- src/routes/+layout.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 61725059..c6492855 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -28,7 +28,7 @@ if (!($settingsStore.autoCheckForUpdates === false)) await checkForUpdates(); }); - onMount(async () => { + onMount(() => { if (!browser || theme) return; $settingsStore.userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark'