refactor global actions to module imports

- Replaces usage of globalThis for chat, task and welcome screen actions with direct imports as per our conventions - Also updates two CSS variables in the welcome screen for correct blue accent color for icons and border color for dashboard cards

Alessandro committed Nov 3, 2025 at 21:29 UTC f5bea00516bda51f63d0662d7687d12f8c865a8c
5 files changed +51 -102
webui/components/sidebar/chats/chats-store.js
+36 -48
@@ -1,5 +1,15 @@
1 import { createStore } from "/js/AlpineStore.js";
2 -import { sendJsonData } from "/index.js";
2 +import {
3 + sendJsonData,
4 + getContext,
5 + setContext,
6 + poll as triggerPoll,
7 + updateAfterScroll,
8 + toastFetchError,
9 + toast,
10 + justToast,
11 + getConnectionStatus,
12 +} from "/index.js";
13 import { store as notificationStore } from "/components/notifications/notification-store.js";
14
15 const model = {
@@ -30,26 +40,20 @@ const model = {
40
41 // Select a chat
42 async selectChat(id) {
33 - const currentContext = globalThis.getContext?.();
43 + const currentContext = getContext();
44 if (id === currentContext) return; // already selected
35 -
45 +
46 // Proceed with context selection
37 - if (globalThis.setContext) {
38 - globalThis.setContext(id);
39 - }
40 -
47 + setContext(id);
48 +
49 // Update selection state (will also persist to localStorage)
50 this.setSelected(id);
43 -
51 +
52 // Trigger immediate poll
45 - if (globalThis.poll) {
46 - globalThis.poll();
47 - }
48 -
53 + triggerPoll();
54 +
55 // Update scroll
50 - if (globalThis.updateAfterScroll) {
51 - globalThis.updateAfterScroll();
52 - }
56 + updateAfterScroll();
57 },
58
59 // Delete a chat
@@ -81,14 +85,10 @@ const model = {
85 this.contexts = [...updatedContexts];
86
87 // Show success notification
84 - if (globalThis.justToast) {
85 - globalThis.justToast("Chat deleted successfully", "success", 1000, "chat-removal");
86 - }
88 + justToast("Chat deleted successfully", "success", 1000, "chat-removal");
89 } catch (e) {
90 console.error("Error deleting chat:", e);
89 - if (globalThis.toastFetchError) {
90 - globalThis.toastFetchError("Error deleting chat", e);
91 - }
91 + toastFetchError("Error deleting chat", e);
92 }
93 },
94
@@ -107,16 +107,14 @@ const model = {
107 await this.selectChat(alternateChat.id);
108 } else {
109 // If no other chats, create a new empty context
110 - if (globalThis.newChat) {
111 - await globalThis.newChat();
112 - }
110 + await this.newChat();
111 }
112 },
113
114 // Reset current chat
115 async resetChat(ctxid = null) {
116 try {
119 - const context = ctxid || this.selected || globalThis.getContext?.();
117 + const context = ctxid || this.selected || getContext();
118 await sendJsonData("/chat_reset", {
119 context
120 });
@@ -126,13 +124,9 @@ const model = {
124 globalThis.resetCounter = globalThis.resetCounter + 1;
125 }
126
129 - if (globalThis.updateAfterScroll) {
130 - globalThis.updateAfterScroll();
131 - }
127 + updateAfterScroll();
128 } catch (e) {
133 - if (globalThis.toastFetchError) {
134 - globalThis.toastFetchError("Error resetting chat", e);
135 - }
129 + toastFetchError("Error resetting chat", e);
130 }
131 },
132
@@ -160,9 +154,7 @@ const model = {
154 // // UX: scroll-to-top
155 // requestAnimationFrame(() => this._scrollChatsToTop());
156 } catch (e) {
163 - if (globalThis.toastFetchError) {
164 - globalThis.toastFetchError("Error creating new chat", e);
165 - }
157 + toastFetchError("Error creating new chat", e);
158 }
159 },
160
@@ -180,37 +172,33 @@ const model = {
172 const response = await sendJsonData("/chat_load", { chats: fileContents });
173
174 if (!response) {
183 - if (globalThis.toast) globalThis.toast("No response returned.", "error");
175 + toast("No response returned.", "error");
176 } else {
177 // Set context to first loaded chat
186 - if (globalThis.setContext && response.ctxids?.[0]) {
187 - globalThis.setContext(response.ctxids[0]);
178 + if (response.ctxids?.[0]) {
179 + setContext(response.ctxids[0]);
180 }
189 - if (globalThis.toast) globalThis.toast("Chats loaded.", "success");
181 + toast("Chats loaded.", "success");
182 }
183 } catch (e) {
192 - if (globalThis.toastFetchError) {
193 - globalThis.toastFetchError("Error loading chats", e);
194 - }
184 + toastFetchError("Error loading chats", e);
185 }
186 },
187
188 // Save current chat
189 async saveChat() {
190 try {
201 - const context = this.selected || globalThis.getContext?.();
191 + const context = this.selected || getContext();
192 const response = await sendJsonData("/chat_export", { ctxid: context });
193
194 if (!response) {
205 - if (globalThis.toast) globalThis.toast("No response returned.", "error");
195 + toast("No response returned.", "error");
196 } else {
197 this.downloadFile(response.ctxid + ".json", response.content);
208 - if (globalThis.toast) globalThis.toast("Chat file downloaded.", "success");
198 + toast("Chat file downloaded.", "success");
199 }
200 } catch (e) {
211 - if (globalThis.toastFetchError) {
212 - globalThis.toastFetchError("Error saving chat", e);
213 - }
201 + toastFetchError("Error saving chat", e);
202 }
203 },
204
@@ -285,7 +273,7 @@ const model = {
273 async restart() {
274 try {
275 // Check connection status
288 - const connectionStatus = globalThis.getConnectionStatus?.();
276 + const connectionStatus = getConnectionStatus();
277 if (connectionStatus === false) {
278 await notificationStore.frontendError(
279 "Backend disconnected, cannot restart.",
webui/components/sidebar/tasks/tasks-store.js
+3 -6
@@ -1,4 +1,5 @@
1 import { createStore } from "/js/AlpineStore.js";
2 +import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
3
4 // Tasks sidebar store: tasks list and selected task id
5 const model = {
@@ -45,9 +46,7 @@ const model = {
46 // Action methods for task management
47 selectTask(taskId) {
48 this.setSelected(taskId);
48 - if (globalThis.selectChat) {
49 - globalThis.selectChat(taskId);
50 - }
49 + chatsStore.selectChat(taskId);
50 },
51
52 openDetail(taskId) {
@@ -57,9 +56,7 @@ const model = {
56 },
57
58 reset(taskId) {
60 - if (globalThis.resetChat) {
61 - globalThis.resetChat(taskId);
62 - }
59 + chatsStore.resetChat(taskId);
60 },
61
62 deleteTask(taskId) {
webui/components/welcome/welcome-screen.html
+2 -2
@@ -161,14 +161,14 @@
161 }
162
163 .welcome-action-card:hover {
164 - border-color: var(--color-accent-dark);
164 + border-color: var(--color-primary);
165 background: var(--color-message-bg);
166 }
167
168 .welcome-action-icon {
169 font-size: 2rem;
170 margin-bottom: 0;
171 - color: var(--color-accent-dark);
171 + color: var(--color-highlight-dark);
172 }
173
174 .welcome-action-description {
webui/components/welcome/welcome-store.js
+4 -4
@@ -1,4 +1,6 @@
1 import { createStore } from "/js/AlpineStore.js";
2 +import { getContext } from "/index.js";
3 +import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
4
5 const model = {
6 // State
@@ -16,7 +18,7 @@ const model = {
18
19 // Update visibility based on current context
20 updateVisibility() {
19 - const hasContext = !!(globalThis.getContext && globalThis.getContext());
21 + const hasContext = !!getContext();
22 this.isVisible = !hasContext;
23 },
24
@@ -34,9 +36,7 @@ const model = {
36 executeAction(actionId) {
37 switch (actionId) {
38 case "new-chat":
37 - if (globalThis.newChat) {
38 - globalThis.newChat();
39 - }
39 + chatsStore.newChat();
40 break;
41 case "settings":
42 // Open settings modal
webui/index.js
+6 -42
@@ -111,7 +111,7 @@ export async function sendMessage() {
111 }
112 globalThis.sendMessage = sendMessage;
113
114 -function toastFetchError(text, error) {
114 +export function toastFetchError(text, error) {
115 console.error(text, error);
116 // Use new frontend error notification system (async, but we don't need to wait)
117 const errorMessage = error?.message || error?.toString() || "Unknown error";
@@ -234,7 +234,7 @@ function generateGUID() {
234 });
235 }
236
237 -function getConnectionStatus() {
237 +export function getConnectionStatus() {
238 return chatTopStore.connected;
239 }
240 globalThis.getConnectionStatus = getConnectionStatus;
@@ -256,7 +256,7 @@ let lastLogVersion = 0;
256 let lastLogGuid = "";
257 let lastSpokenNo = 0;
258
259 -async function poll() {
259 +export async function poll() {
260 let updated = false;
261 try {
262 // Get timezone from navigator
@@ -448,22 +448,6 @@ globalThis.pauseAgent = async function (paused) {
448 await inputStore.pauseAgent(paused);
449 };
450
451 -globalThis.resetChat = async function (ctxid = null) {
452 - await chatsStore.resetChat(ctxid);
453 -};
454 -
455 -globalThis.newChat = async function () {
456 - await chatsStore.newChat();
457 -};
458 -
459 -globalThis.killChat = async function (id) {
460 - await chatsStore.killChat(id);
461 -};
462 -
463 -globalThis.selectChat = async function (id) {
464 - await chatsStore.selectChat(id);
465 -};
466 -
451 function generateShortId() {
452 const chars =
453 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -527,14 +511,6 @@ export const getChatBasedId = function (id) {
511 return context + "-" + globalThis.resetCounter + "-" + id;
512 };
513
530 -globalThis.loadChats = async function () {
531 - await chatsStore.loadChats();
532 -};
533 -
534 -globalThis.saveChat = async function () {
535 - await chatsStore.saveChat();
536 -};
537 -
514 function addClassToElement(element, className) {
515 element.classList.add(className);
516 }
@@ -543,12 +519,12 @@ function removeClassFromElement(element, className) {
519 element.classList.remove(className);
520 }
521
546 -function justToast(text, type = "info", timeout = 5000, group = "") {
522 +export function justToast(text, type = "info", timeout = 5000, group = "") {
523 notificationStore.addFrontendToastOnly(type, text, "", timeout / 1000, group);
524 }
525 globalThis.justToast = justToast;
526
551 -function toast(text, type = "info", timeout = 5000) {
527 +export function toast(text, type = "info", timeout = 5000) {
528 // Convert timeout from milliseconds to seconds for new notification system
529 const display_time = Math.max(timeout / 1000, 1); // Minimum 1 second
530
@@ -574,7 +550,7 @@ function scrollChanged(isAtBottom) {
550 preferencesStore.autoScroll = isAtBottom;
551 }
552
577 -function updateAfterScroll() {
553 +export function updateAfterScroll() {
554 // const toleranceEm = 1; // Tolerance in em units
555 // const tolerancePx = toleranceEm * parseFloat(getComputedStyle(document.documentElement).fontSize); // Convert em to pixels
556 const tolerancePx = 10;
@@ -637,18 +613,6 @@ document.addEventListener("DOMContentLoaded", function () {
613 chatHistory.addEventListener("scroll", updateAfterScroll);
614 }
615
640 - // Restore previously selected context (if any) before polling begins
641 - let storedContext = null;
642 - try {
643 - storedContext = localStorage.getItem("lastSelectedChat");
644 - } catch (_e) {
645 - storedContext = null;
646 - }
647 -
648 - if (storedContext && storedContext !== "null" && storedContext !== "") {
649 - setContext(storedContext);
650 - }
651 -
616 // Start polling for updates
617 startPolling();
618 });