From a9af2a521318630a64fd2aa750f95731bedded28 Mon Sep 17 00:00:00 2001 From: Jean-Marc Date: Thu, 10 Sep 2026 13:49:26 +0000 Subject: [PATCH] =?UTF-8?q?V1.19.21=20R=C3=A9flexions=20strat=C3=A9giques?= =?UTF-8?q?=20et=20gestion=20de=20la=20personnalit=C3=A9=20des=20collabora?= =?UTF-8?q?teurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- collaborateur_dialogue.php | 283 +++++++++ collaborateurs_identite.php | 130 ++++ ecran_fond.html | 635 +++++++++++++++++++- journal/ASPIC - Journal. Index.md | 13 +- journal/ASPIC - Reflexions strategiques.md | 37 ++ journal/ASPIC-Journal. 28. Ecran de fond.md | 49 ++ version.php | 2 +- 7 files changed, 1143 insertions(+), 6 deletions(-) create mode 100644 collaborateur_dialogue.php create mode 100644 collaborateurs_identite.php diff --git a/collaborateur_dialogue.php b/collaborateur_dialogue.php new file mode 100644 index 0000000..acb4cd4 --- /dev/null +++ b/collaborateur_dialogue.php @@ -0,0 +1,283 @@ + 'Non identifié — veuillez vous connecter.']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['error' => 'Méthode non autorisée']); + exit; +} + +const ANTHROPIC_MODEL = 'claude-sonnet-5'; +const ANTHROPIC_API_URL = 'https://api.anthropic.com/v1/messages'; +const ANTHROPIC_VERSION = '2023-06-01'; +const ANTHROPIC_MAX_TOKENS = 1024; + +$data = json_decode(file_get_contents('php://input'), true) ?? []; +$agnIncr = (int) ($data['AGN_Incr'] ?? 0); +$messages = $data['messages'] ?? []; + +if ($agnIncr <= 0) { + http_response_code(400); + echo json_encode(['error' => 'AGN_Incr manquant ou invalide']); + exit; +} +if (!is_array($messages) || !$messages) { + http_response_code(400); + echo json_encode(['error' => 'Aucun message à transmettre']); + exit; +} + +// trim() : même précaution que pour OPENAI_API_KEY (contact_ia_interprete.php) +// — un espace resté collé en copiant-collant la clé dans docker-compose/.env +// suffit à faire échouer l'authentification avec un message qui ne le laisse +// pas deviner. +$apiKey = trim((string) getenv('ANTHROPIC_API_KEY')); +if (!$apiKey) { + http_response_code(500); + echo json_encode(['error' => "Clé ANTHROPIC_API_KEY absente côté serveur — à ajouter dans l'environnement du conteneur avant de pouvoir tester."]); + exit; +} + +// ANTHROPIC_WORKSPACE_ID (optionnelle) — ajoutée le 10 septembre 2026 : +// premier essai réel de Jean-Marc en erreur HTTP 400, message Anthropic sans +// ambiguïté : « This API key is not scoped to a workspace, so this request +// must include the anthropic-workspace-id header ». Arrive avec une clé +// personnelle/de type "organisation" (pas liée à un espace de travail en +// particulier dans la Console) plutôt qu'avec une clé créée directement DANS +// un espace de travail (celles-ci n'ont pas besoin de cet en-tête). Les deux +// façons de résoudre, au choix de Jean-Marc : +// 1. Recréer la clé depuis platform.claude.com/settings/keys en choisissant +// explicitement un espace de travail précis (pas "Toute l'organisation") +// au moment de la création — alors cette variable n'est pas nécessaire. +// 2. Garder la clé actuelle et poser ANTHROPIC_WORKSPACE_ID (format +// wrkspc_..., visible dans les réglages de l'espace de travail concerné +// sur platform.claude.com) à côté d'ANTHROPIC_API_KEY dans l'environnement +// du conteneur. +// Header ajouté seulement si la variable est posée, pour ne rien changer au +// comportement le jour où Jean-Marc bascule sur une clé déjà liée à un espace +// de travail (option 1). +$workspaceId = trim((string) getenv('ANTHROPIC_WORKSPACE_ID')); + +try { + $pdo = getDbConnection(); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Connexion base de données impossible']); + exit; +} + +try { + $stmt = $pdo->prepare( + 'SELECT AGN_Prenom, AGN_Fonction, AGN_Identite FROM ASP_Agents WHERE AGN_Incr = ?' + ); + $stmt->execute([$agnIncr]); + $collaborateur = $stmt->fetch(); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]); + exit; +} + +if (!$collaborateur) { + http_response_code(404); + echo json_encode(['error' => 'Collaborateur introuvable']); + exit; +} + +$prenom = $collaborateur['AGN_Prenom'] ?: 'ce collaborateur'; +$fonction = $collaborateur['AGN_Fonction'] ?: ''; +$identite = trim((string) ($collaborateur['AGN_Identite'] ?? '')); + +$blocIdentite = $identite !== '' + ? $identite + : "(Aucune identité détaillée n'a encore été écrite pour {$prenom} — tiens-toi en à son rôle : {$fonction}.)"; + +// -------- Prompt système -------- +// Distinction "collègue de travail" / "assistant IA" / "compagnon virtuel" +// et règle de non-divulgation d'AGN_Identite : voir commentaire d'en-tête, +// issues directement de l'échange du 10 septembre 2026 avec Jean-Marc. +$promptSysteme = << $role, 'content' => $contenu]; +} +if (!$historique) { + http_response_code(400); + echo json_encode(['error' => 'Aucun message valide à transmettre']); + exit; +} + +// -------- Appel Anthropic -------- +$corpsRequete = [ + 'model' => ANTHROPIC_MODEL, + 'max_tokens' => ANTHROPIC_MAX_TOKENS, + 'system' => $promptSysteme, + 'messages' => $historique, +]; + +$entetes = [ + 'Content-Type: application/json', + 'anthropic-version: ' . ANTHROPIC_VERSION, + 'X-Api-Key: ' . $apiKey, +]; +// Voir commentaire ANTHROPIC_WORKSPACE_ID plus haut — en-tête ajouté +// uniquement si la variable est posée. +if ($workspaceId) { + $entetes[] = 'anthropic-workspace-id: ' . $workspaceId; +} + +$ch = curl_init(ANTHROPIC_API_URL); +curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($corpsRequete), + CURLOPT_HTTPHEADER => $entetes, + CURLOPT_TIMEOUT => 60, + CURLOPT_CONNECTTIMEOUT => 10, +]); +$reponseBrute = curl_exec($ch); +$erreurCurl = curl_error($ch); +$codeHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +if ($reponseBrute === false) { + http_response_code(502); + echo json_encode(['error' => "Appel à l'API Anthropic impossible : " . $erreurCurl]); + exit; +} + +$reponseDecodee = json_decode($reponseBrute, true); + +if ($codeHttp !== 200) { + http_response_code(502); + echo json_encode([ + 'error' => "L'API Anthropic a renvoyé une erreur (HTTP $codeHttp) : " . + ($reponseDecodee['error']['message'] ?? $reponseBrute), + ]); + exit; +} + +// Réponse Messages API : content est un tableau de blocs ({"type":"text", +// "text":"..."} pour une réponse texte simple, seul cas utilisé ici — pas +// d'appel d'outil configuré). +$texteReponse = ''; +foreach ($reponseDecodee['content'] ?? [] as $bloc) { + if (($bloc['type'] ?? '') === 'text') { + $texteReponse .= $bloc['text']; + } +} + +if ($texteReponse === '') { + http_response_code(502); + echo json_encode(['error' => "Réponse Anthropic inattendue (pas de contenu texte) : " . $reponseBrute]); + exit; +} + +echo json_encode(['reponse' => $texteReponse]); diff --git a/collaborateurs_identite.php b/collaborateurs_identite.php new file mode 100644 index 0000000..a0911c7 --- /dev/null +++ b/collaborateurs_identite.php @@ -0,0 +1,130 @@ + 'Non identifié — veuillez vous connecter.']); + exit; +} + +try { + $pdo = getDbConnection(); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Connexion base de données impossible']); + exit; +} + +$method = $_SERVER['REQUEST_METHOD']; + +if ($method === 'GET') { + try { + $stmt = $pdo->query( + 'SELECT AGN_Incr, AGN_Prenom, AGN_Fonction, AGN_Photo, AGN_Couleur, AGN_Identite + FROM ASP_Agents ORDER BY AGN_OrdreAffichage ASC' + ); + echo json_encode(['collaborateurs' => $stmt->fetchAll()]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]); + } + exit; +} + +if ($method === 'PUT') { + $data = json_decode(file_get_contents('php://input'), true) ?? []; + $incr = (int) ($data['AGN_Incr'] ?? 0); + // Champ volontairement libre, sans limite de longueur imposée ici + // (MEDIUMTEXT côté base, voir en-tête) : une identité bien écrite peut + // dépasser largement la taille d'un champ de fiche habituel (Nom, + // Ville...) de ce projet, et grandira encore avec l'usage réel de + // l'application — le seul garde-fou vit côté front-end (voir + // TEXTE_IDENTITE_MAX_OCTETS/compteur-identite dans ecran_fond.html). + $identite = trim((string) ($data['AGN_Identite'] ?? '')); + + if ($incr <= 0) { + http_response_code(400); + echo json_encode(['error' => 'AGN_Incr manquant ou invalide']); + exit; + } + + try { + // Existence vérifiée par un SELECT séparé plutôt que sur rowCount() de + // l'UPDATE (même précaution que contacts.php, section PUT — un texte + // identique au texte déjà enregistré donnerait rowCount() = 0 alors + // que le collaborateur existe bien). + $verif = $pdo->prepare('SELECT AGN_Incr FROM ASP_Agents WHERE AGN_Incr = ?'); + $verif->execute([$incr]); + if (!$verif->fetch()) { + http_response_code(404); + echo json_encode(['error' => 'Collaborateur introuvable']); + exit; + } + + $stmt = $pdo->prepare('UPDATE ASP_Agents SET AGN_Identite = ? WHERE AGN_Incr = ?'); + $stmt->execute([$identite !== '' ? $identite : null, $incr]); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]); + } + exit; +} + +http_response_code(405); +echo json_encode(['error' => 'Méthode non autorisée']); diff --git a/ecran_fond.html b/ecran_fond.html index cc57158..4ea3719 100644 --- a/ecran_fond.html +++ b/ecran_fond.html @@ -539,6 +539,254 @@ font-family:"Segoe UI","Arial",sans-serif; pointer-events:none; } + + /* Outils "Construire une identité" / "Tester l'identité" (10 septembre + 2026, réflexions stratégiques section 20 ; repositionnés et redesignés + le 10 septembre 2026 (suite), retour de Jean-Marc après un premier essai + réel) — deux boutons juste sous le panneau "L'équipe des collaborateurs", + positionnés par JS dans positionner() (comme panneauBureau lui-même : + largeur/police recalculées, pas de pourcentage fixe) puisqu'ils doivent + suivre la même zone sous les 2 dernières colonnes. */ + #outils-collaborateurs{ + position:absolute; + display:flex; + gap:8px; + pointer-events:auto; + } + .bouton-outil-collab{ + font:inherit; + font-family:"Segoe UI","Arial",sans-serif; + font-weight:700; + background:#fff; + color:#2a2a2a; + border:1px solid #999; + border-radius:5px; + cursor:pointer; + box-shadow:2px 3px 6px rgba(0,0,0,.3); + white-space:nowrap; + } + .bouton-outil-collab.actif{ + background:#2a2a2a; + color:#fff; + } + + /* Écran plein cadre "Construire une identité" / "Tester l'identité" (refonte + du 10 septembre 2026, retour explicite de Jean-Marc) : position:fixed sur + TOUT le viewport, au-dessus de tout le reste (fond photo, avant-plan, + portraits) — "efface complètement l'écran, y compris l'écran de fond". + Volontairement hors de #assistants-layer/#fonds-container : un simple + calque opaque par-dessus tout suffit à "effacer" visuellement sans avoir + à toucher à l'état des autres calques (rien à restaurer à la fermeture). + Element statique du body (jamais créé/attaché dynamiquement) : présent + dans le document dès le chargement de la page, contrairement au premier + essai (nœuds attachés à #assistants-layer, donc absents du document tant + que l'écran des collaborateurs n'avait pas encore été affiché une + première fois — cause du bug "écran blanc" remonté par Jean-Marc). */ + #overlay-collab{ + position:fixed; + inset:0; + z-index:500; + background:#1c1c1c; + display:flex; + flex-direction:column; + align-items:center; + box-sizing:border-box; + /* Haut/bas resserrés le 10 septembre 2026 (soir), demande de Jean-Marc : + remonter le titre et redescendre les boutons au plus près des bords, + pour laisser le plus de hauteur possible aux zones de saisie/dialogue + (ni la taille des photos ni celle du texte sous les photos n'y touche, + seul l'espacement autour change). */ + padding:10px 32px; + font-family:"Segoe UI","Arial",sans-serif; + color:#eee; + } + /* Bug trouvé en test automatisé le 10 septembre 2026 : l'ID selector + ci-dessus (display:flex) l'emporte en spécificité sur la règle UA + [hidden]{display:none}, donc SANS cette ligne #overlay-collab reste + affiché (et intercepte tous les clics, plein écran) même avec l'attribut + hidden posé — l'écran plein cadre bloquait alors silencieusement toute + la navigation dès le chargement de la page, hidden ou pas. */ + #overlay-collab[hidden]{ + display:none; + } + #overlay-collab-titre{ + font-size:16px; + font-weight:700; + color:#eee; + margin-bottom:8px; + } + .ligne-photos-collab{ + display:flex; + gap:22px; + flex-wrap:wrap; + justify-content:center; + margin-bottom:10px; + } + .photo-collab-selection{ + display:flex; + flex-direction:column; + align-items:center; + gap:6px; + cursor:pointer; + background:none; + border:none; + padding:6px; + border-radius:8px; + } + .photo-collab-selection:hover{ + background:rgba(255,255,255,.08); + } + .photo-collab-selection.selectionne{ + background:rgba(255,255,255,.16); + } + .photo-collab-selection img, + .photo-collab-selection .repli{ + width:72px;height:72px;border-radius:8px;object-fit:cover; + background:#3a3a3a;box-shadow:1px 2px 6px rgba(0,0,0,.4); + } + .photo-collab-selection .repli{ + display:flex;align-items:center;justify-content:center; + color:#999;font-weight:700; + } + .photo-collab-selection .prenom-collab{ + font-size:13px;font-weight:700;color:#f0f0f0; + } + .photo-collab-selection .fonction-collab{ + font-size:11px;font-weight:700;color:#aaa; + } + + .overlay-collab-corps{ + flex:1; + width:100%; + max-width:760px; + display:flex; + flex-direction:column; + min-height:0; + } + /* Même correction que #overlay-collab[hidden] ci-dessus, pour la même + raison : sans cette règle, corps-identite et corps-test resteraient + TOUS LES DEUX affichés en permanence (superposés), l'attribut hidden + posé par le script (voir ouvrirConstruireIdentite()/ouvrirTesterIdentite()) + n'ayant plus aucun effet face à ce display:flex d'origine auteur. */ + .overlay-collab-corps[hidden]{ + display:none; + } + /* Zone de saisie "terminal" (même esprit que les outils de mise au point + internes déjà livrés sur ce projet — contact_ia_test.html, + dictees_test.php) : fond noir, police mono, ascenseur si le texte + dépasse. */ + #texte-identite{ + flex:1; + min-height:0; + resize:none; + overflow-y:auto; + background:#000; + color:#ddd; + font-family:"Courier New",Consolas,monospace; + font-size:13px; + line-height:1.5; + border:1px solid #444; + border-radius:5px; + padding:12px; + } + .boutons-identite{ + display:flex; + justify-content:flex-end; + gap:10px; + margin-top:12px; + } + #compteur-identite{ + text-align:right; + font-size:11px; + color:#888; + margin-top:4px; + } + #compteur-identite.avertissement{color:#d9a84e;} + #compteur-identite.limite{color:#e14a5c;font-weight:700;} + .boutons-identite button{ + font:inherit;font-size:13px;font-weight:700;border-radius:5px; + padding:9px 20px;cursor:pointer; + } + .boutons-identite button:disabled{opacity:.4;cursor:default;} + #btn-abandonner-identite{ + background:#333;color:#eee;border:1px solid #555; + } + #btn-valider-identite{ + background:#2a4d2a;color:#bdf7bd;border:1px solid #3a6d3a; + } + #btn-fermer-test-identite{ + background:#333;color:#eee;border:1px solid #555; + } + #btn-chat-envoyer{ + background:#2a4d2a;color:#bdf7bd;border:1px solid #3a6d3a; + } + #message-identite{ + font-size:12px;margin-top:6px;min-height:14px; + } + #message-identite.ok{color:#7be07b;} + #message-identite.ko{color:#e14a5c;} + + /* Fenêtre "type session Claude" (Tester l'identité) : bulles alternées, + couleur du collaborateur (AGN_Couleur) pour ses propres bulles — même + teinte que sa carte sur l'écran des collaborateurs et déjà anticipée + pour cet usage (réflexions stratégiques, section 18/28). */ + #chat-messages{ + flex:1; + min-height:0; + overflow-y:auto; + background:#111; + border:1px solid #444; + border-radius:5px; + padding:14px; + display:flex; + flex-direction:column; + gap:10px; + } + .bulle-chat{ + max-width:75%; + padding:9px 13px; + border-radius:12px; + font-size:13px; + line-height:1.5; + white-space:pre-wrap; + } + .bulle-chat.moi{ + align-self:flex-end; + background:#f2d94e; + color:#2a2a2a; + border-bottom-right-radius:3px; + } + .bulle-chat.collab{ + align-self:flex-start; + color:#1a1a1a; + border-bottom-left-radius:3px; + } + .bulle-chat.systeme{ + align-self:center; + color:#999; + font-size:12px; + font-style:italic; + background:none; + } + /* Zone de saisie du message — sur sa propre ligne, deux lignes de haut par + défaut (attribut rows="2" côté HTML), Envoyer et Fermer regroupés + juste en dessous (voir .boutons-identite, réutilisée telle quelle ici : + 10 septembre 2026 soir, demande de Jean-Marc — même agencement que + "Construire une identité", Fermer/Envoyer à la place d'Abandonner/Valider). */ + #chat-input{ + width:100%; + box-sizing:border-box; + resize:none; + background:#000; + color:#ddd; + font:inherit; + font-size:13px; + border:1px solid #444; + border-radius:5px; + padding:9px 12px; + margin-top:12px; + } + #btn-chat-envoyer:disabled{opacity:.4;cursor:default;} @@ -592,6 +840,46 @@ Créé par Mevelia Solutions"> Écran suivant + + +