Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
242d4f0c
Commit
242d4f0c
authored
May 26, 2024
by
Timothy J. Baek
Browse files
feat: banners
Co-Authored-By:
Jun Siang Cheah
<
me@jscheah.me
>
parent
00f32e26
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
461 additions
and
1 deletion
+461
-1
backend/apps/webui/main.py
backend/apps/webui/main.py
+2
-0
backend/apps/webui/routers/configs.py
backend/apps/webui/routers/configs.py
+30
-0
backend/config.py
backend/config.py
+18
-0
src/lib/apis/configs/index.ts
src/lib/apis/configs/index.ts
+58
-0
src/lib/components/admin/Settings/Banners.svelte
src/lib/components/admin/Settings/Banners.svelte
+136
-0
src/lib/components/admin/SettingsModal.svelte
src/lib/components/admin/SettingsModal.svelte
+42
-0
src/lib/components/chat/Chat.svelte
src/lib/components/chat/Chat.svelte
+27
-1
src/lib/components/common/Banner.svelte
src/lib/components/common/Banner.svelte
+126
-0
src/lib/stores/index.ts
src/lib/stores/index.ts
+3
-0
src/lib/types/index.ts
src/lib/types/index.ts
+9
-0
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+10
-0
No files found.
backend/apps/webui/main.py
View file @
242d4f0c
...
@@ -23,6 +23,7 @@ from config import (
...
@@ -23,6 +23,7 @@ from config import (
WEBHOOK_URL
,
WEBHOOK_URL
,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER
,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER
,
JWT_EXPIRES_IN
,
JWT_EXPIRES_IN
,
WEBUI_BANNERS
,
AppConfig
,
AppConfig
,
)
)
...
@@ -40,6 +41,7 @@ app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
...
@@ -40,6 +41,7 @@ app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
app
.
state
.
config
.
DEFAULT_USER_ROLE
=
DEFAULT_USER_ROLE
app
.
state
.
config
.
DEFAULT_USER_ROLE
=
DEFAULT_USER_ROLE
app
.
state
.
config
.
USER_PERMISSIONS
=
USER_PERMISSIONS
app
.
state
.
config
.
USER_PERMISSIONS
=
USER_PERMISSIONS
app
.
state
.
config
.
WEBHOOK_URL
=
WEBHOOK_URL
app
.
state
.
config
.
WEBHOOK_URL
=
WEBHOOK_URL
app
.
state
.
config
.
BANNERS
=
WEBUI_BANNERS
app
.
state
.
MODELS
=
{}
app
.
state
.
MODELS
=
{}
...
...
backend/apps/webui/routers/configs.py
View file @
242d4f0c
...
@@ -8,6 +8,8 @@ from pydantic import BaseModel
...
@@ -8,6 +8,8 @@ from pydantic import BaseModel
import
time
import
time
import
uuid
import
uuid
from
config
import
BannerModel
from
apps.webui.models.users
import
Users
from
apps.webui.models.users
import
Users
from
utils.utils
import
(
from
utils.utils
import
(
...
@@ -57,3 +59,31 @@ async def set_global_default_suggestions(
...
@@ -57,3 +59,31 @@ async def set_global_default_suggestions(
data
=
form_data
.
model_dump
()
data
=
form_data
.
model_dump
()
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
=
data
[
"suggestions"
]
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
=
data
[
"suggestions"
]
return
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
return
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
############################
# SetBanners
############################
class
SetBannersForm
(
BaseModel
):
banners
:
List
[
BannerModel
]
@
router
.
post
(
"/banners"
,
response_model
=
List
[
BannerModel
])
async
def
set_banners
(
request
:
Request
,
form_data
:
SetBannersForm
,
user
=
Depends
(
get_admin_user
),
):
data
=
form_data
.
model_dump
()
request
.
app
.
state
.
config
.
BANNERS
=
data
[
"banners"
]
return
request
.
app
.
state
.
config
.
BANNERS
@
router
.
get
(
"/banners"
,
response_model
=
List
[
BannerModel
])
async
def
get_banners
(
request
:
Request
,
user
=
Depends
(
get_current_user
),
):
return
request
.
app
.
state
.
config
.
BANNERS
backend/config.py
View file @
242d4f0c
...
@@ -8,6 +8,8 @@ from chromadb import Settings
...
@@ -8,6 +8,8 @@ from chromadb import Settings
from
base64
import
b64encode
from
base64
import
b64encode
from
bs4
import
BeautifulSoup
from
bs4
import
BeautifulSoup
from
typing
import
TypeVar
,
Generic
,
Union
from
typing
import
TypeVar
,
Generic
,
Union
from
pydantic
import
BaseModel
from
typing
import
Optional
from
pathlib
import
Path
from
pathlib
import
Path
import
json
import
json
...
@@ -566,6 +568,22 @@ WEBHOOK_URL = PersistentConfig(
...
@@ -566,6 +568,22 @@ WEBHOOK_URL = PersistentConfig(
ENABLE_ADMIN_EXPORT
=
os
.
environ
.
get
(
"ENABLE_ADMIN_EXPORT"
,
"True"
).
lower
()
==
"true"
ENABLE_ADMIN_EXPORT
=
os
.
environ
.
get
(
"ENABLE_ADMIN_EXPORT"
,
"True"
).
lower
()
==
"true"
class
BannerModel
(
BaseModel
):
id
:
str
type
:
str
title
:
Optional
[
str
]
=
None
content
:
str
dismissible
:
bool
timestamp
:
int
WEBUI_BANNERS
=
PersistentConfig
(
"WEBUI_BANNERS"
,
"ui.banners"
,
[
BannerModel
(
**
banner
)
for
banner
in
json
.
loads
(
"[]"
)],
)
####################################
####################################
# WEBUI_SECRET_KEY
# WEBUI_SECRET_KEY
####################################
####################################
...
...
src/lib/apis/configs/index.ts
View file @
242d4f0c
import
{
WEBUI_API_BASE_URL
}
from
'
$lib/constants
'
;
import
{
WEBUI_API_BASE_URL
}
from
'
$lib/constants
'
;
import
type
{
Banner
}
from
'
$lib/types
'
;
export
const
setDefaultModels
=
async
(
token
:
string
,
models
:
string
)
=>
{
export
const
setDefaultModels
=
async
(
token
:
string
,
models
:
string
)
=>
{
let
error
=
null
;
let
error
=
null
;
...
@@ -59,3 +60,60 @@ export const setDefaultPromptSuggestions = async (token: string, promptSuggestio
...
@@ -59,3 +60,60 @@ export const setDefaultPromptSuggestions = async (token: string, promptSuggestio
return
res
;
return
res
;
};
};
export
const
getBanners
=
async
(
token
:
string
):
Promise
<
Banner
[]
>
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/configs/banners`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
setBanners
=
async
(
token
:
string
,
banners
:
Banner
[])
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/configs/banners`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
banners
:
banners
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/components/admin/Settings/Banners.svelte
0 → 100644
View file @
242d4f0c
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { getContext, onMount } from 'svelte';
import { banners as _banners } from '$lib/stores';
import type { Banner } from '$lib/types';
import { getBanners, setBanners } from '$lib/apis/configs';
import type { Writable } from 'svelte/store';
import type { i18n as i18nType } from 'i18next';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte';
const i18n: Writable<i18nType> = getContext('i18n');
export let saveHandler: Function;
let banners: Banner[] = [];
onMount(async () => {
banners = await getBanners(localStorage.token);
});
const updateBanners = async () => {
_banners.set(await setBanners(localStorage.token, banners));
};
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
updateBanners();
saveHandler();
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div class=" space-y-3 pr-1.5">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
{$i18n.t('Banners')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
if (banners.length === 0 || banners.at(-1).content !== '') {
banners = [
...banners,
{
id: uuidv4(),
type: '',
title: '',
content: '',
dismissible: false,
timestamp: Math.floor(Date.now() / 1000)
}
];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
<div class="flex flex-col space-y-1">
{#each banners as banner, bannerIdx}
<div class=" flex justify-between">
<div class="flex flex-row flex-1 border rounded-xl dark:border-gray-800">
<select
class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-none"
bind:value={banner.type}
>
{#if banner.type == ''}
<option value="" selected disabled class="">{$i18n.t('Type')}</option>
{/if}
<option value="info">{$i18n.t('Info')}</option>
<option value="warning">{$i18n.t('Warning')}</option>
<option value="error">{$i18n.t('Error')}</option>
<option value="success">{$i18n.t('Success')}</option>
</select>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none"
placeholder={$i18n.t('Content')}
bind:value={banner.content}
/>
<div class="relative top-1.5 -left-2">
<Tooltip content="Dismissible" className="flex h-fit items-center">
<Switch bind:state={banner.dismissible} />
</Tooltip>
</div>
</div>
<button
class="px-2"
type="button"
on:click={() => {
banners.splice(bannerIdx, 1);
banners = banners;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/each}
</div>
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit"
>
Save
</button>
</div>
</form>
src/lib/components/admin/SettingsModal.svelte
View file @
242d4f0c
...
@@ -6,6 +6,9 @@
...
@@ -6,6 +6,9 @@
import General from './Settings/General.svelte';
import General from './Settings/General.svelte';
import Users from './Settings/Users.svelte';
import Users from './Settings/Users.svelte';
import Banners from '$lib/components/admin/Settings/Banners.svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
export let show = false;
export let show = false;
...
@@ -117,24 +120,63 @@
...
@@ -117,24 +120,63 @@
</div>
</div>
<div class=" self-center">{$i18n.t('Database')}</div>
<div class=" self-center">{$i18n.t('Database')}</div>
</button>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'banners'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'banners';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M5.85 3.5a.75.75 0 0 0-1.117-1 9.719 9.719 0 0 0-2.348 4.876.75.75 0 0 0 1.479.248A8.219 8.219 0 0 1 5.85 3.5ZM19.267 2.5a.75.75 0 1 0-1.118 1 8.22 8.22 0 0 1 1.987 4.124.75.75 0 0 0 1.48-.248A9.72 9.72 0 0 0 19.266 2.5Z"
/>
<path
fill-rule="evenodd"
d="M12 2.25A6.75 6.75 0 0 0 5.25 9v.75a8.217 8.217 0 0 1-2.119 5.52.75.75 0 0 0 .298 1.206c1.544.57 3.16.99 4.831 1.243a3.75 3.75 0 1 0 7.48 0 24.583 24.583 0 0 0 4.83-1.244.75.75 0 0 0 .298-1.205 8.217 8.217 0 0 1-2.118-5.52V9A6.75 6.75 0 0 0 12 2.25ZM9.75 18c0-.034 0-.067.002-.1a25.05 25.05 0 0 0 4.496 0l.002.1a2.25 2.25 0 1 1-4.5 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Banners')}</div>
</button>
</div>
</div>
<div class="flex-1 md:min-h-[380px]">
<div class="flex-1 md:min-h-[380px]">
{#if selectedTab === 'general'}
{#if selectedTab === 'general'}
<General
<General
saveHandler={() => {
saveHandler={() => {
show = false;
show = false;
toast.success($i18n.t('Settings saved successfully!'));
}}
}}
/>
/>
{:else if selectedTab === 'users'}
{:else if selectedTab === 'users'}
<Users
<Users
saveHandler={() => {
saveHandler={() => {
show = false;
show = false;
toast.success($i18n.t('Settings saved successfully!'));
}}
}}
/>
/>
{:else if selectedTab === 'db'}
{:else if selectedTab === 'db'}
<Database
<Database
saveHandler={() => {
saveHandler={() => {
show = false;
show = false;
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'banners'}
<Banners
saveHandler={() => {
show = false;
toast.success($i18n.t('Settings saved successfully!'));
}}
}}
/>
/>
{/if}
{/if}
...
...
src/lib/components/chat/Chat.svelte
View file @
242d4f0c
...
@@ -15,7 +15,8 @@
...
@@ -15,7 +15,8 @@
settings
,
settings
,
showSidebar
,
showSidebar
,
tags
as
_tags
,
tags
as
_tags
,
WEBUI_NAME
WEBUI_NAME
,
banners
}
from
'$lib/stores'
;
}
from
'$lib/stores'
;
import
{
convertMessagesToHistory
,
copyToClipboard
,
splitStream
}
from
'$lib/utils'
;
import
{
convertMessagesToHistory
,
copyToClipboard
,
splitStream
}
from
'$lib/utils'
;
...
@@ -40,6 +41,7 @@
...
@@ -40,6 +41,7 @@
import
{
queryMemory
}
from
'$lib/apis/memories'
;
import
{
queryMemory
}
from
'$lib/apis/memories'
;
import
type
{
Writable
}
from
'svelte/store'
;
import
type
{
Writable
}
from
'svelte/store'
;
import
type
{
i18n
as
i18nType
}
from
'i18next'
;
import
type
{
i18n
as
i18nType
}
from
'i18next'
;
import
Banner
from
'../common/Banner.svelte'
;
const
i18n
:
Writable
<
i18nType
>
=
getContext
(
'i18n'
);
const
i18n
:
Writable
<
i18nType
>
=
getContext
(
'i18n'
);
...
@@ -1004,6 +1006,30 @@
...
@@ -1004,6 +1006,30 @@
{chat}
{chat}
{initNewChat}
{initNewChat}
/>
/>
{#if $banners.length > 0}
<div class="flex flex-col gap-1">
{#each $banners as banner}
<Banner
{banner}
on:dismiss={(e) => {
const bannerId = e.detail;
localStorage.setItem(
'
dismissedBannerIds
',
JSON.stringify(
[
bannerId,
...JSON.parse(localStorage.getItem('
dismissedBannerIds
') ?? '
[]
')
].filter((id) => $banners.find((b) => b.id === id))
)
);
}}
/>
{/each}
</div>
{/if}
<div class="flex flex-col flex-auto">
<div class="flex flex-col flex-auto">
<div
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full"
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full"
...
...
src/lib/components/common/Banner.svelte
0 → 100644
View file @
242d4f0c
<script lang="ts">
import type { Banner } from '$lib/types';
import { onMount, createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
const dispatch = createEventDispatcher();
export let banner: Banner = {
id: '',
type: 'info',
title: '',
content: '',
url: '',
dismissable: true,
timestamp: Math.floor(Date.now() / 1000)
};
export let dismissed = false;
let mounted = false;
const classNames: Record<string, string> = {
info: 'bg-blue-500/20 text-blue-700 dark:text-blue-200 ',
success: 'bg-green-500/20 text-green-700 dark:text-green-200',
warning: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-200',
error: 'bg-red-500/20 text-red-700 dark:text-red-200'
};
const dismiss = (id) => {
dismissed = true;
dispatch('dismiss', id);
};
onMount(() => {
mounted = true;
});
</script>
{#if mounted}
{#if !dismissed}
<div
class=" top-0 left-0 right-0 p-2 mx-4 px-3 flex justify-center items-center relative rounded-xl border dark:border-gray-800 text-gray-800 dark:text-gary-100 bg-white dark:bg-gray-900 backdrop-blur-xl"
transition:fade={{ delay: 0, duration: 300 }}
>
<div class=" flex flex-col md:flex-row md:items-center flex-1 text-sm w-fit gap-1.5">
<div class="flex justify-between self-start">
<div
class=" text-xs font-black {classNames[banner.type] ??
classNames['info']} w-fit px-2 rounded uppercase"
>
{banner.type}
</div>
{#if banner.url}
<div class="flex md:hidden group w-fit md:items-center">
<a
class="text-gray-700 dark:text-white text-xs font-bold underline"
href="/assets/files/whitepaper.pdf"
target="_blank">Learn More</a
>
<div
class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white"
>
<!-- -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
{/if}
</div>
<div class="flex-1 text-xs text-gray-700 dark:text-white">
{banner.content}
</div>
</div>
{#if banner.url}
<div class="hidden md:flex group w-fit md:items-center">
<a
class="text-gray-700 dark:text-white text-xs font-bold underline"
href="/"
target="_blank">Learn More</a
>
<div class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white">
<!-- -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
{/if}
<div class="flex h-full justify-start">
{#if banner.dismissible}
<button
on:click={() => {
dismiss(banner.id);
}}
class="mt-[-2.5px] ml-1.5 mr-1 text-gray-400 dark:hover:text-white h-2 mb-auto"
>×</button
>
{/if}
</div>
</div>
{/if}
{/if}
src/lib/stores/index.ts
View file @
242d4f0c
import
{
APP_NAME
}
from
'
$lib/constants
'
;
import
{
APP_NAME
}
from
'
$lib/constants
'
;
import
{
type
Writable
,
writable
}
from
'
svelte/store
'
;
import
{
type
Writable
,
writable
}
from
'
svelte/store
'
;
import
type
{
GlobalModelConfig
,
ModelConfig
}
from
'
$lib/apis
'
;
import
type
{
GlobalModelConfig
,
ModelConfig
}
from
'
$lib/apis
'
;
import
type
{
Banner
}
from
'
$lib/types
'
;
// Backend
// Backend
export
const
WEBUI_NAME
=
writable
(
APP_NAME
);
export
const
WEBUI_NAME
=
writable
(
APP_NAME
);
...
@@ -36,6 +37,8 @@ export const documents = writable([
...
@@ -36,6 +37,8 @@ export const documents = writable([
}
}
]);
]);
export
const
banners
:
Writable
<
Banner
[]
>
=
writable
([]);
export
const
settings
:
Writable
<
Settings
>
=
writable
({});
export
const
settings
:
Writable
<
Settings
>
=
writable
({});
export
const
showSidebar
=
writable
(
false
);
export
const
showSidebar
=
writable
(
false
);
...
...
src/lib/types/index.ts
0 → 100644
View file @
242d4f0c
export
type
Banner
=
{
id
:
string
;
type
:
string
;
title
?:
string
;
content
:
string
;
url
?:
string
;
dismissible
?:
boolean
;
timestamp
:
number
;
};
src/routes/(app)/+layout.svelte
View file @
242d4f0c
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
prompts,
prompts,
documents,
documents,
tags,
tags,
banners,
showChangelog,
showChangelog,
config
config
} from '$lib/stores';
} from '$lib/stores';
...
@@ -33,6 +34,7 @@
...
@@ -33,6 +34,7 @@
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import { getBanners } from '$lib/apis/configs';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -82,6 +84,14 @@
...
@@ -82,6 +84,14 @@
(async () => {
(async () => {
documents.set(await getDocs(localStorage.token));
documents.set(await getDocs(localStorage.token));
})(),
})(),
(async () => {
let _banners = await getBanners(localStorage.token);
const dismissedBannerIds = JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]');
_banners = _banners.filter((banner) =>
banner.dismissible ? !dismissedBannerIds.includes(banner.id) : true
);
banners.set(_banners);
})(),
(async () => {
(async () => {
tags.set(await getAllChatTags(localStorage.token));
tags.set(await getAllChatTags(localStorage.token));
})()
})()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment