1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 * CREA AGENTS â la page des agents, et ce qui la sépare d'une maquette 3 * REND : dessiner 4 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5 * 6 * ââ CE QUI EXISTAIT ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 7 * 8 * Treize agents d'usine, un éditeur d'agent personnalisé complet, un menu qui 9 * les liste â et RIEN pour les regarder. Choisir un agent se faisait dans un 10 * menu déroulant de dix-neuf rem : on ne pouvait ni comparer, ni chercher, ni 11 * voir ce qu'un agent avait fait, ni savoir à quoi il a accès. 12 * 13 * ââ LA RÃGLE DE CETTE PAGE : ELLE NE MONTRE QUE DU VRAI ââââââââââââââââââ 14 * 15 * Une page d'agents est facile à dessiner et facile à mentir. Les trois 16 * onglets d'une fiche sont donc branchés sur ce qui existe déjà , et sur rien 17 * d'autre : 18 * 19 * ACTIVITà `STATE.bgTasks` filtrées par `agentId` â le champ est posé à 20 * la naissance de la tâche, dans `addBgTask`, à la porte où 21 * TOUTE tâche passe. Sans lui, cet onglet serait une vitr
21ine. 22 * APPLICATIONS `window.CREA_CONNECT` â le catalogue réel et l'état réel de 23 * chaque connecteur. Pas une liste de logos. 24 * MÃMOIRE `STATE.memories` â ce que CRÃA a retenu pour de bon. 25 * 26 * ET QUAND IL N'Y A RIEN, ON LE DIT. Un onglet vide qui affiche un cadre gris 27 * laisse croire à une panne ; il doit dire ce qui le remplirait. 28 * 29 * ââ LA COULEUR N'EST PAS UNE DÃCORATION, C'EST L'IDENTITà ââââââââââââââââ 30 * 31 * « C'est simple, c'est propre, mais c'est simple. » La première version 32 * rangeait treize agents en treize cartes grises : rien ne distinguait le 33 * Juriste du Vidéaste avant d'avoir LU les deux noms. Or on ne lit pas une 34 * liste, on la balaie. 35 * 36 * Chaque agent porte donc une teinte, DÃRIVÃE de son identifiant 37 * (`teinte()`) â pas tirée d'une table qu'il faudrait tenir à jour, pas 38 * choisie au hasard à chaque rendu. Le même agent a la même couleur à chaque 39 * ouverture, sur toutes les surfaces, et un agent créé demain en reçoit une 40 * sans qu'on touche à ce fichier. La teinte porte le glyphe ; la tuile en 41 * porte un voile. C'est ce qui rend la liste repérable de loin. 42 * 43 * ââ CE QUE CETTE PAGE NE FAIT PAS ââââââââââââââââââââââââââââââââââââââââ 44 * 45 * ⢠ELLE NE CRÃE NI NE MODIFIE UN AGENT. Cet écran existe déjà 46 * (`renderAgentEditor`, dans le tronc) ; en écrire un second ferait deux 47 * formulaires qui divergeraient au premier champ ajouté. La page l'OUVRE. 48 * ⢠ELLE N'INVENTE AUCUN ANNUAIRE. Les captures de référence en montrent un ; 49 * il n'existe pas ici, et dessiner des cartes vides serait une promesse. 50 * ⢠ELLE N'INVENTE AUCUNE AMORCE. Les captures montrent trois « démarreurs » 51 * par agent (« Créer une app », « Corriger un bug »). Aucun agent de CRÃA 52 * n'en porte : les écrire ici serait inventer des phrases et les faire 53 * passer pour la voix de l'agent. Les trois cartes de la fiche portent donc 54 * trois MESURES vraies â ce qu'il a fait, quand, ce qu'il atteint. 55 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 56;(function () { 57 'use strict' 58 59 function etat() { 60 var S = (typeof STATE !== 'undefined' && STATE) ? STATE : {} 61 if (!S.agentsPage || typeof S.agentsPage !== 'object') S.agentsPage = {} 62 var p = S.agentsPage 63 if (['tous', 'miens'].indexOf(p.onglet) < 0) p.onglet = 'tous' 64 if (['activite', 'applications', 'memoire'].indexOf(p.fiche) < 0) p.fiche = 'activite' 65 if (typeof p.recherche !== 'string') p.recherche = '' 66 return p 67 } 68 69 function agents() { return ((typeof STATE !== 'undefined' && STATE.agents) || []) } 70 function taches() { return ((typeof STATE !== 'undefined' && STATE.bgTasks) || []) } 71 72 function ech(t) { 73 return String(t == null ? '' : t) 74 .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') 75 .replace(/"/g, '"').replace(/'/g, ''') 76 } 77 78 function visage(a, classe) { 79 return (typeof window !== 'undefined' && window.CREA_VISAGE) 80 ? window.CREA_VISAGE.visage(a, classe) : '' 81 } 82 83 /* ââ LA TEINTE D'UN AGENT âââââââââââââââââââââââââââââââââââââââââââââââââ 84 Dérivée de l'identifiant, donc STABLE : le même agent garde sa couleur 85 d'une ouverture à l'autre, et un agent créé demain en reçoit une sans 86 qu'on vienne l'inscrire ici. Une table de couleurs par agent aurait 87 demandé une ligne de plus à chaque création â et l'aurait oubliée. */ 88 function teinte(id) { 89 var s = String(id || ''), h = 0 90 for (var i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360 91 return h 92 } 93 94 /* La tuile : un voile de la teinte, le glyphe dans la teinte pleine. Les 95 deux se tiennent sur fond clair comme sur fond sombre â c'est la MÃME 96 couleur à deux opacités, pas deux palettes à maintenir. */ 97 function tuile(a, px, rayon, glyphe) { 98 var t = teinte(a.id) 99 return `<span class="cap-tuile" style="width:${px}px;height:${px}px;border-radius:${rayon}px;` 100 + `background:hsl(${t} 70% 55% / .14);color:hsl(${t} 62% 48%)">` 101 + visage(a, glyphe) + '</span>' 102 } 103 104 /* « il y a ⦠» â la même façon de dire le temps que le panneau d'équipe. 105 Deux formulations différentes pour la même durée dans la même application, 106 c'est deux fois où l'on se demande si c'est la même chose. */ 107 function depuis(t) { 108 var ms = Date.now() - (Number(t) || Date.now()) 109 if (ms < 60000) return "à l'instant" 110 if (ms < 3600000) return 'il y a ' + Math.round(ms / 60000) + ' min' 111 if (ms < 86400000) return 'il y a ' + Math.round(ms / 3600000) + ' h' 112 var j = Math.round(ms / 86400000) 113 return 'il y a ' + j + ' jour' + (j > 1 ? 's' : '') 114 } 115 116 /* ââ LES TÃCHES D'UN AGENT ââââââââââââââââââââââââââââââââââââââââââââââââ 117 `agentId` est posé à la naissance. Une tâche née AVANT ce champ n'en a 118 pas : elle n'est attribuée à personne, et c'est plus honnête que de la 119 rattacher au hasard à l'agent courant. */ 120 function tachesDe(id) { 121 return taches().filter(function (t) { return t && t.agentId === id }) 122 } 123 124 function derniereUtilisation(id) { 125 var t = tachesDe(id)[0] 126 return t ? t.createdAt : null 127 } 128 129 function connecteurs() { 130 var C = (typeof window !== 'undefined') ? window.CREA_CONNECT : null 131 return (C && Array.isArray(C.catalog)) ? C : null 132 } 133 134 /* ââ LA FEUILLE DE STYLE ââââââââââââââââââââââââââââââââââââââââââââââââââ 135 Elle vit ici et pas dans le tronc : le survol, la transition et la grille 136 ne s'écrivent pas en `style=` sur chaque nÅud, et une classe posée dans 137 une feuille lointaine se serait décorrélée de la page qui l'utilise. Elle 138 est ré-émise avec la page, donc jamais accumulée : le nÅud est remplacé, 139 pas ajouté. */ 140 function styles() { 141 return `<style id="cap-css"> 142 .cap-tuile{display:flex;align-items:center;justify-content:center;flex-shrink:0} 143 .cap-pleine{border-radius:999px;padding:9px 18px;font-size:max(0.8125rem,13px);font-weight:600; 144 background:var(--t-primary);color:var(--bg-primary);transition:opacity .15s} 145 .cap-pleine:hover{opacity:.85} 146 .cap-creuse{border-radius:999px;padding:9px 16px;font-size:max(0.8125rem,13px); 147 color:var(--t-secondary);border:1px solid var(--border);transition:background .15s} 148 .cap-creuse:hover{background:var(--bg-secondary)} 149 .cap-onglet{padding:7px 15px;border-radius:999px;font-size:max(0.8125rem,13px); 150 color:var(--t-dim);transition:background .15s,color .15s} 151 .cap-onglet:hover{color:var(--t-secondary)} 152 .cap-onglet[aria-selected="true"]{background:var(--bg-secondary);color:var(--t-primary);font-weight:600; 153 box-shadow:inset 0 0 0 1px var(--border)} 154 .cap-rangee{display:flex;align-items:center;gap:14px;width:100%;text-align:left; 155 padding:13px 10px;border-radius:14px;transition:background .15s} 156 .cap-rangee:hover{background:var(--bg-secondary)} 157 .cap-nom{font-weight:600;font-size:max(0.875rem,14px);color:var(--t-primary); 158 white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 159 .cap-meta{font-size:max(0.75rem,12px);color:var(--t-dim);margin-top:2px; 160 white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
161 .cap-quand{font-size:max(0.6875rem,11px);color:var(--t-faint);flex-shrink:0; 162 font-variant-numeric:tabular-nums} 163 .cap-filet{height:1px;background:var(--border);margin:0 10px;opacity:.6} 164 .cap-heros{position:relative;overflow:hidden;border-radius:22px;padding:30px 28px; 165 background:linear-gradient(135deg,var(--accent) 0%,hsl(215 85% 52%) 52%,hsl(196 90% 48%) 100%)} 166 .cap-heros-titre{font-size:max(1.375rem,22px);font-weight:600;line-height:1.25;color:#fff; 167 margin:0 0 6px;text-wrap:balance;max-width:15ch} 168 .cap-heros-sous{font-size:max(0.8125rem,13px);color:rgba(255,255,255,.86);margin:0 0 18px;max-width:34ch;line-height:1.45} 169 .cap-heros-cta{border-radius:999px;padding:9px 18px;font-size:max(0.8125rem,13px);font-weight:600; 170 background:#0b0b0c;color:#fff;transition:transform .15s} 171 .cap-heros-cta:hover{transform:translateY(-1px)} 172 /* ELLES NE S'ÃTIRENT PAS. Une colonne en 1fr sur deux vignettes en 173 faisait deux panneaux de 380 px : la même carte change de forme selon 174 le nombre d'agents employés, et deux tailles pour un même objet se 175 lisent comme deux objets. Elles gardent leur largeur, à droite. */ 176 .cap-vitrine{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end} 177 .cap-vitrine>*{flex:1 1 150px;max-width:220px} 178 .cap-vignette{background:rgba(255,255,255,.97);border-radius:15px;padding:13px;text-align:left; 179 box-shadow:0 8px 26px rgba(9,20,44,.18);transition:transform .15s} 180 .cap-vignette:hover{transform:translateY(-2px)} 181 .cap-vignette-nom{display:block;font-weight:600;font-size:max(0.8125rem,13px);color:#12131a;margin-top:9px} 182 .cap-vignette-sous{display:block;font-size:max(0.6875rem,11px);color:#5d6172;margin-top:2px;line-height:1.35; 183 overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical} 184 .cap-chiffres{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:22px 0} 185 .cap-chiffre{border:1px solid var(--border);border-radius:15px;padding:14px 16px;background:var(--bg-secondary)} 186 .cap-chiffre-quoi{display:block;font-size:max(0.6875rem,11px);color:var(--t-faint); 187 text-transform:uppercase;letter-spacing:.05em} 188 .cap-chiffre-valeur{display:block;font-size:max(1.0625rem,17px);font-weight:600;color:var(--t-primary);margin-top:5px; 189 font-variant-numeric:tabular-nums} 190 .cap-etiquette{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:3px 10px; 191 font-size:max(0.6875rem,11px);color:var(--t-dim);background:var(--bg-secondary); 192 box-shadow:inset 0 0 0 1px var(--border)} 193 @media (max-width:640px){.cap-heros{padding:24px 20px}} 194 </style>` 195 } 196 197 /* ââ LE HÃROS âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 198 Il ne montre PAS des modèles inventés : il montre trois agents réels, et 199 son titre dit lequel des deux cas on regarde. Un bandeau qui annonce 200 « vos plus sollicités » devant trois agents jamais employés serait la 201 première chose fausse de la page. */ 202 function heros() { 203 var l = agents().slice() 204 var employes = l.filter(function (a) { return tachesDe(a.id).length > 0 }) 205 var trois = (employes.length 206 ? employes.sort(function (a, b) { return tachesDe(b.id).length - tachesDe(a.id).length }) 207 : l).slice(0, 3) 208 var vecu = employes.length > 0 209 210 return `<div class="cap-heros" style="display:grid;gap:26px;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);align-items:center"> 211 <div> 212 <h2 class="cap-heros-titre">${vecu ? 'Ceux avec qui vous travaillez' : 'Commencez avec un cadre'}</h2> 213 <p class="cap-heros-sous">${vecu 214 ? 'CRÃA reste la même â le cadre change, pas elle. Reprenez celui qui vous sert, ou créez le vôtre.' 215 : 'Un agent est un cadre que CRÃA prend le temps d'une demande. Choisissez-en un, ou écrivez le vôtre.'}</p> 216 <button onclick="window.CREA_AGENTS_PAGE.creer()" class="cap-heros-cta">Créer un agent</button> 217 </div> 218 <div class="cap-vitrine">${trois.map(function (a) { 219 return `<button onclick="window.CREA_AGENTS_PAGE.ouvrirFiche('${ech(a.id)}')" class="cap-vignette">` 220 + tuile(a, 32, 9, 'w-[17px] h-[17px]') 221 + `<span class="cap-vignette-nom">${ech(a.name || 'Agent')}</span>` 222 + (a.desc ? `<span class="cap-vignette-sous">${ech(a.desc)}</span>` : '') 223 + '</button>' 224 }).join('')}</div> 225 </div>` 226 } 227 228 /* ââ LA LISTE âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 229 230 function rangee(a) { 231 var derniere = derniereUtilisation(a.id) 232 var n = tachesDe(a.id).length 233 return `<button onclick="window.CREA_AGENTS_PAGE.ouvrirFiche('${ech(a.id)}')" class="cap-rangee">` 234 + tuile(a, 40, 12, 'w-[20px] h-[20px]') 235 + '<span style="min-width:0;flex:1">' 236 + `<span class="cap-nom" style="display:block">${ech(a.name || 'Agent')}</span>` 237 + `<span class="cap-meta" style="display:block">${a.custom ? 'Créé par vous' : 'Fourni avec CRÃA'}` 238 + (a.desc ? ' · ' + ech(a.desc) : '') + '</span></span>' 239 /* PAS DE TIRET DANS LA COLONNE VIDE. « â » sur dix rangées sur onze fait 240 une colonne de tirets, c'est-à -dire du bruit qui ressemble à une 241 donnée. Le chevron, lui, dit la même chose à toutes : ça s'ouvre. */ 242 + `<span class="cap-quand">${derniere ? ech(depuis(derniere)) + (n > 1 ? ' · ' + n + ' travaux' : '') : ''}</span>` 243 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ' 244 + 'style="width:15px;height:15px;flex-shrink:0;color:var(--t-faint)" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>' 245 + '</button>' 246 } 247 248 function liste() { 249 var p = etat() 250 var q = p.recherche.trim().toLowerCase() 251 var l = agents().filter(function (a) { 252 if (p.onglet === 'miens' && !a.custom) return false 253 if (!q) return true 254 return (String(a.name || '') + ' ' + String(a.desc || '')).toLowerCase().indexOf(q) >= 0 255 }) 256 257 var onglet = function (id, texte) { 258 return `<button onclick="window.CREA_AGENTS_PAGE.onglet('${id}')" class="cap-onglet" role="tab" ` 259 + `aria-selected="${p.onglet === id ? 'true' : 'false'}">${texte}</button>` 260 } 261 262 /* RIEN à MONTRER SE DIT, ET DIT QUOI FAIRE. « Aucun agent » tout court 263 laisse devant un écran vide sans savoir si c'est une panne. */ 264 var vide = p.onglet === 'miens' 265 ? '<p style="color:var(--t-dim);font-size:max(0.8125rem,13px);padding:34px 10px;line-height:1.55;max-width:52ch">Vous n\'avez pas encore créé d\'agent. Un agent, c\'est un nom, une description et des instructions â CRÃA reste la même, elle prend juste ce cadre-là .</p>' 266 : '<p style="color:var(--t-dim);font-size:max(0.8125rem,13px);padding:34px 10px">Aucun agent ne correspond à « ' + ech(p.recherche) + ' ».</p>' 267 268 var rangees = l.map(function (a, i) { 269 return (i ? '<div class="cap-filet"></div>' : '') + rangee(a) 270 }).join('') 271 272 return `<div style="max-width:900px;margin:0 auto;padding:0 4px 48px"> 273 <div style="display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap;margin-bottom:22px"> 274 <div style="flex:1;min-width:220px"> 275 <h1 style="font-size:max(1.875rem,30px);font-weight:600;color:var(--t-primary);margin:0 0 4px;letter-spacing:-.015em">Agents</h1> 276 <p style="color:var(--t-dim);font-size:max(0.875rem,14px);margin:0">Un cadre de travail que CRÃA prend le temps d'une demande.</p> 277 </div> 278 <div style="display:flex;gap:8px;align-items:center"> 279 <input id="agents-recherche" oninput="window.CREA_AGENTS_PAGE.chercher(this)" value="${ech(p.recherche)}" 280 placeholder="Rechercher un agent" aria-label="Rechercher un agent" 281 style="width:210px;max-width:46vw;border-radius:999px;padding:9px 16px;font-size:max(0.8125rem,13px);outline:none;background:var(--bg-input);color:var(--t-primary);border:1px solid var(--border)" /> 282 <button onclick="window.CREA_AGENTS_PAGE.creer()" class="cap-pleine">Créer</button> 283 </div> 284 </div> 285 ${/* LE HÃROS NE PARAÃT QUE DEVANT LA LISTE ENTIÃRE. Il montre trois 286 agents choisis par leur usage, donc hors du filtre en cours : 287 laissé en place pendant une recherche, il aurait affiché des 288 agents que la recherche venait d'écarter, juste au-dessus d'eux. 289 Et un bandeau au-dessus de « aucun résultat » est le pire des 290 deux â on croit avoir trouvé quelque chose. */''} 291 ${(p.onglet === 'tous' && !q) ? heros() : ''} 292 <div role="tablist" style="display:flex;gap:4px;margin:26px 0 6px">${onglet('tous', 'Tous')}${onglet('miens', 'Créés par moi')}</div> 293 ${l.length ? rangees : vide} 294 </div>` 295 } 296 297 /* ââ LA FICHE âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 298 299 function activite(a) { 300 var l = tachesDe(a.id) 301 if (!l.length) { 302 return bloc('Aucun travail de fond lancé sous cet agent.', 303 'Choisissez-le dans la barre, puis demandez un travail long â il apparaîtra ici, avec son état.') 304 } 305 return l.slice(0, 40).map(function (t, i) { 306 var mot = t.status === 'done' ? 'Terminée' : t.status === 'error' ? 'En échec' : t.status === 'arretee' ? 'Arrêtée' : 'En cours' 307 var couleur = t.status === 'error' ? 'var(--danger)' : t.status === 'done' ? 'var(--success)' : 'var(--accent)' 308 return (i ? '<div class="cap-filet"></div>' : '') 309 + '<div style="display:flex;gap:12px;align-items:center;padding:13px 10px">' 310 + `<span style="width:8px;height:8px;border-radius:999px;background:${couleur};flex-shrink:0"></span>` 311 + `<span style="flex:1;min-width:0"><span class="cap-nom" style="display:block">${ech(t.label)}</span>` 312 + `<span class="cap-meta" style="display:block">${mot}</span></span>` 313 + `<span class="cap-quand">${ech(depuis(t.completedAt || t.createdAt))}</span>` 314 + '</div>' 315 }).join('') 316 } 317 318 function applications() { 319 var C = connecteurs() 320 if (!C) { 321 return bloc('Les connecteurs ne sont pas chargés sur cette page.', 'Rien n\'est cassé â cet onglet lit le poste des connecteurs, et il n\'est pas là .') 322 } 323 /* CE QU'UN AGENT PEUT ATTEINDRE N'EST PAS PROPRE à L'AGENT. Les connecteurs 324 sont ceux du COMPTE : un agent n'a pas ses propres clés. Le dire est plus 325 utile que de laisser croire à des droits par agent qui n'existent pas. */ 326 return '<p style="font-size:max(0.75rem,12px);color:var(--t-dim);margin:0 0 14px;padding:0 10px;line-height:1.5;max-width:60ch">Ces accès sont ceux de votre compte, pas de cet agent : un agent ne porte pas ses propres clés. Il atteint ce que vous avez branché.</p>' 327 + C.catalog.map(function (c, i) { 328 var on = typeof C.estBranche === 'function' ? C.estBranche(c.id) : false 329 return (i ? '<div class="cap-filet"></div>' : '') 330 + '<div style="display:flex;align-items:center;gap:12px;padding:13px 10px">' 331 + `<span style="width:8px;height:8px;border-radius:999px;flex-shrink:0;background:${on ? 'var(--success)' : 'var(--border)'}"></span>` 332 + `<span style="flex:1;min-width:0" class="cap-nom">${ech(c.name)}</span>` 333 + `<span class="cap-quand" style="color:${on ? 'var(--success)' : 'var(--t-faint)'}">${on ? 'Branché' : 'Non branché'}</span>` 334 + '</div>' 335 }).join('') 336 } 337 338 function memoire() { 339 var m = ((typeof STATE !== 'undefined' && STATE.memories) || []) 340 if (!m.length) { 341 return bloc('CRÃA n\'a rien retenu pour l\'instant.', 342 'Ce qu\'elle retient vient de vos conversations, et vous pouvez le relire et l\'effacer depuis Réglages ⺠Mémoire.') 343 } 344 /* LA MÃMOIRE EST CELLE DE CRÃA, PAS CELLE D'UN AGENT. La ranger sous un 345 agent laisserait croire qu'en changer efface ce qu'elle sait. */ 346 return '<p style="font-size:max(0.75rem,12px);color:var(--t-dim);margin:0 0 14px;padding:0 10px;line-height:1.5;max-width:60ch">Cette mémoire est celle de CRÃA, pas de cet agent : changer d\'agent n\'efface rien.</p>'
347 + m.slice(0, 30).map(function (x, i) { 348 var texte = typeof x === 'string' ? x : (x && (x.text || x.contenu || x.value)) || '' 349 return (i ? '<div class="cap-filet"></div>' : '') 350 + '<div style="padding:13px 10px;font-size:max(0.8125rem,13px);color:var(--t-primary);line-height:1.5">' + ech(texte) + '</div>' 351 }).join('') 352 } 353 354 function bloc(titre, quoi) { 355 return '<div style="padding:34px 10px;max-width:52ch"><p style="font-size:max(0.875rem,14px);color:var(--t-secondary);margin:0 0 5px">' + ech(titre) + '</p>' 356 + '<p style="font-size:max(0.8125rem,13px);color:var(--t-dim);margin:0;line-height:1.55">' + ech(quoi) + '</p></div>' 357 } 358 359 /* LES TROIS CHIFFRES DE LA FICHE. Les captures de référence mettent ici trois 360 AMORCES (« Créer une app »). Aucun agent de CRÃA n'en porte : les écrire 361 serait inventer des phrases et les faire passer pour sa voix. Trois mesures 362 vraies tiennent la même place, et elles, on peut les prouver. */ 363 function chiffres(a) { 364 var l = tachesDe(a.id) 365 var C = connecteurs() 366 var branches = C ? C.catalog.filter(function (c) { 367 return typeof C.estBranche === 'function' ? C.estBranche(c.id) : false 368 }).length : null 369 var carte = function (quoi, valeur) { 370 return `<div class="cap-chiffre"><span class="cap-chiffre-quoi">${quoi}</span><span class="cap-chiffre-valeur">${valeur}</span></div>` 371 } 372 return '<div class="cap-chiffres">' 373 + carte('Travaux de fond', l.length ? l.length : 'Aucun') 374 + carte('Dernière fois', l.length ? ech(depuis(derniereUtilisation(a.id))) : 'Jamais') 375 + carte('Applications', C ? branches + ' sur ' + C.catalog.length : 'Non chargées') 376 + '</div>' 377 } 378 379 function fiche(a) { 380 var p = etat() 381 var ong = function (id, texte) { 382 return `<button onclick="window.CREA_AGENTS_PAGE.ongletFiche('${id}')" class="cap-onglet" role="tab" ` 383 + `aria-selected="${p.fiche === id ? 'true' : 'false'}">${texte}</button>` 384 } 385 var corps = p.fiche === 'applications' ? applications() : p.fiche === 'memoire' ? memoire() : activite(a) 386 387 return `<div style="max-width:820px;margin:0 auto;padding:0 4px 48px"> 388 <button onclick="window.CREA_AGENTS_PAGE.retour()" style="font-size:max(0.8125rem,13px);color:var(--t-dim);margin-bottom:22px">â Tous les agents</button> 389 ${tuile(a, 60, 18, 'w-[30px] h-[30px]')} 390 <div style="display:flex;gap:14px;align-items:flex-start;margin-top:16px;flex-wrap:wrap"> 391 <div style="flex:1;min-width:220px"> 392 <div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"> 393 <h1 style="font-size:max(1.75rem,28px);font-weight:600;color:var(--t-primary);margin:0;letter-spacing:-.015em">${ech(a.name || 'Agent')}</h1> 394 <span class="cap-etiquette">${a.custom ? 'Créé par vous' : 'Fourni avec CRÃA'}</span> 395 </div> 396 ${a.desc ? `<p style="font-size:max(0.9375rem,15px);color:var(--t-secondary);margin:8px 0 0;line-height:1.5">${ech(a.desc)}</p>` : ''} 397 </div> 398 <div style="display:flex;gap:8px;flex-shrink:0"> 399 ${a.custom ? `<button onclick="window.CREA_AGENTS_PAGE.modifier('${ech(a.id)}')" class="cap-creuse">Modifier</button>` : ''} 400 <button onclick="window.CREA_AGENTS_PAGE.choisir('${ech(a.id)}')" class="cap-pleine">Travailler avec</button> 401 </div> 402 </div> 403 ${chiffres(a)} 404 <div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:15px;padding:14px 16px;margin-bottom:26px"> 405 <p style="font-size:max(0.6875rem,11px);color:var(--t-faint);margin:0 0 7px;text-transform:uppercase;letter-spacing:.05em">Le cadre qu'il pose</p> 406 ${a.prompt 407 ? `<p style="font-size:max(0.8125rem,13px);color:var(--t-secondary);margin:0;line-height:1.55;white-space:pre-wrap">${ech(a.prompt)}</p>` 408 /* L'ASSISTANT N'A PAS DE CADRE, ET C'EST LE FOND DU DÃPÃT : c'est CRÃA 409 telle qu'elle est, par la gorge. Ãcrire « Tu es CRÃA⦠» ici aurait 410 été une seconde CRÃA. Le dire vaut mieux qu'un cadre vide. */ 411 : '<p style="font-size:max(0.8125rem,13px);color:var(--t-dim);margin:0;line-height:1.55">Cet agent ne pose aucun cadre : c\'est CRÃA telle qu\'elle est.</p>'} 412 </div> 413 <div role="tablist" style="display:flex;gap:4px;margin-bottom:6px">${ong('activite', 'Activité')}${ong('applications', 'Applications')}${ong('memoire', 'Mémoire')}</div> 414 ${corps} 415 </div>` 416 } 417 418 /* ââ L'ÃCRAN ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 419 420 function rendre() { 421 var p = etat() 422 var a = p.agent ? agents().find(function (x) { return x.id === p.agent }) : null 423 return '<div id="agents-page" class="fixed inset-0 z-[55] overflow-y-auto" style="background:var(--bg-primary)">' 424 + styles() 425 + '<div style="position:sticky;top:0;z-index:2;background:var(--bg-primary);padding:12px 18px;display:flex;justify-content:flex-end">' 426 + '<button onclick="window.CREA_AGENTS_PAGE.fermer()" aria-label="Fermer la page des agents" ' 427 + 'style="border-radius:999px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;color:var(--t-dim)">' 428 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="width:17px;height:17px" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>' 429 + '</button></div>' 430 + '<div style="padding:10px 18px 0">' + (a ? fiche(a) : liste()) + '</div>' 431 + '</div>' 432 } 433 434 /* ââ LES GESTES VIVENT ICI, PAS DANS LE TRONC ââââââââââââââââââââââââââââ 435 * 436 * Même convention que le portail du Dispatch : l'écran est rendu par ce 437 * poste, donc il porte ses propres gestes en `onclick`. Poser des écouteurs 438 * dans `bindEvents()` aurait demandé au tronc de connaître chaque bouton de 439 * cette page â et d'en oublier un au prochain ajout. 440 * 441 * `render()` et `save()` sont ceux du tronc : ce poste ne redessine pas 442 * l'application, il demande au tronc de le faire. */ 443 function redessiner() { if (typeof render === 'function') render() } 444 function enregistrer() { if (typeof save === 'function') save() } 445 446 function pageOuvrirFiche(id) { var p = etat(); p.agent = id; p.fiche = 'activite'; redessiner() } 447 function pageRetour() { etat().agent = null; redessiner() } 448 function pageOnglet(id) { etat().onglet = id; redessiner() } 449 function pageOngletFiche(id) { etat().fiche = id; redessiner() } 450 function pageFermer() { if (typeof STATE !== 'undefined') STATE.agentsPageOpen = false; redessiner() } 451 452 /* CRÃER ET MODIFIER OUVRENT L'ÃDITEUR QUI EXISTE (`renderAgentEditor`, dans 453 le tronc). En écrire un second ferait deux formulaires qui divergeraient 454 au premier champ ajouté. */ 455 function pageCreer() { 456 if (typeof STATE === 'undefined') return 457 STATE.editingAgent = null; STATE.agentEditorOpen = true; redessiner() 458 } 459 function pageModifier(id) { 460 var a = agents().find(function (x) { return x.id === id }) 461 if (!a || typeof STATE === 'undefined') return 462 STATE.editingAgent = Object.assign({}, a); STATE.agentEditorOpen = true; redessiner() 463 } 464 465 /* TRAVAILLER AVEC : on choisit l'agent ET on ferme la page. Rester dessus 466 après avoir choisi obligerait à la fermer soi-même pour aller écrire â le 467 geste dit « je pars travailler ». */ 468 function pageChoisir(id) { 469 var a = agents().find(function (x) { return x.id === id }) 470 if (!a || typeof STATE === 'undefined') return 471 STATE.selectedAgent = a 472 STATE.councilMode = false 473 STATE.systemPrompt = a.prompt || '' 474 STATE.agentsPageOpen = false 475 enregistrer(); redessiner() 476 } 477 478 /* LA RECHERCHE NE PASSE PAS PAR `render()`. Un rendu complet à chaque touche 479 reprend le focus au champ : on taperait une lettre sur deux. On remplace 480 la page, puis on remet le curseur là où il était. */ 481 function pageChercher(el) { 482 etat().recherche = el.value 483 var page = document.getElementById('agents-page') 484 if (!page) return 485 var pos = el.selectionStart 486 page.outerHTML = rendre() 487 var neuf = document.getElementById('agents-recherche') 488 if (!neuf) return 489 neuf.focus() 490 try { neuf.setSelectionRange(pos, pos) } 491 catch (e) { /* un champ qui ne sait pas placer son curseur ne vaut pas de perdre la frappe */ } 492 } 493 494 /* LES INTERNES PORTENT UN NOM à EUX, LES CLÃS RESTENT COURTES â la convention 495 du portail du Dispatch (`dispatchAller` exposé sous `aller`). Ce n'est pas 496 cosmétique : une fonction qui n'est nommée qu'à son export, et appelée 497 seulement depuis une chaîne `onclick`, est comptée MORTE par l'anal
497yseur 498 (tools/champs-morts) â il ne lit pas dans les chaînes. Le préfixe dit à qui 499 elle appartient, et la clé dit ce qu'on en fait. */ 500 if (typeof window !== 'undefined') window.CREA_AGENTS_PAGE = { 501 rendre: rendre, ouvrirFiche: pageOuvrirFiche, retour: pageRetour, onglet: pageOnglet, 502 ongletFiche: pageOngletFiche, fermer: pageFermer, creer: pageCreer, modifier: pageModifier, 503 choisir: pageChoisir, chercher: pageChercher, 504 } 505})()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.