🐛 | Fix misc bugs
This commit is contained in:
@@ -117,19 +117,56 @@ async function callUnsloth(prompt) {
|
||||
temperature: store.temperature,
|
||||
};
|
||||
|
||||
const res = await fetch(url, {
|
||||
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();
|
||||
responseBody = await res.text();
|
||||
try {
|
||||
const err = JSON.parse(responseBody);
|
||||
detail = err.error?.message || err.detail || detail;
|
||||
} catch {}
|
||||
throw new Error(detail);
|
||||
} catch {}
|
||||
|
||||
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 (
|
||||
<ModalRoot>
|
||||
<ModalHeader close={props.close}>Unsloth Chat - Error Details</ModalHeader>
|
||||
<ModalBody>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||
{/* Error message */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Error</Header>
|
||||
<div style={{
|
||||
marginTop: "4px",
|
||||
padding: "8px 12px",
|
||||
background: "var(--info-danger-background)",
|
||||
color: "var(--text-danger)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "14px",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
{error.message}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status code */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>HTTP Status</Header>
|
||||
<Text tag={TextTags.textSM} style={{ marginTop: "4px", display: "block" }}>
|
||||
{error.status !== null ? error.status : "N/A (network error)"}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* API URL used */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Request URL</Header>
|
||||
<Text tag={TextTags.textSM} style={{
|
||||
marginTop: "4px",
|
||||
display: "block",
|
||||
wordBreak: "break-all",
|
||||
fontFamily: "var(--font-code, monospace)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
{error.url}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Configured API URL */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Configured API URL</Header>
|
||||
<Text tag={TextTags.textSM} style={{
|
||||
marginTop: "4px",
|
||||
display: "block",
|
||||
wordBreak: "break-all",
|
||||
fontFamily: "var(--font-code, monospace)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
{error.apiUrl}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Model */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Model</Header>
|
||||
<Text tag={TextTags.textSM} style={{ marginTop: "4px", display: "block" }}>
|
||||
{error.model}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Prompt */}
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Prompt</Header>
|
||||
<div style={{
|
||||
marginTop: "4px",
|
||||
padding: "8px 12px",
|
||||
background: "var(--background-secondary)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "13px",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
maxHeight: "120px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
{error.prompt}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Response body */}
|
||||
{error.responseBody && (
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Response Body</Header>
|
||||
<div style={{
|
||||
marginTop: "4px",
|
||||
padding: "8px 12px",
|
||||
background: "var(--background-secondary)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "12px",
|
||||
fontFamily: "var(--font-code, monospace)",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
maxHeight: "200px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
{error.responseBody}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Cause (network error details) */}
|
||||
{error.cause && (
|
||||
<div>
|
||||
<Header tag={HeaderTags.HeadingSM} margin={false}>Network Error</Header>
|
||||
<div style={{
|
||||
marginTop: "4px",
|
||||
padding: "8px 12px",
|
||||
background: "var(--background-secondary)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "12px",
|
||||
fontFamily: "var(--font-code, monospace)",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
{error.cause.toString()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
<div>
|
||||
<Text tag={TextTags.textXS} style={{ color: "var(--text-muted)", display: "block" }}>
|
||||
Occurred at: {error.timestamp}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div style={{ display: "flex", gap: "8px", justifyContent: "flex-end", width: "100%" }}>
|
||||
<Button
|
||||
look={ButtonLooks.FILLED}
|
||||
color={ButtonColors.PRIMARY}
|
||||
onClick={props.close}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
// ── The prompt modal ────────────────────────────────────────────
|
||||
function PromptModal(props) {
|
||||
const [prompt, setPrompt] = shelter.solid.createSignal("");
|
||||
@@ -184,7 +387,11 @@ function PromptModal(props) {
|
||||
|
||||
props.close();
|
||||
} catch (e) {
|
||||
if (e instanceof DetailedApiError) {
|
||||
openModal((p) => <ErrorDetailModal close={p.close} error={e} />);
|
||||
} 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"}
|
||||
</Button>
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user