尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Le chatbot de duhalle-boutique.fr : un conseiller en ligne qui répond aux questions des clients sur les produits Duhallé et sur le savoir-faire du bouchage, de la mise en bouteille, du cidre, du vinaigre et des conserves maison.檢視完整 README ↗
Le chatbot de duhalle-boutique.fr : un conseiller en ligne qui répond aux questions des clients sur les produits Duhallé et sur le savoir-faire du bouchage, de la mise en bouteille, du cidre, du vinaigre et des conserves maison.
L'algorithme s'inspire de l'« assistant déterministe » d'APISAAS (lib/assistant-deterministe). On n'y trouve pas de fiches question-réponse entières, mais :
| Élément | Rôle | Où |
|---|---|---|
| Concepts | Une notion, un produit ou une rubrique, avec ses alias et sa famille (bouchon-naturel → bouchon-liege → bouchon → bouchage → catalogue) | src/savoir/*.ts |
| Faits | Un énoncé court rattaché à un ou plusieurs concepts, avec son aspect (définition, choix, procédure, durée, prix…), sa source et sa date de vérification | src/savoir/*.ts |
| Types de question | Ce que la question demande, lu dans sa forme : « comment » (procédure), « combien de temps » (durée), « lequel » (choix), « où acheter » (lieu)… Une table ordonnée : la première marque qui s'applique gagne | src/moteur/types-de-question.ts |
| Plans de réponse | Pour chaque type, les aspects cherchés dans l'ordre | src/savoir/types.ts |
Pour chaque question, le moteur procède ainsi :
Cas particuliers :
…/mustimetre-c2x35079023) obtient la réponse sur ce produit, car l'adresse en contient le nom ; un lien vers un autre site n'est pas lisible, et le chatbot le dit. Seul le texte de l'adresse est lu : aucune page n'est ouverte.src/savoir/ventes.ts, sans toucher au moteur ; les tests vérifient que chaque bouton mène à une vraie réponse.trace qui dit pourquoi (type lu, concepts, faits retenus).Aujourd'hui : 252 concepts et 704 faits, répartis en 10 fichiers de rubriques : entreprise, commande (livraison, paiement, retours), mise en bouteille, bouchage, cire, cave et service du vin, cidre, vinaigre, conserves, droguerie.
test/banc.test.ts) : 186 questions formulées comme les clients les posent, avec la réponse attendue. Tout doit passer.test/clients-reels.test.ts) : les questions posées par les vrais clients (lues dans la base D1), puis des questions voisines (autres formulations, cas proches) pour que la réponse vise plus large que la phrase vue. Chaque lot relu y est ajouté.test/inedit.test.ts) : 25 questions écrites après le réglage, qu'on ne règle jamais une par une. Il est mesuré à 68 % de premières réponses idéales, et les écarts restent en général dans le bon sujet. Quand un jeu inédit a servi à corriger, il rejoint le banc et on en écrit un nouveau. Les trois premiers jeux, mesurés à 57 %, 57 % puis 77 %, ont fait progresser les règles.src/
index.ts le Worker : /api/chat, /api/accueil, /api/sante, CORS
moteur/ l'algorithme (aucun mot de métier)
texte.ts normalisation, mots vides, racinisation, fautes de frappe
types-de-question.ts
concepts.ts détection des concepts, lignée
reponse.ts choix des faits selon le plan
recherche.ts filet de sécurité : recherche plein texte dans les faits
assistant.ts orchestration, politesse, relances, repli
savoir/ LA BASE DE CONNAISSANCES
coordonnees.ts téléphone, e-mail, seuils de livraison (à un seul endroit)
ventes.ts les boutons de suggestion : questions de départ, vitrine, compléments
liens.ts les URL du site
entreprise.ts, commande.ts, mise-en-bouteille.ts, bouchage.ts, cire.ts,
cave-service.ts, cidre.ts, vinaigre.ts, conserves.ts, droguerie.ts
widget/widget.ts le script affiché sur le site (bulle + fenêtre)
public/
v/<version>.js les versions compilées du widget (npm run build, versionnées)
widget.js ancienne adresse unique du widget, figée
index.html page de démonstration
oxatis/ les codes à coller dans le back-office Oxatis
test/ base, banc, jeu inédit, Worker, robustesse
Le déploiement passe par Workers Builds : Cloudflare se branche sur ce dépôt GitHub et redéploie à chaque modification de main.
name du fichier wrangler.jsonc : aujourd'hui chatbot-duhalle. Pour le renommer, changez les deux.npx wrangler deploy. Le widget compilé est déjà dans le dépôt.https://chatbot-duhalle.<votre-sous-domaine>.workers.dev.https://chatbot-duhalle.<votre-sous-domaine>.workers.dev/ affiche la page de démonstration, et /api/sante renvoie {"ok":true,…}.Aucune clé secrète n'est nécessaire. Le plan gratuit de Cloudflare Workers (100 000 requêtes par jour) suffit largement.
Sites autorisés. La variable ALLOWED_ORIGINS de wrangler.jsonc liste les sites qui peuvent interroger le chatbot : https://www.duhalle-boutique.fr et https://duhalle-boutique.fr. Pour tester depuis un autre domaine, par exemple l'aperçu Oxatis, ajoutez-le, séparé par une virgule. Évitez * en production : n'importe quel site pourrait alors afficher votre chatbot.
Deux codes sont prêts dans le dossier oxatis/.
oxatis/1-script-chatbot.htmlVOTRE-SOUS-DOMAINE par celui de l'adresse workers.dev.<head> du site, et dans celui du site mobile si Oxatis en a un séparé.Chaque version du widget a sa propre adresse (/v/<version>.js), qui ne change plus et reste en ligne (sauf une version reconnue défectueuse, retirée). Publier une nouvelle version ne touche donc pas au site : il garde la précédente. Pour passer à la nouvelle, attendez la fin du déploiement Cloudflare, puis recopiez la balise du fichier oxatis/1-script-chatbot.html dans Oxatis (ordinateur et mobile). Vérifiez après l'enregistrement que le bloc se termine bien par </script> : un bloc trop long est coupé par Oxatis, et une balise coupée rend la page blanche.
Après 6 secondes sur une page, une petite carte au-dessus de la bulle invite le client à poser sa question, avec un message et une question adaptés à la page (cidre, cire, bouchons, conserves…, voir src/widget/invitations.ts). Elle apparaît sur deux pages au plus par visite, jamais au panier ni pendant la commande, et plus du tout une fois fermée ou le conseiller ouvert ; une pastille « 1 » reste ensuite sur la bulle.
Le visuel (bulle, fenêtre, vert et ocre Duhallé) est dessiné par le script lui-même, isolé du thème : il ne déforme pas le site et le site ne le déforme pas. Sur mobile, la discussion s'ouvre en plein écran. Réglages facultatifs, en attributs de la balise : data-titre, data-couleur, data-position (droite ou gauche), data-decalage (distance au bas de l'écran en pixels, 84 par défaut), data-telephone.
oxatis/2-bloc-bouton-conseiller.htmlUn bloc de design à coller dans un élément « Code HTML » d'une page (contact, FAQ, conseils…). Son bouton ouvre le chatbot et ses questions cliquables le lancent directement. Si le script du chatbot n'est pas chargé, les liens mènent à la page Contact.
Conseil : testez d'abord sur une page brouillon. Si votre offre Oxatis filtre les balises
<script>, contactez le support Oxatis pour savoir où les scripts sont autorisés.
Tout se passe dans src/savoir/. Un ajout sur GitHub, dans main, est redéployé automatiquement.
Ajouter un fait à un concept existant, dans le fichier de la rubrique :
r.fait("bouchon-naturel", "duree", `
Un bouchon en liège naturel de 45 mm est fait pour les vins de longue garde.`);
Ajouter un concept avec ses alias (pluriels, accents et majuscules sont gérés tout seuls) :
r.concept("muselet", "Muselets", ["muselet", "muselets", "agrafe de bouchon"], {
famille: "bouchage",
lien: page("/muselets-c102x…", "Muselets"),
});
Un fait qui croise deux concepts ne sort que si les deux sont cités :
r.fait(["bouchon", "vin-de-garde"], "choix", `Pour un vin de garde, …`);
Une mise en garde (loi, sécurité) se marque avec avertissement: true : elle accompagne toute réponse qui cite son concept, même quand la question porte surtout sur autre chose (aujourd'hui : la distillation à domicile, interdite sans autorisation).
r.fait("distillation", "condition", `En France, la distillation à domicile est interdite…`, { avertissement: true });
Un même énoncé peut être rattaché à plusieurs aspects (par exemple « procédure » et « condition ») pour être trouvé quel que soit le type de la question : la réponse ne l'affiche qu'une fois.
Les aspects possibles sont : definition, usage, gamme, choix, dimension, procedure, duree, moment, condition, raison, erreur, entretien, lieu, prix.
Règles de la maison :
formules.exclusif: true sur un concept (la disponibilité, le mustimètre, le Larroseptol…) fait que ses faits répondent seuls quand il est cité, sans les faits des autres concepts de la phrase (« quand les mèches soufrées reviennent-elles en stock ? » ne rappelle pas le mode d'emploi des mèches). Sans fait de ce concept pour le type de la question, la réponse suit son cours.formules, qui exigent l'expression entière.npm test. Si une question du banc change de réponse, vérifiez que la nouvelle est meilleure avant de mettre le banc à jour.Modifier les boutons de suggestion (questions de départ, vitrine, compléments par produit) : tout est dans src/savoir/ventes.ts. Chaque question y est rangée avec le concept dont sa réponse parle, par exemple q("Quelle boucheuse choisir ?", "boucheuse") ; npm test vérifie que le chatbot y répond. Le widget affiche 5 boutons au plus.
Les questions posées sont enregistrées, anonymisées, dans la base D1 chatbot-duhalle (table questions_chatbot, décrite dans schema/questions_chatbot.sql) : une ligne par question, avec son nombre d'occurrences et la nature de la dernière réponse. C'est la meilleure source pour savoir quels alias et quels faits ajouter. Les plus fréquentes restées sans réponse :
SELECT question, occurrences, dernier_jour FROM questions_chatbot
WHERE statut = 'a_revoir' AND nature_derniere IN ('inconnu', 'recherche')
ORDER BY occurrences DESC LIMIT 50;
Après tri, passez le statut à retenue, integree ou ignoree. Chaque nuit, une tâche planifiée du Worker supprime les questions a_revoir ou ignoree vues pour la dernière fois il y a plus de 90 jours.
Le chatbot ajoute un script à une boutique en ligne : c'est le point à protéger. Ce qui est en place :
| Risque | Protection |
|---|---|
Un widget.js modifié (compte GitHub ou Cloudflare piraté) qui lirait les pages de la boutique | Empreinte d'intégrité (SRI) sur la balise : le navigateur refuse tout fichier qui n'est pas exactement celui publié. Chaque version est publiée à une adresse fixe (/v/<version>.js) ; npm run build reporte adresse et empreinte dans oxatis/1-script-chatbot.html, et la CI vérifie qu'elles sont à jour. |
| Injection de code dans la fenêtre de discussion | Aucun texte reçu (réponse, stockage) n'est inséré comme HTML : tout passe par textContent. Les liens ne mènent qu'à duhalle-boutique.fr en https. Les réponses et la conversation gardée en sessionStorage sont vérifiées avant affichage. |
| Robot qui inonde l'API (quota, coûts) | 30 appels par minute et par adresse IP (binding ratelimits de Cloudflare), puis réponse 429 et message « merci de patienter ». |
| Message énorme ou piégé | Corps limité à 4 000 octets, lu en flux et coupé au-delà ; question lue sur 40 mots au plus ; correcteur de fautes indexé : un message piégé coûte moins de 5 ms de calcul (plus de 50 ms avant). Testé dans test/robustesse.test.ts. |
| Autre site qui utiliserait le chatbot | CORS : seuls les sites de ALLOWED_ORIGINS sont acceptés (403 sinon). |
| Données personnelles | Le chatbot ne demande rien. Les questions sont enregistrées anonymisées (e-mails, numéros, références de commande, adresses, noms, liens masqués — src/questions.ts), et le client en est informé sous la zone de saisie. Les questions non retenues sont effacées après 90 jours. La conversation reste dans l'onglet du client et disparaît à sa fermeture. Pensez à le mentionner dans la politique de confidentialité du site. |
| Fuite d'erreurs techniques | Toute erreur renvoie un message générique ; le détail reste dans les journaux Cloudflare. |
| En-têtes HTTP | API : nosniff, Content-Security-Policy: default-src 'none', no-store. Fichiers statiques (public/_headers) : CSP stricte, X-Frame-Options: DENY, noindex. |
À faire de votre côté : activer la double authentification sur les comptes GitHub et Cloudflare, et protéger la branche main (relecture obligatoire avant fusion).
Le site n'était pas accessible depuis l'environnement de développement. Les informations commerciales viennent donc d'extraits indexés du site et sont à confirmer. Elles sont centralisées dans src/savoir/coordonnees.ts et src/savoir/commande.ts :
02 47 53 00 26 et e-mail [email protected] (le seul numéro à jour, confirmé par l'équipe le 12/10/2026 ; l'ancien numéro ne doit figurer nulle part dans le chatbot).HORAIRES dans src/savoir/coordonnees.ts) : du lundi au jeudi 9h-12h et 14h-18h, le vendredi 9h-12h et 14h-16h, identiques sur la page Contact et le pied de page du site au 28/09/2026.src/savoir/liens.ts), relevées le 24/09/2026.Les faits de savoir-faire général (étapes de la mise en bouteille, du cidre, du vinaigre, conserves…) portent la source « Savoir-faire général ». Ils méritent une relecture par l'équipe Duhallé.
npm install
npm test # base, banc de questions, jeu inédit, Worker, robustesse
npm run typecheck # types du Worker et du widget
npm run build # compile une nouvelle version public/v/<version>.js et son empreinte (à commiter)
npm run dev # Worker en local sur http://localhost:8787 (page de démonstration)
Le widget compilé ne contient que de l'ASCII. Les pages Oxatis ne sont pas en UTF-8 : un navigateur qui lit le fichier avec l'encodage de la page (Windows-1252) abîme chaque accent, et un accent dans une expression régulière fait planter tout le script (« Range out of order in character class »), donc plus de bulle. esbuild échappe les accents des textes mais pas ceux des expressions régulières. npm run build refuse donc tout caractère non ASCII et dit où il se trouve ; dans src/widget/, écrivez \u0300 ou \u00e9, jamais le caractère. test/widget-ascii.test.mjs le vérifie aussi, en compilant le fichier lu en Windows-1252. Les en-têtes de public/_headers annoncent en plus charset=utf-8.
API :
POST /api/chat {"message": "Quel bouchon pour un vin de garde ?", "contexte": {…}}
GET /api/accueil message d'accueil et questions de départ
GET /api/sante {"ok": true, "concepts": 252, "faits": 704}
La réponse de /api/chat contient texte (gras, listes, paragraphes), liens, suggestions, contexte (à renvoyer avec la question suivante pour les relances) et trace.