feat(r5.3): écrans IA — assistant web, corpus administrable, suggestions OT/mobile

- Contrat (76 opérations) : Document expose inCorpus/indexedAt/chunkCount,
  PATCH /documents/{id}/corpus (ASSETS.edit), POST /assistant/reindex
  (bilan chiffré) ; ci-contract vérifie désormais aussi le client mobile.
- Web : page /assistant (chat sourcé — extraits exacts cités, Ouvrir vers
  PDF authentifié ou fiche OT, avertissement permanent ; refus honnête
  chiffré avec action utile) ; Bibliothèque = corpus (bandeau 09-08,
  statut d'indexation par document, interrupteur d'exclusion PDF,
  Réindexer tout) ; fiche OT : « Décrire pour suggérer » (Appliquer =
  geste humain, liseré « suggéré » retiré au choix manuel).
- Mobile : chips de suggestion dans la clôture (un appui = un champ
  pré-rempli, « réseau requis » hors-ligne — la file R4 n'en dépend pas).
- apps/ai : seuils AI_SEUIL_* configurables par env (CI + calibrage).
- CI e2e : service siop2-ai (embeddeur déterministe, seuils calibrés sur
  mesures : match 0,66 vs bruit 0,11) + parcours R5 Playwright (PDF généré
  xref valide → réindexation → réponse sourcée → refus → suggestion).
- Vérifié : 16/16 Playwright, 78 tests API, 23 pytest, 17 jest-expo ;
  chaîne réelle au vrai modèle ONNX (web 7/7, mobile Expo web 6/6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-17 17:26:22 +01:00
parent 45ae491827
commit 28eecc1fb9
31 changed files with 1431 additions and 24 deletions

View File

@@ -1016,3 +1016,91 @@ table {
border: 1px solid var(--bordure);
margin-bottom: 4px;
}
/* ═══ R5 · Assistant (chat sourcé — maquette-r5, écrans 1-2) ═══ */
.chat { display: flex; flex-direction: column; gap: 12px; max-width: 760px; }
.msg-q {
align-self: flex-end; background: var(--primaire); color: #fff;
border-radius: 14px 14px 4px 14px; padding: 10px 14px; max-width: 75%; font-size: 13.5px;
}
.msg-r {
background: var(--surface); border: 1px solid var(--bordure);
border-radius: 14px 14px 14px 4px; padding: 12px 14px; max-width: 88%;
font-size: 13.5px; display: flex; flex-direction: column; gap: 10px;
}
.msg-r p b { color: var(--encre); }
.cite {
display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px;
border-radius: 5px; background: var(--primaire-doux); color: var(--primaire);
font-size: 10.5px; font-weight: 800; vertical-align: 2px; margin: 0 1px;
}
.sources {
display: flex; flex-direction: column; gap: 6px;
border-top: 1px dashed var(--bordure); padding-top: 10px;
}
.source {
display: flex; gap: 10px; align-items: flex-start; background: var(--surface-2);
border-radius: 9px; padding: 8px 10px;
}
.source .no {
flex: none; width: 18px; height: 18px; border-radius: 5px; background: var(--primaire-doux);
color: var(--primaire); display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 800;
}
.source b { font-size: 12.5px; }
.source .ou { color: var(--encre-2); font-size: 11.5px; }
.source .extrait {
color: var(--encre-2); font-size: 12px; font-style: italic;
border-left: 2px solid var(--safran); padding-left: 8px; margin-top: 3px;
}
.source .ouvrir {
margin-left: auto; color: var(--primaire); font-weight: 700; font-size: 12px; white-space: nowrap;
}
.avert {
display: flex; gap: 8px; align-items: center; background: var(--safran-doux); color: var(--alerte);
border-radius: 9px; padding: 8px 10px; font-size: 12px; font-weight: 600;
}
.saisie-chat { display: flex; gap: 8px; max-width: 760px; }
.saisie-chat input {
flex: 1; border: 1.5px solid var(--bordure-forte); border-radius: 10px;
background: var(--surface); padding: 11px 13px; font: inherit; color: var(--encre);
}
.refus {
background: var(--surface); border: 1.5px dashed var(--bordure-forte); border-radius: 14px;
padding: 12px 14px; max-width: 88%; font-size: 13.5px;
display: flex; flex-direction: column; gap: 8px;
}
.refus b { color: var(--encre); }
.refus .pourquoi { color: var(--encre-2); font-size: 12.5px; }
/* ═══ R5 · Suggestion de bilan (écran 3) ═══ */
.zone-libre {
width: 100%; min-height: 74px; border: 1.5px solid var(--bordure-forte); border-radius: 10px;
background: var(--surface); padding: 10px 12px; font: inherit; font-size: 13px; color: var(--encre);
}
.suggestion {
display: flex; gap: 10px; align-items: flex-start; border: 1.5px solid var(--primaire);
background: var(--primaire-doux); border-radius: 10px; padding: 10px 12px;
}
.suggestion .ia { flex: none; font-size: 15px; }
.suggestion b { font-size: 13px; }
.suggestion .just { color: var(--encre-2); font-size: 12px; }
.confiance {
margin-left: auto; font-size: 10.5px; font-weight: 800; color: var(--primaire); white-space: nowrap;
}
.actions-sug { display: flex; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
/* Liseré « suggéré » : la valeur vient d'une suggestion APPLIQUÉE par l'humain */
.champ-b[data-suggere] { position: relative; }
.champ-b[data-suggere] select { border-color: var(--primaire); background: var(--primaire-doux); }
.champ-b[data-suggere]::after {
content: 'suggéré'; position: absolute; top: 14px; right: 8px; font-size: 9px; font-weight: 800;
color: var(--primaire); background: var(--surface); padding: 0 5px; border-radius: 99px;
border: 1px solid var(--primaire); pointer-events: none;
}
/* ═══ R5 · Corpus (écran 5) ═══ */
.st.ok { color: var(--st-termine); }
.st.ok::before { background: var(--st-termine); }
.st.exclu { color: var(--encre-3); }
.st.exclu::before { background: var(--encre-3); }
.interrupteur.corpus[aria-checked='true'] { background: var(--succes); }