124
apps/web/src/lib/ai/ai-normalize.ts
Executable file
124
apps/web/src/lib/ai/ai-normalize.ts
Executable file
@@ -0,0 +1,124 @@
|
||||
import { parseAiJson } from "./parse-json";
|
||||
|
||||
export function safeParseAiJson(raw: string): unknown | null {
|
||||
try {
|
||||
return parseAiJson(raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeTeacherPayload(data: unknown): unknown {
|
||||
if (!data || typeof data !== "object") return data;
|
||||
const o = data as Record<string, unknown>;
|
||||
|
||||
const flashcards = Array.isArray(o.flashcards)
|
||||
? o.flashcards
|
||||
.map((c) => {
|
||||
if (!c || typeof c !== "object") return null;
|
||||
const card = c as Record<string, unknown>;
|
||||
const front = String(card.front ?? "").trim();
|
||||
const back = String(card.back ?? "").trim();
|
||||
if (!front && !back) return null;
|
||||
return { front, back };
|
||||
})
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const quiz = Array.isArray(o.quiz)
|
||||
? o.quiz
|
||||
.map((q) => {
|
||||
if (!q || typeof q !== "object") return null;
|
||||
const item = q as Record<string, unknown>;
|
||||
const options = Array.isArray(item.options)
|
||||
? item.options.map((opt) => String(opt))
|
||||
: [];
|
||||
if (options.length === 0) return null;
|
||||
let answer = Number(item.answer ?? 0);
|
||||
if (!Number.isFinite(answer)) answer = 0;
|
||||
if (answer < 0 || answer >= options.length) answer = 0;
|
||||
return {
|
||||
question: String(item.question ?? "Question"),
|
||||
options,
|
||||
answer,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
return {
|
||||
title: o.title != null ? String(o.title) : undefined,
|
||||
introduction: o.introduction != null ? String(o.introduction) : undefined,
|
||||
objectives: Array.isArray(o.objectives)
|
||||
? o.objectives.map(String).filter(Boolean)
|
||||
: undefined,
|
||||
readingSteps: Array.isArray(o.readingSteps)
|
||||
? o.readingSteps.map(String).filter(Boolean)
|
||||
: undefined,
|
||||
reflectionPrompt:
|
||||
o.reflectionPrompt != null ? String(o.reflectionPrompt) : undefined,
|
||||
flashcards,
|
||||
quiz,
|
||||
assignment: String(o.assignment ?? o.homework ?? "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeExplorationPayload(data: unknown): unknown {
|
||||
if (!data || typeof data !== "object") return data;
|
||||
const o = data as Record<string, unknown>;
|
||||
|
||||
const items = Array.isArray(o.explorations)
|
||||
? o.explorations
|
||||
: Array.isArray(o.quests)
|
||||
? o.quests
|
||||
: [];
|
||||
|
||||
return {
|
||||
explorations: items
|
||||
.map((item) => {
|
||||
if (!item || typeof item !== "object") return null;
|
||||
const e = item as Record<string, unknown>;
|
||||
const title = String(e.title ?? "").trim();
|
||||
if (!title) return null;
|
||||
const minutes = Number(e.minutes ?? e.duration ?? 20);
|
||||
return {
|
||||
title,
|
||||
hook: String(e.hook ?? e.description ?? e.reason ?? title).trim(),
|
||||
category: String(e.category ?? "general").trim(),
|
||||
minutes: Number.isFinite(minutes) && minutes > 0 ? minutes : 20,
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
export function debugAiLog(
|
||||
location: string,
|
||||
message: string,
|
||||
data: Record<string, unknown>
|
||||
) {
|
||||
console.error(`[adventureos-ai] ${location}: ${message}`, JSON.stringify(data));
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7257/ingest/abfa22e4-3d86-4176-9679-7c59b1f0489f", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Debug-Session-Id": "04e12c",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sessionId: "04e12c",
|
||||
location,
|
||||
message,
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
}
|
||||
|
||||
export function isAiTimeoutError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) return false;
|
||||
if (error.name === "TimeoutError") return true;
|
||||
if (error.message.toLowerCase().includes("timeout")) return true;
|
||||
return (error as Error & { code?: number }).code === 23;
|
||||
}
|
||||
Reference in New Issue
Block a user