Compare commits
5 Commits
feat/outil
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd8fe9e258 | ||
|
|
ea7c8cc91e | ||
|
|
538c490e76 | ||
|
|
d584d04e3d | ||
|
|
bd95c0f00d |
9
app.vue
9
app.vue
@@ -34,7 +34,7 @@
|
|||||||
class="nav-tab"
|
class="nav-tab"
|
||||||
:class="{ 'nav-tab--active': route.path === '/' }"
|
:class="{ 'nav-tab--active': route.path === '/' }"
|
||||||
>
|
>
|
||||||
Écosystème Entraide Architecture
|
Écosystème Entraide
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/agences"
|
to="/agences"
|
||||||
@@ -58,12 +58,11 @@
|
|||||||
Codev
|
Codev
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/rag"
|
to="/media"
|
||||||
class="nav-tab"
|
class="nav-tab"
|
||||||
:class="{ 'nav-tab--active': route.path === '/rag' }"
|
:class="{ 'nav-tab--active': route.path.startsWith('/media') }"
|
||||||
>
|
>
|
||||||
RAG
|
recherche-média
|
||||||
<span class="nav-tab-badge">en construction</span>
|
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PLACEHOLDER — DNS en attente
|
|
||||||
TODO: Décommenter iframe + supprimer placeholder une fois lightrag.trans-former.fr propagé.
|
|
||||||
DNS A record à créer sur OVH : lightrag → 178.104.106.195 TTL 300
|
|
||||||
-->
|
|
||||||
<div style="margin-top: 1.5rem; padding: 2rem; border: 2px dashed var(--nav-bg-alt, #ddd); border-radius: 8px; text-align: center; color: var(--nav-text-muted);">
|
|
||||||
<p style="font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem;">⏳ Backend en cours d'exposition publique — bientôt accessible.</p>
|
|
||||||
<p style="font-size: 0.85rem;">L'interface LightRAG sera disponible ici dès la mise en place du sous-domaine <code>lightrag.trans-former.fr</code>.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<iframe
|
<iframe
|
||||||
src="https://lightrag.trans-former.fr/"
|
src="https://lightrag.trans-former.fr/"
|
||||||
style="width: 100%; height: 70vh; border: 1px solid var(--nav-bg-alt, #ddd); border-radius: 8px; margin-top: 1.5rem;"
|
style="width: 100%; height: 70vh; border: 1px solid var(--nav-bg-alt, #ddd); border-radius: 8px; margin-top: 1.5rem;"
|
||||||
@@ -25,6 +15,5 @@
|
|||||||
sandbox="allow-same-origin allow-scripts"
|
sandbox="allow-same-origin allow-scripts"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="media-visuel">
|
<div class="media-visuel">
|
||||||
|
|
||||||
<!-- Conteneur split / plein ecran -->
|
<!-- Conteneur principal : carte → toolbar → chatbot (ou bonpote/rag-backend) -->
|
||||||
<div class="layout-container">
|
<div class="layout-container">
|
||||||
|
|
||||||
<!-- Slot carte D3 -->
|
<!-- SLOT CARTE D3 (mode carte uniquement) -->
|
||||||
<div
|
<div
|
||||||
|
v-if="contentView === 'carte'"
|
||||||
class="carte-slot"
|
class="carte-slot"
|
||||||
:class="[
|
:class="[
|
||||||
layoutMode === 'split' ? 'carte-split' : '',
|
layoutMode === 'split' ? 'carte-split' : '',
|
||||||
@@ -44,10 +45,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Barre de toggle -->
|
<!-- BARRE DE TOGGLE (entre carte et chatbot, toujours visible) -->
|
||||||
<div class="layout-toggle-bar shrink-0">
|
<div class="layout-toggle-bar shrink-0">
|
||||||
|
|
||||||
|
<!-- Gauche : contrôles layout (seulement en mode carte) -->
|
||||||
|
<template v-if="contentView === 'carte'">
|
||||||
<button
|
<button
|
||||||
@click="setLayoutMode('carte-full')"
|
@click="layoutMode = 'carte-full'"
|
||||||
:class="{ active: layoutMode === 'carte-full' }"
|
:class="{ active: layoutMode === 'carte-full' }"
|
||||||
class="toggle-btn"
|
class="toggle-btn"
|
||||||
title="Carte en plein ecran"
|
title="Carte en plein ecran"
|
||||||
@@ -60,7 +64,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="layoutMode !== 'split'"
|
v-if="layoutMode !== 'split'"
|
||||||
@click="setLayoutMode('split')"
|
@click="layoutMode = 'split'"
|
||||||
class="toggle-btn"
|
class="toggle-btn"
|
||||||
title="Vue partagee"
|
title="Vue partagee"
|
||||||
>
|
>
|
||||||
@@ -70,36 +74,23 @@
|
|||||||
Vue partagee
|
Vue partagee
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="setLayoutMode('chatbot-full')"
|
@click="layoutMode = 'chatbot-full'"
|
||||||
:class="{ active: layoutMode === 'chatbot-full' }"
|
:class="{ active: layoutMode === 'chatbot-full' }"
|
||||||
class="toggle-btn"
|
class="toggle-btn"
|
||||||
title="Chatbot en plein ecran"
|
title="Chatbot plein ecran"
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Chatbot plein ecran
|
Chatbot plein ecran
|
||||||
</button>
|
</button>
|
||||||
<button
|
</template>
|
||||||
@click="setLayoutMode('bonpote')"
|
|
||||||
:class="{ active: layoutMode === 'bonpote' }"
|
|
||||||
class="toggle-btn"
|
|
||||||
title="A propos de la carte FRACAS Bonpote V2"
|
|
||||||
style="margin-left: auto;"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
||||||
<circle cx="12" cy="12" r="10"/><polyline points="12 8 12 12 14 14"/>
|
|
||||||
</svg>
|
|
||||||
Bonpote V2
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Toggle PDF FRACAS -->
|
<!-- Droite : contrôles contenu (toujours, indépendants du layoutMode) -->
|
||||||
<label class="layer-toggle" title="Superposer la carte FRACAS Bonpote V2 en PDF">
|
<div style="margin-left: auto; display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||||||
<input type="checkbox" v-model="showFracasPdf" />
|
<!-- Slider opacité PDF -->
|
||||||
📄 Carte FRACAS (PDF)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
v-if="showFracasPdf"
|
v-if="showFracasPdf && contentView === 'carte'"
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
@@ -107,11 +98,56 @@
|
|||||||
class="opacity-slider"
|
class="opacity-slider"
|
||||||
:title="`Opacité ${fracasOpacity}%`"
|
:title="`Opacité ${fracasOpacity}%`"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- CARTE PRINCIPALE -->
|
||||||
|
<button
|
||||||
|
@click="showCarte"
|
||||||
|
:class="{ active: contentView === 'carte' }"
|
||||||
|
class="toggle-btn"
|
||||||
|
title="Vue principale : carte D3 + chatbot"
|
||||||
|
>
|
||||||
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<circle cx="12" cy="12" r="2"/><path d="M12 2a10 10 0 0 0-7.07 17.07M12 2a10 10 0 0 1 7.07 17.07M3.34 7h17.32M3.34 17h17.32"/>
|
||||||
|
</svg>
|
||||||
|
CARTE PRINCIPALE
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Tickbox PDF + carte des pensées -->
|
||||||
|
<div class="carte-pensees-ctrl">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="showFracasPdf"
|
||||||
|
class="fracas-check"
|
||||||
|
title="Superposer la carte FRACAS en PDF"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
@click="contentView = 'bonpote'"
|
||||||
|
:class="{ active: contentView === 'bonpote' }"
|
||||||
|
class="toggle-btn carte-pensees-btn"
|
||||||
|
title="Carte des pensées écologiques — référence FRACAS Bonpote V2"
|
||||||
|
>
|
||||||
|
📗 carte des pensées écologiques
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Poignee draggable (visible uniquement en mode split, pas sur mobile) -->
|
<!-- RAG backend -->
|
||||||
|
<button
|
||||||
|
@click="contentView = 'rag-backend'"
|
||||||
|
:class="{ active: contentView === 'rag-backend' }"
|
||||||
|
class="toggle-btn"
|
||||||
|
title="Interface LightRAG backend"
|
||||||
|
>
|
||||||
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/>
|
||||||
|
</svg>
|
||||||
|
RAG backend
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- POIGNEE DRAGGABLE (split uniquement) -->
|
||||||
<div
|
<div
|
||||||
v-if="layoutMode === 'split'"
|
v-if="contentView === 'carte' && layoutMode === 'split'"
|
||||||
class="split-handle"
|
class="split-handle"
|
||||||
@mousedown.prevent="onHandleMousedown"
|
@mousedown.prevent="onHandleMousedown"
|
||||||
title="Redimensionner"
|
title="Redimensionner"
|
||||||
@@ -119,8 +155,9 @@
|
|||||||
<span class="split-handle-grip"></span>
|
<span class="split-handle-grip"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Slot chatbot inline -->
|
<!-- SLOT CHATBOT (mode carte uniquement) -->
|
||||||
<div
|
<div
|
||||||
|
v-if="contentView === 'carte'"
|
||||||
class="chatbot-slot"
|
class="chatbot-slot"
|
||||||
:class="[
|
:class="[
|
||||||
layoutMode === 'split' ? 'chatbot-split' : '',
|
layoutMode === 'split' ? 'chatbot-split' : '',
|
||||||
@@ -134,9 +171,9 @@
|
|||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vue Bonpote V2 -->
|
<!-- VUE BONPOTE -->
|
||||||
<div
|
<div
|
||||||
v-if="layoutMode === 'bonpote'"
|
v-if="contentView === 'bonpote'"
|
||||||
class="flex-1 overflow-y-auto px-6 py-8"
|
class="flex-1 overflow-y-auto px-6 py-8"
|
||||||
style="max-width: 680px; margin: 0 auto;"
|
style="max-width: 680px; margin: 0 auto;"
|
||||||
>
|
>
|
||||||
@@ -165,7 +202,7 @@
|
|||||||
Telecharger le poster PDF (recto/verso)
|
Telecharger le poster PDF (recto/verso)
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
@click="setLayoutMode('split')"
|
@click="showCarte"
|
||||||
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:opacity-80 transition-opacity text-left"
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:opacity-80 transition-opacity text-left"
|
||||||
style="background: var(--nav-bg-alt); color: var(--nav-text); font-size: 0.875rem; font-weight: 500; border: none; cursor: pointer;">
|
style="background: var(--nav-bg-alt); color: var(--nav-text); font-size: 0.875rem; font-weight: 500; border: none; cursor: pointer;">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||||
@@ -189,6 +226,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- VUE RAG BACKEND -->
|
||||||
|
<div
|
||||||
|
v-if="contentView === 'rag-backend'"
|
||||||
|
style="flex: 1; overflow: hidden; display: flex; flex-direction: column;"
|
||||||
|
>
|
||||||
|
<MediaTabBackend />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fiche auteur modal -->
|
<!-- Fiche auteur modal -->
|
||||||
@@ -264,9 +309,11 @@ 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 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[] }
|
interface PenseesData { meta: any; ecoles: EcoleData[]; auteurs: AuteurData[] }
|
||||||
|
|
||||||
type LayoutMode = 'split' | 'carte-full' | 'chatbot-full' | 'bonpote'
|
type LayoutMode = 'split' | 'carte-full' | 'chatbot-full'
|
||||||
|
type ContentView = 'carte' | 'bonpote' | 'rag-backend'
|
||||||
|
|
||||||
const STORAGE_KEY = 'media-layout-mode'
|
const LAYOUT_KEY = 'media-layout-mode'
|
||||||
|
const CONTENT_KEY = 'media-content-view'
|
||||||
const SPLIT_RATIO_KEY = 'media-split-ratio'
|
const SPLIT_RATIO_KEY = 'media-split-ratio'
|
||||||
const DEFAULT_SPLIT_RATIO = 0.66
|
const DEFAULT_SPLIT_RATIO = 0.66
|
||||||
|
|
||||||
@@ -276,32 +323,39 @@ const ficheEcoleOpen = ref(false)
|
|||||||
const ficheEcoleId = ref<string | null>(null)
|
const ficheEcoleId = ref<string | null>(null)
|
||||||
const ragInfoOpen = ref(false)
|
const ragInfoOpen = ref(false)
|
||||||
const chatbotAuteur = ref<string | null>(null)
|
const chatbotAuteur = ref<string | null>(null)
|
||||||
|
|
||||||
const layoutMode = ref<LayoutMode>('split')
|
const layoutMode = ref<LayoutMode>('split')
|
||||||
|
const contentView = ref<ContentView>('carte')
|
||||||
const cartePenseesRef = ref<{ triggerResize: () => void } | null>(null)
|
const cartePenseesRef = ref<{ triggerResize: () => void } | null>(null)
|
||||||
|
|
||||||
// Toggle PDF FRACAS
|
|
||||||
const showFracasPdf = ref(false)
|
const showFracasPdf = ref(false)
|
||||||
const fracasOpacity = ref(60)
|
const fracasOpacity = ref(60)
|
||||||
|
|
||||||
// Props injectées depuis le parent (penseesData)
|
const penseesData = ref<PenseesData | null>(null)
|
||||||
const props = defineProps<{ penseesData: PenseesData | null }>()
|
|
||||||
|
|
||||||
// Ratio de la carte vs chatbot en mode split (0.2 a 0.8)
|
|
||||||
const splitRatio = ref(DEFAULT_SPLIT_RATIO)
|
const splitRatio = ref(DEFAULT_SPLIT_RATIO)
|
||||||
const carteFlexBasis = computed(() => `${splitRatio.value * 100}%`)
|
const carteFlexBasis = computed(() => `${splitRatio.value * 100}%`)
|
||||||
const chatbotFlexBasis = computed(() => `${(1 - splitRatio.value) * 100}%`)
|
const chatbotFlexBasis = computed(() => `${(1 - splitRatio.value) * 100}%`)
|
||||||
|
|
||||||
// Logique poignee draggable
|
|
||||||
let dragStartY = 0
|
let dragStartY = 0
|
||||||
let dragStartRatio = DEFAULT_SPLIT_RATIO
|
let dragStartRatio = DEFAULT_SPLIT_RATIO
|
||||||
let containerHeight = 0
|
let containerHeight = 0
|
||||||
|
|
||||||
|
function showCarte() {
|
||||||
|
contentView.value = 'carte'
|
||||||
|
layoutMode.value = 'split'
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(CONTENT_KEY, 'carte')
|
||||||
|
localStorage.setItem(LAYOUT_KEY, 'split')
|
||||||
|
}
|
||||||
|
nextTick(() => cartePenseesRef.value?.triggerResize())
|
||||||
|
}
|
||||||
|
|
||||||
function onHandleMousedown(e: MouseEvent) {
|
function onHandleMousedown(e: MouseEvent) {
|
||||||
dragStartY = e.clientY
|
dragStartY = e.clientY
|
||||||
dragStartRatio = splitRatio.value
|
dragStartRatio = splitRatio.value
|
||||||
const container = (e.target as HTMLElement)?.closest('.layout-container') as HTMLElement | null
|
const container = (e.target as HTMLElement)?.closest('.layout-container') as HTMLElement | null
|
||||||
containerHeight = container ? container.clientHeight : window.innerHeight
|
containerHeight = container ? container.clientHeight : window.innerHeight
|
||||||
|
|
||||||
window.addEventListener('mousemove', onHandleMousemove)
|
window.addEventListener('mousemove', onHandleMousemove)
|
||||||
window.addEventListener('mouseup', onHandleMouseup)
|
window.addEventListener('mouseup', onHandleMouseup)
|
||||||
}
|
}
|
||||||
@@ -315,17 +369,19 @@ function onHandleMousemove(e: MouseEvent) {
|
|||||||
function onHandleMouseup() {
|
function onHandleMouseup() {
|
||||||
window.removeEventListener('mousemove', onHandleMousemove)
|
window.removeEventListener('mousemove', onHandleMousemove)
|
||||||
window.removeEventListener('mouseup', onHandleMouseup)
|
window.removeEventListener('mouseup', onHandleMouseup)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') localStorage.setItem(SPLIT_RATIO_KEY, String(splitRatio.value))
|
||||||
localStorage.setItem(SPLIT_RATIO_KEY, String(splitRatio.value))
|
|
||||||
}
|
|
||||||
cartePenseesRef.value?.triggerResize()
|
cartePenseesRef.value?.triggerResize()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const saved = localStorage.getItem(STORAGE_KEY) as LayoutMode | null
|
const savedLayout = localStorage.getItem(LAYOUT_KEY) as LayoutMode | null
|
||||||
if (saved && ['split', 'carte-full', 'chatbot-full', 'bonpote'].includes(saved)) {
|
if (savedLayout && (['split', 'carte-full', 'chatbot-full'] as string[]).includes(savedLayout)) {
|
||||||
layoutMode.value = saved
|
layoutMode.value = savedLayout
|
||||||
|
}
|
||||||
|
const savedContent = localStorage.getItem(CONTENT_KEY) as ContentView | null
|
||||||
|
if (savedContent && (['carte', 'bonpote', 'rag-backend'] as string[]).includes(savedContent)) {
|
||||||
|
contentView.value = savedContent
|
||||||
}
|
}
|
||||||
const savedRatio = parseFloat(localStorage.getItem(SPLIT_RATIO_KEY) ?? '')
|
const savedRatio = parseFloat(localStorage.getItem(SPLIT_RATIO_KEY) ?? '')
|
||||||
if (!isNaN(savedRatio) && savedRatio >= 0.20 && savedRatio <= 0.80) {
|
if (!isNaN(savedRatio) && savedRatio >= 0.20 && savedRatio <= 0.80) {
|
||||||
@@ -336,26 +392,24 @@ onMounted(() => {
|
|||||||
localStorage.setItem('rag-fracas-info-seen', '1')
|
localStorage.setItem('rag-fracas-info-seen', '1')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
penseesData.value = await $fetch<PenseesData>('/data/auteurs-pensees.json')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Erreur chargement auteurs-pensees.json', e)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function setLayoutMode(mode: LayoutMode) {
|
|
||||||
layoutMode.value = mode
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
localStorage.setItem(STORAGE_KEY, mode)
|
|
||||||
}
|
|
||||||
if (mode !== 'chatbot-full') {
|
|
||||||
setTimeout(() => {
|
|
||||||
cartePenseesRef.value?.triggerResize()
|
|
||||||
}, 350)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(layoutMode, (v) => {
|
watch(layoutMode, (v) => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') localStorage.setItem(LAYOUT_KEY, v)
|
||||||
localStorage.setItem(STORAGE_KEY, v)
|
if (v === 'split' || v === 'carte-full') {
|
||||||
|
setTimeout(() => cartePenseesRef.value?.triggerResize(), 350)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(contentView, (v) => {
|
||||||
|
if (typeof window !== 'undefined') localStorage.setItem(CONTENT_KEY, v)
|
||||||
|
})
|
||||||
|
|
||||||
function onSelectAuteur(id: string) {
|
function onSelectAuteur(id: string) {
|
||||||
ficheAuteurId.value = id
|
ficheAuteurId.value = id
|
||||||
ficheOpen.value = true
|
ficheOpen.value = true
|
||||||
@@ -374,18 +428,18 @@ function onSelectAuteurFromEcole(auteurId: string) {
|
|||||||
|
|
||||||
function onInterrogerEcole(ecoleId: string) {
|
function onInterrogerEcole(ecoleId: string) {
|
||||||
ficheEcoleOpen.value = false
|
ficheEcoleOpen.value = false
|
||||||
const ecole = props.penseesData?.ecoles.find(e => e.id === ecoleId)
|
const ecole = penseesData.value?.ecoles.find(e => e.id === ecoleId)
|
||||||
chatbotAuteur.value = ecole?.label ?? null
|
chatbotAuteur.value = ecole?.label ?? null
|
||||||
if (layoutMode.value === 'carte-full') setLayoutMode('split')
|
if (contentView.value !== 'carte') showCarte()
|
||||||
|
else if (layoutMode.value === 'carte-full') layoutMode.value = 'split'
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInterrogerRag(auteurId: string) {
|
function onInterrogerRag(auteurId: string) {
|
||||||
ficheOpen.value = false
|
ficheOpen.value = false
|
||||||
const auteur = props.penseesData?.auteurs.find(a => a.id === auteurId)
|
const auteur = penseesData.value?.auteurs.find(a => a.id === auteurId)
|
||||||
chatbotAuteur.value = auteur?.nom ?? null
|
chatbotAuteur.value = auteur?.nom ?? null
|
||||||
if (layoutMode.value === 'carte-full') {
|
if (contentView.value !== 'carte') showCarte()
|
||||||
setLayoutMode('split')
|
else if (layoutMode.value === 'carte-full') layoutMode.value = 'split'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -398,7 +452,6 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Conteneur des slots carte + toggle + chatbot */
|
|
||||||
.layout-container {
|
.layout-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -441,7 +494,7 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Barre de toggle --- */
|
/* --- Barre de toggle (entre carte et chatbot) --- */
|
||||||
.layout-toggle-bar {
|
.layout-toggle-bar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -481,26 +534,25 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
border-color: var(--nav-primary);
|
border-color: var(--nav-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Toggle layer PDF FRACAS --- */
|
/* --- Contrôle fusionné carte des pensées --- */
|
||||||
.layer-toggle {
|
.carte-pensees-ctrl {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 0;
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 0.75rem;
|
overflow: hidden;
|
||||||
font-weight: 500;
|
border: 1px solid rgba(180, 170, 160, 0.3);
|
||||||
cursor: pointer;
|
|
||||||
background: var(--nav-bg-alt);
|
|
||||||
color: var(--nav-text-muted);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
user-select: none;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-toggle input[type="checkbox"] {
|
.fracas-check {
|
||||||
margin: 0;
|
margin: 0 2px 0 7px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
accent-color: var(--nav-primary, #3b6ea5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carte-pensees-btn {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity-slider {
|
.opacity-slider {
|
||||||
@@ -509,7 +561,7 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
accent-color: var(--nav-primary, #3b6ea5);
|
accent-color: var(--nav-primary, #3b6ea5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Poignee draggable entre carte et chatbot --- */
|
/* --- Poignee draggable --- */
|
||||||
.split-handle {
|
.split-handle {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@@ -541,11 +593,8 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Masquer la poignee sur mobile (ratio fixe) */
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.split-handle {
|
.split-handle { display: none; }
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Slot chatbot --- */
|
/* --- Slot chatbot --- */
|
||||||
@@ -583,19 +632,16 @@ function onInterrogerRag(auteurId: string) {
|
|||||||
.modal-enter-from { opacity: 0; transform: translate(-50%,-48%) scale(0.94); }
|
.modal-enter-from { opacity: 0; transform: translate(-50%,-48%) scale(0.94); }
|
||||||
.modal-leave-to { opacity: 0; transform: translate(-50%,-48%) scale(0.96); }
|
.modal-leave-to { opacity: 0; transform: translate(-50%,-48%) scale(0.96); }
|
||||||
|
|
||||||
/* --- Responsive mobile (<768px) --- */
|
/* --- Mobile --- */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.carte-split {
|
.carte-split {
|
||||||
flex: 0 0 60vh;
|
flex: 0 0 60vh;
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatbot-split {
|
.chatbot-split {
|
||||||
flex: 0 0 calc(40vh - 38px);
|
flex: 0 0 calc(40vh - 38px);
|
||||||
height: calc(40vh - 38px);
|
height: calc(40vh - 38px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-btn span,
|
|
||||||
.toggle-btn {
|
.toggle-btn {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
padding: 3px 7px;
|
padding: 3px 7px;
|
||||||
|
|||||||
@@ -5,24 +5,17 @@
|
|||||||
:class="['subtab-btn', { active: tab === 'visuel' }]"
|
:class="['subtab-btn', { active: tab === 'visuel' }]"
|
||||||
@click="tab = 'visuel'"
|
@click="tab = 'visuel'"
|
||||||
>
|
>
|
||||||
🌳 RAG visuel
|
📚 bibliothèque des pensées écologiques
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:class="['subtab-btn', { active: tab === 'backend' }]"
|
|
||||||
@click="tab = 'backend'"
|
|
||||||
>
|
|
||||||
⚙ LightRAG backend
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="['subtab-btn', { active: tab === 'projets' }]"
|
:class="['subtab-btn', { active: tab === 'projets' }]"
|
||||||
@click="tab = 'projets'"
|
@click="tab = 'projets'"
|
||||||
>
|
>
|
||||||
📚 Projets
|
📐 Projets
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<MediaTabVisuel v-if="tab === 'visuel'" />
|
<MediaTabVisuel v-if="tab === 'visuel'" />
|
||||||
<MediaTabBackend v-else-if="tab === 'backend'" />
|
|
||||||
<MediaTabProjets v-else-if="tab === 'projets'" />
|
<MediaTabProjets v-else-if="tab === 'projets'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,9 +24,9 @@
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const tab = ref<'visuel' | 'backend' | 'projets'>(
|
const tab = ref<'visuel' | 'projets'>(
|
||||||
(['visuel', 'backend', 'projets'].includes(route.query.tab as string)
|
(['visuel', 'projets'].includes(route.query.tab as string)
|
||||||
? route.query.tab as 'visuel' | 'backend' | 'projets'
|
? route.query.tab as 'visuel' | 'projets'
|
||||||
: 'visuel')
|
: 'visuel')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,3 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col items-center justify-center h-full gap-6" style="background: var(--nav-bg);">
|
|
||||||
<div class="text-center max-w-md px-6">
|
|
||||||
<div
|
|
||||||
class="inline-flex items-center justify-center w-14 h-14 rounded-2xl mb-5"
|
|
||||||
style="background: var(--nav-bg-alt);"
|
|
||||||
>
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--nav-text-muted);">
|
|
||||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
|
||||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
|
||||||
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h1 class="text-2xl font-bold mb-3" style="color: var(--nav-text);">RAG — Retrieval Augmented Generation</h1>
|
|
||||||
<p class="text-sm leading-relaxed mb-6" style="color: var(--nav-text-muted);">
|
|
||||||
Une base de connaissances interrogeable par IA — textes, rapports, manifestes et ressources documentaires sur l'architecture d'écologie politique.
|
|
||||||
</p>
|
|
||||||
<p class="text-xs font-semibold uppercase tracking-widest mb-6" style="color: var(--nav-text-muted); opacity: 0.6;">
|
|
||||||
Bientôt disponible
|
|
||||||
</p>
|
|
||||||
<NuxtLink
|
|
||||||
to="/"
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-semibold transition-all hover:opacity-80"
|
|
||||||
style="background: var(--nav-primary); color: var(--nav-text-on-primary);"
|
|
||||||
>
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
|
|
||||||
<line x1="19" y1="12" x2="5" y2="12"/>
|
|
||||||
<polyline points="12 19 5 12 12 5"/>
|
|
||||||
</svg>
|
|
||||||
Retour à l'écosystème
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
useHead({ title: 'RAG — AEP (bientôt disponible)' })
|
navigateTo('/media', { redirectCode: 301 })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
10
server/routes/data/auteurs-pensees.json.get.ts
Normal file
10
server/routes/data/auteurs-pensees.json.get.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
export default defineEventHandler((event) => {
|
||||||
|
const path = join(process.cwd(), 'public', 'data', 'auteurs-pensees.json')
|
||||||
|
const raw = readFileSync(path, 'utf-8')
|
||||||
|
setResponseHeader(event, 'content-type', 'application/json; charset=utf-8')
|
||||||
|
setResponseHeader(event, 'cache-control', 'public, max-age=300')
|
||||||
|
return raw
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user