SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
4.5 KB · 93 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// fiche/KaAssistant.tsx : « ✦ Demander à Ka » — bouton discret (bas droite,5//   au-dessus du CTA) qui remplace la grosse bulle KA Agent sur la fiche, et6//   bottom sheet de démarrage : contexte du logement, suggestions prédéfinies,7//   champ libre. L'envoi passe par le widget KA Agent existant (ka-agent.js,8//   backend api-ka inchangé) : on ouvre son panneau et on lui transmet la9//   question, enrichie du contexte de la fiche (titre, ville, prix, lien).10// -----------------------------------------------------------------------------11import { useEffect, useState } from "react";12import { Listing, fmtPrice } from "../api";13import { IcoChevronRight, IcoSparkles } from "../components/Icons";14import BottomSheet from "./BottomSheet";1516const SUGGESTIONS = [17  "Est-ce une bonne affaire ?",18  "Compare ce loyer au quartier",19  "Le secteur est-il bon sans voiture ?",20  "Quels sont les points négatifs ?",21  "Explique le registre des loyers",22];2324/** Ouvre le panneau KA Agent (widget partagé) et envoie la question. */25function envoyerAKa(question: string): boolean {26  const btn = document.querySelector<HTMLButtonElement>(".kaa-btn");27  const ta = document.querySelector<HTMLTextAreaElement>(".kaa-panel textarea");28  const send = document.querySelector<HTMLButtonElement>(".kaa-in button");29  if (!btn || !ta || !send) return false;30  btn.click();                       // ouvre le panneau (plein écran)31  ta.value = question;32  setTimeout(() => send.click(), 60);33  return true;34}3536export default function KaAssistant({ l, hidden }: { l: Listing; hidden?: boolean }) {37  const [open, setOpen] = useState(false);38  const [q, setQ] = useState("");39  const [err, setErr] = useState(false);40  // ouverture depuis un autre composant (bouton de l'aside desktop)41  useEffect(() => {42    const on = () => setOpen(true);43    window.addEventListener("lk:askka", on);44    return () => window.removeEventListener("lk:askka", on);45  }, []);4647  const contexte = () =>48    `(Logement consulté sur Lou-Ka : ${l.title || l.address}${l.city ? `, ${l.city}` : ""}` +49    `${l.price != null ? ` — ${fmtPrice(l.price)}/mois` : ""}${l.unit_type ? ` — ${l.unit_type}` : ""}` +50    ` — https://www.lou-ka.com/logement/${encodeURIComponent(l.uid)})`;5152  const poser = (question: string) => {53    const ok = envoyerAKa(`${question}\n\n${contexte()}`);54    if (ok) { setOpen(false); setQ(""); setErr(false); } else setErr(true);55  };5657  return (58    <>59      <button type="button" className={`lk-ka-btn ${hidden ? "hide" : ""}`} onClick={() => setOpen(true)}60              aria-label="Demander à Ka, l'assistant Groupe KA">61        <IcoSparkles size={16} /> Demander à Ka62      </button>63      <BottomSheet open={open} onClose={() => setOpen(false)} title="Demander à Ka" sub="Assistant Groupe KA · connaît cette fiche"64                   footer={65                     <form className="lk-ka-in" onSubmit={(e) => { e.preventDefault(); if (q.trim()) poser(q.trim()); }}>66                       <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Posez votre question sur ce logement…" aria-label="Votre question" />67                       <button type="submit" aria-label="Envoyer" disabled={!q.trim()}><IcoChevronRight size={20} /></button>68                     </form>69                   }>70        <div className="lk-ka-intro">71          <span className="lk-ka-avatar" aria-hidden="true"><IcoSparkles size={18} /></span>72          <p>Je peux comparer ce loyer au quartier, expliquer les données de la fiche et chercher dans les autres plateformes Groupe KA.</p>73        </div>74        <div className="lk-ka-ctx">75          {l.images?.[0] && <img src={`/api/img?u=${encodeURIComponent(l.images[0])}&w=160`} alt="" />}76          <span style={{ minWidth: 0 }}>77            <b>{l.title || l.address}</b>78            {[l.city, l.price != null ? `${fmtPrice(l.price)}/mois` : null, l.unit_type].filter(Boolean).join(" · ")}79          </span>80        </div>81        <div className="lk-ka-sugs">82          {SUGGESTIONS.map((s) => (83            <button type="button" className="lk-ka-sug" key={s} onClick={() => poser(s)}>84              {s} <IcoChevronRight size={16} />85            </button>86          ))}87        </div>88        {err && <p className="lk-note warn">L'assistant n'est pas encore chargé — réessayez dans un instant.</p>}89      </BottomSheet>90    </>91  );92}93