import { useState, useEffect, ReactNode } from 'react' import ReactMarkdown, { Components } from 'react-markdown' import { TierComparatif, TierSingle } from './TierDashboard' import { AgentCatalog } from './AgentDashboard' interface DocFile { name: string label: string path: string group: string } const API_BASE = import.meta.env.VITE_BRAIN_API ?? '' // Fallback statique β€” utilisΓ© si brain-engine n'est pas dispo const STATIC_DOCS: DocFile[] = [ { name: 'getting-started', label: 'Demarrer', path: import.meta.env.BASE_URL + 'docs/getting-started.md', group: 'Guides' }, { name: 'architecture', label: 'Architecture', path: import.meta.env.BASE_URL + 'docs/architecture.md', group: 'Guides' }, { name: 'sessions', label: 'Sessions', path: import.meta.env.BASE_URL + 'docs/sessions.md', group: 'Guides' }, { name: 'workflows', label: 'Workflows', path: import.meta.env.BASE_URL + 'docs/workflows.md', group: 'Guides' }, { name: 'satellites', label: 'Satellites', path: import.meta.env.BASE_URL + 'docs/satellites.md', group: 'Guides' }, { name: 'brain-engine-guide', label: 'Brain-engine', path: import.meta.env.BASE_URL + 'docs/brain-engine-guide.md', group: 'Guides' }, { name: 'agents', label: "Vue d'ensemble", path: import.meta.env.BASE_URL + 'docs/agents.md', group: 'Agents' }, { name: 'agents-code', label: 'Code & Qualite', path: import.meta.env.BASE_URL + 'docs/agents-code.md', group: 'Agents' }, { name: 'agents-infra', label: 'Infra & Deploy', path: import.meta.env.BASE_URL + 'docs/agents-infra.md', group: 'Agents' }, { name: 'agents-brain', label: 'Brain & Systeme', path: import.meta.env.BASE_URL + 'docs/agents-brain.md', group: 'Agents' }, { name: 'vue-tiers', label: 'Comparatif', path: import.meta.env.BASE_URL + 'docs/vue-tiers.md', group: 'Vues' }, { name: 'vue-free', label: '🟒 free', path: import.meta.env.BASE_URL + 'docs/vue-free.md', group: 'Vues' }, { name: 'vue-featured', label: 'πŸ”΅ featured', path: import.meta.env.BASE_URL + 'docs/vue-featured.md', group: 'Vues' }, { name: 'vue-pro', label: '🟠 pro', path: import.meta.env.BASE_URL + 'docs/vue-pro.md', group: 'Vues' }, { name: 'vue-full', label: '🟣 full', path: import.meta.env.BASE_URL + 'docs/vue-full.md', group: 'Vues' }, ] // Order for consistent sidebar display const GROUP_ORDER = ['Guides', 'Agents', 'Vues'] // Detect tier markers in blockquote content and apply CSS class const TIER_MARKERS: Record = { '\u{1F7E2}': 'tier-free', // 🟒 '\u{1F535}': 'tier-featured', // πŸ”΅ '\u{1F7E0}': 'tier-pro', // 🟠 '\u{1F7E3}': 'tier-full', // 🟣 } function extractText(children: ReactNode): string { if (typeof children === 'string') return children if (Array.isArray(children)) return children.map(extractText).join('') if (children && typeof children === 'object' && 'props' in children) { return extractText((children as { props: { children?: ReactNode } }).props.children) } return '' } const mdComponents: Components = { blockquote({ children }) { const text = extractText(children) let tierClass = '' for (const [marker, cls] of Object.entries(TIER_MARKERS)) { if (text.includes(marker)) { tierClass = cls break } } return
{children}
}, } export default function DocsView() { const [docs, setDocs] = useState(STATIC_DOCS) const [activeDoc, setActiveDoc] = useState('getting-started') const [content, setContent] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [liveMode, setLiveMode] = useState(false) // Fetch docs list from brain-engine API β€” fallback to static useEffect(() => { fetch(`${API_BASE}/docs`) .then((res) => { if (!res.ok) throw new Error('API indisponible') return res.json() }) .then((data) => { if (data.docs && data.docs.length > 0) { setDocs(data.docs) setLiveMode(true) } }) .catch(() => { // Silencieux β€” on reste sur les docs statiques }) }, []) // Fetch active doc content useEffect(() => { setLoading(true) setError(null) if (liveMode) { // Mode live β€” fetch depuis l'API brain-engine fetch(`${API_BASE}/docs/${activeDoc}.md`) .then((res) => { if (!res.ok) throw new Error(`${res.status}`) return res.json() }) .then((data) => { setContent(data.content) setLoading(false) }) .catch((err) => { setError(`Impossible de charger ${activeDoc}: ${err.message}`) setLoading(false) }) } else { // Mode statique β€” fetch depuis les fichiers dist/ const doc = docs.find((d) => d.name === activeDoc) if (!doc) { setLoading(false); return } fetch(doc.path) .then((res) => { if (!res.ok) throw new Error(`${res.status}`) return res.text() }) .then((text) => { const stripped = text.replace(/^---[\s\S]*?---\n*/, '') setContent(stripped) setLoading(false) }) .catch((err) => { setError(`Impossible de charger ${doc.path}: ${err.message}`) setLoading(false) }) } }, [activeDoc, liveMode, docs]) // Group docs const groups = docs.reduce>((acc, doc) => { const g = doc.group || 'Autres' if (!acc[g]) acc[g] = [] acc[g].push(doc) return acc }, {}) // Sort groups by defined order const sortedGroups = GROUP_ORDER .filter((g) => groups[g]) .map((g) => [g, groups[g]] as [string, DocFile[]]) // Add any groups not in ORDER Object.entries(groups).forEach(([g, d]) => { if (!GROUP_ORDER.includes(g)) sortedGroups.push([g, d]) }) return (
{/* Sidebar docs */}
Documentation {liveMode && ( live )}
{/* Content */}
{(() => { // Mode live + page tier β†’ composant React dynamique if (liveMode && activeDoc === 'vue-tiers') { return
} if (liveMode && activeDoc.startsWith('vue-')) { const tierName = activeDoc.replace('vue-', '') return
} if (liveMode && activeDoc === 'agents') { return
} // Mode standard β€” markdown return ( <> {loading && (
Chargement...
)} {error && (
{error}
)} {!loading && !error && (
{content}
)} ) })()}
) }