feat(ux): markdown chatbots + header Jobs centré + cible archi indépendants
- composables/useMarkdown.ts : renderer MD léger (bold/italic/listes/titres) - ChatbotSheet.vue + trouver-du-taf.vue : v-html renderMd() sur messages bot - assets/css/main.css : styles .md-content globaux pour tous les chatbots - taff-header centré + phrase cible 'architectes indépendants, 70% de la profession' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,3 +108,16 @@
|
||||
.dark .leaflet-popup-tip {
|
||||
background: var(--nav-surface);
|
||||
}
|
||||
|
||||
/* ── Rendu Markdown chatbot (useMarkdown composable) ────────────────────── */
|
||||
.md-content { font-size: inherit; line-height: 1.6; }
|
||||
.md-content p { margin: 0 0 0.5em; }
|
||||
.md-content p:last-child { margin-bottom: 0; }
|
||||
.md-content strong, .md-h1, .md-h2, .md-h3 { font-weight: 700; }
|
||||
.md-h2 { font-size: 0.9375em; display: block; margin-bottom: 0.25em; }
|
||||
.md-h3 { font-size: 0.875em; display: block; }
|
||||
.md-content em { font-style: italic; }
|
||||
.md-list { margin: 0.375em 0 0.375em 1em; padding: 0; list-style: disc; }
|
||||
.md-list li { margin-bottom: 0.2em; }
|
||||
.md-link { text-decoration: underline; opacity: 0.85; }
|
||||
.md-link:hover { opacity: 1; }
|
||||
|
||||
@@ -92,7 +92,7 @@ employeur, besoin conseil juridique droit du travail,
|
||||
|
||||
<!-- Message assistant -->
|
||||
<div v-else class="assistant-bubble">
|
||||
<p>{{ msg.content }}</p>
|
||||
<div class="md-content" v-html="renderMd(msg.content)" />
|
||||
|
||||
<!-- Fiches recommandées -->
|
||||
<div v-if="msg.fiches && msg.fiches.length > 0" class="fiches-list">
|
||||
@@ -164,6 +164,8 @@ employeur, besoin conseil juridique droit du travail,
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { render: renderMd } = useMarkdown()
|
||||
|
||||
interface FicheReco {
|
||||
id: number | string
|
||||
nom: string
|
||||
|
||||
35
composables/useMarkdown.ts
Normal file
35
composables/useMarkdown.ts
Normal file
@@ -0,0 +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.
|
||||
*/
|
||||
export function useMarkdown() {
|
||||
function render(text: string): string {
|
||||
if (!text) return ''
|
||||
let html = text
|
||||
// Échappement XSS de base
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// Titres ## et ###
|
||||
.replace(/^### (.+)$/gm, '<strong class="md-h3">$1</strong>')
|
||||
.replace(/^## (.+)$/gm, '<strong class="md-h2">$1</strong>')
|
||||
.replace(/^# (.+)$/gm, '<strong class="md-h1">$1</strong>')
|
||||
// Bold et italic
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
// Listes (- item ou • item en début de ligne)
|
||||
.replace(/^[-•]\s+(.+)$/gm, '<li>$1</li>')
|
||||
// Liens [texte](url)
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" class="md-link">$1</a>')
|
||||
// Grouper les <li> consécutifs dans un <ul>
|
||||
html = html.replace(/(<li>.*<\/li>\n?)+/g, match => `<ul class="md-list">${match}</ul>`)
|
||||
// Paragraphes : double saut de ligne → séparateur
|
||||
html = html.replace(/\n{2,}/g, '</p><p>')
|
||||
// Saut de ligne simple → <br>
|
||||
html = html.replace(/\n/g, '<br>')
|
||||
return `<p>${html}</p>`
|
||||
}
|
||||
|
||||
return { render }
|
||||
}
|
||||
@@ -6,9 +6,12 @@
|
||||
<div class="taff-header-inner">
|
||||
<h1 class="taff-title">Trouver du taf en archi</h1>
|
||||
<p class="taff-subtitle">
|
||||
Annuaire critique des plateformes de mise en relation archi ↔ particulier.
|
||||
Évaluées sur 5 axes éthiques — rémunération, transparence, pratiques pro, écologie, qualité du matching.
|
||||
Cible : archi freelance indépendant en France.
|
||||
Annuaire critique des plateformes de mise en relation archi ↔ particulier,
|
||||
évaluées sur 5 axes éthiques — rémunération, transparence, pratiques pro, écologie, qualité du matching.
|
||||
</p>
|
||||
<p class="taff-cible">
|
||||
Cette carte s'adresse aux <strong>architectes indépendants</strong> —
|
||||
70 % de la profession et sa part la plus précaire économiquement.
|
||||
</p>
|
||||
<div class="taff-stats">
|
||||
<span class="taff-stat" style="color: #3d5534;">
|
||||
@@ -186,7 +189,8 @@
|
||||
<!-- Messages de la conversation -->
|
||||
<template v-for="(msg, i) in chatMessages" :key="i">
|
||||
<div :class="['taff-msg', msg.role === 'user' ? 'taff-msg--user' : 'taff-msg--bot']">
|
||||
<p>{{ msg.content }}</p>
|
||||
<div v-if="msg.role === 'bot'" class="md-content" v-html="renderMd(msg.content)" />
|
||||
<span v-else>{{ msg.content }}</span>
|
||||
</div>
|
||||
<!-- Plateformes recommandées -->
|
||||
<div v-if="msg.role === 'bot' && msg.recommandations?.length" class="taff-chat-recos">
|
||||
@@ -499,6 +503,8 @@ interface ChatMessage {
|
||||
recommandations?: { id: string; nom: string; raison: string }[]
|
||||
}
|
||||
|
||||
const { render: renderMd } = useMarkdown()
|
||||
|
||||
const chatOpen = ref(false)
|
||||
const chatInput = ref('')
|
||||
const chatLoading = ref(false)
|
||||
@@ -562,11 +568,13 @@ const parsedDescription = computed(() => {
|
||||
<style scoped>
|
||||
.taff-page { max-width: 1280px; margin: 0 auto; padding-bottom: 3rem; }
|
||||
|
||||
.taff-header { padding: 2.5rem 1.5rem 1.5rem; border-bottom: 1px solid var(--nav-bg-alt); }
|
||||
.taff-header-inner { max-width: 680px; }
|
||||
.taff-header { padding: 2.5rem 1.5rem 1.5rem; border-bottom: 1px solid var(--nav-bg-alt); text-align: center; }
|
||||
.taff-header-inner { max-width: 680px; margin: 0 auto; }
|
||||
.taff-title { font-size: 1.875rem; font-weight: 800; color: var(--nav-text); margin-bottom: 0.5rem; letter-spacing: -0.02em; }
|
||||
.taff-subtitle { font-size: 0.9375rem; color: var(--nav-text-muted); line-height: 1.6; margin-bottom: 1rem; }
|
||||
.taff-stats { display: flex; gap: 1.25rem; flex-wrap: wrap; }
|
||||
.taff-subtitle { font-size: 0.9375rem; color: var(--nav-text-muted); line-height: 1.6; margin-bottom: 0.625rem; }
|
||||
.taff-cible { font-size: 0.875rem; color: var(--nav-text-muted); line-height: 1.55; margin-bottom: 1rem; font-style: italic; }
|
||||
.taff-cible strong { color: var(--nav-text); font-style: normal; }
|
||||
.taff-stats { display: flex; gap: 1.25rem; flex-wrap: wrap; justify-content: center; }
|
||||
.taff-stat { display: flex; align-items: center; gap: 0.375rem; font-size: 0.8125rem; font-weight: 600; }
|
||||
.taff-stat-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user