diff --git a/aiserver.py b/aiserver.py index 92f32c82..33589d84 100644 --- a/aiserver.py +++ b/aiserver.py @@ -348,6 +348,7 @@ class ImportBuffer: print("[eph] Weird char") print(f"Char: {char}") print(f"Ph_id: {ph_id}") + show_error_notification("Error loading prompt", f"Bad character '{char}' in prompt placeholder.") return placeholders.append(self.PromptPlaceholder( @@ -368,6 +369,7 @@ class ImportBuffer: default_match = next(re.finditer(r"\[(.*?)\]", ph_text)) except StopIteration: print("[eph] Weird brackets") + show_error_notification("Error loading prompt", f"Unusual bracket structure in prompt.") return placeholders ph_default = default_match.group(1) @@ -409,8 +411,11 @@ class ImportBuffer: r = requests.get(f"https://aetherroom.club/api/{club_id}") if not r.ok: - # TODO: Show error message on client print(f"[import] Got {r.status_code} on request to club :^(") + message = f"Club responded with {r.status_code}" + if r.status_code == "404": + message = f"Prompt not found for ID {club_id}" + show_error_notification("Error loading prompt", message) return j = r.json() @@ -735,6 +740,9 @@ api_v1 = KoboldAPISpec( tags=tags, ) +def show_error_notification(title: str, text: str) -> None: + socketio.emit("show_error_notification", {"title": title, "text": text}, broadcast=True, room="UI_2") + # Returns the expected config filename for the current setup. # If the model_name is specified, it returns what the settings file would be for that model def get_config_filename(model_name = None): diff --git a/static/koboldai.css b/static/koboldai.css index 3792f3f0..522ec427 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -2459,6 +2459,87 @@ body { border-radius: 3px; } +/* Notifcations */ +#notification-container { + position: absolute; + top: 15px; + right: 0px; + height: calc(100vh - 15px); + width: 400px; + z-index: 8; + pointer-events: none; + overflow-y: hidden; + overflow-x: hidden; + transition: 200ms top; +} + +.notification { + z-index: 8; + background-color: #30414e; + animation: 10s 1 alternate swoosh-in; + margin-right: 15px; + overflow: hidden; + pointer-events: all; + margin-bottom: 15px; + left: 110%; +} + +.notification.notification-error { + background-color: #4e3030; +} + +@keyframes swoosh-in { + from { + transform: translateX(110%); + } + + 5% { + transform: translateX(0%); + } + + 95% { + transform: translateX(0%); + } + + to { + transform: translateX(110%); + } +} + +.notification .notif-text { + margin: 5px; +} + +.notification .notif-title { + color: #6f96b1; + display: block; + font-weight: bold; +} + +.notification.notification-error .notif-title { + color: #b16f6f; +} + +.notification .notif-body { + display: block; +} + +.notification .notif-bar { + background-color: #407497; + height: 3px; + width: 100%; + animation: 10s 1 alternate shrink-away; +} + +.notification.notification-error .notif-bar { + background-color: #974040; +} + +@keyframes shrink-away { + from { width: 100%; } + to { width: 0%; } +} + /*---------------------------------- Global ------------------------------------------------*/ .hidden { display: none; diff --git a/static/koboldai.js b/static/koboldai.js index 4ef27cb6..296fb460 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -34,6 +34,7 @@ socket.on("log_message", function(data){process_log_message(data);}); socket.on("debug_message", function(data){console.log(data);}); socket.on("scratchpad_response", recieveScratchpadResponse); socket.on("scratchpad_response", recieveScratchpadResponse); +socket.on("show_error_notification", function(data) { reportError(data.title, data.text) }); //socket.onAny(function(event_name, data) {console.log({"event": event_name, "class": data.classname, "data": data});}); // Must be done before any elements are made; we track their changes. @@ -4109,6 +4110,21 @@ function sendPromptConfiguration() { $(".prompt-config-ph").remove(); } +async function postWI(wiData) { + let r = await fetch("/upload_wi", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(wiData) + }); + + if (!r.ok) { + reportError("WI Upload Error", `WI upload failed with status code ${r.status}. Please report this.`); + return; + } +} + async function loadNAILorebook(data, filename) { let lorebookVersion = data.lorebookVersion; let wi_data = {folders: {[filename]: []}, entries: {}}; @@ -4145,15 +4161,7 @@ async function loadNAILorebook(data, filename) { i++; } - let r = await fetch("/upload_wi", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(wi_data) - }); - - if (!r.ok) alert("WI upload errored! Please report this."); + await postWI(wi_data); } async function loadKoboldData(data, filename) { @@ -4163,17 +4171,10 @@ async function loadKoboldData(data, filename) { socket.emit("load_story_list", ""); } else if (data.folders !== undefined && data.entries !== undefined) { // World Info Folder - let r = await fetch("/upload_wi", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(data) - }); - - if (!r.ok) alert("WI upload errored! Please report this."); + await postWI(data); } else { // Bad data + reportError("Error loading file", `Unable to detect ${filename} as a valid KoboldAI file.`); console.error("Bad data!"); return; } @@ -4198,7 +4199,10 @@ function readLoreCard(file) { return true; }); - if (offset === null) throw Error("Couldn't find offset!"); + if (offset === null) { + reportError("Error reading Lorecard", "Unable to find NAIDATA offset. Is this a valid Lorecard?"); + throw Error("Couldn't find offset!"); + } let lengthBytes = bin.slice(offset - 8, offset - 4); let length = 0; @@ -4238,9 +4242,11 @@ async function processDroppedFile(file) { break; case "css": console.warn("TODO: THEME"); + reportError("Unsupported", "Theme drag and drop is not implemented yet. Check back later!"); break; case "lua": console.warn("TODO: USERSCRIPT"); + reportError("Unsupported", "Userscript drag and drop is not implemented yet. Check back later!"); break } } @@ -5119,7 +5125,7 @@ let load_substitutions; // Sanity check; never 100% cpu! tries++; if (tries > 2000) { - alert("Some Substitution shenanigans are afoot; please send the developers your substitutions!"); + reportError("Substitution error", "Some Substitution shenanigans are afoot; please send the developers your substitutions!"); throw Error("Substitution shenanigans!") return; } @@ -5149,6 +5155,7 @@ let load_substitutions; } } + reportError("Substitution error", "Couldn't find substitution index from card."); throw Error("Didn't find substitution!"); } @@ -5452,6 +5459,29 @@ function initalizeTooltips() { } })(); +function showNotification(title, text, type) { + if (!["error", "info"].includes(type)) return; + const nContainer = $el("#notification-container"); + const notification = $e("div", nContainer, {classes: ["notification", `notification-${type}`]}); + const nTextContainer = $e("div", notification, {classes: ["notif-text"]}); + const titleEl = $e("span", nTextContainer, {classes: ["notif-title"], innerText: title}); + const bodyEl = $e("span", nTextContainer, {classes: ["notif-body"], innerText: text}); + const bar = $e("div", notification, {classes: ["notif-bar"]}); + notification.style.left = "0px"; + + setTimeout(function() { + notification.remove(); + }, 10_000); +} + +function reportError(title, text) { + // TODO: Send to server and log there? + console.error(`${title}: ${text}`); + showNotification(title, text, "error"); +} + +showNotification("Be aware!", "Things are happening at an alarming pace!"); + //function to load more actions if nessisary function infinite_scroll() { if (scroll_trigger_element != undefined) { diff --git a/templates/popups.html b/templates/popups.html index 375dc2aa..d369b1a6 100644 --- a/templates/popups.html +++ b/templates/popups.html @@ -267,4 +267,6 @@ - \ No newline at end of file + + +
\ No newline at end of file