
    Qip              "       
   d dl Z d dlZd dlmZ d dlmZmZmZ  ee      Z	i dddddd	d
dddddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-Z
d.efd/Zd0ed.efd1Ze	j                  d2        Zd3ed4efd5Zd3ed4efd6Zdd4efd7Zd4efd8Zd9 Zd: Zd;g d<g d=d>d?d@g dAg dBd?dCg dDg dEd?dFg dGg dHd?dIg dJg dKd?dLg dMd dgdNd?dOg dPdd"gdQd?dRg dSg dTd?dUg dVg dWd?dXg dYg dZd?d[g d\g d]d?d^g d_ddgd`d?dag dbg dcddd?deg dfg dgdhd?dig djg dkd?dlg dmg dnd?dog dpg dqd?drg dsg dtd?dug dvg dwd?dxdygg dgdzd?d{g d|g d}d~d?ddgg ddd?dg dg ddd?dg dg d}dd?dg dg ddd?dg dg ddd?dg dg ddd?dg dg ddd?dg dg dd?dg g ddd?gZdefdZd Ze	j5                  ddg      d        Ze	j5                  d      d        Zedk(  re	j;                  dd       yy)    N)SequenceMatcher)Flaskrequestjsonifyaccueil)zAccueil CPNVzhttps://www.cpnv.ch/presentation)zPresentation CPNVz!https://www.cpnv.ch/presentation/
formations)
Formationszhttps://www.cpnv.ch/formations/	admission)	Admissionzhttps://www.cpnv.ch/admission/inscription_es)zInscription ESz-https://www.cpnv.ch/admission/inscription-es/inscriptions_em)zInscriptions ecole des metiersz$https://www.cpnv.ch/inscriptions-em/inscription_preapp)zInscription preapprentissagez1https://www.cpnv.ch/admission/inscription-preapp/contact)Contactzhttps://www.cpnv.ch/contact/sainte_landing)zSainte-Croix (CPNV) - sitez,https://www.sainte-croix.ch/_rte/schule/7096sainte_contenu)zCPNV sur Sainte-Croixz(https://www.sainte-croix.ch/contenu/cpnv	stages_sc)zStages a Ste-Croixz4https://www.cpnv.ch/admission/stages/ste-croix-2024/stages_info)zStages d'informationz%https://www.cpnv.ch/admission/stages/
vivre_cpnv) Vivre au CPNV (documents utiles)z"https://www.cpnv.ch/vivre-au-cpnv/intranet_accueil)zIntranet CPNV (connexion)z"https://intranet.cpnv.ch/connexionintranet_calendriers)zIntranet CPNV (calendriers)z$https://intranet.cpnv.ch/calendriersintranet_documents)zIntranet CPNV (documents)z"https://intranet.cpnv.ch/documentsvivre_documents)r   z,https://www.cpnv.ch/vivre-au-cpnv/documents/)u   Vivre au CPNV (règlements)z-https://www.cpnv.ch/vivre-au-cpnv/reglements/)z#Inscription CFC (ecole des metiers)z.https://www.cpnv.ch/admission/inscription-cfc/)u   Médiamaticien-ne CFCz7https://www.cpnv.ch/formations/technique/mediamaticien/)zInformaticien-ne CFCz7https://www.cpnv.ch/formations/technique/informaticien/)u   Polymécanicien-ne CFCz<https://www.cpnv.ch/formations/ecole-metiers/polymecanicien/)zElectronicien-ne CFCz;https://www.cpnv.ch/formations/ecole-metiers/electronicien/)zAutomaticien-ne CFCz6https://www.cpnv.ch/formations/technique/automaticien/)
reglementscfc_inscriptionmediamaticien_cfcinformaticien_cfcpolymecanicien_cfcelectronicien_cfcautomaticien_cfc
user_queryc                   	 | xs dj                         	t        	fddD              }t        	fddD              }t        	fddD              }t        d   t        d	   t        d
   g}|r9|t        d   t        d   t        d   gz   }d	v r|j                  t        d          |r'|t        d   gz   }|r|j                  t        d          |r0|j                  t        d          |j                  t        d          t	               }g }|D ].  \  }}||v r|j                  |       |j                  ||f       0 |d d S )N c              3   &   K   | ]  }|v  
 y wN .0kqs     app.py	<genexpr>z#_pick_cpnv_links.<locals>.<genexpr>(   s       	
