Conception technique pour ajouter un chat IA de contact à un site Astro
Sommaire
- Structure générale
- Garder le contrat d’API petit
- Contrôler la validation et le modèle côté serveur
- Rendre le contexte du site explicite
- Écrire des règles dans le prompt
- Séparer les parcours de contact
- Préserver les URLs par locale
- Ajouter Origin check et rate limit
- Rendre les liens Markdown avec une liste d’autorisation
- Tester local, preview et production
- Indicateurs d’exploitation
- Périmètre séparé
- Résumé
Ajouter un chat IA à un site web est simple. Le vrai sujet est l’exploitation : jusqu’où l’IA peut répondre, où elle doit guider le visiteur, quelles URLs elle peut afficher et comment maîtriser le coût API.
Acecore a ajouté un chat IA de contact à un site statique Astro + Cloudflare Pages. L’implémentation principale se trouve dans le PR qui ajoute l’IA de contact et le flux de traduction limité au CMS. Le rendu sécurisé des liens Markdown a ensuite été ajusté dans un autre PR. Les détails sont séparés dans Rendre en sécurité les liens Markdown dans les réponses de chat IA.
Cet article présente le design comme un modèle réutilisable pour d’autres sites statiques. Hors Astro, le principe reste le même : séparer le widget client, la frontière API, la construction du prompt et le renderer.
Structure générale
| Couche | Responsabilité |
|---|---|
| Chat widget | UI, saisie, locale courant, historique minimal et rendu Markdown |
/api/ai-contact | Validation, Origin check, rate limit, prompt et appel OpenAI |
| OpenAI Responses API | Générer une réponse depuis le contexte public et la conversation |
Le navigateur ne doit pas appeler OpenAI directement. Placer l’appel derrière un endpoint évite l’exposition de clé, permet de changer prompt et contexte côté serveur, et centralise limites et erreurs.
Avec Astro + Cloudflare Pages, cette frontière est une Pages Function /api/ai-contact. Dans Next.js, ce serait un Route Handler ; dans Hono ou Express, une route API.
Garder le contrat d’API petit
type ContactAiRequest = {
message: string
locale: 'ja' | 'en' | 'zh-cn' | 'es' | 'pt' | 'fr' | 'ko' | 'de' | 'ru'
history?: Array<{
role: 'user' | 'assistant'
content: string
}>
}
type ContactAiResponse = {
answer: string
}
Nom, e-mail, téléphone, entreprise ou champs détaillés du formulaire n’ont pas besoin de passer par le chat. Le chat aide à choisir un service et un canal de contact, il ne collecte pas les données personnelles.
L’historique doit aussi être limité aux derniers échanges, avec une taille maximale par message. Cela réduit le prompt et le coût.
Contrôler la validation et le modèle côté serveur
export async function onRequestPost({ request, env }: PagesFunction<Env>) {
assertSameOrigin(request)
assertRateLimit(request)
const body = await request.json()
const message = validateMessage(body.message)
const locale = validateLocale(body.locale)
const history = trimHistory(body.history)
const prompt = buildContactPrompt({
locale,
message,
history,
siteContext: buildPublicSiteContext(locale),
})
const answer = await callOpenAIResponsesApi({
apiKey: env.OPENAI_API_KEY,
model: env.OPENAI_MODEL,
prompt,
})
return Response.json({ answer })
}
L’essentiel est de réduire et valider l’entrée avant l’appel IA. Les longs textes, historiques illimités et appels externes répétés peuvent rendre l’exploitation instable.
OPENAI_MODEL doit être une variable d’environnement, et OPENAI_API_KEY doit rester côté serveur. Pour la distribution et CSP, voir la sécurité Cloudflare Pages.
Rendre le contexte du site explicite
Pour un site de cette taille, il n’est pas nécessaire de commencer par une base vectorielle. Un contexte structuré avec informations publiques suffit souvent.
Ce contexte peut inclure : résumé de l’entreprise, services, exemples de demandes, URLs, FAQ, règles de contact, zones où l’IA ne doit pas affirmer, et URLs internes par locale.
function buildPublicSiteContext(locale: Locale) {
return {
services: [
{
name: 'Web production',
summary: 'Corporate sites, recruiting sites, and landing pages',
url: localizePath('/services/web-production/', locale),
},
{
name: 'Business systems',
summary: 'Reservation, inventory, and customer management systems',
url: localizePath('/services/business-system/', locale),
},
],
contact: {
form: localizePath('/contact/', locale),
line: 'https://lin.ee/...',
emailPolicy:
'Show email only when the form cannot be used or follow-up is needed',
phonePolicy: 'Show phone only for urgent confirmation',
},
}
}
Le but n’est pas de laisser le modèle répondre depuis son savoir général, mais de définir ce que ce site peut dire. Quand le site grandit, cette couche peut évoluer vers Pagefind, CMS JSON, D1, Vectorize ou une autre recherche.
Écrire des règles dans le prompt
You are the contact guidance AI for this website.
Answer only from public site information.
Rules:
- Do not make firm statements about pricing, contracts, schedules, or guarantees
- Send formal consultations and estimates to the contact form
- Also suggest LINE for short questions and school-related inquiries
- Show email and phone only when the user asks for direct contact
- Use URLs that match the current locale
- If unsure, do not guess; guide the user to the form
L’échec classique est une IA trop serviable qui promet trop. Les prix, délais et garanties doivent recevoir une orientation générale puis être renvoyés vers le formulaire.
Séparer les parcours de contact
| Parcours | Rôle |
|---|---|
| FAQ | Répondre aux questions fréquentes dans la page |
| Chat IA | Orienter vers services, canaux et pages liées |
| LINE | Questions courtes, sujets d’école et vérifications légères |
| Formulaire | Devis, production, partenariats et recrutement |
| Contact direct | Complément après formulaire ou confirmation urgente |
L’IA relie des contenus généraux comme l’article de présentation des services aux entrées concrètes de la page contact. Le modèle s’applique aux sites B2B, agences, écoles et supports SaaS.
Préserver les URLs par locale
Dans un site multilingue, la langue de réponse ne suffit pas. Les URLs doivent aussi correspondre au locale.
function localizePath(path: string, locale: Locale) {
if (locale === 'ja') return path
return `/${locale}${path}`
}
La génération côté serveur est plus fiable qu’une simple instruction de prompt. La base de traduction est décrite dans Comment gérer un blog multilingue avec Sveltia CMS.
Ajouter Origin check et rate limit
function assertSameOrigin(request: Request) {
const origin = request.headers.get('Origin')
if (!origin) return
const requestUrl = new URL(request.url)
const originUrl = new URL(origin)
if (originUrl.host !== requestUrl.host) {
throw new Response('Forbidden', { status: 403 })
}
}
Un rate limit par IP est un premier frein. Avec Cloudflare, utilisez CF-Connecting-IP, X-Forwarded-For ou CF-Ray. Pour plus de trafic, préférez Cloudflare WAF, Turnstile, KV, D1 ou Durable Objects. L’exploitation CMS côté contenu est décrite dans le Guide d’installation de Sveltia CMS ; la protection anti-bot des formulaires et commentaires est une autre couche.
Rendre les liens Markdown avec une liste d’autorisation
Autorisez seulement paragraphes, listes, gras, code inline et liens Markdown. Limitez ensuite les destinations aux chemins internes, à l’origin courant, à https://acecore.net, au LINE officiel et aux mailto: ou tel: nécessaires.
function sanitizeHref(rawHref: string, currentOrigin: string) {
const href = rawHref.trim()
if (href.startsWith('/')) return href
if (href.startsWith(`${currentOrigin}/`)) return href
if (href.startsWith('https://acecore.net/')) return href
if (href.startsWith('https://lin.ee/')) return href
if (href === 'mailto:[email protected]') return href
if (href === 'tel:05088902788') return href
return null
}
Le trim() est important, car l’IA peut produire [Services]( /services/ ). Un renderer petit et strict est plus simple à maintenir qu’une implémentation Markdown complète.
Tester local, preview et production
Astro dev ou preview n’est pas identique à Cloudflare Pages Functions. Sans OPENAI_API_KEY, le local doit tester fallback et erreurs UI.
En preview ou production, vérifiez POST sur /api/ai-contact, variables OPENAI_API_KEY et OPENAI_MODEL, rejet d’un autre Origin, limites d’entrée, réponses dans le bon locale, URLs localisées, absence d’affirmations sur devis ou contrat, et liens Markdown seulement si l’URL est autorisée.
Testez aussi longues entrées, questions inattendues, pages anglaises, demandes de contact direct et questions de prix.
Indicateurs d’exploitation
Suivez taux d’erreur API, déclenchements de rate limit, messages moyens par demande, passages vers formulaire et LINE, cas renvoyés vers le formulaire et usage par locale.
Si vous stockez les conversations, définissez d’abord les règles de confidentialité. Un début plus sûr consiste à ne conserver que les événements et erreurs, sans texte de message.
Périmètre séparé
Cet article couvre seulement le design technique du chat IA. Le parcours transmettant le contexte d’une page service vers le formulaire est également implémenté, et est organisé dans Conception technique pour passer le contexte d’un CTA service vers le formulaire de contact.
- Chat IA : clarifier les hésitations par conversation et guider en sécurité
- CTA service : transmettre au formulaire le contexte lu par le visiteur
Les séparer rend les articles plus lisibles et plus faciles à relier.
Résumé
Pour ajouter un chat IA à un site statique, concevez d’abord la frontière API et le contrôle des réponses.
Les décisions clés : appeler OpenAI depuis Cloudflare Pages Function, garder l’entrée petite, construire contexte et URLs côté serveur, écrire les limites dans le prompt, séparer formulaire, LINE et contact direct, ajouter Origin check et rate limit, puis rendre les liens Markdown avec trim() et liste d’autorisation.
Un site statique peut avoir un chat de contact IA utile. L’objectif n’est pas de mettre l’IA en avant, mais d’aider le visiteur à choisir l’action suivante en sécurité.
Architecture de référence
Widget
L'interface de chat Astro envoie seulement la question, le locale courant et un historique minimal.
Function
La Cloudflare Pages Function valide l'entrée, vérifie Origin, applique le rate limit et construit le prompt.
Model
OpenAI Responses API reçoit le contexte public du site et l'état de conversation.
Renderer
Le client rend uniquement le Markdown autorisé et dirige vers des liens internes ou contacts approuvés.
Tout mélanger
- Appeler l'API IA directement depuis le navigateur
- Mélanger contexte du site, clé API, UI et rendu de liens
- Laisser l'IA affirmer prix, contrats ou délais
- Injecter Markdown et URLs directement en HTML
Responsabilités séparées
- Garder clés API et appels modèle côté serveur
- Gérer les informations publiques du site comme contexte explicite
- Contrôler portée de réponse et parcours de contact dans le prompt
- Rendre Markdown et URLs par listes d'autorisation
- Terminé : Définir le chat comme aide d'orientation, pas comme remplacement complet du formulaire
- Terminé : Créer une frontière API côté serveur et ne jamais exposer la clé au navigateur
- Terminé : Limiter les réponses aux informations publiques du site
- Terminé : Définir ce que l'IA ne doit pas affirmer, comme prix, contrats, délais et garanties
- Terminé : Définir les rôles du formulaire, de LINE, de l'e-mail et du téléphone
- Terminé : Générer des URLs par locale pour préserver la navigation multilingue
- Terminé : Ajouter Origin check, limites de longueur, limites d'historique et rate limiting
- Terminé : Appliquer trim aux URLs Markdown avant validation
Faut-il RAG ou une base vectorielle pour créer ce chat ?
La clé OpenAI est-elle exposée au navigateur ?
L'IA peut-elle afficher n'importe quel lien ?
Commentaires
Gui
PDG d'Acecore. Pilote les systèmes métier, le web, les bases de données et l'infrastructure, la qualité et l'adoption de l'IA, du cadrage des enjeux métier à la conception, au déploiement et à l'amélioration continue. S'appuie sur une capacité pratique en C#/.NET tout en couvrant aussi PHP/JavaScript, SQL Server/PostgreSQL/MySQL et Linux/Windows Server, afin de concevoir les besoins, les choix technologiques, les standards de qualité et les opérations de développement basées sur GitHub comme un flux cohérent. Intègre l'IA générative aux processus de développement, de vérification et d'organisation de l'information, comme une base pratique pour aider les petites équipes à livrer plus vite et plus sûrement.
Envie d'en savoir plus sur nos services ?
Nous offrons un accompagnement complet : développement de systèmes, design web, design graphique et éducation IT.
Articles connexes
Concevoir un site Astro + Cloudflare qui grandit fonctionnalité par fonctionnalité 7 juin 2026 à 19:00
Ajouter des commentaires à un blog Astro avec Cloudflare uniquement 7 juin 2026 à 18:00