toast errors
frdel committed
Dec 6, 2024 at 14:33 UTC
f0be03ea77c3d4ef72ec8180df87e0bfffb46198
7 files changed
+152
-95
agent.py
+2
-2
@@ -87,7 +87,7 @@ class AgentContext:
87
88
self.process = DeferredTask(current_agent.monologue)
89
return self.process
90
-
90
+
91
def communicate(self, msg: "UserMessage", broadcast_level: int = 1):
92
self.paused = False # unpause if paused
93
@@ -394,7 +394,7 @@ class Agent:
394
# Handling for general exceptions
395
error_message = errors.format_error(exception)
396
PrintStyle(font_color="red", padding=True).print(error_message)
397
- self.context.log.log(type="error", content=error_message)
397
+ self.context.log.log(type="error", heading="Error", content=error_message)
398
raise HandledException(exception) # Re-raise the exception to kill the loop
399
400
async def get_system_prompt(self, loop_data: LoopData) -> list[str]:
webui/css/toast.css
+1
-1
@@ -37,7 +37,7 @@
37
38
.toast__message {
39
margin: 0;
40
- max-width: 320px;
40
+ /* max-width: 320px; */
41
text-overflow: ellipsis !important;
42
}
43
webui/index.js
+76
-41
@@ -12,9 +12,13 @@ const statusSection = document.getElementById('status-section');
12
const chatsSection = document.getElementById('chats-section');
13
const progressBar = document.getElementById('progress-bar');
14
const autoScrollSwitch = document.getElementById('auto-scroll-switch');
15
+const timeDate = document.getElementById('time-date-container');
16
+
17
18
let autoScroll = true;
19
let context = "";
20
+let connectionStatus = false
21
+
22
23
// Initialize the toggle button
24
setupSidebarToggle();
@@ -157,9 +161,19 @@ export async function sendMessage() {
161
adjustTextareaHeight();
162
}
163
} catch (e) {
160
- toast(e.message, "error");
164
+ toastFetchError("Error sending message", e)
165
+ }
166
+}
167
+
168
+function toastFetchError(text, error) {
169
+ if (getConnectionStatus()) {
170
+ toast(`${text}: ${error.message}`, "error");
171
+ } else {
172
+ toast(`${text} (it seems the backend is not running): ${error.message}`, "error");
173
}
174
+ console.error(text, error);
175
}
176
+window.toastFetchError = toastFetchError
177
178
chatInput.addEventListener('keydown', (e) => {
179
if (e.key === 'Enter' && !e.shiftKey) {
@@ -302,6 +316,16 @@ function generateGUID() {
316
});
317
}
318
319
+function getConnectionStatus() {
320
+ return connectionStatus
321
+}
322
+
323
+function setConnectionStatus(connected) {
324
+ connectionStatus = connected
325
+ const statusIcon = Alpine.$data(timeDate.querySelector('.status-icon'));
326
+ statusIcon.connected = connected
327
+}
328
+
329
let lastLogVersion = 0;
330
let lastLogGuid = ""
331
let lastSpokenNo = 0
@@ -336,11 +360,7 @@ async function poll() {
360
inputAD.paused = response.paused;
361
362
// Update status icon state
339
- const timeDate = document.getElementById('time-date-container');
340
- if (timeDate) {
341
- const statusIcon = Alpine.$data(timeDate.querySelector('.status-icon'));
342
- statusIcon.connected = true;
343
- }
363
+ setConnectionStatus(true)
364
365
const chatsAD = Alpine.$data(chatsSection);
366
chatsAD.contexts = response.contexts;
@@ -350,11 +370,7 @@ async function poll() {
370
371
} catch (error) {
372
console.error('Error:', error);
353
- const timeDate = document.getElementById('time-date-container');
354
- if (timeDate) {
355
- const statusIcon = Alpine.$data(timeDate.querySelector('.status-icon'));
356
- statusIcon.connected = false;
357
- }
373
+ setConnectionStatus(false)
374
}
375
376
return updated
@@ -396,41 +412,56 @@ function updateProgress(progress) {
412
}
413
414
window.pauseAgent = async function (paused) {
399
- const resp = await sendJsonData("/pause", { paused: paused, context });
415
+ try {
416
+ const resp = await sendJsonData("/pause", { paused: paused, context });
417
+ } catch (e) {
418
+ window.toastFetchError("Error pausing agent", e)
419
+ }
420
}
421
422
window.resetChat = async function () {
403
- const resp = await sendJsonData("/chat_reset", { context });
404
- updateAfterScroll()
423
+ try {
424
+ const resp = await sendJsonData("/chat_reset", { context });
425
+ updateAfterScroll()
426
+ } catch (e) {
427
+ window.toastFetchError("Error resetting chat", e)
428
+ }
429
}
430
431
window.newChat = async function () {
408
- setContext(generateGUID());
409
- updateAfterScroll()
432
+ try {
433
+ setContext(generateGUID());
434
+ updateAfterScroll()
435
+ } catch (e) {
436
+ window.toastFetchError("Error creating new chat", e)
437
+ }
438
}
439
440
window.killChat = async function (id) {
441
+ try {
442
+ const chatsAD = Alpine.$data(chatsSection);
443
+ let found, other
444
+ for (let i = 0; i < chatsAD.contexts.length; i++) {
445
+ if (chatsAD.contexts[i].id == id) {
446
+ found = true
447
+ } else {
448
+ other = chatsAD.contexts[i]
449
+ }
450
+ if (found && other) break
451
+ }
452
414
-
415
- const chatsAD = Alpine.$data(chatsSection);
416
- let found, other
417
- for (let i = 0; i < chatsAD.contexts.length; i++) {
418
- if (chatsAD.contexts[i].id == id) {
419
- found = true
420
- } else {
421
- other = chatsAD.contexts[i]
453
+ if (context == id && found) {
454
+ if (other) setContext(other.id)
455
+ else setContext(generateGUID())
456
}
423
- if (found && other) break
424
- }
457
426
- if (context == id && found) {
427
- if (other) setContext(other.id)
428
- else setContext(generateGUID())
429
- }
458
+ if (found) sendJsonData("/chat_remove", { context: id });
459
431
- if (found) sendJsonData("/chat_remove", { context: id });
460
+ updateAfterScroll()
461
433
- updateAfterScroll()
462
+ } catch (e) {
463
+ window.toastFetchError("Error creating new chat", e)
464
+ }
465
}
466
467
window.selectChat = async function (id) {
@@ -493,21 +524,25 @@ window.nudge = async function () {
524
try {
525
const resp = await sendJsonData("/nudge", { ctxid: getContext() });
526
} catch (e) {
496
- toast(e.message, "error")
527
+ toastFetchError("Error nudging agent", e)
528
}
529
}
530
531
window.restart = async function () {
532
try {
533
+ if (!getConnectionStatus()) {
534
+ toast("Backend disconnected, cannot restart.", "error");
535
+ return
536
+ }
537
// First try to initiate restart
538
const resp = await sendJsonData("/restart", {});
539
} catch (e) {
540
// Show restarting message
541
toast("Restarting...", "info", 0);
507
-
542
+
543
let retries = 0;
544
const maxRetries = 60; // Maximum number of retries (15 seconds with 250ms interval)
510
-
545
+
546
while (retries < maxRetries) {
547
try {
548
const resp = await sendJsonData("/health", {});
@@ -523,7 +558,7 @@ window.restart = async function () {
558
await new Promise(resolve => setTimeout(resolve, 250));
559
}
560
}
526
-
561
+
562
// If we get here, restart failed or took too long
563
hideToast();
564
await new Promise(resolve => setTimeout(resolve, 400));
@@ -592,7 +627,7 @@ window.loadChats = async function () {
627
}
628
629
} catch (e) {
595
- toast(e.message, "error")
630
+ toastFetchError("Error loading chats", e)
631
}
632
}
633
@@ -616,7 +651,7 @@ window.saveChat = async function () {
651
}
652
653
} catch (e) {
619
- toast(e.message, "error")
654
+ toastFetchError("Error saving chat", e)
655
}
656
}
657
@@ -751,7 +786,7 @@ function toast(text, type = 'info', timeout = 5000) {
786
// If a toast is visible, hide it first then show the new one
787
toast.classList.remove('show');
788
toast.classList.add('hide');
754
-
789
+
790
// Wait for hide animation to complete before showing new toast
791
setTimeout(() => {
792
toast.classList.remove('hide');
@@ -765,13 +800,13 @@ function toast(text, type = 'info', timeout = 5000) {
800
801
function hideToast() {
802
const toast = document.getElementById('toast');
768
-
803
+
804
// Clear any existing timeout
805
if (toast.timeoutId) {
806
clearTimeout(toast.timeoutId);
807
toast.timeoutId = null;
808
}
774
-
809
+
810
toast.classList.remove('show');
811
toast.classList.add('hide');
812
webui/js/file_browser.js
+4
-5
@@ -51,7 +51,7 @@ const fileBrowserModalProxy = {
51
this.browser.entries = [];
52
}
53
} catch (error) {
54
- console.error('Error fetching files:', error);
54
+ window.toastFetchError("Error fetching files", error)
55
this.browser.entries = [];
56
} finally {
57
this.isLoading = false;
@@ -129,7 +129,7 @@ const fileBrowserModalProxy = {
129
alert(`Error deleting file: ${await response.text()}`);
130
}
131
} catch (error) {
132
- console.error('Error deleting file:', error);
132
+ window.toastFetchError("Error deleting file", error)
133
alert('Error deleting file');
134
}
135
},
@@ -180,7 +180,7 @@ const fileBrowserModalProxy = {
180
}
181
182
} catch (error) {
183
- console.error('Error uploading files:', error);
183
+ window.toastFetchError("Error uploading files", error)
184
alert('Error uploading files');
185
}
186
},
@@ -209,8 +209,7 @@ const fileBrowserModalProxy = {
209
window.URL.revokeObjectURL(link.href);
210
211
} catch (error) {
212
-
213
- console.error('Error downloading file:', error);
212
+ window.toastFetchError("Error downloading file", error)
213
alert('Error downloading file');
214
}
215
},
webui/js/history.js
+17
-6
@@ -1,20 +1,31 @@
1
import { getContext } from "../index.js";
2
3
export async function openHistoryModal() {
4
- const hist = await window.sendJsonData("/history_get", { context: getContext() });
4
+ try {
5
+ const hist = await window.sendJsonData("/history_get", { context: getContext() });
6
+ } catch (e) {
7
+ window.toastFetchError("Error fetching history", e)
8
+ return
9
+ }
10
const data = JSON.stringify(hist.history, null, 4);
11
const size = hist.tokens
7
- await showEditorModal(data, "json", `History ~${size} tokens`,"Conversation history visible to the LLM. History is compressed to fit into the context window over time.");
12
+ await showEditorModal(data, "json", `History ~${size} tokens`, "Conversation history visible to the LLM. History is compressed to fit into the context window over time.");
13
+
14
}
15
16
export async function openCtxWindowModal() {
11
- const win = await window.sendJsonData("/ctx_window_get", { context: getContext() });
17
+ try {
18
+ const win = await window.sendJsonData("/ctx_window_get", { context: getContext() });
19
+ } catch (e) {
20
+ window.toastFetchError("Error fetching context", e)
21
+ return
22
+ }
23
const data = win.content
24
const size = win.tokens
14
- await showEditorModal(data, "markdown", `Context window ~${size} tokens`,"Data passed to the LLM during last interaction. Contains system message, conversation history and RAG.");
25
+ await showEditorModal(data, "markdown", `Context window ~${size} tokens`, "Data passed to the LLM during last interaction. Contains system message, conversation history and RAG.");
26
}
27
17
-async function showEditorModal(data, type = "json", title, description="") {
28
+async function showEditorModal(data, type = "json", title, description = "") {
29
// Generate the HTML with JSON Viewer container
30
const html = `<div id="json-viewer-container"></div>`;
31
@@ -29,7 +40,7 @@ async function showEditorModal(data, type = "json", title, description="") {
40
const dark = localStorage.getItem('darkMode')
41
if (dark != "false") {
42
editor.setTheme("ace/theme/github_dark");
32
- }else{
43
+ } else {
44
editor.setTheme("ace/theme/tomorrow");
45
}
46
webui/js/settings.js
+48
-37
@@ -10,32 +10,38 @@ const settingsModalProxy = {
10
const modalAD = Alpine.$data(modalEl);
11
12
//get settings from backend
13
- const set = await sendJsonData("/settings_get", null);
14
-
15
- const settings = {
16
- "title": "Settings page",
17
- "buttons": [
18
- {
19
- "id": "save",
20
- "title": "Save",
21
- "classes": "btn btn-ok"
22
- },
23
- {
24
- "id": "cancel",
25
- "title": "Cancel",
26
- "type": "secondary",
27
- "classes": "btn btn-cancel"
28
- }
29
- ],
30
- "sections": set.settings.sections
31
- }
32
-
33
- modalAD.isOpen = true; // Update directly
34
- modalAD.settings = settings; // Update directly
13
+ try {
14
+ const set = await sendJsonData("/settings_get", null);
15
+
16
+
17
+ const settings = {
18
+ "title": "Settings page",
19
+ "buttons": [
20
+ {
21
+ "id": "save",
22
+ "title": "Save",
23
+ "classes": "btn btn-ok"
24
+ },
25
+ {
26
+ "id": "cancel",
27
+ "title": "Cancel",
28
+ "type": "secondary",
29
+ "classes": "btn btn-cancel"
30
+ }
31
+ ],
32
+ "sections": set.settings.sections
33
+ }
34
+
35
+ modalAD.isOpen = true; // Update directly
36
+ modalAD.settings = settings; // Update directly
37
+
38
+ return new Promise(resolve => {
39
+ this.resolvePromise = resolve;
40
+ });
41
36
- return new Promise(resolve => {
37
- this.resolvePromise = resolve;
38
- });
42
+ } catch (e) {
43
+ window.toastFetchError("Error getting settings", e)
44
+ }
45
},
46
47
async handleButton(buttonId) {
@@ -43,7 +49,12 @@ const settingsModalProxy = {
49
50
const modalEl = document.getElementById('settingsModal');
51
const modalAD = Alpine.$data(modalEl);
46
- resp = await window.sendJsonData("/settings_set", modalAD.settings);
52
+ try {
53
+ resp = await window.sendJsonData("/settings_set", modalAD.settings);
54
+ } catch (e) {
55
+ window.toastFetchError("Error saving settings", e)
56
+ return
57
+ }
58
document.dispatchEvent(new CustomEvent('settings-updated', { detail: resp.settings }));
59
this.resolvePromise({
60
status: 'saved',
@@ -86,16 +97,16 @@ const settingsModalProxy = {
97
// });
98
99
function getIconName(title) {
89
- const iconMap = {
90
- 'Agent Config': 'agentconfig',
91
- 'Chat Model': 'chat-model',
92
- 'Utility model': 'utility-model',
93
- 'Embedding Model': 'embed-model',
94
- 'Speech to Text': 'voice',
95
- 'API Keys': 'api-keys',
96
- 'Authentication': 'auth',
97
- 'Development': 'dev'
98
- };
99
- return iconMap[title] || 'default';
100
+ const iconMap = {
101
+ 'Agent Config': 'agentconfig',
102
+ 'Chat Model': 'chat-model',
103
+ 'Utility model': 'utility-model',
104
+ 'Embedding Model': 'embed-model',
105
+ 'Speech to Text': 'voice',
106
+ 'API Keys': 'api-keys',
107
+ 'Authentication': 'auth',
108
+ 'Development': 'dev'
109
+ };
110
+ return iconMap[title] || 'default';
111
}
112
webui/js/speech.js
+4
-3
@@ -33,7 +33,7 @@ async function loadMicSettings() {
33
const response = await fetch('/settings_get');
34
const data = await response.json();
35
const sttSettings = data.settings.sections.find(s => s.title === 'Speech to Text');
36
-
36
+
37
if (sttSettings) {
38
// Update options from server settings
39
sttSettings.fields.forEach(field => {
@@ -42,6 +42,7 @@ async function loadMicSettings() {
42
});
43
}
44
} catch (error) {
45
+ window.toastFetchError("Failed to load speech settings", error)
46
console.error('Failed to load speech settings:', error);
47
}
48
}
@@ -282,8 +283,8 @@ class MicrophoneInput {
283
await this.updateCallback(result.text, true);
284
}
285
} catch (error) {
286
+ window.toastFetchError("Transcription error", error)
287
console.error('Transcription error:', error);
286
- toast('Transcription failed.', 'error');
288
} finally {
289
this.audioChunks = [];
290
this.status = Status.LISTENING;
@@ -407,7 +408,7 @@ class Speech {
408
}
409
410
replaceURLs(text) {
410
- const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])|(\b(www\.)[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])|(\b[-A-Z0-9+&@#\/%?=~_|!:,.;]*\.(?:[A-Z]{2,})[-A-Z0-9+&@#\/%?=~_|])/ig; return text.replace(urlRegex, (url) => {
411
+ const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])|(\b(www\.)[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])|(\b[-A-Z0-9+&@#\/%?=~_|!:,.;]*\.(?:[A-Z]{2,})[-A-Z0-9+&@#\/%?=~_|])/ig; return text.replace(urlRegex, (url) => {
412
let text = url
413
// if contains ://, split by it
414
if (text.includes('://')) text = text.split('://')[1];