Q   )r   inscriptioninscrirecandiddossierzes zes/ecole des metiersmetierspreapprentissagefpaboursec              3   &   K   | ]  }|v  
 y wr'   r(   r)   s     r-   r.   z#_pick_cpnv_links.<locals>.<genexpr>9   s     S!qAvSr/   )stagestagesemploi3+1r=   c              3   &   K   | ]  }|v  
 y wr'   r(   r)   s     r-   r.   z#_pick_cpnv_links.<locals>.<genexpr>:   s     d!qAvdr/   )saintesainte-croix	ste-croix	ste croixcpnvr   r   r	   r   r   r   r6   r   r   r   r   r      )lowerany
CPNV_LINKSappendsetadd)
r#   wants_admissionwants_stageswants_saintelinksseendedupedlabelurlr,   s
            @r-   _pick_cpnv_linksrS   &   sR   		r  "A 
 O" S'RSSLd'cddL	"Jy$9:l;STEK0*=M2NPZ[lPmnn"LL$89:M233LLK01Z 012Z 012 5DG %
s$;s|$	%
 2A;    replyc                     | xs dj                         } t        |      }|s| S ddg}|D ]  \  }}|j                  d| d|         | dz   dj                  |      z   S Nr%   zLiens CPNV (officiels):z- z: 
)rstriprS   rH   join)rU   r#   rN   addendum_linesrQ   rR   s         r-   _append_cpnv_linksr\   U   sw    [b  "EZ(E34N 3
s5'C51234<$))N333rT   c                     t         j                  dk(  r-d| j                  d<   d| j                  d<   d| j                  d<   | S )N/z.no-store, no-cache, must-revalidate, max-age=0zCache-Controlzno-cachePragma0Expires)r   pathheaders)resps    r-   add_no_cache_headersre   a   s?     ||s(X_%!+X"%YKrT   sreturnc                 j    | xs d} t        j                  d|       } dj                  d | D              S )Nr%   NFDc              3   R   K   | ]  }t        j                  |      d k7  s| ! yw)MnN)unicodedatacategory)r*   chs     r-   r.   z!_strip_accents.<locals>.<genexpr>o   s"     F"[%9%9"%=%E2Fs   '')rl   	normalizerZ   rf   s    r-   _strip_accentsrq   l   s2    	RAeQ'A77FFFFrT   c                     t        t        |             j                         } t        j                  dd|       } dj                  | j                               S )zN
    Normalize french queries so we can handle typos and missing accents.
    z
[^a-z0-9]+ )rq   strrE   resubrZ   splitrp   s    r-   _normalize_textrx   r   sB     	s1v$$&A
}c1%A88AGGIrT   c                 F   t        |      }|sy|j                         }|syt        |      dk(  r-|d   }| D ]"  }t        d ||      j	                         |k\  s" y yt        d|dz
        }|D ]1  }d}| D ]$  }t        d ||      j	                         |k\  s"d} n |r1 y y)NF   r   Tg(\?g{Gz?)rx   rw   lenr   ratiomax)	query_tokenskeyword	thresholdkw_wordskwtthreshold_multikw_wordmatcheds	            r-   _token_fuzzy_hitsr   |   s    g&G}}H 8}a[ 	AtQ+113y@	  $	D 01O  	AtQ0668OK	  rT   c                 "   g }t        | t              r_| dd D ]W  }t        |t              s|j                  d      dk(  s)|j                  dd      }|s>|j	                  t        |             Y dj                  |      j                         S )zK
    Use the last user messages as extra context for intent detection.
    iNroleusercontentr%   rs   )
isinstancelistdictgetrH   rt   rZ   strip)historypartsitemr   s       r-   _extract_contextr      s~     E'4 BCL 	/D$%$((6*:f*D((9b1LLW.		/
 88E?  ""rT   c                    t        | t              syg }| dd D ]V  }t        |t              r|j                  d      dk7  r(|j                  dd      }|s=|j	                  t        |             X |sydj                  |      }t        |      }|j                         syfd}d} |d	      s |d
      rd}nS |d      s |d      s |d      rd}n8 |d      s |d      rd}n% |d      s |d      rd}n |d      s |d      rd} |d      rd|dS  |d      rdddS  |d      s |d      rdddS  |d      rdddS y)z~
    Extract a coarse track hint from the conversation.
    Used only for tailoring links/text, not for intent selection.
    Nir   r   r   r%   rs   c                      t        | d      S )Ng(\?)r   )r   )toktokenss    r-   hasz _extract_track_hint.<locals>.has   s     ==rT   
