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>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user