Project Files
src / corpus.ts
import { SYSTEM_PROMPT_TEMPLATE, VERIFIED_JSONL } from "./data";
import { normalizeNotion } from "./data/notions";
import { listUserCitations } from "./corpus-builder";
export type Citation = {
id: string;
auteur: string;
auteur_slug?: string;
oeuvre: string;
partie?: string;
reference?: string;
texte_fr: string;
aliases?: string[];
notions?: string[];
sources?: Array<{ url: string; [k: string]: unknown }>;
};
let seedCache: Citation[] | null = null;
function loadSeed(): Citation[] {
if (seedCache) return seedCache;
seedCache = VERIFIED_JSONL
.split("\n")
.filter((l) => l.trim().length > 0 && !l.trim().startsWith("//"))
.map((l) => JSON.parse(l) as Citation);
return seedCache;
}
// Pas de cache global pour le merge : les user citations peuvent bouger
// entre deux loadCorpus(). Le seed lui est immuable et caché.
export function loadCorpus(): Citation[] {
const merged: Citation[] = [...loadSeed(), ...listUserCitations()];
// Filtre canonique : ne sont actionnables par le narrateur que les
// citations avec >= 2 sources indépendantes. Une citation user à 1
// source reste visible dans le catalogue corpus (via une autre API)
// mais n'est pas injectée dans le prompt système.
return merged.filter((c) => Array.isArray(c.sources) && c.sources!.length >= 2);
}
// Pour le catalogue corpus côté UI : retourne TOUTES les citations user
// (y compris celles à 1 source qui sont en attente de validation).
export function loadAllCitationsForCatalog(): Citation[] {
return [...loadSeed(), ...listUserCitations()];
}
export function loadSystemPrompt(): string {
return SYSTEM_PROMPT_TEMPLATE;
}
export function filterByNotion(corpus: Citation[], notion: string): Citation[] {
// Le corpus utilise des slugs ("verite", "liberte", "etat"…).
// L'input peut venir sous forme canonique ("La vérité") depuis le
// catalogue, ou déjà slugifié ("verite") depuis l'ancien flow CLI.
// normalizeNotion() gère les deux : il strip article/accents/casse.
const n = normalizeNotion(notion);
return corpus.filter((c) => !c.notions || c.notions.includes(n));
}
export function formatCorpusBlock(citations: Citation[]): string {
if (citations.length === 0) return "*(corpus vide pour cette notion — aucune citation autorisée)*";
return citations
.map((c) => {
const ref = [c.auteur, `*${c.oeuvre}*`, c.partie, c.reference ? `(${c.reference})` : ""]
.filter(Boolean)
.join(", ");
return `- **${ref}** : « ${c.texte_fr} »`;
})
.join("\n");
}