mediamaticmedia	cfc_media
informaticinformatiqueitcfc_informatique	polymecan	mecaniquecfc_polymecanique
electronicelectroniquecfc_electroniqueautomati	automaticcfc_automatiquecfc)tracksubtypeafpesecole superieurer7   )	r   r   r   r   rH   rt   rZ   rx   rw   )	r   textsr   r   combinedqnormr   r   r   s	           @r-   _extract_track_hintr      se   
 gt$E '$%&)9V)C((9b)LLW&' xxHH%E[[]F> G
<CL	\	c.1SY$	[	S-%	\	c.1$	ZC,# 5z733
5z400
4yC*+$//
5z400rT   c                 z    | syddg}| D ]  \  }}|j                  d| d|         ddj                  |      z   S rW   )rH   rZ   )rN   linesrQ   rR   s       r-   _format_links_sectionr      sS    *+E *
sr%3%()*$))E"""rT   location_sainte_croix)	zsainte croixr@   rB   rA   rC   r?   croixadressegare)r   r   r   zqTu parles de Sainte-Croix (CPNV). Dis-moi ce que tu cherches exactement (inscription, stages, horaires, filiere).)idkeywordsrN   rU   greeting)salutbonjourbonsoircoucouzSalut ! Je suis le chatbot du CPNV. Que veux-tu savoir: admission/inscription, filieres (AFP/CFC/ES), stages, ou contact/horaire ?how_are_you)zcomment ca vazca vazcomment tu vasu   ça vaz]Ca va bien, merci ! Et toi ? Dis-moi ta question sur le CPNV (Sainte-Croix) et je te reponds.thanks)mercithx	gratitudezVAvec plaisir ! Tu veux plutot des infos sur admission/inscription, ou sur les stages ?goodbye)z	au revoirz	a bientotbyeciaoz@A bientot ! Si tu as une autre question sur le CPNV, je suis la.timetable_cours)	courszx courshorairehoraireszemploi du tempsplanning
calendriercalendriersintranetzPour voir quand tu as tes cours (planning/horaires), connecte-toi a l'intranet CPNV: https://intranet.cpnv.ch/calendriers. Cherche ensuite la periode qui t'interesse. Bon courage !conge_absence)	congezdemande de congejustificationjustifcatifjustifabsencer   zjustification d absencezjustificatif d absencezPour une demande de conge et/ou un justificatif d'absence:
- Page CPNV (documents utiles): https://www.cpnv.ch/vivre-au-cpnv/
- Intranet CPNV (documents): https://intranet.cpnv.chintranet_connexion)	r   	connexionzse connecterzmot de passemdpcompteloginaccesu   accèszPour te connecter a l'intranet CPNV: https://intranet.cpnv.ch/connexion
Si tu as un probleme de compte/mot de passe, contacte le support: helpdesk.cpnv@eduvaud.ch.reglements_charte)	reglementu
   règlementchartertt
disciplinesanctionregleszhPour les regles officielles (reglements, memento, charte): https://www.cpnv.ch/vivre-au-cpnv/reglements/documents_utiles)zdocuments utilesmemento	documentszformulaires utiles
formulairezformulaire utilezLa page 'Vivre au CPNV' avec les documents utiles: https://www.cpnv.ch/vivre-au-cpnv/documents/
Si tu me dis ce que tu cherches (conge/absence, dispense EPS, adresse, etc.), je t'oriente.vacances_jours_feries)vacancesvacancezjours feriesu   jours feriésferiesfetes
rattrapagezzPour les vacances et jours feries, regarde la rubrique 'Vivre au CPNV' du site du CPNV: https://www.cpnv.ch/vivre-au-cpnv/contact_horaires)	r   	telephonetelmailemailr   r   secretariat
secretaireu   Pour les coordonnées et horaires, le plus fiable est la page officielle de contact. Si tu me dis ton filiere, je peux aussi te diriger vers la bonne page.formations_overview)r	   	formationfilierer   metierr5   r   r   r   r7   maturitemct)r	   r   r   r   zfLe CPNV propose plusieurs filieres et niveaux. Dis-moi: tu vises AFP/CFC, ES, FPA, ou une autre voie ?	cfc_track)r   zc.f.czc f c)r   r   r   r	   uZ  OK, CFC.
1) Choisis le metier/filiere (exemples: media/mediamaticien, informatique/informaticien, polymecanique, electronique, automatique)
2) Suis la procedure d'inscription (delais, exigences, eventuellement entretien/examen selon la filiere)
3) Prepare les documents demandés.
Dis-moi: tu veux `comment s'inscrire` ou `les delais/documents` ?r   )r   mediamatiquemediamaticienmediammatiquezmediamaticien-nezmediammaticien-nea$  OK, tu parles de la filiere CFC du mediamaticien-ne.
Infos officielles (metier): https://www.cpnv.ch/formations/technique/mediamaticien/
Pour l'inscription CFC (ecole des metiers): https://www.cpnv.ch/admission/inscription-cfc/
Tu veux plutot: `comment s'inscrire` ou `les delais/documents` ?r   )r   informaticieninfor   a  OK, tu parles de la filiere CFC informaticien-ne.
Infos officielles (metier): https://www.cpnv.ch/formations/technique/informaticien/
Pour l'inscription CFC: https://www.cpnv.ch/admission/inscription-cfc/
Tu veux `comment s'inscrire` ou `les delais/documents` ?r   )polymecaniquepolymecanicienr   zpoli mecaniquea  OK, tu parles de la filiere CFC polymecanicien-ne.
Infos officielles (metier): https://www.cpnv.ch/formations/ecole-metiers/polymecanicien/
Pour l'inscription CFC: https://www.cpnv.ch/admission/inscription-cfc/
Tu veux `comment s'inscrire` ou `les delais/documents` ?r   )r   electronicienr   r  a	  OK, tu parles de la filiere CFC electronicien-ne.
Infos officielles (metier): https://www.cpnv.ch/formations/ecole-metiers/electronicien/
Pour l'inscription CFC: https://www.cpnv.ch/admission/inscription-cfc/
Tu veux `comment s'inscrire` ou `les delais/documents` ?r   )automatiqueautomaticien
automationa  OK, tu parles de la filiere CFC automaticien-ne.
Infos officielles (metier): https://www.cpnv.ch/formations/technique/automaticien/
Pour l'inscription CFC: https://www.cpnv.ch/admission/inscription-cfc/
Tu veux `comment s'inscrire` ou `les delais/documents` ?	afp_trackr   z{OK, AFP.
Je peux t'aider pour l'inscription: dis-moi si tu cherches `comment s'inscrire`, `les delais`, ou `les documents`.es_track)r   r   ecole superieur
technicienr   )r   r   r	   r   z~OK, ES (ecole superieure).
Donne-moi le domaine/filiare ES si tu sais, et dis-moi si tu veux `inscription` ou `delais/examen`.	fpa_trackr7   r   r	   r   zxOK, FPA.
Les etapes depend de ton profil. Dis-moi si tu as une maturite gymnasiale ou un certificat de culture generale.admission_general)r   admisadmettreentrerentreer0   r1   candidaturecandidatr3   r   documentpiecespiecedelaisdatesdate)r   r	   r   r   u   Pour etre admis au CPNV, la procedure depend de ta voie (AFP/CFC, ES, preapprentissage, FPA). En general, il faut suivre les etapes et delais indiqués sur la page officielle. Dis-moi ton niveau et ton objectif, et je te guide vers la bonne etape.)r   r   r
  r  zinscription eszinscrire eszInscription en Ecole Superieure (ES): les etapes et delais exacts sont sur la page officielle. Indique-moi la filiere ES que tu vises (si tu sais).inscriptions_ecole_metiers)r4   zecole metiersr5   inscriptionsr   r   )r   r   r	   r   zInscription pour l'ecole des metiers: les conditions et inscriptions se trouvent sur la page officielle. Si tu me dis ton metier/filiere (ex: informatique, polymecanique, mediammatique), je t'oriente.inscription_preapprentissage)r6   preappzpre-apprentissagezpre app)r   r   r   zPour le preapprentissage (etapes d'inscription): consulte la page officielle. Dis-moi aussi d'ou tu viens (transition/sem o autre) si tu sais.r;   )r:   r;   z3 plus 1r=   informationr<   )r   r   r   r   zPour les stages (information et delais), regarde la page officielle. Si tu cherches un stage a Sainte-Croix, precise la periode/annee.)r7   r   	accelereer   u   accélérée	accelererzLa FPA (formation professionnelle acceleree) depend de ton profil. Dis-moi si tu as une maturite gymnasiale ou un certificat de culture generale, et je te pointe vers l'etape officielle.capabilities)a quoi tu sersr#  a quoi tu peux repondrer$  zquelles questionsz"quelles questions je peux te poserzque peux tuzque peux tu repondrezce que tu saisztu sais faireutiliteu  Je peux t'aider sur tout ce qui concerne le CPNV (Sainte-Croix) :
- Admission/inscription (AFP/CFC, ES, preapprentissage, FPA)
- Filières CFC (ex: media -> mediamaticien-ne, informatique -> informaticien-ne, etc.)
- Stages (information et stages a Sainte-Croix quand c'est pertinent)
- Horaires/cours : je te guide vers l'intranet (calendriers)
- Congé / justification d'absence
- Documents utiles (formulaires) + reglements/charte/memento
- Vacances et jours feries (ou ou trouver l'info)
- Contact (ou trouver l'adresse/telephone)

Pose-moi ta question, meme si tu fais des fautes d'orthographe. Si tu veux, dis aussi ta voie (AFP/CFC/ES) pour que ce soit plus precis.default)r   r	   r   r   a&  Je peux t'aider sur le CPNV (Sainte-Croix): admission/inscription (AFP/CFC/ES/FPA), stages, horaires/cours (intranet), conge/absence, documents et contact.
Dis-moi ta question (meme avec des fautes) et si possible: ta voie (AFP/CFC/ES) et ton besoin (inscription, delais, documents, stages...).user_msgc                    t        |       }|j                         }t        |      dk(  rB|d   }ddddd}|j                  |      }|r#t        D ]  }|j                  d      |k(  s|c S  t        |       }|j                         }	d }
d}t        D ]G  }|d   d	k(  r|j                  d
g       }|s!d}|D ]  }t        |	|      s|dz  } |}||kD  sD|}|}
I |
|dk  rt        d t        D              S |
S )Nrz   r   r   r  r	  r  )r   r   r   r7   r   r&  r   c              3   2   K   | ]  }|d    dk(  s|  yw)r   r&  Nr(   )r*   is     r-   r.   z_pick_intent.<locals>.<genexpr>i  s     ?!!D'Y*>A?s   )rx   rw   r{   r   INTENTSr   next)r'  r   current_qnormcurrent_tokenssingle	track_maptrack_idintentr   qtokensbest
best_scorer   hitsr   scores                   r-   _pick_intentr8  A  s.   #H-M"((*N
>a"':Vab	==(! "::d#x/!M" H%EkkmGDJ $<9$::j"- 	B "-		
 :JD!$ |zQ?w???KrT   c                 x     xs dj                           syt         |      t        |      }d}j                  d      dvr7t	        t               j                               }|j                  h d      rd} fd} |       }g }|rj                  d      d	k(  r4|r1|j                  d
      r|j                  d
      }|j                  d      }	|dk(  rVddg}
dddddd}|	|v r|
j                  d||	          |
D ]+  }t        j                  |      }|s|j                  |       - n|dk(  r1dD ]+  }t        j                  |      }|s|j                  |       - n|dk(  r1dD ]+  }t        j                  |      }|s|j                  |       - nv|dk(  rqdD ]+  }t        j                  |      }|s|j                  |       - n@j                  dg       D ]+  }t        j                  |      }|s|j                  |       - j                  d      xs dj                         }t               }t	        |j                               }j                  d      d	k(  r+|j                  h d      rd}n|j                  h d       rd!}|r||z   n|}||rt        |      z   S dz   S )"Nr%   z Ecris un message pour commencer.r'  r   r   >   r   r   >   r   r   r   r   zSalut ! c                     t              } t        | j                               }j                  d      dv ryj                  d      dv ryh d}j                  d      |vrj                  d      dvryh d}|j	                  |      ryd	|v r|j	                  h d
      ryj                  d      dv r|j                  h d      ryy)Nr   >   r   r   r   r   r   r   r   F>   r   r   r   r   r   r   >	   r;   r   r   r   r   r  r   r  r  >   r	  r  r   r  >   r  lienr  r   r  liensr  r:   r  r  r  examenr  r;   r   r   r   r3   examensperioder  r  r   r1   officielperiodesr   
renseignerr0   renseignementTcomment>   r  r:   r  r  r;   r  r1   >   r   r   r   r7   )rx   rI   rw   r   intersectionissubset)r   r   always_link_fordetail_tokensr2  r'  s       r-   wants_linksz$_generate_reply.<locals>.wants_links}  s    )U[[]# ::d   a  a::d   \  \

 ::d?2vzz$7G  PC  8C
B }- 6#6#67  $A ::dRRW]WfWf  hC  XD rT   r  r   r   r   r   r   r   r   r    r!   r"   )r   r   r   r   r   rz   r   )r   r   r   r   )r   r   r   r7   r  rN   rU   >   cvr  r  r3   r  r   
certificatattestationa  Pour les documents/dossier a fournir, cela depend de ta voie (AFP/CFC, ES, preapprentissage, FPA). Verifie la liste exacte sur la page officielle d'admission/inscription, et si tu as un doute (niveau/filiere), dis-moi laquelle tu vises et je t'oriente vers la bonne etape.>   r  r  r  deadlinezLes delais et dates exacts dependent de la voie (AFP/CFC, ES, preapprentissage, FPA). Consulte la page officielle d'admission/inscription: tu y trouves les periodes, examens et inscriptions.)r   r8  r   r   rI   rx   rw   rF  insertrG   rH   r   )r'  r   
track_hintprefix_salutationr.  rJ  
want_linksrN   r   r   links_to_addsubtype_key_to_linkkeypairrU   r   r3  final_replyr2  s   `                 @r-   _generate_replyrX  m  s   B%%'H18W=F$W-J zz$::_X6<<>?&&'PQ *JX JE::d22zjnnU\F]w'A nnY/GEz 19=!4(;)=(;'9'# 11 ''+>w+GH' +C%>>#.DT*+ dE +C%>>#.DT*+ eF +C%>>#.DT*+ eA +C%>>#.DT*+
 zz'2. '!~~c*LL&'
 ZZ &B--/E H%E%++- Gzz$.. z{Q 
 !!"IJ{ 
 0A#e+eK*/6MM"MMrT   z/chatPOST)methodsc                      t        j                  d      xs i } | j                  dd      }| j                  dg       }t        ||      }t	        d|i      S )NT)silentmessager%   r   r:  rU   )r   get_jsonr   rX  r   )datar'  r   rU   s       r-   chatr`    sS    4(.BDxx	2&Hhhy"%GXw?EGU#$$rT   r^   c                       y)Nu(  
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Chatbot</title>
    <style>
      :root {
        --bg1: #0f172a;
        --bg2: #111827;
        --card: rgba(255,255,255,0.92);
        --text: #0f172a;
        --muted: #64748b;
        --user: #2563eb;
        --bot: #f1f5f9;
      }

      * { box-sizing: border-box; }

      body {
        margin: 0;
        font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
        color: var(--text);
        background: radial-gradient(1200px circle at 20% 10%, #1d4ed8 0%, transparent 35%),
                    radial-gradient(1000px circle at 70% 30%, #22c55e 0%, transparent 40%),
                    linear-gradient(180deg, var(--bg1), var(--bg2));
        min-height: 100vh;
        display: flex;
        align-items: flex-start;
        justify-content: center;
      }

      .container {
        width: 100%;
        max-width: 980px;
        padding: 22px;
      }

      .header {
        display: flex;
        gap: 14px;
        align-items: center;
        margin-bottom: 14px;
        color: #e5e7eb;
      }

      .badge {
        background: rgba(255,255,255,0.12);
        border: 1px solid rgba(255,255,255,0.18);
        padding: 6px 10px;
        border-radius: 999px;
        font-size: 12px;
        color: #e5e7eb;
      }

      .card {
        background: var(--card);
        border-radius: 18px;
        box-shadow: 0 10px 30px rgba(0,0,0,0.25);
        overflow: hidden;
        border: 1px solid rgba(15,23,42,0.08);
        backdrop-filter: blur(8px);
      }

      .chat {
        height: 70vh;
        min-height: 420px;
        padding: 16px;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        gap: 10px;
        background:
          linear-gradient(180deg, rgba(37,99,235,0.06), transparent 40%),
          linear-gradient(0deg, rgba(34,197,94,0.05), transparent 45%);
      }

      .msg {
        display: flex;
      }
      .msg.user { justify-content: flex-end; }
      .msg.bot { justify-content: flex-start; }

      .bubble {
        max-width: 78%;
        padding: 10px 12px;
        border-radius: 16px;
        line-height: 1.4;
        white-space: pre-wrap;
        word-break: break-word;
        border: 1px solid rgba(15,23,42,0.08);
      }
      .bubble.user {
        background: var(--user);
        color: white;
        border-bottom-right-radius: 6px;
        border-color: rgba(37,99,235,0.3);
      }
      .bubble.bot {
        background: var(--bot);
        color: #0f172a;
        border-bottom-left-radius: 6px;
      }

      .meta {
        font-size: 11px;
        color: rgba(255,255,255,0.85);
        margin-bottom: 6px;
        font-weight: 500;
      }
      .bubble.bot .meta {
        color: rgba(15,23,42,0.55);
      }

      .composer {
        padding: 14px;
        border-top: 1px solid rgba(15,23,42,0.08);
        display: flex;
        gap: 10px;
        align-items: flex-end;
        background: rgba(255,255,255,0.7);
      }

      textarea {
        width: 100%;
        resize: none;
        min-height: 46px;
        max-height: 140px;
        padding: 10px 12px;
        border-radius: 12px;
        border: 1px solid rgba(15,23,42,0.14);
        outline: none;
        font-size: 14px;
        background: rgba(255,255,255,0.95);
      }
      textarea:focus {
        border-color: rgba(37,99,235,0.55);
        box-shadow: 0 0 0 4px rgba(37,99,235,0.15);
      }

      .actions {
        display: flex;
        flex-direction: column;
        gap: 8px;
        align-items: stretch;
        min-width: 140px;
      }

      button {
        border: 0;
        border-radius: 12px;
        padding: 11px 12px;
        font-weight: 700;
        cursor: pointer;
        background: rgba(37,99,235,0.95);
        color: white;
        box-shadow: 0 6px 18px rgba(37,99,235,0.28);
      }
      button:disabled {
        cursor: not-allowed;
        opacity: 0.6;
        box-shadow: none;
      }

      .hint {
        font-size: 12px;
        color: var(--muted);
        line-height: 1.2;
      }

      .typing {
        display: inline-flex;
        gap: 6px;
        align-items: center;
      }
      .dot {
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: currentColor;
        opacity: 0.55;
        animation: pulse 1.1s infinite ease-in-out;
      }
      .dot:nth-child(2) { animation-delay: 0.12s; }
      .dot:nth-child(3) { animation-delay: 0.24s; }

      @keyframes pulse {
        0% { transform: translateY(0); opacity: 0.35; }
        50% { transform: translateY(-3px); opacity: 0.95; }
        100% { transform: translateY(0); opacity: 0.35; }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="header">
        <div>
          <div style="font-size: 22px; font-weight: 850;">Chatbot</div>
          <div style="font-size: 13px; color: #cbd5e1;">Tape un message pour commencer</div>
        </div>
        <div class="badge">UI simple + historique</div>
      </div>

      <div class="card">
        <div id="chat" class="chat"></div>

        <div class="composer">
          <textarea id="msg" placeholder="Ecris ton message... (Enter pour envoyer, Shift+Enter pour retour a la ligne)"></textarea>
          <div class="actions">
            <button id="send" type="button">Envoyer</button>
            <div id="status" class="hint"></div>
          </div>
        </div>
      </div>
    </div>

    <script>
      const chat = document.getElementById('chat');
      const msg = document.getElementById('msg');
      const send = document.getElementById('send');
      const statusEl = document.getElementById('status');

      // Historique cote client (le serveur ne garde que le dernier N).
      const history = [];

      function formatTime(d) {
        const hh = String(d.getHours()).padStart(2, '0');
        const mm = String(d.getMinutes()).padStart(2, '0');
        return hh + ':' + mm;
      }

      function escapeHtml(s) {
        return String(s).replace(/[&<>"']/g, function (m) {
          return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]);
        });
      }

      function linkify(text) {
        // Turns URLs into clickable anchors while keeping other text safe.
        const frag = document.createDocumentFragment();
        const urlRegex = /https?:\/\/[^\s<]+/g;
        let lastIndex = 0;
        let match;

        while ((match = urlRegex.exec(text)) !== null) {
          const before = text.slice(lastIndex, match.index);
          if (before) {
            const span = document.createElement('span');
            span.textContent = before;
            frag.appendChild(span);
          }

          const url = match[0];
          const a = document.createElement('a');
          a.href = url;
          a.textContent = url;
          a.target = '_blank';
          a.rel = 'noopener noreferrer';
          a.style.wordBreak = 'break-all';
          frag.appendChild(a);

          lastIndex = match.index + url.length;
        }

        const remaining = text.slice(lastIndex);
        if (remaining) {
          const tail = document.createElement('span');
          tail.textContent = remaining;
          frag.appendChild(tail);
        }

        return frag;
      }

      function appendMessage(role, text) {
        const wrapper = document.createElement('div');
        wrapper.className = 'msg ' + (role === 'user' ? 'user' : 'bot');

        const bubble = document.createElement('div');
        bubble.className = 'bubble ' + (role === 'user' ? 'user' : 'bot');

        const who = role === 'user' ? 'Vous' : 'Bot';
        const meta = document.createElement('div');
        meta.className = 'meta';
        meta.textContent = who + ' • ' + formatTime(new Date());
        bubble.appendChild(meta);

        const content = document.createElement('div');
        content.appendChild(linkify(String(text)));
        bubble.appendChild(content);

        wrapper.appendChild(bubble);
        chat.appendChild(wrapper);
        chat.scrollTop = chat.scrollHeight;
        return wrapper;
      }

      function setLoading(isLoading) {
        send.disabled = isLoading;
        statusEl.textContent = isLoading ? 'En train de repondre...' : '';
      }

      function createTypingPlaceholder() {
        const wrapper = document.createElement('div');
        wrapper.className = 'msg bot';

        const bubble = document.createElement('div');
        bubble.className = 'bubble bot';
        bubble.innerHTML =
          '<div class="meta">Bot • ' + formatTime(new Date()) + '</div>' +
          '<div class="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>';

        wrapper.appendChild(bubble);
        chat.appendChild(wrapper);
        chat.scrollTop = chat.scrollHeight;
        return wrapper;
      }

      async function sendMsg() {
        const text = (msg.value || '').trimEnd();
        if (!text) return;

        appendMessage('user', text);
        history.push({ role: 'user', content: text });

        msg.value = '';
        msg.focus();

        setLoading(true);
        const typingEl = createTypingPlaceholder();

        try {
          const res = await fetch('/chat', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ message: text, history })
          });

          const data = await res.json().catch(() => ({}));
          const reply = (data && data.reply) ? data.reply : '';

          chat.removeChild(typingEl);
          appendMessage('assistant', reply || '(reponse vide)');
          history.push({ role: 'assistant', content: reply || '' });
        } catch (e) {
          chat.removeChild(typingEl);
          appendMessage('assistant', 'Erreur de connexion.');
        } finally {
          setLoading(false);
        }
      }

      send.addEventListener('click', sendMsg);
      msg.addEventListener('keydown', (e) => {
        // Enter pour envoyer, Shift+Enter pour inserer une nouvelle ligne.
        if (e.key === 'Enter' && !e.shiftKey) {
          e.preventDefault();
          sendMsg();
        }
      });
    </script>
  </body>
</html>
    r(   r(   rT   r-   homerb    s    lrT   __main__z0.0.0.0i  )hostport)gQ?)ru   rl   difflibr   flaskr   r   r   __name__apprG   rt   rS   r\   after_requestre   rq   rx   boolr   r   r   r   r+  r8  rX  router`  rb  runr(   rT   r-   <module>rn     s   	  # ) )Ho7N C @	
 Y a o : d [ _ T \ [ c  ]!" k#$ cpmltpi1
8, ,^	4c 	4s 	4  Gc Gc Gs s  ># #3l# &|@^ =m JO 1i	 =S	  

 )*<=H* 

  45D* #zj "dv !xj &m1 !xY'U $ IEF -JP xO
 !CG
 "VG
 !VG
  AG
 GJA cIr G7~ "
& CV/: pIC	 +cJn	 -R?O	 SJP _7F 
 
W#> IY[
Vr
)3 )X\N~ 7VH%% &% 3m m^ zGGG& rT   