mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-05 02:41:34 +00:00
203 lines
5.7 KiB
Svelte
203 lines
5.7 KiB
Svelte
<script lang="ts">
|
|
import DOMPurify from 'dompurify';
|
|
|
|
import { getContext } from 'svelte';
|
|
|
|
import { WEBUI_NAME, config, settings } from '$lib/stores';
|
|
|
|
import { WEBUI_VERSION } from '$lib/constants';
|
|
import { getChangelog } from '$lib/apis';
|
|
|
|
import Modal from './common/Modal.svelte';
|
|
import { updateUserSettings } from '$lib/apis/users';
|
|
import XMark from '$lib/components/icons/XMark.svelte';
|
|
|
|
const i18n = getContext('i18n');
|
|
|
|
export let show = false;
|
|
|
|
type ChangelogEntry = {
|
|
raw?: string;
|
|
};
|
|
type ChangelogVersion = {
|
|
date: string;
|
|
[section: string]: string | ChangelogEntry[];
|
|
};
|
|
|
|
let changelog: Record<string, ChangelogVersion> | null = null;
|
|
let error = false;
|
|
|
|
const init = async () => {
|
|
if (changelog || error) {
|
|
return;
|
|
}
|
|
|
|
changelog = await getChangelog().catch(() => {
|
|
error = true;
|
|
return null;
|
|
});
|
|
};
|
|
|
|
const closeModal = async () => {
|
|
localStorage.version = $config.version;
|
|
await settings.set({ ...$settings, ...{ version: $config.version } });
|
|
await updateUserSettings(localStorage.token, { ui: $settings });
|
|
show = false;
|
|
};
|
|
|
|
$: if (show) {
|
|
init();
|
|
}
|
|
|
|
const formatDate = (date: string) => {
|
|
if (!date) {
|
|
return '';
|
|
}
|
|
|
|
const [year, month, day] = date.split('-');
|
|
const months = [
|
|
'Jan',
|
|
'Feb',
|
|
'Mar',
|
|
'Apr',
|
|
'May',
|
|
'Jun',
|
|
'Jul',
|
|
'Aug',
|
|
'Sep',
|
|
'Oct',
|
|
'Nov',
|
|
'Dec'
|
|
];
|
|
const monthIndex = Number(month) - 1;
|
|
|
|
return year && monthIndex >= 0 && monthIndex < months.length && day
|
|
? `${months[monthIndex]} ${Number(day)}, ${year}`
|
|
: date;
|
|
};
|
|
|
|
const getSectionEntries = (version: string, section: string) => {
|
|
const entries = changelog?.[version]?.[section];
|
|
return Array.isArray(entries) ? entries : [];
|
|
};
|
|
</script>
|
|
|
|
<Modal
|
|
bind:show
|
|
size="lg"
|
|
className="bg-white dark:bg-gray-900 rounded-3xl overflow-hidden"
|
|
containerClassName="p-3"
|
|
>
|
|
<div class="flex max-h-[58vh] flex-col">
|
|
<div
|
|
class="flex shrink-0 items-start justify-between gap-4 px-4 pb-2.5 pt-3.5 dark:text-white text-black"
|
|
>
|
|
<div class="min-w-0">
|
|
<!-- LICENSE covers this Open WebUI release identifier.
|
|
Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
|
https://docs.openwebui.com/license. -->
|
|
<h2 class="m-0 truncate text-base font-normal">
|
|
{$i18n.t("What's New in")}
|
|
{$WEBUI_NAME}
|
|
</h2>
|
|
<div class="mt-1 flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>{$i18n.t('Release Notes')}</span>
|
|
<span class="h-1 w-1 rounded-full bg-gray-300 dark:bg-gray-700"></span>
|
|
<span>v{WEBUI_VERSION}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl text-gray-400 transition hover:bg-gray-100 hover:text-gray-700 dark:text-gray-500 dark:hover:bg-white/10 dark:hover:text-gray-200"
|
|
on:click={closeModal}
|
|
aria-label={$i18n.t('Close')}
|
|
>
|
|
<XMark className={'size-4'} />
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
class="min-h-0 flex-1 overflow-y-auto px-4 py-2 text-gray-700 scrollbar-hidden dark:text-gray-100"
|
|
>
|
|
{#if changelog}
|
|
<div class="space-y-4">
|
|
{#each Object.keys(changelog) as version}
|
|
<section class="pr-1">
|
|
<div class="mb-2">
|
|
<h3 class="m-0 text-sm font-normal text-gray-950 dark:text-white">v{version}</h3>
|
|
<div class="mt-0.5 text-[0.6875rem] text-gray-400 dark:text-gray-500">
|
|
{formatDate(changelog[version].date)}
|
|
</div>
|
|
</div>
|
|
|
|
{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
|
|
<div class="mb-3 w-full">
|
|
<div
|
|
class="mb-2 text-[0.6875rem] font-normal uppercase tracking-wide {section ===
|
|
'added'
|
|
? 'text-blue-600 dark:text-blue-300'
|
|
: section === 'fixed'
|
|
? 'text-green-600 dark:text-green-300'
|
|
: section === 'changed'
|
|
? 'text-yellow-700 dark:text-yellow-300'
|
|
: section === 'removed'
|
|
? 'text-red-600 dark:text-red-300'
|
|
: 'text-gray-500 dark:text-gray-400'}"
|
|
>
|
|
{section}
|
|
</div>
|
|
|
|
<div class="space-y-2 text-[0.8125rem] leading-relaxed">
|
|
{#each getSectionEntries(version, section) as entry}
|
|
<div class="flex gap-2.5">
|
|
<span
|
|
class="mt-[0.6em] h-1 w-1 shrink-0 rounded-full bg-gray-300 dark:bg-gray-700"
|
|
></span>
|
|
<div
|
|
class="min-w-0 markdown-prose-sm !max-w-none !text-[0.8125rem] text-gray-600 dark:text-gray-300 [&_*]:!my-0 [&_b]:!font-normal [&_strong]:!font-normal"
|
|
>
|
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
{@html DOMPurify.sanitize(entry?.raw)}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</section>
|
|
{/each}
|
|
</div>
|
|
{:else if error}
|
|
<div class="flex flex-col items-center justify-center gap-3 py-16 text-center">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
{$i18n.t('Could not load release notes.')}
|
|
</p>
|
|
<button
|
|
on:click={() => {
|
|
error = false;
|
|
init();
|
|
}}
|
|
class="text-sm font-normal text-gray-700 transition hover:text-black dark:text-gray-300 dark:hover:text-white"
|
|
>
|
|
{$i18n.t('Retry')}
|
|
</button>
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="flex items-center justify-center py-16 text-sm text-gray-400 dark:text-gray-500"
|
|
>
|
|
{$i18n.t('Loading release notes...')}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="flex shrink-0 justify-end px-4 pb-3.5 pt-1.5 text-sm">
|
|
<button
|
|
on:click={closeModal}
|
|
class="font-normal text-gray-600 transition hover:text-gray-950 dark:text-gray-400 dark:hover:text-white"
|
|
>
|
|
<span class="relative">{$i18n.t("Okay, Let's Go!")}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|