feat(rag-pe): PRG-5 + PRG-6 frontend pensees ecologiques

- server/api/chatbot-pensees.post.ts : endpoint LightRAG VPS (hybrid mode, preface militante, rate limit 20/jour, health guard)
- nuxt.config.ts : ragPeUrl runtimeConfig (NUXT_RAG_PE_URL)
- public/data/auteurs-pensees.json : 18 auteurs FRACAS, 8 ecoles, theses, livres RAG
- components/CartePensees.vue : D3 force-directed (8 ecoles fixes + auteurs gravitants)
- components/FicheAuteur.vue : modal auteur (bio + theses + livres RAG + bouton RAG)
- components/ChatbotPensees.vue : overlay chatbot bottom-right (sources expansibles)
- pages/pensees-ecologiques.vue : page dedicee /pensees-ecologiques (toggle Familiale/Graphe)
- pages/agences.vue : 4e onglet "Pensees" (desktop + mobile) -> /pensees-ecologiques

Branche : feat/aep-rag-pensees-ecologiques
Checkpoint Jules requis avant merge main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jules Neny
2026-05-11 15:07:42 +02:00
parent f5732bf336
commit 668ae5caff
8 changed files with 890 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
<template>
<div class="flex h-full overflow-hidden" style="background: var(--nav-bg);">
<!-- ZONE PRINCIPALE (pleine largeur, pas de sidebar) -->
<main class="flex-1 flex flex-col overflow-hidden relative">
<!-- Header onglet -->
<div class="shrink-0 flex items-center justify-between px-5 py-3"
style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
<div>
<h1 class="font-bold text-base" style="color: var(--nav-text);">Pensees Ecologiques</h1>
<p class="text-xs mt-0.5" style="color: var(--nav-text-muted);">
{{ corpusCount }} auteurs ingeres dans le RAG - carte FRACAS Bonpote V2
</p>
</div>
<!-- Toggle vue -->
<div class="flex items-center gap-1 p-1 rounded-lg" style="background: var(--nav-bg-alt);">
<button
@click="vue = 'familiale'"
class="px-3 py-1.5 rounded-md text-xs font-semibold transition-all"
:style="vue === 'familiale'
? 'background: var(--nav-surface); color: var(--nav-text); box-shadow: 0 1px 3px rgba(0,0,0,0.12);'
: 'color: var(--nav-text-muted);'"
>Familiale</button>
<button
@click="vue = 'graphe'"
class="px-3 py-1.5 rounded-md text-xs font-semibold transition-all"
:style="vue === 'graphe'
? 'background: var(--nav-surface); color: var(--nav-text); box-shadow: 0 1px 3px rgba(0,0,0,0.12);'
: 'color: var(--nav-text-muted);'"
>Graphe</button>
</div>
</div>
<!-- Vue Familiale (CartePensees D3 force-directed) -->
<div v-show="vue === 'familiale'" class="flex-1 overflow-hidden relative">
<ClientOnly>
<CartePensees
:data="penseesData"
:active="vue === 'familiale'"
@select-auteur="onSelectAuteur"
/>
<template #fallback>
<div class="w-full h-full flex items-center justify-center" style="color: var(--nav-text-muted);">
Chargement de la carte...
</div>
</template>
</ClientOnly>
</div>
<!-- Vue Graphe (GraphView existant adapte) -->
<div v-show="vue === 'graphe'" class="flex-1 overflow-hidden relative">
<ClientOnly>
<CartePensees
:data="penseesData"
:active="vue === 'graphe'"
@select-auteur="onSelectAuteur"
/>
<template #fallback>
<div class="w-full h-full flex items-center justify-center" style="color: var(--nav-text-muted);">
Chargement du graphe...
</div>
</template>
</ClientOnly>
</div>
</main>
<!-- Fiche auteur modal -->
<FicheAuteur
:open="ficheOpen"
:auteurId="ficheAuteurId"
:data="penseesData"
@close="ficheOpen = false"
@interroger-rag="onInterrogerRag"
/>
<!-- Chatbot flottant -->
<ChatbotPensees :auteurContext="chatbotAuteur" />
</div>
</template>
<script setup lang="ts">
interface EcoleData { id: string; label: string; description: string; color: string; x_hint: number; y_hint: number }
interface LivreRag { slug: string; titre: string; annee: number; couches: string[] }
interface AuteurData { id: string; nom: string; dates: string; ecoles: string[]; ecole_principale: string; livres_rag: LivreRag[]; theses_cles: string[]; bio_courte: string }
interface PenseesData { meta: any; ecoles: EcoleData[]; auteurs: AuteurData[] }
const vue = ref<'familiale' | 'graphe'>('familiale')
const ficheOpen = ref(false)
const ficheAuteurId = ref<string | null>(null)
const chatbotAuteur = ref<string | null>(null)
const penseesData = ref<PenseesData | null>(null)
const corpusCount = computed(() => penseesData.value?.auteurs.length ?? 0)
onMounted(async () => {
try {
penseesData.value = await $fetch<PenseesData>('/data/auteurs-pensees.json')
} catch (e) {
console.error('Erreur chargement auteurs-pensees.json', e)
}
})
function onSelectAuteur(id: string) {
ficheAuteurId.value = id
ficheOpen.value = true
chatbotAuteur.value = null
}
function onInterrogerRag(auteurId: string) {
ficheOpen.value = false
const auteur = penseesData.value?.auteurs.find(a => a.id === auteurId)
chatbotAuteur.value = auteur?.nom ?? null
}
useHead({ title: 'AEP - Pensees Ecologiques - Carte FRACAS' })
</script>