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];