125 lines
3.8 KiB
TypeScript
Executable File
125 lines
3.8 KiB
TypeScript
Executable File
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;
|
|
}
|