From 5967a5af570827d361d2df2c0c4da5461a8d3553 Mon Sep 17 00:00:00 2001 From: Jules Neny Date: Thu, 7 May 2026 02:38:47 +0200 Subject: [PATCH] =?UTF-8?q?fix(chatbot):=20s=C3=A9paration=20d=C3=A9finiti?= =?UTF-8?q?ve=20Carte1/Carte2=20+=20markdown=20inline=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatbotReseaux.vue : composant standalone, endpoint hardcodé /api/chatbot-reseaux, onboarding 120 réseaux AEP, aucun prop partagé avec ChatbotSheet - ChatbotSheet.vue : restauré état simple, /api/chatbot hardcodé, onboarding Carte 1 - agences.vue : ChatbotReseaux au lieu de ChatbotSheet - useMarkdown.ts : inline styles (font-weight:700 etc) — zéro dépendance CSS, fonctionne dans tout contexte Vue scoped/v-html sans exception Co-Authored-By: Claude Sonnet 4.6 --- components/ChatbotReseaux.vue | 208 ++++++++++++++++++++++++++++++++++ components/ChatbotSheet.vue | 38 ++----- composables/useMarkdown.ts | 49 ++++---- pages/agences.vue | 6 +- 4 files changed, 244 insertions(+), 57 deletions(-) create mode 100644 components/ChatbotReseaux.vue diff --git a/components/ChatbotReseaux.vue b/components/ChatbotReseaux.vue new file mode 100644 index 0000000..9a5fb9a --- /dev/null +++ b/components/ChatbotReseaux.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/components/ChatbotSheet.vue b/components/ChatbotSheet.vue index 344ec58..891687d 100644 --- a/components/ChatbotSheet.vue +++ b/components/ChatbotSheet.vue @@ -61,9 +61,7 @@ - - {{ props.title ?? (activeEndpoint === '/api/chatbot-reseaux' ? 'Réseaux AEP' : 'Chatbot') }} - + Chatbot @@ -71,19 +69,14 @@
- - +

Ce chatbot fonctionne sur un serveur européen souverain (Mistral FR, zéro rétention), conçu sobre en énergie.

+

Pour m'aider à te répondre efficacement, formule ta requête ainsi :

+
    +
  • • Besoin : [ce que tu cherches]
  • +
  • • Thématique : [juridique / technique / économique / ...]
  • +
  • • Lieu : [région ou ville]
  • +
+

Exemple : "Je suis salarié d'agence, litige avec mon employeur, besoin conseil juridique droit du travail, Île-de-France."

@@ -170,14 +163,6 @@ import { useMarkdown } from '~/composables/useMarkdown' const { render: renderMd } = useMarkdown() -// Détection double-sécurité : prop endpoint > route-based fallback -const route = useRoute() -const activeEndpoint = computed(() => { - if (props.endpoint) return props.endpoint - if (route.path.startsWith('/agences')) return '/api/chatbot-reseaux' - return '/api/chatbot' -}) - interface FicheReco { id: number | string nom: string @@ -192,9 +177,6 @@ interface ChatMessage { const props = defineProps<{ modelValue: boolean - endpoint?: string // défaut: /api/chatbot (Carte 1 NocoDB) - title?: string // label dans le header - onboarding?: string // message d'accueil personnalisé (markdown ok) }>() const emit = defineEmits<{ @@ -242,7 +224,7 @@ async function sendMessage() { const res = await $fetch<{ reponse_texte: string fiches_recommandees: { id: number | string; nom: string; explication: string }[] - }>(activeEndpoint.value, { + }>('/api/chatbot', { method: 'POST', body: { question }, }) diff --git a/composables/useMarkdown.ts b/composables/useMarkdown.ts index 640bf0c..6624a4c 100644 --- a/composables/useMarkdown.ts +++ b/composables/useMarkdown.ts @@ -1,34 +1,35 @@ /** - * Convertit du Markdown Mistral en HTML sécurisé. - * Gère : **bold**, *italic*, ## titres, listes - et •, retours à la ligne. - * Pas de dépendance externe. + * Convertit du Markdown Mistral en HTML avec inline styles. + * Inline styles = zéro dépendance CSS, fonctionne dans tout contexte Vue (scoped, v-html, etc.) */ export function useMarkdown() { + const S = { + p: 'style="margin:0 0 0.45em;line-height:1.6;"', + strong: 'style="font-weight:700;"', + em: 'style="font-style:italic;"', + h2: 'style="font-weight:700;display:block;margin-bottom:0.2em;"', + h3: 'style="font-weight:700;display:block;font-size:0.95em;margin-bottom:0.15em;"', + ul: 'style="margin:0.3em 0 0.3em 1.2em;padding:0;list-style:disc;"', + li: 'style="margin-bottom:0.15em;"', + a: 'style="text-decoration:underline;opacity:0.85;"', + } + function render(text: string): string { if (!text) return '' let html = text - // Échappement XSS de base - .replace(/&/g, '&') - .replace(//g, '>') - // Titres ## et ### - .replace(/^### (.+)$/gm, '$1') - .replace(/^## (.+)$/gm, '$1') - .replace(/^# (.+)$/gm, '$1') - // Bold et italic - .replace(/\*\*(.+?)\*\*/g, '$1') - .replace(/\*(.+?)\*/g, '$1') - // Listes (- item ou • item en début de ligne) - .replace(/^[-•]\s+(.+)$/gm, '
  • $1
  • ') - // Liens [texte](url) - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') - // Grouper les
  • consécutifs dans un
      - html = html.replace(/(
    • .*<\/li>\n?)+/g, match => `
        ${match}
      `) - // Paragraphes : double saut de ligne → séparateur - html = html.replace(/\n{2,}/g, '

      ') - // Saut de ligne simple →
      + .replace(/&/g, '&').replace(//g, '>') + .replace(/^### (.+)$/gm, `$1`) + .replace(/^## (.+)$/gm, `$1`) + .replace(/^# (.+)$/gm, `$1`) + .replace(/\*\*(.+?)\*\*/g, `$1`) + .replace(/\*(.+?)\*/g, `$1`) + .replace(/^[-•]\s+(.+)$/gm, `

    • $1
    • `) + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, `$1`) + + html = html.replace(/(]*>.*<\/li>\n?)+/g, m => `
        ${m}
      `) + html = html.replace(/\n{2,}/g, `

      `) html = html.replace(/\n/g, '
      ') - return `

      ${html}

      ` + return `

      ${html}

      ` } return { render } diff --git a/pages/agences.vue b/pages/agences.vue index 8acb3f5..27e84b8 100644 --- a/pages/agences.vue +++ b/pages/agences.vue @@ -374,13 +374,9 @@ -