SPB Git forge

spb/qc26

Public
10commits 1branches 0releases
2.8 MBsize
maindefault branch
17 days agolast push
Python 51.6% TypeScript 46.7% CSS 1.7%
2.8 KB · 43 lines tsx
Raw Blame History
1"use client";23import Link from "next/link";4import { useState } from "react";5import { PARTY_IDS, partyVar, partyLabel, partyName } from "@/lib/parties";6import { Card } from "./ui";78interface Ask { query: string; terms: string[]; parties: Record<string, { id: number; proposal: string; quote: string; page: number | null; topic: string; confidence: number; document: { id: number; title: string | null; url: string | null } }[]>; note: string }910export function AskBox() {11  const [q, setQ] = useState("");12  const [res, setRes] = useState<Ask | null>(null);13  const [busy, setBusy] = useState(false);14  const run = async (query: string) => {15    if (query.trim().length < 3) return;16    setBusy(true);17    try { setRes(await fetch(`/api/ask?q=${encodeURIComponent(query)}`).then((r) => r.json())); } finally { setBusy(false); }18  };19  return (20    <Card className="card-lg">21      <div className="eyebrow mb-2">Recherche documentaire</div>22      <form onSubmit={(e) => { e.preventDefault(); run(q); }} className="flex gap-2">23        <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Que proposent les partis pour le logement ?" className="flex-1 card px-3.5 py-2.5 text-[15px] outline-none focus:border-accent" aria-label="Question" />24        <button className="rounded-[8px] bg-brand text-white px-4 py-2.5 text-[14px] font-semibold disabled:opacity-40" disabled={busy}>{busy ? "…" : "Chercher"}</button>25      </form>26      <div className="flex flex-wrap gap-1.5 mt-2 text-[12px]">{["logement", "immigration", "santé", "troisième lien", "français", "impôts", "souveraineté", "Hydro-Québec"].map((s) => <button key={s} onClick={() => { setQ(s); run(s); }} className="rounded-full border border-border px-2.5 py-1 hover:bg-surface-2">{s}</button>)}</div>27      {res && (28        <div className="mt-5 grid md:grid-cols-2 xl:grid-cols-5 gap-3">29          {PARTY_IDS.map((id) => (30            <div key={id} className="surface-2 rounded-[8px] p-3" style={{ borderTop: `3px solid ${partyVar(id)}` }}>31              <div className="font-semibold text-[13px] mb-2">{partyName(id)}</div>32              {(res.parties[id] ?? []).length === 0 ? <div className="text-[12px] text-ink-3">Aucune proposition trouvée dans les documents ingérés.</div> : (res.parties[id] ?? []).map((p) => (33                <div key={p.id} className="mb-2.5 text-[12.5px] leading-snug"><div>{p.proposal}</div><div className="text-[11.5px] text-ink-3 mt-0.5"><Link href={`/documents/${p.document.id}#p${p.id}`} className="link">{p.document.title ?? "Document"}</Link>{p.page ? ` · p. ${p.page}` : ""}</div></div>34              ))}35            </div>36          ))}37          <p className="md:col-span-2 xl:col-span-5 text-[11.5px] text-ink-3">{res.note} {PARTY_IDS.map(partyLabel).join(", ")} — classement lexical sur {res.terms.join(", ")}.</p>38        </div>39      )}40    </Card>41  );42}43