auto refresh welcome screen on settings change

frdel committed Feb 10, 2026 at 12:17 UTC 69cb4f131a43b35d618eaa085738160aced8f67d
3 files changed +62 -63
python/helpers/settings.py
+3 -3
@@ -490,7 +490,7 @@ def get_default_settings() -> Settings:
490 return Settings(
491 version=_get_version(),
492 chat_model_provider=get_default_value("chat_model_provider", "openrouter"),
493 - chat_model_name=get_default_value("chat_model_name", "moonshotai/kimi-k2.5"),
493 + chat_model_name=get_default_value("chat_model_name", "anthropic/claude-sonnet-4.5"),
494 chat_model_api_base=get_default_value("chat_model_api_base", ""),
495 chat_model_kwargs=get_default_value("chat_model_kwargs", {"temperature": "0"}),
496 chat_model_ctx_length=get_default_value("chat_model_ctx_length", 100000),
@@ -500,7 +500,7 @@ def get_default_settings() -> Settings:
500 chat_model_rl_input=get_default_value("chat_model_rl_input", 0),
501 chat_model_rl_output=get_default_value("chat_model_rl_output", 0),
502 util_model_provider=get_default_value("util_model_provider", "openrouter"),
503 - util_model_name=get_default_value("util_model_name", "z-ai/glm-4.7-flash"),
503 + util_model_name=get_default_value("util_model_name", "google/gemini-3-flash-preview"),
504 util_model_api_base=get_default_value("util_model_api_base", ""),
505 util_model_ctx_length=get_default_value("util_model_ctx_length", 100000),
506 util_model_ctx_input=get_default_value("util_model_ctx_input", 0.7),
@@ -515,7 +515,7 @@ def get_default_settings() -> Settings:
515 embed_model_rl_requests=get_default_value("embed_model_rl_requests", 0),
516 embed_model_rl_input=get_default_value("embed_model_rl_input", 0),
517 browser_model_provider=get_default_value("browser_model_provider", "openrouter"),
518 - browser_model_name=get_default_value("browser_model_name", "moonshotai/kimi-k2.5"),
518 + browser_model_name=get_default_value("browser_model_name", "anthropic/claude-sonnet-4.5"),
519 browser_model_api_base=get_default_value("browser_model_api_base", ""),
520 browser_model_vision=get_default_value("browser_model_vision", True),
521 browser_model_rl_requests=get_default_value("browser_model_rl_requests", 0),
webui/components/welcome/welcome-screen.html
+2 -2
@@ -7,9 +7,9 @@
7 </head>
8
9 <body>
10 - <div x-data x-show="$store.welcomeStore && $store.welcomeStore.isVisible">
10 + <div x-data >
11 <template x-if="$store.welcomeStore">
12 - <div x-data="$store.welcomeStore" class="welcome-container">
12 + <div x-data="$store.welcomeStore" x-create="$store.welcomeStore.onCreate()" class="welcome-container">
13 <div class="welcome-content">
14 <!-- Action Cards -->
15 <div class="welcome-actions">
webui/components/welcome/welcome-store.js
+57 -58
@@ -8,48 +8,28 @@ import * as API from "/js/api.js";
8
9 const model = {
10 // State
11 - isVisible: true,
11 banners: [],
12 bannersLoading: false,
13 lastBannerRefresh: 0,
14 hasDismissedBanners: false,
15
17 - init() {
18 - // Initialize visibility based on current context
19 - this.updateVisibility();
20 -
21 - if (this.isVisible) {
22 - this.refreshBanners();
23 - }
24 -
25 - // Watch for context changes with faster polling for immediate response
26 - setInterval(() => {
27 - this.updateVisibility();
28 - }, 50); // 50ms for very responsive updates
16 + get isVisible() {
17 + return !chatsStore.selected;
18 },
19
31 - // Update visibility based on current context
32 - updateVisibility() {
33 - const hasContext = !!getContext();
34 - const wasVisible = this.isVisible;
35 - this.isVisible = !hasContext;
20 + init() {
21 + // Reload banners when settings change
22 + document.addEventListener("settings-updated", () => {
23 + this.refreshBanners(true);
24 + });
25 + },
26
37 - if (this.isVisible && !wasVisible) {
27 + onCreate() {
28 + if (this.isVisible) {
29 this.refreshBanners();
30 }
31 },
32
42 - // Hide welcome screen
43 - hide() {
44 - this.isVisible = false;
45 - },
46 -
47 - // Show welcome screen
48 - show() {
49 - this.isVisible = true;
50 - this.refreshBanners();
51 - },
52 -
33 // Build frontend context to send to backend
34 buildFrontendContext() {
35 return {
@@ -83,8 +63,12 @@ const model = {
63
64 // Get list of dismissed banner IDs from storage
65 getDismissedBannerIds() {
86 - const permanent = JSON.parse(localStorage.getItem('dismissed_banners') || '[]');
87 - const temporary = JSON.parse(sessionStorage.getItem('dismissed_banners') || '[]');
66 + const permanent = JSON.parse(
67 + localStorage.getItem("dismissed_banners") || "[]",
68 + );
69 + const temporary = JSON.parse(
70 + sessionStorage.getItem("dismissed_banners") || "[]",
71 + );
72 return new Set([...permanent, ...temporary]);
73 },
74
@@ -92,19 +76,27 @@ const model = {
76 mergeBanners(frontendBanners, backendBanners) {
77 const dismissed = this.getDismissedBannerIds();
78 const bannerMap = new Map();
95 -
79 +
80 for (const banner of frontendBanners) {
97 - if (banner.id && (banner.dismissible === false || !dismissed.has(banner.id))) {
81 + if (
82 + banner.id &&
83 + (banner.dismissible === false || !dismissed.has(banner.id))
84 + ) {
85 bannerMap.set(banner.id, banner);
86 }
87 }
88 for (const banner of backendBanners) {
102 - if (banner.id && (banner.dismissible === false || !dismissed.has(banner.id))) {
89 + if (
90 + banner.id &&
91 + (banner.dismissible === false || !dismissed.has(banner.id))
92 + ) {
93 bannerMap.set(banner.id, banner);
94 }
95 }
106 -
107 - return Array.from(bannerMap.values()).sort((a, b) => (b.priority || 0) - (a.priority || 0));
96 +
97 + return Array.from(bannerMap.values()).sort(
98 + (a, b) => (b.priority || 0) - (a.priority || 0),
99 + );
100 },
101
102 // Refresh banners: frontend checks → backend checks → merge
@@ -113,19 +105,24 @@ const model = {
105 if (!force && now - this.lastBannerRefresh < 1000) return;
106 this.lastBannerRefresh = now;
107 this.bannersLoading = true;
116 -
108 +
109 try {
110 const frontendContext = this.buildFrontendContext();
111 const frontendBanners = this.runFrontendBannerChecks();
120 - const backendBanners = await this.runBackendBannerChecks(frontendBanners, frontendContext);
112 + const backendBanners = await this.runBackendBannerChecks(
113 + frontendBanners,
114 + frontendContext,
115 + );
116
117 const dismissed = this.getDismissedBannerIds();
118 const loadIds = new Set(
119 [...frontendBanners, ...backendBanners]
125 - .filter(b => b?.id && b.dismissible !== false)
126 - .map(b => b.id)
120 + .filter((b) => b?.id && b.dismissible !== false)
121 + .map((b) => b.id),
122 + );
123 + this.hasDismissedBanners = Array.from(loadIds).some((id) =>
124 + dismissed.has(id),
125 );
128 - this.hasDismissedBanners = Array.from(loadIds).some(id => dismissed.has(id));
126
127 this.banners = this.mergeBanners(frontendBanners, backendBanners);
128 } catch (error) {
@@ -138,29 +135,31 @@ const model = {
135 },
136
137 get sortedBanners() {
141 - return [...this.banners].sort((a, b) => (b.priority || 0) - (a.priority || 0));
138 + return [...this.banners].sort(
139 + (a, b) => (b.priority || 0) - (a.priority || 0),
140 + );
141 },
142
143 /**
144 * Dismiss a banner by ID.
146 - *
145 + *
146 * Usage:
147 * dismissBanner('banner-id') - Temporary dismiss (sessionStorage, cleared on browser close)
148 * dismissBanner('banner-id', true) - Permanent dismiss (localStorage, persists across sessions)
150 - *
149 + *
150 * Dismissed banners are filtered out in mergeBanners() and won't appear until storage is cleared.
152 - *
151 + *
152 * @param {string} bannerId - The unique ID of the banner to dismiss
153 * @param {boolean} permanent - If true, store in localStorage; if false, store in sessionStorage
154 */
155 dismissBanner(bannerId, permanent = false) {
157 - this.banners = this.banners.filter(b => b.id !== bannerId);
158 -
156 + this.banners = this.banners.filter((b) => b.id !== bannerId);
157 +
158 const storage = permanent ? localStorage : sessionStorage;
160 - const dismissed = JSON.parse(storage.getItem('dismissed_banners') || '[]');
159 + const dismissed = JSON.parse(storage.getItem("dismissed_banners") || "[]");
160 if (!dismissed.includes(bannerId)) {
161 dismissed.push(bannerId);
163 - storage.setItem('dismissed_banners', JSON.stringify(dismissed));
162 + storage.setItem("dismissed_banners", JSON.stringify(dismissed));
163 }
164
165 this.hasDismissedBanners = this.getDismissedBannerIds().size > 0;
@@ -175,20 +174,20 @@ const model = {
174
175 getBannerClass(type) {
176 const classes = {
178 - info: 'banner-info',
179 - warning: 'banner-warning',
180 - error: 'banner-error',
177 + info: "banner-info",
178 + warning: "banner-warning",
179 + error: "banner-error",
180 };
182 - return classes[type] || 'banner-info';
181 + return classes[type] || "banner-info";
182 },
183
184 getBannerIcon(type) {
185 const icons = {
187 - info: 'info',
188 - warning: 'warning',
189 - error: 'error',
186 + info: "info",
187 + warning: "warning",
188 + error: "error",
189 };
191 - return icons[type] || 'info';
190 + return icons[type] || "info";
191 },
192
193 // Execute an action by ID