fix: robust LLM JSON parsing across all AI components
- Created shared lib/llm-parse.ts with extractJson(), parseLlmJson(), flattenValue(), and ensureArray() utilities - Strips markdown code fences (\`\`\`json ... \`\`\`) before parsing - Handles nested objects where strings were expected (flattens them) - Handles arrays that come back as strings or objects - Updated all AI prompts to explicitly request raw JSON with no fences - All 5 AI components now use parseLlmJson() instead of raw regex - ItemForge shows raw LLM response in expandable <details> when parsing fails - Added 'IMPORTANT: Return ONLY raw JSON' instruction to all prompts - Full production build passing
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { parseLlmJson, ensureArray, flattenValue } from "../lib/llm-parse";
|
||||
import { useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
@@ -32,20 +33,21 @@ Alignment: ${alignment}
|
||||
|
||||
Provide the response as a JSON object with exactly these keys:
|
||||
- "bio": a 2-3 sentence backstory
|
||||
- "personality": an array of 3-5 personality traits
|
||||
- "goals": an array of 2-3 goals or motivations`;
|
||||
- "personality": an array of 3-5 personality traits (each trait must be a string, not an object)
|
||||
- "goals": an array of 2-3 goals or motivations (each goal must be a string, not an object)
|
||||
|
||||
IMPORTANT: Return ONLY a raw JSON object. NO markdown fences, NO code blocks, NO extra text. Start with { and end with }.`;
|
||||
|
||||
const result = await invoke<string>("generate", {
|
||||
req: { prompt, system: "You are a creative D&D dungeon master. Always respond with valid JSON only.", temperature: 0.8, max_tokens: 512 },
|
||||
req: { prompt, system: "You are a creative D&D dungeon master. You MUST respond with ONLY valid JSON. No markdown fences, no code blocks, no explanation. Just the JSON object.", temperature: 0.8, max_tokens: 512 },
|
||||
});
|
||||
|
||||
// Try to parse JSON from the response
|
||||
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
const parsed = JSON.parse(jsonMatch[0]) as NpcResponse;
|
||||
const parsed = parseLlmJson<NpcResponse>(result);
|
||||
if (parsed) {
|
||||
setNpc(parsed);
|
||||
} else {
|
||||
setError("LLM did not return valid JSON. Raw response:\n" + result);
|
||||
setError("Could not parse LLM response. Try again or check your LLM connection.");
|
||||
}
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
@@ -120,9 +122,9 @@ Provide the response as a JSON object with exactly these keys:
|
||||
<div>
|
||||
<span className="text-[var(--color-text-secondary)] text-xs font-medium">Personality:</span>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{npc.personality?.map((p, i) => (
|
||||
{ensureArray(npc.personality).map((p, i) => (
|
||||
<span key={i} className="rounded-full bg-[var(--color-gold-glow)] text-[var(--color-gold-bright)] px-2 py-0.5 text-xs">
|
||||
{p}
|
||||
{flattenValue(p)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -130,7 +132,7 @@ Provide the response as a JSON object with exactly these keys:
|
||||
<div>
|
||||
<span className="text-[var(--color-text-secondary)] text-xs font-medium">Goals:</span>
|
||||
<ul className="list-disc list-inside text-[var(--color-text-primary)] text-xs mt-1">
|
||||
{npc.goals?.map((g, i) => <li key={i}>{g}</li>)}
|
||||
{ensureArray(npc.goals).map((g, i) => <li key={i}>{flattenValue(g)}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user