diff --git a/plugins/unsloth-chat/index.jsx b/plugins/unsloth-chat/index.jsx index 48ddd39..429f5e7 100644 --- a/plugins/unsloth-chat/index.jsx +++ b/plugins/unsloth-chat/index.jsx @@ -117,19 +117,56 @@ async function callUnsloth(prompt) { temperature: store.temperature, }; - const res = await fetch(url, { - method: "POST", - headers, - body: JSON.stringify(body), - }); + let res; + let fetchError = null; + + try { + res = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body), + }); + } catch (e) { + fetchError = e; + } + + if (fetchError) { + // Network-level error (e.g. Failed to fetch, CORS, DNS, etc.) + throw new DetailedApiError({ + message: fetchError.message || "Failed to fetch", + url, + prompt, + model: store.model, + apiUrl: store.apiUrl, + status: null, + responseBody: null, + timestamp: new Date().toISOString(), + cause: fetchError, + }); + } if (!res.ok) { + let responseBody = null; let detail = `API returned ${res.status}`; try { - const err = await res.json(); - detail = err.error?.message || err.detail || detail; + responseBody = await res.text(); + try { + const err = JSON.parse(responseBody); + detail = err.error?.message || err.detail || detail; + } catch {} } catch {} - throw new Error(detail); + + throw new DetailedApiError({ + message: detail, + url, + prompt, + model: store.model, + apiUrl: store.apiUrl, + status: res.status, + responseBody, + timestamp: new Date().toISOString(), + cause: null, + }); } const data = await res.json(); @@ -137,6 +174,172 @@ async function callUnsloth(prompt) { return text; } +// ── Detailed error class ───────────────────────────────────────── +class DetailedApiError extends Error { + constructor(details) { + super(details.message); + this.name = "DetailedApiError"; + this.url = details.url; + this.prompt = details.prompt; + this.model = details.model; + this.apiUrl = details.apiUrl; + this.status = details.status; + this.responseBody = details.responseBody; + this.timestamp = details.timestamp; + this.cause = details.cause; + } +} + +// ── Error detail modal ────────────────────────────────────────── +function ErrorDetailModal(props) { + const error = props.error; + + return ( + + Unsloth Chat - Error Details + +
+ {/* Error message */} +
+
Error
+
+ {error.message} +
+
+ + {/* Status code */} +
+
HTTP Status
+ + {error.status !== null ? error.status : "N/A (network error)"} + +
+ + {/* API URL used */} +
+
Request URL
+ + {error.url} + +
+ + {/* Configured API URL */} +
+
Configured API URL
+ + {error.apiUrl} + +
+ + {/* Model */} +
+
Model
+ + {error.model} + +
+ + {/* Prompt */} +
+
Prompt
+
+ {error.prompt} +
+
+ + {/* Response body */} + {error.responseBody && ( +
+
Response Body
+
+ {error.responseBody} +
+
+ )} + + {/* Cause (network error details) */} + {error.cause && ( +
+
Network Error
+
+ {error.cause.toString()} +
+
+ )} + + {/* Timestamp */} +
+ + Occurred at: {error.timestamp} + +
+
+
+ +
+ +
+
+
+ ); +} + // ── The prompt modal ──────────────────────────────────────────── function PromptModal(props) { const [prompt, setPrompt] = shelter.solid.createSignal(""); @@ -184,7 +387,11 @@ function PromptModal(props) { props.close(); } catch (e) { - setError(e.message || "An error occurred"); + if (e instanceof DetailedApiError) { + openModal((p) => ); + } else { + setError(e.message || "An error occurred"); + } } finally { setLoading(false); } @@ -238,6 +445,7 @@ function PromptModal(props) { color={ButtonColors.PRIMARY} onClick={handleSubmit} disabled={loading() || !prompt().trim()} + style={{ minWidth: "180px" }} > {loading() ? "Generating..." : "Generate & Insert"} diff --git a/plugins/unsloth-chat/plugin.json b/plugins/unsloth-chat/plugin.json index 8b65d90..8b948c1 100644 --- a/plugins/unsloth-chat/plugin.json +++ b/plugins/unsloth-chat/plugin.json @@ -1,6 +1,6 @@ { "name": "Unsloth Chat", - "author": "You", + "author": "NikkeDoy", "description": "Adds a button to the chatbar that lets you fill text from a local LLM via Unsloth Studio API.", "hash": "" }