bizzle 5a51a0f112 Mr. Drew's Assignment Creator — Docker share build
Self-contained Dockerized build for end users. Run via docker compose;
see README.md for setup. Source-only, no sample data or build artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:58:36 -04:00

91 lines
2.7 KiB
JavaScript

// lib/json-utils.js — tolerant JSON extraction for LLM responses.
// Local models often wrap JSON in prose or code fences, or leave trailing commas.
export function extractJson(text) {
if (text == null) throw new Error("Model returned an empty response.");
let s = String(text).trim();
// Strip code fences anywhere
s = s.replace(/```(?:json|javascript|js)?/gi, "```");
const fenced = s.match(/```([\s\S]*?)```/);
if (fenced && fenced[1].trim().match(/^[\[{]/)) s = fenced[1].trim();
// Slice from first brace/bracket to its matching end; try whichever starts first, first.
const candidates = [];
const firstObj = s.indexOf("{");
const firstArr = s.indexOf("[");
const objSlice = firstObj !== -1 ? s.slice(firstObj, s.lastIndexOf("}") + 1) : null;
const arrSlice = firstArr !== -1 ? s.slice(firstArr, s.lastIndexOf("]") + 1) : null;
if (firstArr !== -1 && (firstObj === -1 || firstArr < firstObj)) {
if (arrSlice) candidates.push(arrSlice);
if (objSlice) candidates.push(objSlice);
} else {
if (objSlice) candidates.push(objSlice);
if (arrSlice) candidates.push(arrSlice);
}
candidates.unshift(s);
let lastErr = null;
for (const c of candidates) {
if (!c) continue;
for (const attempt of [c, repair(c)]) {
try {
return JSON.parse(attempt);
} catch (e) {
lastErr = e;
}
}
}
const preview = s.slice(0, 300).replace(/\s+/g, " ");
throw new Error(
"Could not read the model's response as JSON. Try again, lower the temperature, or use a stronger model. Response began: " + preview
);
}
function repair(s) {
let out = s;
// Smart quotes -> straight quotes
out = out.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'");
// Remove trailing commas before } or ]
out = out.replace(/,\s*([}\]])/g, "$1");
// Remove JS-style comments
out = out.replace(/^\s*\/\/.*$/gm, "");
// Control characters inside strings break JSON.parse; replace bare newlines in strings
out = sanitizeNewlinesInStrings(out);
return out;
}
function sanitizeNewlinesInStrings(s) {
let result = "";
let inStr = false;
let escaped = false;
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (inStr) {
if (escaped) {
result += ch;
escaped = false;
continue;
}
if (ch === "\\") {
result += ch;
escaped = true;
continue;
}
if (ch === '"') {
inStr = false;
result += ch;
continue;
}
if (ch === "\n") { result += "\\n"; continue; }
if (ch === "\r") { continue; }
if (ch === "\t") { result += "\\t"; continue; }
result += ch;
} else {
if (ch === '"') inStr = true;
result += ch;
}
}
return result;
}