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
});