v2 - new interface, many new page for fine tuning

This commit is contained in:
pporcheret
2026-10-28 21:45:40 +01:00
parent a6169bcfdb
commit a17c3cb614
89 changed files with 8691 additions and 13 deletions
@@ -0,0 +1,71 @@
/* Alfred Glass Admin — identité visuelle de l'accueil Glass V1 */
:root{--glass-border:#b8d9fa32;--glass-field:#08182bd8;--glass-shadow:0 16px 45px #0006;--glass-text:#eff7ff}
body.alfred-admin-glass{color:var(--glass-text);background:#0b1726;font-family:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;min-height:100dvh}
body.alfred-admin-glass::before{content:"";position:fixed;inset:-18px;z-index:-2;pointer-events:none;background:url('/static/img/alfred-ambience.webp') center/cover no-repeat;filter:blur(4px) saturate(1.05)}
body.alfred-admin-glass::after{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;background:linear-gradient(120deg,#07101ce8,#06111f90 45%,#051324d5 100%)}
.alfred-admin-glass .admin-layout{max-width:none;padding:0 30px 65px 132px;min-height:100dvh}
.alfred-admin-glass .admin-header{height:94px;border:0;gap:20px;padding:10px 5px;background:transparent}
.alfred-admin-glass .brand{gap:15px}
.alfred-admin-glass .brand-orb{width:53px;height:53px;border-radius:15px;font-size:32px;background:linear-gradient(140deg,#7acaff,#0866f0);box-shadow:0 10px 25px #006cff45}
.alfred-admin-glass .brand strong{font-size:28px;font-weight:700;letter-spacing:-.04em;text-shadow:0 2px 10px #0007}
.alfred-admin-glass .brand small{color:#e4efff;font-size:12px}
.alfred-admin-glass .admin-header nav{gap:9px}
.alfred-admin-glass .admin-header nav a,.alfred-admin-glass .admin-header nav button{color:#d9e6f6;border:1px solid #d6e7ff2b;border-radius:23px;background:#0e1c2dbd;backdrop-filter:blur(14px);font-size:12px;padding:10px 15px}
.alfred-admin-glass .admin-header nav .selected{background:linear-gradient(145deg,#188af2,#0750c4);border-color:#6bbcff8a;color:white}
.alfred-admin-glass .ag-rail{position:fixed;z-index:8;top:113px;bottom:24px;left:14px;width:98px;padding:13px 8px;border-radius:20px;background:#081321e8;backdrop-filter:blur(18px);border:1px solid #d7eaff24;display:flex;flex-direction:column;gap:12px;box-shadow:var(--glass-shadow)}
.alfred-admin-glass .ag-rail a{min-height:87px;border-radius:14px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:6px;text-align:center;font-size:12px;color:#e7f2ff}
.alfred-admin-glass .ag-rail a:hover{background:#2b405dbb}
.alfred-admin-glass .ag-rail a.is-active{background:linear-gradient(145deg,#008bff,#0a58d9);box-shadow:0 8px 23px #007afa55}
.alfred-admin-glass .ag-rail a span{font-size:28px;line-height:1.2}
.alfred-admin-glass .admin-main{padding-top:23px;max-width:1800px;margin:0 auto}
.alfred-admin-glass .page-top{margin:6px 0 23px;align-items:center}
.alfred-admin-glass .page-top h1{font-size:clamp(31px,3vw,47px);font-weight:680;color:#fff;text-shadow:0 2px 12px #0008}
.alfred-admin-glass .eyebrow{color:#74bfff;letter-spacing:.18em}
.alfred-admin-glass .page-top p.muted{color:#d8e6f6}
.alfred-admin-glass .panel,.alfred-admin-glass .metric-card{background:linear-gradient(130deg,#15243ae8,#111c30d9)!important;border:1px solid var(--glass-border);border-radius:21px;box-shadow:var(--glass-shadow);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}
.alfred-admin-glass .panel:hover{border-color:#9bcaff75}
.alfred-admin-glass .panel-title{border-color:#dfeaff22}
.alfred-admin-glass .panel-title h2{font-size:1.14rem;font-weight:680;color:white}
.alfred-admin-glass .panel-title p{color:#b5cce7}
.alfred-admin-glass .panel-symbol{width:47px;height:47px;background:#144d827d;border-radius:14px;color:#bbebff;font-size:23px}
.alfred-admin-glass .field label{color:#e4f3ff}
.alfred-admin-glass .field small,.alfred-admin-glass .muted{color:#b6c9dc}
.alfred-admin-glass .field input:not([type=range]):not([type=checkbox]),.alfred-admin-glass .field select,.alfred-admin-glass .auth-box input{background:var(--glass-field);color:white;border:1px solid #9bbbde61;border-radius:12px;min-height:45px}
.alfred-admin-glass .field input:focus,.alfred-admin-glass .field select:focus,.alfred-admin-glass .ag-row-fields :focus{outline:2px solid #48a9fc;outline-offset:2px}
.alfred-admin-glass .button{border-radius:12px;background:#182b42db;border-color:#9bbadd54;color:#edf7ff}
.alfred-admin-glass .button.primary{background:linear-gradient(135deg,#058bff,#0755d3);border-color:#68beffaf;box-shadow:0 8px 20px #0875f637}
.alfred-admin-glass .button.danger{background:#653039;border-color:#cf7583}
.alfred-admin-glass .notice{background:#153e2cdb;border-color:#50bf85a1}
.alfred-admin-glass .tag{background:#10283ebd;border-color:#88c8ff6b;color:#e0f5ff}
.alfred-admin-glass .metric-icon{background:#25619d74;color:#dcf1ff;border:1px solid #80c7ff75}
.alfred-admin-glass .metric-card{min-height:170px}
.alfred-admin-glass .status-pill[data-state="ok"]{background:#0c62395c;border-color:#3ac98b8c;color:#a3f9cd}
.alfred-admin-glass .meter{background:#070f1ca8}
.alfred-admin-glass .meter span{background:linear-gradient(90deg,#0569cf,#49b7ff)}
.alfred-admin-glass .service-row{border-color:#ffffff20}
.alfred-admin-glass .service-label{color:#eff7ff}
.alfred-admin-glass .service-sub,.alfred-admin-glass .mcp-item p{color:#b0cae2}
.alfred-admin-glass .mcp-item{background:#182e46a8;border:1px solid #aac8eb38;border-radius:14px}
.alfred-admin-glass .auth-screen{background:radial-gradient(ellipse at center,#152741e2,#061222ef 88%);backdrop-filter:blur(16px)}
.alfred-admin-glass .auth-box{background:#14253cde;border:1px solid #a2c7ee60;backdrop-filter:blur(20px)}
.alfred-admin-glass .monitor-grid{gap:16px}
.alfred-admin-glass .two-col{gap:18px}
.alfred-admin-glass .ag-maison-editor{margin-top:19px;scroll-margin-top:25px}
.alfred-admin-glass .ag-help{color:#d2e5fa;font-size:13px;line-height:1.65;margin:0 0 18px}
.alfred-admin-glass .ag-maison-rows{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:13px}
.alfred-admin-glass .ag-row{background:#09172ad9;border:1px solid #99c5eb47;border-radius:16px;padding:15px;min-width:0}
.alfred-admin-glass .ag-row-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:13px}
.alfred-admin-glass .ag-row-header strong{font-size:14px;color:#ecf7ff}
.alfred-admin-glass .ag-row-buttons{display:flex;gap:5px}
.alfred-admin-glass .ag-small-button{width:30px;height:30px;border-radius:8px;border:1px solid #9cb6d45b;background:#1a3350;color:white;font-size:18px;cursor:pointer}
.alfred-admin-glass .ag-small-button:hover{background:#2278be}
.alfred-admin-glass .ag-small-button:disabled{opacity:.4}
.alfred-admin-glass .ag-row-fields{display:grid;grid-template-columns:1fr;gap:10px}
.alfred-admin-glass .ag-field{display:flex;flex-direction:column;gap:6px;min-width:0;font-size:12px;color:#c9def4}
.alfred-admin-glass .ag-field input,.alfred-admin-glass .ag-field select{width:100%;min-width:0;min-height:43px;padding:9px 12px;color:white;background:#111f32;border:1px solid #95bce76e;border-radius:10px}
.alfred-admin-glass .ag-maison-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:10px;margin-top:18px}
.alfred-admin-glass #agMaisonMessage{margin-top:10px;min-height:19px;color:#a7e5c9}
.alfred-admin-glass #agMaisonMessage.ag-error{color:#ffb4ad}
@media(max-width:1250px){.alfred-admin-glass .ag-maison-rows{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:900px){.alfred-admin-glass .admin-layout{padding-left:115px;padding-right:14px}.alfred-admin-glass .ag-rail{left:8px;width:92px}.alfred-admin-glass .ag-maison-rows{grid-template-columns:1fr}}
@media(max-width:650px){.alfred-admin-glass .admin-layout{padding:0 12px 40px}.alfred-admin-glass .ag-rail{position:relative;inset:auto;width:auto;height:auto;margin:12px 0 6px;flex-direction:row;justify-content:space-around;padding:5px}.alfred-admin-glass .ag-rail a{min-height:58px;flex:1;font-size:11px}.alfred-admin-glass .ag-rail a span{font-size:22px}.alfred-admin-glass .admin-header{height:auto;min-height:85px;flex-wrap:wrap}.alfred-admin-glass .admin-header nav{display:flex;justify-content:flex-end}.alfred-admin-glass .page-top{align-items:flex-start}.alfred-admin-glass .ag-maison-actions{justify-content:flex-start}}
@@ -0,0 +1 @@
:root{color-scheme:dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;color:#f0f5ff;--line:rgba(191,215,255,.18);--muted:#b9c6dc;--blue:#388fff}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:linear-gradient(120deg,rgba(5,12,26,.78),rgba(7,13,27,.66)),url('/static/img/alfred-ambience.webp') center/cover fixed #0e1b2c;font-size:15px}.id-head{display:flex;justify-content:space-between;align-items:center;gap:18px;padding:22px clamp(20px,3.5vw,64px);background:rgba(10,18,33,.4);border-bottom:1px solid var(--line);backdrop-filter:blur(20px)}.id-brand{display:flex;gap:13px;align-items:center;color:inherit;text-decoration:none}.id-letter{font-size:45px;line-height:1;font-weight:850;color:#398dff;min-width:45px}.id-brand strong{display:block;font-size:24px}.id-brand small{display:block;color:var(--muted);font-size:12px}.id-head nav{display:flex;gap:9px;flex-wrap:wrap}.id-head nav a{color:#cbd8ed;text-decoration:none;padding:10px 13px;border-radius:11px}.id-head nav a:hover,.id-head nav a.active{background:rgba(51,127,244,.25);color:#fff}.id-main{width:min(1480px,95vw);margin:35px auto 60px}.id-title{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:24px}.id-eyebrow,.id-kicker{font-size:11px;color:#73b6ff;letter-spacing:.13em;font-weight:700}.id-title h1{font-size:clamp(28px,3.4vw,42px);margin:2px 0 7px}.id-title p:not(.id-eyebrow){color:var(--muted);margin:0}.id-pill{border:1px solid var(--line);border-radius:30px;padding:9px 13px;white-space:nowrap;color:#b5d6f6;background:rgba(13,29,48,.54)}.id-pill.ok{color:#75f0c0}.id-panel{background:rgba(11,20,37,.57);border:1px solid var(--line);border-radius:23px;padding:25px;backdrop-filter:blur(19px);box-shadow:0 14px 38px rgba(0,0,0,.15)}.id-layout{display:grid;grid-template-columns:minmax(0,1fr) 310px;gap:23px}.id-side{display:flex;flex-direction:column;gap:20px}.id-panel h2{margin:0 0 12px;font-size:19px}.id-panel p{line-height:1.55;color:var(--muted)}.id-panel a{color:#7cbaff}.id-editor-title{display:flex;justify-content:space-between;gap:8px;align-items:start}.id-editor-title h2{margin:0}.id-editor-title p{font-size:13px;color:var(--muted);margin:8px 0 18px}#idCount{font-size:12px;color:var(--muted);white-space:nowrap}#idText{display:block;width:100%;min-height:55vh;resize:vertical;padding:20px;border-radius:15px;border:1px solid rgba(174,212,255,.25);outline:none;background:rgba(4,13,24,.78);color:#f2f5fb;font:14px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;tab-size:2}#idText:focus{border-color:#409cff;box-shadow:0 0 0 3px rgba(52,139,255,.14)}.id-tip{font-size:12px}.id-buttons{display:flex;justify-content:flex-end;gap:12px;flex-wrap:wrap;margin-top:20px}.id-button{cursor:pointer;border:1px solid var(--line);background:rgba(24,45,72,.65);border-radius:12px;color:white;padding:12px 18px;font:inherit}.id-button:hover{border-color:#5baaff}.id-button.primary{border:0;background:linear-gradient(115deg,#147bff,#3d99f5);font-weight:650}.id-button:disabled{opacity:.48;cursor:wait}.id-current{display:flex;align-items:center;gap:16px;margin:17px 0}.id-current>span{font-weight:800;font-size:42px;color:#499eff}.id-current strong{display:block;font-size:23px}.id-current small{display:block;color:var(--muted);font-size:12px}.id-example{font:12px/1.65 ui-monospace,Consolas,monospace;color:#d4e4fa;background:rgba(4,14,27,.7);border-radius:12px;padding:14px}#idMessage,#idLoginMessage{min-height:21px;font-size:14px;color:#a6f1dc}#idMessage.error,#idLoginMessage.error{color:#ffb2b2}#idLoginPanel{width:min(450px,100%)}#idLoginPanel form{display:grid;gap:12px}#idPassword{border-radius:10px;border:1px solid var(--line);color:white;background:rgba(6,18,34,.7);padding:13px;font:inherit}.id-sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}[hidden]{display:none!important}@media(max-width:920px){.id-layout{grid-template-columns:1fr}.id-side{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}}@media(max-width:650px){.id-head{align-items:start;flex-direction:column}.id-title{align-items:start;flex-direction:column}.id-main{width:94vw}.id-panel{padding:17px}#idText{min-height:50vh}}
@@ -0,0 +1,40 @@
/* ALFRED_LISTES_GLASS_V1 — conserve l'Accueil Glass et ajoute un panneau réversible. */
.alfred-main-v1 .av-shortcuts {grid-template-columns: repeat(8,minmax(0,1fr));}
.av-rail-link.al-rail-link {color:#e0ebf6;}
.av-rail-link.al-rail-link svg {height:24px;width:24px;}
#alfredListsGlassDialog {box-sizing:border-box;width:min(1080px,95vw);max-width:95vw;max-height:min(870px,90dvh);margin:auto;
padding:0;overflow:hidden;color:#eaf3ff;border:1px solid #c6e5ff58;border-radius:24px;
background:linear-gradient(150deg,#11283beb,#0c1728f5 65%);box-shadow:0 30px 100px #000e;backdrop-filter:blur(22px);font:inherit;}
#alfredListsGlassDialog::backdrop {background:#030a13cf;backdrop-filter:blur(9px);}
#alfredListsGlassDialog * {box-sizing:border-box;}
.al-list-head {display:flex;justify-content:space-between;gap:18px;align-items:center;padding:28px 31px 20px;border-bottom:1px solid #aad8ff29;}
.al-eyebrow {margin:0 0 5px;color:#86cbff;letter-spacing:.16em;font-size:11px;font-weight:700;}
.al-list-head h2 {font-size:clamp(24px,3vw,34px);margin:0 0 7px;font-weight:740;letter-spacing:-.035em;}
.al-subtitle {margin:0;color:#b9c9de;font-size:13px;line-height:1.4;}
.al-close {color:#eff8ff;font:normal 31px/1 Arial;cursor:pointer;background:#1f3855;border:1px solid #b5d5f55c;border-radius:12px;width:44px;height:44px;}
.al-list-grid {display:grid;grid-template-columns:1fr 1fr;gap:19px;padding:24px 29px 20px;overflow:auto;max-height:calc(min(870px,90dvh) - 182px);}
.al-list-section {min-width:0;background:#111f32c9;border:1px solid #a4d1f53d;border-radius:19px;padding:20px;}
.al-list-sec-head {display:flex;align-items:center;gap:14px;margin-bottom:22px;}
.al-list-sec-icon {display:grid;place-items:center;background:#1266ae44;color:#5dbaff;width:47px;height:47px;border-radius:14px;flex-shrink:0;}
.al-list-sec-icon.al-task-icon {background:#755afc42;color:#c0b4ff;}
.al-list-sec-head h3 {font-size:18px;margin:0 0 4px;}.al-list-sec-head p {font-size:12px;color:#a8c1dc;margin:0;}
.al-add-form {display:flex;gap:8px;margin-bottom:18px;}
.al-add-form input {background:#081525ed;border:1px solid #90b3d45c;border-radius:11px;color:white;flex:1;min-width:0;padding:12px;font:inherit;font-size:13px;}
.al-add-form input:focus {outline:2px solid #379df5;outline-offset:1px;}
.al-add-form button {background:#087fea;color:white;border:1px solid #5fb1ff;border-radius:11px;padding:10px 12px;font-weight:700;cursor:pointer;white-space:nowrap;}
.al-items {list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:9px;}
.al-item {display:flex;align-items:center;gap:12px;background:#ffffff0a;border:1px solid #7aafd128;border-radius:12px;min-height:49px;padding:7px 9px;}
.al-check {width:27px;height:27px;border-radius:9px;flex:none;background:#0e1e2b;color:#fff;border:1.5px solid #6999bb;cursor:pointer;font-weight:800;}
.al-item.is-done .al-check {background:#147b68;border-color:#36d6a7;}
.al-item-label {min-width:0;flex:1;overflow-wrap:anywhere;font-size:14px;line-height:1.4;}
.al-item.is-done .al-item-label {text-decoration:line-through;color:#8eacb5;}
.al-remove {color:#f5c7cf;background:none;border:none;font-size:25px;line-height:1;cursor:pointer;padding:4px 8px;border-radius:6px;}
.al-remove:hover {background:#ef587d33;}
.al-empty {color:#a8bdce;font-size:13px;padding:22px 10px;text-align:center;}
.al-footer {display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid #aad8ff29;padding:15px 29px;font-size:12px;color:#9fcbf4;}
.al-footer .al-error {color:#ff9ca9;}.al-footer button {padding:8px 13px;color:#fff;border:1px solid #6f9ec677;border-radius:9px;background:#1b3550;cursor:pointer;}
#alfredListsGlassDialog button:focus-visible {outline:2px solid #70c6ff;outline-offset:2px;}
@media(max-width:1370px) {.alfred-main-v1 .av-shortcuts{grid-template-columns:repeat(4,minmax(0,1fr));} }
@media(max-width:900px) {.alfred-main-v1 .av-rail {grid-template-columns:repeat(7,1fr);}}
@media(max-width:740px) {.al-list-grid{grid-template-columns:1fr;}.al-list-head{padding:20px 16px 15px;}.al-list-grid{padding:15px;}.al-footer{padding:12px 15px;}.al-list-section{padding:14px;}}
@media(max-width:550px) {.alfred-main-v1 .av-shortcuts{grid-template-columns:repeat(3,minmax(0,1fr));}}
+16
View File
@@ -0,0 +1,16 @@
:root{color-scheme:dark;font-family:Inter,system-ui,-apple-system,'Segoe UI',sans-serif;color:#f0f6fd;background:#0c1521}*{box-sizing:border-box}
body{min-height:100vh;margin:0;background:linear-gradient(180deg,rgba(5,15,26,.77),rgba(5,15,26,.62)),url('/static/img/alfred-ambience.webp') center/cover fixed no-repeat;}
button,a,input,select{font:inherit}a{color:inherit;text-decoration:none}button{cursor:pointer}
.lg-header{display:flex;justify-content:space-between;align-items:center;gap:22px;padding:24px clamp(20px,3vw,45px);background:rgba(5,14,24,.55);backdrop-filter:blur(16px);border-bottom:1px solid #ffffff19;}
.lg-brand{display:flex;align-items:center;gap:14px}.lg-letter{font-size:46px;font-weight:900;line-height:1;background:linear-gradient(145deg,#70cfff,#006cff);color:transparent;background-clip:text;}
.lg-brand strong{font-size:25px;display:block;line-height:1}.lg-brand small{color:#bccde2;font-size:12px;display:block;margin-top:6px}
.lg-nav{display:flex;gap:7px;flex-wrap:wrap}.lg-nav a{padding:10px 14px;border-radius:11px;color:#d1e2f2}.lg-nav a:hover,.lg-nav a.active{background:rgba(35,113,233,.67);color:#fff}
.lg-shell{max-width:1600px;margin:auto;padding:26px clamp(14px,3vw,45px) 40px;}
.lg-title{display:flex;align-items:flex-end;justify-content:space-between;gap:25px;margin-bottom:23px}.lg-title h1{font-size:clamp(27px,3vw,44px);margin:4px 0}.lg-title p{margin:8px 0;color:#c8d9eb;font-size:14px}.lg-eyebrow{color:#70b6ff!important;letter-spacing:.12em;font-size:11px!important;font-weight:800}
.lg-actions{display:flex;gap:10px;flex-wrap:wrap}button{background:rgba(19,77,145,.8);border:1px solid #8ebfe858;color:#fff;border-radius:11px;padding:11px 15px}button:hover{background:rgba(25,111,217,.92)}
.lg-filters,.lg-table-wrap{border:1px solid rgba(195,220,252,.22);background:rgba(9,21,36,.57);backdrop-filter:blur(18px);border-radius:19px;box-shadow:0 18px 60px #0003;}
.lg-filters{display:flex;align-items:end;flex-wrap:wrap;gap:15px;padding:18px;margin-bottom:18px}.lg-filters label{display:flex;flex-direction:column;gap:7px;color:#c9daed;font-size:12px;font-weight:650}.lg-filters input[type=search]{width:min(360px,70vw)}.lg-filters input,.lg-filters select{background:rgba(3,14,29,.72);color:white;border:1px solid #87acd04e;border-radius:10px;min-height:40px;padding:8px 11px}.lg-filters .lg-check{flex-direction:row;align-items:center;min-height:40px}.lg-check input{min-height:0;width:16px;height:16px}
.lg-table-wrap{padding:16px}.lg-table-top{display:flex;justify-content:space-between;align-items:center;gap:10px;border-bottom:1px solid #c3d9ef26;padding:4px 6px 14px;color:#bbdefd;font-size:13px}.lg-events{max-height:62vh;min-height:260px;overflow:auto;scrollbar-color:#6995b3 transparent}.lg-event{display:grid;grid-template-columns:180px 94px 106px minmax(0,1fr);gap:12px;padding:12px 7px;border-bottom:1px solid #a9c7df18;align-items:start;font-size:13px}.lg-event time{color:#a6bcd0;font-variant-numeric:tabular-nums}.lg-component{font-weight:700;color:#8cc2ff}.lg-level{color:#afd6f5}.lg-event[data-level='Erreur'] .lg-level{color:#ff9898}.lg-event[data-level='Avertissement'] .lg-level{color:#ffd18a}.lg-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.5;color:#e5eff9}.lg-note{color:#adbfd0;font-size:12px;line-height:1.6;padding:12px 6px 0}.lg-note code{color:#c8e6ff}.lg-empty{padding:30px;color:#adc6dc;text-align:center}
:focus-visible{outline:2px solid #98d2ff;outline-offset:3px}
@media(max-width:850px){.lg-header,.lg-title{flex-wrap:wrap}.lg-event{grid-template-columns:95px 65px 1fr;gap:6px}.lg-event .lg-message{grid-column:1/-1;padding-left:2px}}
@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
+156
View File
@@ -0,0 +1,156 @@
/* ALFRED_MAIN_VISUAL_V1 — overlay of the existing functional Alfred UI. */
body.alfred-glass-theme { color:#f5f8fc; overflow:hidden; background:linear-gradient(180deg,#07101b80 0%,#09152352 42%,#05111ed1 100%),url('/static/img/alfred-ambience.webp') center center / cover fixed no-repeat,#14202b; }
body.alfred-glass-theme::before { content:""; position:fixed; inset:-50px; z-index:-3;
background:url('/static/img/alfred-ambience.webp') center center / cover no-repeat;
filter:blur(3px) saturate(105%); transform:scale(1.06); }
body.alfred-glass-theme::after { content:""; position:fixed; inset:0; z-index:-2;
background:linear-gradient(180deg,#07101b7c 0%,#09152353 42%,#05111ebc 100%); pointer-events:none; }
.alfred-glass-theme .alfred-shell { display:block;min-height:100dvh;padding:0;gap:0;isolation:auto; }
.alfred-glass-theme .alfred-shell > .topbar { position:absolute; left:-9999px; top:-9999px; pointer-events:none; }
.alfred-glass-theme .alfred-shell > .topbar #audioSettingsButton { pointer-events:auto; }
.alfred-glass-theme .audio-popover { position:fixed!important;top:78px!important;right:24px!important;left:auto!important;z-index:300!important; }
.alfred-glass-theme .alfred-main-v1 { position:fixed;inset:0;z-index:1;display:grid;grid-template-columns:98px minmax(0,1fr);
grid-template-rows:95px minmax(190px,215px) minmax(170px,1fr) 114px 86px;
gap:12px 15px;padding:0 17px 15px 0;overflow:hidden; font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif; }
.alfred-main-v1 * { box-sizing:border-box; }
.alfred-main-v1 button,.alfred-main-v1 a { font:inherit; }
.alfred-main-v1 button { cursor:pointer; }
.alfred-main-v1 a { color:inherit;text-decoration:none; }
.alfred-main-v1 :is(button,a,textarea):focus-visible { outline:2px solid #9bd7ff;outline-offset:3px; }
.alfred-main-v1 .a-icon { width:22px;height:22px;flex:0 0 auto; }
.alfred-main-v1 .av-head { grid-column:1/-1;grid-row:1;display:flex;align-items:center;justify-content:space-between;padding:14px 24px 12px 30px;min-width:0; }
.av-brand { display:flex;align-items:center;gap:16px; }
.av-logo { font-size:54px;line-height:1;font-weight:900;letter-spacing:-.12em;padding-right:9px;
background:linear-gradient(145deg,#65c6ff,#006af7 85%);background-clip:text;-webkit-background-clip:text;color:transparent;
filter:drop-shadow(0 3px 12px #007bff55); }
.av-brand strong { display:block;font-size:29px;line-height:1.05;letter-spacing:-.03em; }
.av-brand small { display:block;color:#d7e4f0;font-size:13px;margin-top:5px; }
.av-header-right { display:flex;align-items:center;gap:30px; }
.av-time { display:flex;flex-direction:column;text-align:right;white-space:nowrap; }
.av-time strong { font-size:33px;line-height:1.05;font-weight:670;font-variant-numeric:tabular-nums;letter-spacing:-.04em; }
.av-time span { font-size:12px;color:#d5e3f1;margin-top:6px; }
.av-head-actions { display:flex;align-items:center;gap:9px; }
.av-round { display:grid;place-items:center;flex:0 0 auto;width:40px;height:40px;border:1px solid #ffffff16;border-radius:50%;
background:#111b28ba;color:#f1f6fc;box-shadow:0 5px 12px #0002;backdrop-filter:blur(10px); }
.av-round:hover,.av-rail-link:hover,.av-shortcuts button:hover {background:#2c415abc;}
.av-round[aria-pressed="true"] {color:#ff858b;background:#54232fe8;border-color:#ff88906f;}
.alfred-main-v1 .av-rail {grid-column:1;grid-row:2/6;background:#09111ee3;border:1px solid #ffffff15;border-radius:0 19px 19px 0;
display:flex;flex-direction:column;align-items:center;justify-content:space-evenly;gap:5px;padding:9px 5px;backdrop-filter:blur(16px); }
.av-rail-link { border:0;color:#e5f0fc;background:transparent;border-radius:14px;width:83px;min-height:68px;display:flex;align-items:center;flex-direction:column;
justify-content:center;gap:7px;transition:background .2s;font-size:12px; }
.av-rail-link.selected {background:linear-gradient(155deg,#0988ff,#0758e0);box-shadow:0 5px 20px #007aff44;}
.av-rail-link .a-icon {width:25px;height:25px;}
.alfred-main-v1 .av-summary {grid-column:2;grid-row:2;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;min-height:0;}
.av-stat-card { background:linear-gradient(135deg,#142235df,#111e30c9);border:1px solid #c7d8f22f;border-radius:20px;backdrop-filter:blur(18px);
text-align:left;color:#eef7ff;padding:21px 22px;box-shadow:0 12px 30px #00000026;min-width:0;overflow:hidden;position:relative; }
.av-stat-card:hover {border-color:#8ecbff79;background:linear-gradient(135deg,#203650ed,#14273fe8);}
.av-card-top {display:flex;align-items:center;gap:13px;}
.av-card-top > span:nth-child(2) {min-width:0;flex:1;}
.av-card-top strong {font-size:18px;line-height:1.3;font-weight:690;display:block;}
.av-card-top small {font-size:11px;line-height:1.6;display:block;color:#bdcde0;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.av-circle {display:grid;place-items:center;flex:0 0 54px;height:54px;border-radius:18px;background:#2e507787;color:#dff1ff;}
.av-circle .a-icon {width:28px;height:28px;}
.av-circle.ai {background:#5733b794;color:#e6d5ff;}.av-circle.weather {background:#dba53b36;color:#ffeab2;}
.av-chevron {width:18px!important;opacity:.8;}
.av-lines {margin-top:18px;display:grid;gap:10px;}
.av-lines p {margin:0;display:flex;align-items:center;gap:10px;min-width:0;color:#e1edfa;font-size:13px;line-height:1.2;}
.av-lines p .a-icon {width:17px;height:17px;color:#7bc9ff;}
.av-lines p span:last-child {overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
.av-mini-dot {display:inline-block;width:9px;height:9px;border-radius:50%;flex-shrink:0;background:#8696a8;box-shadow:0 0 0 3px #ffffff08;}
.av-mini-dot.is-ok {background:#23e98a;box-shadow:0 0 8px #23e98a57;}
.av-mini-dot.is-error {background:#ff8d7e;}
.av-weather {background:linear-gradient(135deg,#203a54ea,#172c47d7);}
.av-weather-body {display:flex;align-items:center;gap:13px;margin:14px 0 0 2px;}
.av-weather-icon {font-size:52px;filter:drop-shadow(0 8px 9px #10182766);}
.av-weather-body div {display:flex;flex-direction:column;min-width:0;}
.av-weather-body strong {font-size:38px;font-weight:600;letter-spacing:-.05em;white-space:nowrap;}
.av-weather-body span {font-size:14px;color:#e2ecfa;}
.av-weather-body small {margin-top:4px;color:#bed5eb;font-size:12px;}
.alfred-main-v1 .av-voice-hero {grid-column:2;grid-row:3;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;min-height:170px;}
.av-voice-controls {display:flex;align-items:center;justify-content:center;gap:clamp(18px,6vw,120px);width:100%;z-index:1;}
.av-voice-center {display:flex;align-items:center;flex-direction:column;min-width:260px;text-align:center;}
.av-mic-orb {height:clamp(116px,12.5vw,178px);width:clamp(116px,12.5vw,178px);border:3px solid #23acff;
border-radius:50%;background:radial-gradient(circle at 50% 45%,#0e529b,#001a44 70%);box-shadow:0 0 0 8px #0a76ff1a,0 0 32px #0084ffb9,inset 0 0 34px #0672f06e;
display:grid;place-items:center;margin-bottom:12px;transition:filter .2s,box-shadow .2s;}
.av-mic-orb .a-icon {width:58px;height:58px;stroke-width:1.65;filter:drop-shadow(0 2px 5px #dff3ff44);}
.av-voice-center > strong {font-size:clamp(22px,2vw,32px);font-weight:680;text-shadow:0 2px 14px #07111bcc;}
.av-voice-center > p {margin:3px 0 7px;color:#e4ecf7;font-size:clamp(12px,1.1vw,16px);text-shadow:0 1px 8px #000b;}
.av-status-line {display:flex;align-items:center;gap:9px;font-size:11px;color:#d1e7f8;background:#0513268f;border:1px solid #ffffff20;border-radius:30px;padding:4px 12px;}
.av-status-dot {height:7px;width:7px;border-radius:50%;background:#42e893;}
.av-sound-control {width:130px;min-height:124px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;
background:#101c2ada;border:1px solid #9fc6f664;border-radius:55px;color:#eff6ff;box-shadow:0 12px 23px #0003;backdrop-filter:blur(12px); }
.av-sound-control .a-icon {width:30px;height:30px;margin-bottom:1px;color:#1d9eff;}
.av-sound-control strong {font-size:14px;}.av-sound-control small {font-size:12px;color:#2af0a1;}
.av-sound-control[aria-pressed="true"] {border-color:#fe778a;color:#ffe0e3;background:#45232de8;}
.av-sound-control[aria-pressed="true"] .a-icon,.av-sound-control[aria-pressed="true"] small {color:#ff808b;}
.av-wave-host {position:absolute;left:50%;top:43%;width:min(620px,65%);height:120px;transform:translate(-50%,-50%);pointer-events:none;opacity:.75;}
.alfred-glass-theme .av-wave-host #wave {position:relative;display:block;width:100%;height:120px;max-width:none;margin:0;}
.alfred-glass-theme .av-wave-host #wave canvas {width:100%;height:100%;filter:drop-shadow(0 0 3px #1688ff);}
.alfred-main-v1 .av-shortcuts {grid-column:2;grid-row:4;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:11px;min-height:0;}
.av-shortcuts button {display:flex;align-items:center;justify-content:center;flex-direction:column;gap:8px;color:#f2f7fc;
border:1px solid #b0ccea55;border-radius:16px;background:#111d2ce8;backdrop-filter:blur(14px);box-shadow:0 6px 25px #0003;padding:8px;min-width:0;}
.av-shortcuts .a-icon {width:27px;height:27px;color:#d7eafb;}.av-shortcuts span {font-size:12px;line-height:1.15;font-weight:590;text-align:center;}
.alfred-main-v1 .av-history {grid-column:2;grid-row:5;display:flex;align-items:center;gap:12px;background:#0b1828e8;border:1px solid #b0ccea4a;border-radius:16px;min-width:0;padding:10px 17px;overflow:hidden;backdrop-filter:blur(14px);}
.av-history-heading {display:flex;align-items:center;gap:10px;white-space:nowrap;font-size:14px;padding-right:16px;border-right:1px solid #abc7e84a;}
.av-history-heading .a-icon {width:24px;height:24px;}
.av-history #avHistoryList {display:flex;align-items:center;gap:0;min-width:0;flex:1;overflow-x:auto;scrollbar-width:thin;}
.av-history-item {display:inline-flex;gap:8px;align-items:center;padding:0 14px;border-right:1px solid #a1bed735;font-size:12px;white-space:nowrap;min-width:0;}
.av-history-item time {color:#9ec7ec;font-variant-numeric:tabular-nums;}.av-history-item span {max-width:240px;overflow:hidden;text-overflow:ellipsis;}
.av-history-empty {font-size:12px;color:#a4b9ca;padding-left:10px;}
.alfred-main-v1 .av-back {position:fixed;top:112px;right:32px;z-index:15;display:flex;align-items:center;gap:8px;color:white;
padding:9px 14px;border:1px solid #d5e8ff88;border-radius:10px;background:#183452ee;font-size:13px;}
.av-back[hidden],.av-toast[hidden] {display:none!important;}
.av-toast {position:fixed;z-index:80;bottom:115px;right:22px;padding:12px 18px;background:#142b43ed;color:#f0faff;border-radius:12px;border:1px solid #9ccfff8c;max-width:min(460px,90vw);box-shadow:0 12px 35px #0009;}
.av-image-dialog {width:min(520px,94vw);background:#162538;color:#f5f9fd;border:1px solid #7fa9c85a;border-radius:18px;padding:26px;box-shadow:0 20px 100px #000c;}
.av-image-dialog::backdrop {background:#040b14d2;backdrop-filter:blur(6px);}
.av-image-dialog h2 {font-size:21px;margin:0 0 12px;}.av-image-dialog p {font-size:13px;color:#cad7e8;line-height:1.6;}
.av-image-dialog label {display:block;font-size:13px;font-weight:700;margin:14px 0 7px;}
.av-image-dialog textarea {width:100%;background:#07182a;color:#fff;border:1px solid #9db7ce7e;border-radius:10px;padding:12px;resize:vertical;font:inherit;}
.av-dialog-close {float:right;background:#284259;border:1px solid #ffffff30;border-radius:8px;color:white;padding:6px;}
.av-dialog-actions {display:flex;justify-content:flex-end;margin-top:14px;}.av-dialog-actions button {padding:10px 16px;background:#0677ec;border:0;border-radius:10px;color:#fff;}
.av-more-grid {display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:20px;}.av-more-grid a,.av-more-grid button {display:flex;align-items:center;gap:8px;padding:12px;background:#243950;color:#fff;border:1px solid #ffffff26;border-radius:10px;text-align:left;font-size:13px;}
.alfred-glass-theme #stage {position:fixed;left:calc(98px + 26px);right:25px;top:114px;bottom:103px;z-index:8;display:flex;align-items:center;justify-content:center;
opacity:0;visibility:hidden;pointer-events:none;padding:28px;background:#0a1625e8;border:1px solid #d4e8ff42;border-radius:23px;backdrop-filter:blur(21px);overflow:auto; }
.alfred-glass-theme #content {max-width:100%;width:100%;max-height:100%;overflow:auto;}
.alfred-glass-theme .alfred-main-v1.is-focused ~ #stage {opacity:1;visibility:visible;pointer-events:auto;}
.alfred-main-v1.is-focused .av-summary,.alfred-main-v1.is-focused .av-voice-hero,.alfred-main-v1.is-focused .av-shortcuts {opacity:0;pointer-events:none;}
.alfred-glass-theme .transcript-panel {z-index:18;right:24px;bottom:111px;width:min(390px,33vw);border:1px solid #91b4d14f;background:#101e2ddb;min-height:75px;padding:10px 14px;box-shadow:0 12px 30px #0006;}
.alfred-glass-theme .transcript-panel .transcript-text {font-size:12px;max-height:62px;margin-top:6px;}
.alfred-glass-theme .transcript-panel:not([data-active="true"]) {opacity:.62;transition:opacity .2s;}
.alfred-glass-theme .transcript-panel:not([data-active="true"]):hover {opacity:1;}
.alfred-glass-theme #alfredTimersDock {z-index:25;left:118px;bottom:112px;width:300px;max-height:35vh;background:#101e2def;border-color:#75b7e47e;}
.alfred-glass-theme #alfredTimerToast {z-index:40;bottom:220px;left:119px;}
.alfred-glass-theme .camera-panel:not(.camera-popup-body) {display:none;}
.alfred-glass-theme.av-legacy-camera-open .camera-panel:not(.camera-popup-body) {display:block;position:fixed;top:100px;right:35px;left:auto;bottom:auto;width:480px;max-width:90vw;z-index:120;background:#172637f2;}
#avLegacyCameraClose {border:0;background:#2a557a;color:white;padding:8px 15px;width:100%;}
.alfred-main-v1[data-mode="listening"] .av-mic-orb {box-shadow:0 0 0 10px #16bfff22,0 0 42px #0084ff,inset 0 0 35px #0572f3;animation:av-breathe 1.4s ease-in-out infinite;}
.alfred-main-v1[data-mode="thinking"] .av-mic-orb {border-color:#f2c16a;box-shadow:0 0 28px #d89c2866;}
.alfred-main-v1[data-mode="speaking"] .av-mic-orb {border-color:#51f2ae;box-shadow:0 0 32px #51f2ae55;}
.alfred-main-v1.av-mic-is-muted .av-mic-orb {border-color:#e68b95;filter:grayscale(.7);box-shadow:0 0 25px #b24e4966;}
.alfred-main-v1.av-mic-is-muted .av-status-dot {background:#f28b91;}
@keyframes av-breathe {50%{transform:scale(1.045)}}
@media (max-width:1360px) {
.alfred-glass-theme .alfred-main-v1 {grid-template-columns:88px minmax(0,1fr);grid-template-rows:80px minmax(180px,205px) minmax(150px,1fr) 98px 70px;gap:10px;padding-right:10px;padding-bottom:10px;}
.av-stat-card {padding:14px;}.av-card-top strong{font-size:15px}.av-circle{flex-basis:43px;height:43px}.av-lines{margin-top:13px;gap:8px}.av-lines p{font-size:12px}
.av-weather-body strong {font-size:31px;}.av-weather-icon{font-size:39px}.av-rail-link{width:75px;min-height:62px}.av-voice-controls{gap:clamp(12px,3vw,60px)}.av-sound-control{width:105px;min-height:105px;}
.alfred-glass-theme #stage {left:112px;top:100px;bottom:92px;}.alfred-glass-theme #alfredTimersDock{left:100px;bottom:93px;}
}
@media(max-width:900px) {
body.alfred-glass-theme {overflow:auto;}
.alfred-glass-theme .alfred-main-v1 {position:relative;min-height:100dvh;overflow:visible;grid-template-columns:1fr;grid-template-rows:auto auto auto auto auto auto;gap:10px;padding:0 12px 18px;}
.alfred-main-v1 .av-head {grid-column:1;grid-row:1;padding:15px 0;}.av-head-actions{gap:4px}.av-round{width:36px;height:36px}.av-brand strong{font-size:22px}.av-brand small{font-size:11px}.av-logo{font-size:38px}.av-header-right{gap:12px}.av-time strong{font-size:22px}.av-time span{font-size:10px;}
.alfred-main-v1 .av-rail {grid-column:1;grid-row:2;display:grid;grid-template-columns:repeat(6,1fr);border-radius:14px;gap:3px;padding:6px;}.av-rail-link{width:auto;min-height:58px;}
.alfred-main-v1 .av-summary {grid-column:1;grid-row:3;grid-template-columns:repeat(2,minmax(0,1fr));}.av-stat-card {min-height:160px;}
.alfred-main-v1 .av-voice-hero{grid-column:1;grid-row:4;min-height:260px;}.av-wave-host{width:90%;}.av-voice-controls{gap:12px;}.av-voice-center{min-width:190px}.av-sound-control{width:86px;min-height:95px;border-radius:34px;}.av-sound-control strong{font-size:11px;}.av-mic-orb{width:120px;height:120px;}.av-mic-orb .a-icon{width:45px;height:45px;}
.alfred-main-v1 .av-shortcuts{grid-column:1;grid-row:5;grid-template-columns:repeat(4,1fr);}.av-shortcuts button{min-height:78px;}
.alfred-main-v1 .av-history{grid-column:1;grid-row:6;min-height:70px;}
.alfred-glass-theme #stage{left:12px;right:12px;top:145px;bottom:20px;max-height:calc(100dvh - 165px);}
.alfred-glass-theme #alfredTimersDock{left:15px;bottom:95px;width:min(290px,70vw);}.alfred-glass-theme .transcript-panel{right:13px;bottom:16px;width:min(350px,55vw);}
}
@media(max-width:550px) {
.av-head{flex-wrap:wrap;gap:8px}.av-header-right{flex-wrap:wrap;margin-left:auto}.av-head-actions{gap:2px}.av-time span{display:none}.av-brand small{display:none}.av-round{width:34px;height:34px}
.av-card-top small{font-size:10px;}.av-stat-card{padding:13px;min-height:152px;}.av-lines p{font-size:11px;gap:5px}.av-weather-icon{font-size:30px;}.av-weather-body strong{font-size:25px;}
.av-voice-controls{gap:8px;}.av-voice-center{min-width:130px;}.av-voice-center>strong{font-size:23px;}.av-voice-center>p{font-size:11px}.av-mic-orb{width:96px;height:96px;}.av-sound-control{width:65px;min-height:90px;padding:3px;}.av-sound-control .a-icon{width:22px;height:22px;}.av-sound-control strong{font-size:10px;}.av-sound-control small{font-size:10px;}
.alfred-main-v1 .av-shortcuts{grid-template-columns:repeat(3,minmax(0,1fr));}.av-history{flex-wrap:wrap;}.alfred-glass-theme .transcript-panel{position:relative;left:auto;right:auto;bottom:auto;margin:8px 12px;width:auto;}.alfred-glass-theme #alfredTimersDock{bottom:12px;left:12px;}
}
@media(prefers-reduced-motion:reduce){.alfred-main-v1 *{animation:none!important;transition:none!important}}
+72
View File
@@ -0,0 +1,72 @@
/* ALFRED_GLASS_V2 — supplement au theme salon V1. Ne supprime aucun controle audio. */
.alfred-glass-theme .alfred-main-v1 {
grid-template-columns:minmax(0,1fr)!important;
grid-template-rows: minmax(76px,95px) minmax(185px,225px) minmax(165px,1fr) minmax(98px,120px)!important;
gap:clamp(9px,1.15vw,16px)!important;
padding:0 clamp(12px,1.65vw,28px) clamp(12px,1.55vw,23px)!important;
}
.alfred-glass-theme .alfred-main-v1 .av-head {grid-column:1!important;grid-row:1!important;}
/* Le menu lateral est masque mais reste dans le DOM pour les modules deja installes. */
.alfred-glass-theme .alfred-main-v1 .av-rail,
.alfred-glass-theme .alfred-main-v1 .av-history {display:none!important;}
.alfred-glass-theme .alfred-main-v1 .av-summary {grid-column:1!important;grid-row:2!important;gap:clamp(9px,1.05vw,17px);}
.alfred-glass-theme .alfred-main-v1 .av-voice-hero {grid-column:1!important;grid-row:3!important;min-height:160px;}
.alfred-glass-theme .alfred-main-v1 .av-shortcuts {grid-column:1!important;grid-row:4!important;gap:clamp(7px,.9vw,14px);}
/* Plus de microphone central, ni de commandes en double : les boutons du bandeau superieur restent fonctionnels. */
.alfred-glass-theme .alfred-main-v1 .av-mic-orb,
.alfred-glass-theme .alfred-main-v1 .av-sound-control {display:none!important;}
.alfred-glass-theme .alfred-main-v1 .av-voice-controls {position:relative;z-index:2;gap:0;align-self:stretch;min-height:100%;padding-top:clamp(70px,12vh,130px);pointer-events:none;}
.alfred-glass-theme .alfred-main-v1 .av-voice-center {pointer-events:auto;min-width:0;width:100%;}
.alfred-glass-theme .alfred-main-v1 .av-voice-center > strong {font-size:clamp(23px,2.25vw,38px);}
.alfred-glass-theme .alfred-main-v1 .av-voice-center > p {font-size:clamp(12px,1vw,16px);}
.alfred-glass-theme .alfred-main-v1 .av-wave-host {top:24%;width:min(800px,88%);height:110px;opacity:1;z-index:1;}
.alfred-glass-theme .alfred-main-v1 .av-wave-host #wave {height:110px;}
.alfred-glass-theme .alfred-main-v1.av-mic-is-muted .av-wave-host {opacity:.18;filter:grayscale(1);}
.alfred-glass-theme .alfred-main-v1[data-mode="thinking"] .av-wave-host {filter:hue-rotate(25deg);}
.alfred-glass-theme .alfred-main-v1[data-mode="speaking"] .av-wave-host {filter:hue-rotate(-24deg);}
/* Cartes moins opaques pour laisser respirer l'ambiance salon. */
.alfred-glass-theme .alfred-main-v1 .av-stat-card {
background:linear-gradient(135deg,rgba(12,25,41,.67),rgba(9,20,34,.54))!important;
border-color:rgba(206,228,252,.23)!important;
backdrop-filter:blur(15px) saturate(120%)!important;
-webkit-backdrop-filter:blur(15px) saturate(120%)!important;
}
.alfred-glass-theme .alfred-main-v1 .av-stat-card:hover {
background:linear-gradient(135deg,rgba(20,42,65,.80),rgba(13,28,47,.68))!important;
border-color:rgba(142,203,255,.55)!important;
}
.alfred-glass-theme .alfred-main-v1 .av-shortcuts button {
background:rgba(9,20,34,.65)!important;
border-color:rgba(200,224,247,.32)!important;
backdrop-filter:blur(14px)!important;
}
.alfred-glass-theme .alfred-main-v1 .av-shortcuts button:hover {background:rgba(26,56,85,.8)!important;}
.alfred-glass-theme .av-round {background:rgba(11,24,38,.68)!important;}
.alfred-glass-theme .av-round[aria-pressed="true"] {background:rgba(92,26,39,.83)!important;}
.alfred-glass-theme #stage {left:clamp(12px,1.65vw,28px)!important;right:clamp(12px,1.65vw,28px)!important;bottom:clamp(12px,1.55vw,23px)!important;background:rgba(7,20,37,.83)!important;}
.alfred-glass-theme .transcript-panel {bottom:20px!important;background:rgba(9,22,38,.79)!important;}
.alfred-glass-theme #alfredTimersDock {left:24px!important;bottom:21px!important;background:rgba(9,22,38,.82)!important;}
.alfred-glass-theme #alfredTimerToast {left:24px!important;bottom:125px!important;}
/* Le bouton de menu est un vrai bouton, sans lien direct vers /config. */
.alfred-glass-theme #avTopMenu {position:relative;}
@media(max-width:1100px){
body.alfred-glass-theme{overflow:auto!important;}
.alfred-glass-theme .alfred-main-v1 {position:relative!important;min-height:100dvh;height:auto;overflow:visible!important;grid-template-rows:auto auto minmax(180px,240px) auto!important;}
.alfred-glass-theme .alfred-main-v1 .av-summary {grid-template-columns:repeat(2,minmax(0,1fr));}
.alfred-glass-theme .alfred-main-v1 .av-stat-card {min-height:175px;}
.alfred-glass-theme .alfred-main-v1 .av-voice-controls {padding-top:90px;}
.alfred-glass-theme .alfred-main-v1 .av-wave-host {top:21%;}
.alfred-glass-theme .alfred-main-v1 .av-shortcuts {grid-template-columns:repeat(4,minmax(0,1fr));}
.alfred-glass-theme #stage {top:105px!important;bottom:22px!important;}
}
@media(max-width:600px){
.alfred-glass-theme .alfred-main-v1 {padding:0 11px 15px!important;grid-template-rows:auto auto minmax(170px,210px) auto!important;}
.alfred-glass-theme .alfred-main-v1 .av-head {padding:14px 2px!important;}
.alfred-glass-theme .av-header-right{gap:6px;}
.alfred-glass-theme .alfred-main-v1 .av-stat-card {min-height:155px;padding:12px;}
.alfred-glass-theme .alfred-main-v1 .av-shortcuts{grid-template-columns:repeat(3,minmax(0,1fr));}
.alfred-glass-theme .alfred-main-v1 .av-voice-controls {padding-top:80px;}
.alfred-glass-theme .alfred-main-v1 .av-wave-host{width:100%;}
.alfred-glass-theme #alfredTimersDock{left:11px!important;bottom:12px!important;}
}
@media(prefers-reduced-motion:reduce){.alfred-main-v1 .av-wave-host canvas{animation:none!important;}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

+137
View File
@@ -0,0 +1,137 @@
/* Admin Alfred Glass — style harmonisé et configuration du cadre Maison. */
(() => {
'use strict';
const page = document.body.dataset.page;
if (!['config','surveillance'].includes(page)) return;
document.body.classList.add('alfred-admin-glass');
const rail = document.createElement('aside');
rail.className = 'ag-rail';
rail.setAttribute('aria-label','Navigation Alfred');
rail.innerHTML = `<a href="/" title="Accueil"><span>⌂</span>Accueil</a>
<a href="/config" title="Configuration" class="${page==='config'?'is-active':''}"><span>⚙</span>Réglages</a>
<a href="/surveillance" title="Surveillance" class="${page==='surveillance'?'is-active':''}"><span>▤</span>Suivi</a>`;
document.querySelector('.admin-layout')?.prepend(rail);
const brand = document.querySelector('.admin-header .brand small');
if (brand) brand.textContent = 'Assistant vocal · Centre de contrôle';
const main = document.getElementById('mainArea');
if (!main || page !== 'config') return;
const editor = document.createElement('section');
editor.id='agMaisonEditor';
editor.className='panel ag-maison-editor';
editor.innerHTML = `
<div class="panel-title"><span class="panel-symbol">⌂</span>
<div><h2>Cadre « Maison » de l’accueil</h2><p>Personnalise jusqu’à trois informations affichées en haut de l’écran principal</p></div></div>
<p class="ag-help">Choisis un compteur global ou une entité précise de Home Assistant, et personnalise le libellé. Les valeurs sont actualisées automatiquement sans redémarrer Alfred.</p>
<div id="agMaisonRows" class="ag-maison-rows"></div>
<div class="ag-maison-actions">
<button class="button ghost" type="button" id="agAddMaison">+ Ajouter une ligne</button>
<button class="button ghost" type="button" id="agDefaultMaison">Valeurs par défaut</button>
<button class="button primary" type="button" id="agSaveMaison">Enregistrer le cadre Maison</button>
</div><p id="agMaisonMessage" role="status" aria-live="polite" class="message"></p>`;
main.querySelector('.restart-panel')?.before(editor);
const types = [
['lights_on','Nombre de lumières allumées'],
['covers_closed','Nombre de volets fermés'],
['indoor_temperature','Température intérieure'],
['entity','Entité Home Assistant précise']
];
const defaults = [
{kind:'lights_on',label:'Lumières allumées'},
{kind:'covers_closed',label:'Volets fermés'},
{kind:'indoor_temperature',label:'Température intérieure'}
];
const list = editor.querySelector('#agMaisonRows');
const message = editor.querySelector('#agMaisonMessage');
const add = editor.querySelector('#agAddMaison');
let entities = [];
let rows = defaults.map(x=>({...x}));
let busy=false;
const msg=(text,error=false)=>{message.textContent=text;message.classList.toggle('ag-error',error);};
const el=(name,cls,text)=>{const node=document.createElement(name);if(cls)node.className=cls;if(text!==undefined)node.textContent=text;return node;};
function render() {
list.replaceChildren();
rows.forEach((row,i)=>{
const box=el('div','ag-row');
const header=el('div','ag-row-header');
header.append(el('strong','',`Ligne ${i+1}`));
const nav=el('div','ag-row-buttons');
const action=(name,symbol,title,fn)=>{
const b=el('button','ag-small-button',symbol);b.type='button';b.title=title;b.setAttribute('aria-label',title);
b.disabled=busy;b.addEventListener('click',fn);nav.append(b);
};
action('up','↑','Monter',()=>{if(i){[rows[i-1],rows[i]]=[rows[i],rows[i-1]];render();}});
action('down','↓','Descendre',()=>{if(i<rows.length-1){[rows[i],rows[i+1]]=[rows[i+1],rows[i]];render();}});
action('remove','×','Supprimer',()=>{if(rows.length>1){rows.splice(i,1);render();}});
header.append(nav);box.append(header);
const settings=el('div','ag-row-fields');
const kindWrap=el('label','ag-field');kindWrap.append(el('span','', 'Information affichée'));
const kind=el('select');
types.forEach(([value,txt])=>{const o=el('option','',txt);o.value=value;o.selected=row.kind===value;kind.append(o);});
kind.addEventListener('change',()=>{row.kind=kind.value;if(row.kind==='entity'&&!row.entity_id)row.entity_id=entities[0]?.id||'';render();});
kindWrap.append(kind);settings.append(kindWrap);
const labelWrap=el('label','ag-field');labelWrap.append(el('span','', 'Intitulé personnalisé'));
const text=el('input');text.type='text';text.maxLength=38;text.required=true;text.value=row.label||'';
text.addEventListener('input',()=>{row.label=text.value;});labelWrap.append(text);settings.append(labelWrap);
if(row.kind==='entity'){
const entityWrap=el('label','ag-field ag-wide');entityWrap.append(el('span','', 'Entité Home Assistant'));
const field=el('input');field.type='text';field.maxLength=120;
field.value=row.entity_id||'';field.placeholder='sensor.salon_temperature';
field.setAttribute('list',`agEntityList${i}`);
field.addEventListener('input',()=>{row.entity_id=field.value.trim();});
const choices=el('datalist');choices.id=`agEntityList${i}`;
entities.forEach(entry=>{const o=el('option');o.value=entry.id;o.label=entry.name;choices.append(o);});
entityWrap.append(field,choices);
const helper=el('small','',entities.length
? 'Choisis une entité proposée ou saisis son identifiant.'
: 'Saisis un identifiant comme sensor.salon_temperature si Home Assistant est inaccessible.');
entityWrap.append(helper);settings.append(entityWrap);
}
box.append(settings);list.append(box);
});
add.disabled=rows.length>=3 || busy;
}
function tryAuthFetch(url,options={}){
return fetch('/api/control/auth',{cache:'no-store',credentials:'same-origin'}).then(async r=>{
const a=await r.json();if(!a.authenticated)throw new Error('Connexion administrateur nécessaire.');
const headers=options.method==='POST'?{'Content-Type':'application/json','X-Alfred-CSRF':a.csrf}:{};
const response=await fetch(url,{...options,headers,credentials:'same-origin',cache:'no-store'});
const body=await response.json().catch(()=>({}));
if(!response.ok)throw new Error(body.error||`Erreur HTTP ${response.status}`);
return body;
});
}
async function refresh(){
if(busy)return;
busy=true;
try{
const config=await tryAuthFetch('/api/control/home-card');
rows=Array.isArray(config.rows)?config.rows:defaults.map(x=>({...x}));
const response=await tryAuthFetch('/api/control/home-entities').catch(err=>{msg('Liste des entités indisponible : '+err.message,true);return null;});
entities=response?.entities||[];
if(response)msg(`${entities.length} entités Home Assistant disponibles.`);
}catch(e){msg(e.message,true);}
finally{busy=false;render();}
}
editor.querySelector('#agDefaultMaison').addEventListener('click',()=>{rows=defaults.map(x=>({...x}));render();msg('Valeurs par défaut prêtes à enregistrer.');});
add.addEventListener('click',()=>{if(rows.length>=3)return;rows.push({kind:'entity',label:'Information',entity_id:entities[0]?.id||''});render();});
editor.querySelector('#agSaveMaison').addEventListener('click',async()=>{
if(busy)return;
busy=true;
try{
const result=await tryAuthFetch('/api/control/home-card',{method:'POST',body:JSON.stringify({rows})});
rows=result.rows;
msg('Cadre Maison enregistré. Les changements apparaîtront sur l’accueil en quelques secondes.');
}catch(e){msg('Impossible d’enregistrer : '+e.message,true);}
finally{busy=false;render();}
});
let loaded=false;
async function checkLogin(){
if(!main.hidden&&!loaded){loaded=true;await refresh();}
if(main.hidden)loaded=false;
}
new MutationObserver(checkLogin).observe(main,{attributes:true,attributeFilter:['hidden']});
checkLogin();
})();
+3 -2
View File
@@ -54,6 +54,7 @@
configDirty();
$('pendingNotice').hidden = false;
setMessage(result.ok ? 'Configuration sauvegardée.' : 'Enregistrement inconnu.');
if (result.identity_sync_warning) setMessage('Nom enregistré, mais identity.md non synchronisé : '+result.identity_sync_warning, true);
} catch (error) { setMessage(error.message, true); }
}
function serviceStatus(el, data) {
@@ -165,12 +166,12 @@
fields.forEach(el=>{el.addEventListener('change',markInput);el.addEventListener('input',markInput);});
$('saveConfig')?.addEventListener('click',saveConfig);
$('restartButton')?.addEventListener('click',async ()=>{
if(!confirm('Redémarrer Alfred maintenant ? Les opérations en cours seront interrompues.'))return;
if(!confirm('Redémarrer '+(document.querySelector('.brand strong')?.textContent || 'l’assistant')+' maintenant ? Les opérations en cours seront interrompues.'))return; // ALFRED_IDENTITY_GLASS_V1
try{const data=await fetchJSON('/api/control/restart',{method:'POST'});setMessage(data.message||'Demande transmise.');$('restartButton').disabled=true;setTimeout(()=>$('restartButton').disabled=false,5000);}
catch(err){setMessage(err.message,true);}
});
$('testVoice')?.addEventListener('click',async()=>{
try{await fetchJSON('/api/tts/say',{method:'POST',body:JSON.stringify({text:'Bonjour, je suis Alfred. Voici un test de ma voix actuelle.'})});setMessage('Test vocal demandé.');}
try{await fetchJSON('/api/tts/say',{method:'POST',body:JSON.stringify({text:'Bonjour, je suis '+(document.querySelector('.brand strong')?.textContent || 'votre assistant')+'. Voici un test de ma voix actuelle.'})});setMessage('Test vocal demandé.');}
catch(err){setMessage(err.message,true);}
});
let volDelay, gainDelay;
@@ -0,0 +1,60 @@
/* Identity editing: admin-protected API, optimistic revision checks, no HTML injection. */
(() => {
'use strict';
const $=id=>document.getElementById(id);
let csrf='',revision='',dirty=false,busy=false;
const msg=(s,error=false,id='idMessage')=>{const e=$(id);e.textContent=s;e.classList.toggle('error',error);};
const setName=name=>{
const n=String(name||'Assistant').trim();
const initial=Array.from(n)[0]?.toLocaleUpperCase('fr-FR')||'A';
$('idName').textContent=n;$('idInitial').textContent=initial;
$('idActiveName').textContent=n;$('idAvatar').textContent=initial;
document.title='Identité · '+n;
};
function updateCount(){$('idCount').textContent=$('idText').value.length.toLocaleString('fr-FR')+' caractères';}
async function jsonFetch(url,options={}){
const r=await fetch(url,{credentials:'same-origin',cache:'no-store',...options});
const d=await r.json().catch(()=>({}));
if(!r.ok)throw Error(d.error||'Erreur HTTP '+r.status);
return d;
}
async function auth(){
const a=await jsonFetch('/api/control/auth');
csrf=a.csrf||'';
$('idLoginPanel').hidden=!!a.authenticated;
$('idEditor').hidden=!a.authenticated;
$('idBadge').textContent=a.authenticated?'Administrateur connecté':'Connexion requise';
$('idBadge').classList.toggle('ok',!!a.authenticated);
if(a.authenticated)await load();
}
async function load(){
try{
const d=await jsonFetch('/api/control/identity');
$('idText').value=d.text;revision=d.revision;dirty=false;
setName(d.name);updateCount();msg('Document chargé.');
}catch(e){msg(e.message,true);}
}
$('idLoginForm').addEventListener('submit',async e=>{
e.preventDefault();
try{
await jsonFetch('/api/control/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:$('idPassword').value})});
$('idPassword').value='';await auth();
}catch(err){msg(err.message,true,'idLoginMessage');}
});
$('idText').addEventListener('input',()=>{dirty=true;updateCount();msg('Modifications non enregistrées.');});
$('idSave').addEventListener('click',async()=>{
if(busy||!revision)return;
busy=true;$('idSave').disabled=true;
try{
const d=await jsonFetch('/api/control/identity',{
method:'POST',headers:{'Content-Type':'application/json','X-Alfred-CSRF':csrf},
body:JSON.stringify({text:$('idText').value,revision})
});
revision=d.revision;dirty=false;msg(d.updated?'Identité enregistrée. Nouvelle version sauvegardée.':'Aucun changement à enregistrer.');
}catch(e){msg(e.message,true);}
finally{busy=false;$('idSave').disabled=false;}
});
$('idReload').addEventListener('click',()=>{if(dirty&&!confirm('Abandonner les modifications non enregistrées ?'))return;load();});
window.addEventListener('beforeunload',event=>{if(dirty){event.preventDefault();event.returnValue='';}});
auth().catch(e=>{msg(e.message,true,'idLoginMessage');$('idLoginPanel').hidden=false;});
})();
+125
View File
@@ -0,0 +1,125 @@
/* ALFRED_LISTES_GLASS_V1 — course/tâches, fenêtre Glass, pas de dépendance JS. */
(() => {
'use strict';
if (document.getElementById('alfredListsGlassDialog')) return;
const $ = id => document.getElementById(id);
const icon = (d) => `<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
const shopping = icon('<path d="M3 7h18l-2 12H5L3 7ZM8 8V6a4 4 0 0 1 8 0v2"/>');
const tasks = icon('<rect x="3" y="3" width="18" height="18" rx="3"/><path d="m7 12 3 3 6-6"/>');
const shell = document.getElementById('alfredMainV1');
if (!shell) return; // Le tableau de bord doit être installé avant ce module.
const modal = document.createElement('dialog');
modal.id = 'alfredListsGlassDialog';
modal.className = 'al-list-dialog';
modal.innerHTML = `<div class="al-list-head">
<div><p class="al-eyebrow">ALFRED · ORGANISATION</p><h2>Courses & tâches</h2><p class="al-subtitle">Tes listes sont conservées sur Alfred et mises à jour en temps réel.</p></div>
<button type="button" class="al-close" data-al-close title="Fermer" aria-label="Fermer">×</button>
</div><div class="al-list-grid">
<section class="al-list-section" data-kind="courses"><header class="al-list-sec-head"><div class="al-list-sec-icon">${shopping}</div><div><h3>Liste de courses</h3><p><span id="alCountCourses">—</span> article(s) à acheter</p></div></header>
<form class="al-add-form" data-kind="courses"><input name="label" maxlength="160" placeholder="Pain, lait, tomates…" aria-label="Nouvelle course" required /><button type="submit">+ Ajouter</button></form>
<ul class="al-items" id="alItemsCourses" aria-label="Courses"> </ul></section>
<section class="al-list-section" data-kind="taches"><header class="al-list-sec-head"><div class="al-list-sec-icon al-task-icon">${tasks}</div><div><h3>Liste de tâches</h3><p><span id="alCountTaches">—</span> tâche(s) à faire</p></div></header>
<form class="al-add-form" data-kind="taches"><input name="label" maxlength="160" placeholder="Peindre le mur…" aria-label="Nouvelle tâche" required /><button type="submit">+ Ajouter</button></form>
<ul class="al-items" id="alItemsTaches" aria-label="Tâches"> </ul></section>
</div><div class="al-footer"><span id="alListStatus" role="status" aria-live="polite">Chargement…</span><button type="button" data-al-refresh>Actualiser</button></div>`;
document.body.append(modal);
// Navigation: garder toutes les entrées existantes, y compris « Plus ».
const rail = shell.querySelector('.av-rail');
if (rail && !rail.querySelector('[data-al-open]')) {
const btn = document.createElement('button');
btn.type = 'button'; btn.className = 'av-rail-link al-rail-link';
btn.setAttribute('data-al-open', '');
btn.innerHTML = `${tasks}<span>Listes</span>`;
rail.querySelector('[data-av-action="more"]')?.before(btn);
if (!btn.isConnected) rail.append(btn);
}
const shortcuts = shell.querySelector('.av-shortcuts');
if (shortcuts && !shortcuts.querySelector('[data-al-open]')) {
const btn = document.createElement('button'); btn.type = 'button';
btn.setAttribute('data-al-open', '');
btn.innerHTML = `${shopping}<span id="alShortcutText">Courses & tâches</span>`;
shortcuts.querySelector('[data-av-action="more"]')?.before(btn);
if (!btn.isConnected) shortcuts.append(btn);
}
const more = $('avMoreDialog')?.querySelector('.av-more-grid');
if (more && !more.querySelector('[data-al-open]')) {
const btn = document.createElement('button');btn.type='button';btn.setAttribute('data-al-open','');
btn.innerHTML = `${shopping} Courses & tâches`; more.append(btn);
}
function status(msg, error = false) {
const el = $('alListStatus'); if (!el) return;
el.textContent = msg;el.classList.toggle('al-error',error);
}
async function fetchLists() {
const res = await fetch('/api/alfred/lists', {cache:'no-store'});
const data = await res.json(); if (!res.ok || !data.ok) throw Error(data.error || `HTTP ${res.status}`);
return data;
}
async function send(action, payload) {
const res = await fetch('/api/alfred/lists/action', {
method: 'POST', headers: {'Content-Type':'application/json','X-Alfred-Request':'1'},
body: JSON.stringify({action,...payload})
});
const data = await res.json();if (!res.ok || !data.ok)throw Error(data.error || `HTTP ${res.status}`);
return data;
}
function element(tag, text, cls) {
const e=document.createElement(tag); if (cls)e.className=cls; if (text!==undefined)e.textContent=text;return e;
}
function render(items, kind) {
const target=$(kind==='courses'?'alItemsCourses':'alItemsTaches');if(!target)return;
target.replaceChildren();
if(!items.length) {
const li=element('li', 'Ta liste est vide. Ajoute un premier élément !', 'al-empty');target.append(li);return;
}
for(const item of items) {
const li=element('li',undefined,'al-item'+(item.done?' is-done':''));
const check=element('button',item.done?'✓':'','al-check');check.type='button';
check.setAttribute('aria-label',item.done?'Réouvrir '+item.label:'Marquer comme terminé : '+item.label);
check.title=item.done?'Réouvrir':'Cocher';
check.addEventListener('click',()=>modify(item.done?'reopen_item':'complete_item',kind,item.id));
const label=element('span',item.label,'al-item-label');
const remove=element('button','×','al-remove');remove.type='button';remove.title='Supprimer';
remove.setAttribute('aria-label','Supprimer '+item.label);
remove.addEventListener('click',()=>modify('delete_item',kind,item.id));
li.append(check,label,remove);target.append(li);
}
}
async function refresh() {
try {
const data = await fetchLists();
for(const kind of ['courses','taches']) {
render(data.lists[kind] || [],kind);
const dest=$(kind==='courses'?'alCountCourses':'alCountTaches');
if(dest)dest.textContent=data.counts[kind]?.active ?? 0;
}
const shortcut=$('alShortcutText');
if(shortcut)shortcut.textContent=`Courses ${data.counts.courses.active} · Tâches ${data.counts.taches.active}`;
status('Listes à jour');
} catch(e) {status(`Erreur : ${e.message}`,true);}
}
async function modify(action,kind,id) {
try {await send(action,{kind,id});await refresh();}catch(e){status(e.message,true);}
}
document.addEventListener('click',(ev)=>{
if(!ev.target.closest('[data-al-open]'))return;
if($('avMoreDialog')?.open)$('avMoreDialog').close();
modal.showModal();refresh();
});
modal.querySelector('[data-al-close]').addEventListener('click',()=>modal.close());
modal.querySelector('[data-al-refresh]').addEventListener('click',refresh);
modal.querySelectorAll('.al-add-form').forEach(form=>form.addEventListener('submit',async ev=>{
ev.preventDefault();const input=form.querySelector('input');const label=input.value.trim();if(!label)return;
try {
const result=await send('add_item',{kind:form.dataset.kind,label});
input.value='';await refresh();status(result.already_present?'Cet élément est déjà présent.':'Élément ajouté.');input.focus();
}catch(e){status(e.message,true);}
}));
window.alfredSocket?.on('alfred_lists_changed',()=>refresh());
window.addEventListener('focus',refresh);
setInterval(refresh,30000);
refresh();
window.AlfredLists={open:()=>{modal.showModal();refresh();},refresh};
})();
+45
View File
@@ -0,0 +1,45 @@
/* ALFRED_GLASS_LOGS_V2 — aucun HTML de journal injecte, seulement textContent. */
(() => {
'use strict';
const $=id=>document.getElementById(id);
let all=[];let authenticated=false;let pending=false;
const when=new Intl.DateTimeFormat('fr-FR',{dateStyle:'short',timeStyle:'medium',timeZone:'Europe/Paris'});
const el=(tag,cls,text)=>{const e=document.createElement(tag);if(cls)e.className=cls;if(text!=null)e.textContent=String(text);return e;};
async function identity(){try{const r=await fetch('/api/state',{cache:'no-store'});if(!r.ok)return;const d=await r.json();const n=String(d.assistant_name||d.title||'').trim();if(!n)return;$('lgName').textContent=n;$('lgLetter').textContent=Array.from(n)[0].toLocaleUpperCase('fr-FR');document.title='Journaux · '+n;}catch(_){}}
identity();
function render(){
const needle=$('lgSearch').value.trim().toLocaleLowerCase('fr-FR');
const component=$('lgComponent').value,level=$('lgLevel').value;
const filtered=all.filter(x=>(!component||x.component===component)&&(!level||x.level===level)&&(!needle||(x.message||'').toLocaleLowerCase('fr-FR').includes(needle)));
const root=$('lgEvents');root.replaceChildren();
$('lgCount').textContent=`${filtered.length} / ${all.length} événement(s)`;
if(!filtered.length){root.append(el('div','lg-empty',authenticated?'Aucun événement correspondant aux filtres.':'Connecte-toi via la page Configuration pour consulter les journaux.'));return;}
for(const x of filtered.slice().reverse()){
const line=el('div','lg-event');line.dataset.level=x.level;
let date='—';try{date=when.format(new Date(x.at));}catch(_){}
const time=el('time','',date);time.dateTime=x.at;
line.append(time,el('span','lg-component',x.component),el('span','lg-level',x.level),el('span','lg-message',x.message));
root.append(line);
}
}
async function refresh(){
if(pending)return;pending=true;$('lgStatus').textContent='Actualisation…';
try{const r=await fetch('/api/alfred/logs?limit='+encodeURIComponent($('lgLimit').value),{cache:'no-store'});const d=await r.json();
if(r.status===401){authenticated=false;all=[];$('lgStatus').textContent='Accès administrateur requis · Connexion depuis /config';render();return;}
if(!r.ok||!d.ok)throw Error(d.error||`HTTP ${r.status}`);
authenticated=true;all=Array.isArray(d.items)?d.items:[];
$('lgStatus').textContent='Dernière lecture : '+new Date().toLocaleTimeString('fr-FR',{timeZone:'Europe/Paris'});
render();
}catch(e){$('lgStatus').textContent='Journal indisponible : '+e.message;}finally{pending=false;}
}
for(const id of ['lgSearch','lgComponent','lgLevel'])$(id).addEventListener('input',render);
$('lgLimit').addEventListener('change',refresh);
$('lgRefresh').addEventListener('click',refresh);
$('lgExport').addEventListener('click',()=>{
if(!authenticated||!all.length)return;
const file=new Blob([JSON.stringify({source:'alfred.service',exported_at:new Date().toISOString(),events:all},null,2)],{type:'application/json'});
const url=URL.createObjectURL(file);const a=document.createElement('a');a.href=url;a.download='alfred-journaux-'+new Date().toISOString().slice(0,10)+'.json';a.click();URL.revokeObjectURL(url);
});
setInterval(()=>{if($('lgAuto').checked&&!document.hidden)refresh();},10000);
window.addEventListener('focus',refresh);refresh();
})();
+319
View File
@@ -0,0 +1,319 @@
/* ALFRED_MAIN_VISUAL_V1 — accueil vivant, conservation des contrôleurs existants. */
(() => {
'use strict';
if (document.getElementById('alfredMainV1')) return;
const shell = document.querySelector('.alfred-shell');
if (!shell || !document.getElementById('stage') || !document.getElementById('content')) return;
const I = {
home:'<path d="m3 10 9-7 9 7v10a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1z"/>',
bulb:'<path d="M9 18h6m-5 3h4m-6-7c-1-1.2-2-2.5-2-5a6 6 0 0 1 12 0c0 2.5-1 3.8-2 5-.8 1-1 1.9-1 2H9c0-.1-.2-1-1-2z"/>',
server:'<rect x="3" y="3" width="18" height="8" rx="2"/><rect x="3" y="13" width="18" height="8" rx="2"/><path d="M7 7h.01M7 17h.01M12 7h6M12 17h6"/>',
brain:'<path d="M12 18V5a3 3 0 0 0-5-2 3 3 0 0 0-3 4 4 4 0 0 0 0 7 3.5 3.5 0 0 0 3.3 5A3.5 3.5 0 0 0 12 18m0-13a3 3 0 0 1 5-2 3 3 0 0 1 3 4 4 4 0 0 1 0 7 3.5 3.5 0 0 1-3.3 5A3.5 3.5 0 0 1 12 18"/><path d="m8 10 4 2 4-2"/>',
media:'<rect x="3" y="4" width="18" height="16" rx="3"/><circle cx="8.5" cy="9" r="1.3"/><path d="m4 17 5-5 4 4 3-3 4 4"/>',
more:'<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
mic:'<rect x="9" y="2.5" width="6" height="13" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v4m-4 0h8"/>',
micOff:'<path d="M9 9v3a3 3 0 0 0 5.5 1.7M15 9V5.5a3 3 0 0 0-5.5-1.7M5 11a7 7 0 0 0 11.5 5.4M19 11a7 7 0 0 1-.5 2.6M12 18v4m-4 0h8M3 3l18 18"/>',
speaker:'<path d="M11 5 6 9H3v6h3l5 4V5ZM15 9a4 4 0 0 1 0 6M18 6a8 8 0 0 1 0 12"/>',
speakerOff:'<path d="M11 5 6 9H3v6h3l5 4V5ZM16 9l5 6m0-6-5 6"/>',
camera:'<rect x="2" y="5" width="14" height="14" rx="3"/><path d="m16 10 6-3v10l-6-3"/>',
settings:'<path d="M12 3v2m0 14v2M3 12h2m14 0h2M5.6 5.6 7 7m10 10 1.4 1.4M18.4 5.6 17 7M7 17l-1.4 1.4"/><circle cx="12" cy="12" r="4"/>',
calendar:'<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4m10-4v4M3 10h18"/>',
clock:'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
cloud:'<path d="M7 18h11a4 4 0 0 0 0-8h-1a6 6 0 0 0-11-1 4.5 4.5 0 0 0 1 9Z"/>',
shield:'<path d="m12 2 8 3v6c0 6-3.7 9-8 11-4.3-2-8-5-8-11V5z"/><path d="m9 12 2 2 4-4"/>',
arrow:'<path d="m9 5 7 7-7 7"/>',
chat:'<path d="M5 5h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-5 3v-3a2 2 0 0 1-1-2V7a2 2 0 0 1 2-2z"/>',
close:'<path d="M5 5l14 14M19 5 5 19"/>',
sun:'<circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4 4l2 2m12 12 2 2m0-16-2 2M6 18l-2 2"/>',
spark:'<path d="m12 2 2.5 7.5L22 12l-7.5 2.5L12 22l-2.5-7.5L2 12l7.5-2.5z"/>',
};
const icon = (name, extra = '') => `<svg class="a-icon ${extra}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${I[name] || I.more}</svg>`;
const initialName = (document.title || 'Alfred').trim();
const dashboard = document.createElement('div');
dashboard.id = 'alfredMainV1';
dashboard.className = 'alfred-main-v1';
dashboard.innerHTML = `
<header class="av-head">
<div class="av-brand"><div class="av-logo">A</div><div><strong id="avName"></strong><small>Assistant vocal</small></div></div>
<div class="av-header-right"><div class="av-time"><strong id="avTime">--:--</strong><span id="avDate">—</span></div>
<div class="av-head-actions">
<button type="button" class="av-round" id="avTopCamera" aria-label="Ouvrir la caméra" title="Caméra">${icon('camera')}</button>
<button type="button" class="av-round" id="avTopMic" aria-label="Couper le microphone" aria-pressed="false" title="Couper le micro">${icon('mic')}</button>
<button type="button" class="av-round" id="avTopSpeaker" aria-label="Couper la sortie audio" aria-pressed="false" title="Couper le son">${icon('speaker')}</button>
<button type="button" class="av-round" id="avTopSettings" aria-label="Réglages audio" title="Réglages audio">${icon('settings')}</button>
<a class="av-round" href="/config" aria-label="Configuration d'Alfred" title="Configuration">${icon('more')}</a>
</div>
</div>
</header>
<nav class="av-rail" aria-label="Navigation principale">
<button type="button" class="av-rail-link selected" data-av-action="home">${icon('home')}<span>Accueil</span></button>
<button type="button" class="av-rail-link" data-av-action="house">${icon('bulb')}<span>Maison</span></button>
<a class="av-rail-link" href="/surveillance">${icon('server')}<span>Système</span></a>
<a class="av-rail-link" href="/surveillance">${icon('brain')}<span>IA</span></a>
<button type="button" class="av-rail-link" data-av-action="media">${icon('media')}<span>Médias</span></button>
<button type="button" class="av-rail-link" data-av-action="more">${icon('more')}<span>Plus</span></button>
</nav>
<section class="av-summary" aria-label="État de la maison et des services">
<button class="av-stat-card" type="button" data-av-action="house"><div class="av-card-top"><span class="av-circle home">${icon('home')}</span><span><strong>Maison</strong><small id="avHomeSummary">Chargement…</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p>${icon('bulb')}<span id="avLights">Lumières : —</span></p><p>${icon('home')}<span id="avCovers">Volets : —</span></p><p>${icon('sun')}<span id="avIndoor">Température : —</span></p></div></button>
<a class="av-stat-card" href="/surveillance"><div class="av-card-top"><span class="av-circle system">${icon('server')}</span><span><strong>Système</strong><small>Mini-PC Alfred</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p><span class="av-mini-dot"></span><span id="avLoad">Charge : —</span></p><p><span class="av-mini-dot"></span><span id="avMemory">Mémoire : —</span></p><p><span class="av-mini-dot"></span><span id="avDisk">Stockage : —</span></p></div></a>
<a class="av-stat-card" href="/surveillance"><div class="av-card-top"><span class="av-circle ai">${icon('brain')}</span><span><strong>Services IA</strong><small id="avAiSummary">Statuts en cours…</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p><span class="av-mini-dot" id="avOllamaDot"></span><span id="avOllama">Ollama : —</span></p><p><span class="av-mini-dot" id="avHermesDot"></span><span id="avHermes">Hermes : —</span></p><p><span class="av-mini-dot" id="avComfyDot"></span><span id="avComfy">ComfyUI : —</span></p></div></a>
<button class="av-stat-card av-weather" type="button" data-av-action="weather"><div class="av-card-top"><span class="av-circle weather">${icon('cloud')}</span><span><strong>Météo</strong><small>Home Assistant</small></span>${icon('arrow','av-chevron')}</div><div class="av-weather-body"><span class="av-weather-icon" id="avWeatherIcon">☁️</span><div><strong id="avWeatherTemp">—</strong><span id="avWeatherCondition">Chargement…</span><small id="avWeatherRange"></small></div></div></button>
</section>
<section class="av-voice-hero" aria-label="Commande vocale Alfred">
<div class="av-voice-controls">
<button type="button" id="avMicButton" class="av-sound-control" aria-pressed="false">${icon('mic')}<strong>Micro</strong><small id="avMicLabel">Actif</small></button>
<div class="av-voice-center"><div class="av-mic-orb" id="avOrb">${icon('mic')}</div><strong id="avVoiceTitle">En veille</strong><p id="avVoiceSubtitle">Dites « Alfred » puis votre demande</p><span class="av-status-line"><span class="av-status-dot" id="avStatusDot"></span><span id="avStatusText">Prêt à vous écouter</span></span></div>
<button type="button" id="avSpeakerButton" class="av-sound-control" aria-pressed="false">${icon('speaker')}<strong>Sortie audio</strong><small id="avSpeakerLabel">Activée</small></button>
</div>
<div class="av-wave-host" id="avWaveHost"></div>
</section>
<div class="av-shortcuts" role="group" aria-label="Raccourcis d'Alfred">
<button type="button" data-av-action="time">${icon('clock')}<span>Quelle heure est-il ?</span></button>
<button type="button" data-av-action="weather">${icon('cloud')}<span>Météo</span></button>
<button type="button" data-av-action="house">${icon('home')}<span>État de la maison</span></button>
<button type="button" data-av-action="proxmox">${icon('server')}<span>Vérifier Proxmox</span></button>
<button type="button" data-av-action="wazuh">${icon('shield')}<span>Alertes Wazuh</span></button>
<button type="button" data-av-action="comfyui">${icon('media')}<span>Créer une image</span></button>
<button type="button" data-av-action="more">${icon('more')}<span>Plus d'outils</span></button>
</div>
<footer class="av-history"><span class="av-history-heading">${icon('chat')} <strong>Interactions récentes</strong></span><div id="avHistoryList" aria-live="polite"><span class="av-history-empty">Aucune interaction depuis l’ouverture de cette page</span></div></footer>
<button type="button" class="av-back" id="avBack" hidden>${icon('close')} Retour à l’accueil</button>
<div class="av-toast" id="avToast" role="status" aria-live="polite" hidden></div>
<dialog class="av-image-dialog" id="avComfyDialog"><form method="dialog"><button type="submit" class="av-dialog-close" aria-label="Fermer">${icon('close')}</button></form><h2>Créer une image avec ComfyUI</h2><p>Décris l’image à générer. Alfred transmettra ta demande à Hermes.</p><label for="avComfyPrompt">Description</label><textarea id="avComfyPrompt" rows="4" maxlength="450" placeholder="Une maison futuriste au coucher du soleil…"></textarea><div class="av-dialog-actions"><button id="avComfySubmit" type="button">Envoyer à Alfred</button></div></dialog>
<dialog class="av-image-dialog" id="avMoreDialog"><form method="dialog"><button type="submit" class="av-dialog-close" aria-label="Fermer">${icon('close')}</button></form><h2>Outils Alfred</h2><div class="av-more-grid"><a href="/config">${icon('settings')} Configuration</a><a href="/surveillance">${icon('server')} Surveillance</a><button type="button" data-av-action="calendar">${icon('calendar')} Agenda</button><button type="button" data-av-action="camera">${icon('camera')} Caméra</button><button type="button" data-av-action="audio">${icon('speaker')} Réglages audio</button></div></dialog>
`;
shell.insertBefore(dashboard, shell.firstChild);
document.body.classList.add('alfred-glass-theme');
document.getElementById('avName').textContent = initialName;
const wave = document.getElementById('wave');
document.getElementById('avWaveHost').appendChild(wave);
window.dispatchEvent(new Event('resize'));
const $ = id => document.getElementById(id);
const timeZone = 'Europe/Paris';
let lastStatus = 'idle';
let mute = {mic_muted:false,speaker_muted:false};
let refreshingAudio = false;
let toastTimer;
function toast(message) {
const el = $('avToast'); el.textContent = String(message); el.hidden = false;
clearTimeout(toastTimer); toastTimer = setTimeout(() => { el.hidden = true; }, 5500);
}
function clock() {
const now = new Date();
$('avTime').textContent = new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit'}).format(now);
const date = new Intl.DateTimeFormat('fr-FR',{timeZone,weekday:'long',day:'numeric',month:'long',year:'numeric'}).format(now);
$('avDate').textContent = date.charAt(0).toUpperCase() + date.slice(1);
}
clock(); setInterval(clock, 15000);
function updateAudio(a) {
mute = {...mute,...a};
const mic = Boolean(mute.mic_muted), speaker = Boolean(mute.speaker_muted);
for (const id of ['avTopMic','avMicButton']) {
const button = $(id); button.setAttribute('aria-pressed',String(mic));
button.setAttribute('aria-label', mic ? 'Réactiver le microphone' : 'Couper le microphone');
button.title = button.getAttribute('aria-label');
button.querySelector('svg').outerHTML = icon(mic?'micOff':'mic');
}
for (const id of ['avTopSpeaker','avSpeakerButton']) {
const button = $(id); button.setAttribute('aria-pressed',String(speaker));
button.setAttribute('aria-label', speaker?'Réactiver la sortie audio':'Couper la sortie audio');
button.title = button.getAttribute('aria-label');
button.querySelector('svg').outerHTML = icon(speaker?'speakerOff':'speaker');
}
$('avMicLabel').textContent = mic?'Coupé':'Actif';
$('avSpeakerLabel').textContent = speaker?'Coupée':'Activée';
dashboard.classList.toggle('av-mic-is-muted',mic);
dashboard.classList.toggle('av-speaker-is-muted',speaker);
renderMode(lastStatus);
}
async function refreshAudio() {
if (refreshingAudio) return;
refreshingAudio = true;
try { const r=await fetch('/api/audio/settings',{cache:'no-store'});
if (!r.ok) throw Error('HTTP '+r.status);
updateAudio(await r.json());
} catch (e) { console.warn('[ALFRED UI] Réglages audio :',e); }
finally {refreshingAudio=false;}
}
async function toggleAudio(kind) {
const prop = kind==='mic'?'mic_muted':'speaker_muted';
try {
const r=await fetch('/api/audio/settings',{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({[prop]:!mute[prop]})});
const data=await r.json();
if (!r.ok) throw Error(data.error || 'HTTP '+r.status);
updateAudio(data);
toast(kind==='mic' ? (data.mic_muted?'Microphone coupé':'Microphone réactivé') : (data.speaker_muted?'Sortie audio coupée':'Sortie audio réactivée'));
} catch (e) { toast('Impossible de modifier le son : '+e.message); }
}
for (const id of ['avTopMic','avMicButton']) $(id).addEventListener('click',()=>toggleAudio('mic'));
for (const id of ['avTopSpeaker','avSpeakerButton']) $(id).addEventListener('click',()=>toggleAudio('speaker'));
$('avTopSettings').addEventListener('click',()=>{const btn=$('audioSettingsButton'); if(btn)btn.click();else location.assign('/config');});
refreshAudio(); setInterval(()=>{if(!document.hidden)refreshAudio();},30000);
function serviceText(state) {return state==='ok'?'Connecté':state==='offline'?'Hors ligne':state==='installed'?'Installé (non testé)':state==='unconfigured'?'Non configuré':'Non vérifié';}
function setService(id,dotid,label,state) {
$(id).textContent = `${label} : ${serviceText(state)}`;
$(dotid).className = `av-mini-dot ${state==='ok'?'is-ok':state==='offline'?'is-error':''}`;
}
async function refreshDashboard() {
try {
const r=await fetch('/api/alfred/dashboard',{cache:'no-store'});
if(!r.ok) throw Error('HTTP '+r.status);
const d=await r.json();
const h=d.home||{}, s=d.system||{}, ai=d.services||{}, w=d.weather||{};
$('avHomeSummary').textContent=h.available?'Home Assistant connecté':(h.detail||'Données non disponibles');
$('avLights').textContent = h.available?`${h.lights_on} lumière(s) allumée(s)`:'Lumières : indisponible';
$('avCovers').textContent = h.available?`${h.covers_closed} volet(s) fermé(s)`:'Volets : indisponible';
$('avIndoor').textContent = h.indoor_temperature!=null?`${h.indoor_temperature} °C · intérieur`:'Température : non disponible';
if (window.alfredHomeCardCustomUpdate) window.alfredHomeCardCustomUpdate();
$('avLoad').textContent = s.load_1m!=null?`Charge 1 min : ${s.load_1m}`:'Charge : non disponible';
$('avMemory').textContent = s.memory_percent!=null?`Mémoire : ${s.memory_percent} %`:'Mémoire : non disponible';
$('avDisk').textContent = s.disk_percent!=null?`Stockage : ${s.disk_percent} %`:'Stockage : non disponible';
setService('avOllama','avOllamaDot','Ollama',ai.ollama);
setService('avHermes','avHermesDot','Hermes',ai.hermes);
setService('avComfy','avComfyDot','ComfyUI',ai.comfyui);
$('avAiSummary').textContent='États vérifiés / installation locale';
$('avWeatherTemp').textContent=w.available&&w.temperature!=null?`${w.temperature} ${w.unit||'°C'}`:'—';
$('avWeatherCondition').textContent=w.available?w.condition:'Non disponible';
const sunny=['sunny','clear-night','partlycloudy'].includes(w.condition_id);
$('avWeatherIcon').textContent=sunny?'🌤️':w.condition_id==='rainy'?'🌧️':w.condition_id==='snowy'?'❄️':'☁️';
} catch (e) {
$('avHomeSummary').textContent='Données indisponibles';
$('avAiSummary').textContent='Surveillance indisponible';
$('avWeatherCondition').textContent='Indisponible';
console.warn('[ALFRED UI] Tableau de bord :',e);
}
}
refreshDashboard(); setInterval(()=>{if(!document.hidden)refreshDashboard();},30000);
async function refreshForecast() {
try {
const r=await fetch('/api/ha/weather',{cache:'no-store'});
if(!r.ok)return;
const d=await r.json();
const day=(d.daily||[])[0];
const vals=[];
if(day?.temperature!=null) vals.push('↑ '+day.temperature+'°');
if(day?.templow!=null) vals.push('↓ '+day.templow+'°');
$('avWeatherRange').textContent=vals.join(' ');
} catch (_) { /* Not all installations expose the weather forecast route. */ }
}
refreshForecast();setInterval(()=>{if(!document.hidden)refreshForecast();},180000);
function renderMode(mode) {
lastStatus = mode||'idle';
const states = {idle:['En veille','Dites « '+initialName+' » puis votre demande','Prêt'],
waiting:['En veille','Dites « '+initialName+' » puis votre demande','Prêt'],
listening:['Je vous écoute…','Quelle est votre demande ?','Écoute en cours'],
thinking:['Je réfléchis…','Traitement de la demande','Hermes travaille'],
speaking:['Je vous réponds…','Réponse vocale en cours','Piper parle'],
error:['Un problème est survenu','Consultez la transcription ou la surveillance','Erreur']};
const item=states[mode]||states.idle;
$('avVoiceTitle').textContent=mute.mic_muted?'Microphone coupé':item[0];
$('avVoiceSubtitle').textContent=mute.mic_muted?'Réactivez le micro pour utiliser le mot d’activation':item[1];
$('avStatusText').textContent=mute.mic_muted?'Écoute désactivée':item[2];
dashboard.dataset.mode=mode;
$('avOrb').setAttribute('aria-label',item[0]);
}
renderMode('idle');
const recent=[];
let lastTranscript='';
function pushRecent(text) {
const value=String(text||'').trim();
if(!value || value===lastTranscript)return;
lastTranscript=value;
recent.unshift({text:value,at:new Date()});
if(recent.length>5)recent.pop();
const root=$('avHistoryList');root.replaceChildren();
for(const e of recent) {
const node=document.createElement('span');node.className='av-history-item';
const t=document.createElement('time');t.textContent=new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit'}).format(e.at);
const content=document.createElement('span');content.textContent=e.text;
node.append(t,content);root.append(node);
}
}
function focusScene(show) {
dashboard.classList.toggle('is-focused', Boolean(show));
$('avBack').hidden = !show;
}
function onRender(d) { if(!d || typeof d!=='object')return;
if(d.type==='home')focusScene(false);
else if(d.type && d.type!=='text' || (d.text||d.title||d.url))focusScene(true);
}
const socket=window.alfredSocket;
socket?.on('alfred_state',state=>{
renderMode(String(state.mode||'idle').toLowerCase());
if(state.mode==='thinking' && state.transcript)pushRecent(state.transcript);
});
socket?.on('alfred_render',onRender);
socket?.on('audio_settings',updateAudio);
fetch('/api/state',{cache:'no-store'}).then(r=>r.ok?r.json():null).then(s=>{if(s)renderMode(String(s.mode||'idle').toLowerCase());}).catch(()=>{});
async function goHome() {
focusScene(false);
if(window.AlfredViews?.goHome){try{await window.AlfredViews.goHome();}catch(_){}}
else if($('viewHome'))$('viewHome').click();
}
$('avBack').addEventListener('click',goHome);
function openCamera() {
if($('cameraPopupButton')){ $('cameraPopupButton').click();return; }
const camera=document.querySelector('.camera-panel');
if(!camera){ toast('Caméra non installée');return; }
const isOpen=document.body.classList.toggle('av-legacy-camera-open');
const toggle=$('cameraToggle');
if(toggle && (toggle.getAttribute('aria-pressed')==='true')!==isOpen)toggle.click();
if(isOpen && !$('avLegacyCameraClose')){
const close=document.createElement('button');close.id='avLegacyCameraClose';close.type='button';close.textContent='Fermer la caméra';
close.addEventListener('click',openCamera);camera.prepend(close);
}
}
$('avTopCamera').addEventListener('click',openCamera);
function openView(name) {
const f=window.AlfredViews?.[name];
if(typeof f==='function'){ focusScene(true);f();return; }
const fallback={openWeather:'viewWeather',openTemperatures:'viewTemperatures',openCalendar:'viewCalendar'}[name];
if(fallback && $(fallback)){focusScene(true);$(fallback).click();return;}
toast('Cette fonction n’est pas disponible sur cette installation.');
}
async function quickCommand(action,description) {
try {
const r=await fetch('/api/alfred/dashboard/command',{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({action,description})});
const data=await r.json(); if(!r.ok)throw Error(data.error||'HTTP '+r.status);
pushRecent(({proxmox:'Vérifier Proxmox',wazuh:'Consulter les alertes Wazuh',comfyui:'Générer une image'})[action]||action);
toast('Demande transmise à Hermes');
} catch(e) {toast('Commande indisponible : '+e.message);}
}
function showTime() {
const current=new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit',second:'2-digit'}).format(new Date());
const content=$('content');content.replaceChildren();
const title=document.createElement('h1');title.textContent='Il est '+current;
const subtitle=document.createElement('p');subtitle.textContent='Heure de Paris · horloge locale du navigateur';
content.append(title,subtitle); focusScene(true);pushRecent('Quelle heure est-il ?');
}
async function act(name) {
if(name==='home'){await goHome();return;}
if(name==='house'){openView('openTemperatures');return;}
if(name==='weather'){openView('openWeather');return;}
if(name==='calendar'){ $('avMoreDialog').close();openView('openCalendar');return; }
if(name==='camera'){ $('avMoreDialog').close();openCamera();return; }
if(name==='media'){ $('avComfyDialog').showModal();$('avComfyPrompt').focus();return;}
if(name==='time'){showTime();return;}
if(name==='proxmox'||name==='wazuh'){await quickCommand(name);return;}
if(name==='comfyui'){ $('avComfyDialog').showModal();$('avComfyPrompt').focus();return;}
if(name==='more'){ $('avMoreDialog').showModal();return;}
if(name==='audio'){ $('avMoreDialog').close();$('avTopSettings').click();return; }
}
dashboard.addEventListener('click',event=>{
const button=event.target.closest('[data-av-action]');
if(button)act(button.dataset.avAction);
});
$('avComfySubmit').addEventListener('click',()=>{
const text=$('avComfyPrompt').value.trim();
if(text.length<4){toast('Décris l’image à générer.');return;}
$('avComfyDialog').close();quickCommand('comfyui',text);
});
window.addEventListener('resize',()=>{ /* legacy alfred.js recalculates the waveform canvas */ });
})();
+71
View File
@@ -0,0 +1,71 @@
/* ALFRED_GLASS_V2 — mise en page uniquement ; logique V1 et extensions conservees. */
(() => {
'use strict';
const main = document.getElementById('alfredMainV1');
if (!main || main.dataset.glassV2 === 'ready') return;
main.dataset.glassV2 = 'ready';
const logo = main.querySelector('.av-logo');
const name = document.getElementById('avName');
const subtitle = document.getElementById('avVoiceSubtitle');
if (!logo || !name) return;
let assistantName = String(name.textContent || '').trim() || 'Alfred';
const cleanName = value => {
if(typeof value !== 'string')return '';
const s = value.trim();
return s.length >= 2 && s.length <= 30 ? s : '';
};
function setName(value) {
const s = cleanName(value);
if(!s) return;
assistantName=s;
name.textContent=s;
const first=Array.from(s)[0] || 'A';
logo.textContent=first.toLocaleUpperCase('fr-FR');
logo.setAttribute('aria-label',`Initiale de ${s}`);
// La V1 conserve son nom initial dans la phrase de veille : mise a jour apres chaque evenement.
const title=document.getElementById('avVoiceTitle');
if(title && subtitle && (title.textContent === 'En veille' || title.textContent === 'En attente')) {
subtitle.textContent=`Dites « ${s} » puis votre demande`;
}
}
setName(assistantName);
async function fetchName(){
try{
const r=await fetch('/api/state',{cache:'no-store'});
if(!r.ok)return;
const d=await r.json();
setName(d.assistant_name || d.title || '');
} catch(_){ /* La page reste utilisable si /api/state est indisponible. */ }
}
fetchName();
setInterval(()=>{if(!document.hidden)fetchName();},20000);
window.addEventListener('focus',fetchName);
window.alfredSocket?.on('alfred_state',state=>{
setName(state?.assistant_name || state?.title || assistantName);
});
// Le menu ··· ouvre les options de la V1 au lieu de naviguer directement vers /config.
const oldMenu=main.querySelector('.av-head-actions > a[href="/config"]');
if(oldMenu){
const btn=document.createElement('button');
btn.type='button';btn.id='avTopMenu';btn.className=oldMenu.className;
btn.setAttribute('data-av-action','more');
btn.setAttribute('aria-label','Ouvrir le menu principal');
btn.title='Menu : Configuration, Surveillance, Journaux';
btn.innerHTML=oldMenu.innerHTML;
oldMenu.replaceWith(btn);
}
const grid=document.querySelector('#avMoreDialog .av-more-grid');
if(grid&&!grid.querySelector('a[href="/logs"]')){
const link=document.createElement('a');
link.href='/logs';link.textContent='Journaux et historiques';
link.setAttribute('aria-label','Ouvrir les journaux');
const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 24 24');svg.setAttribute('class','a-icon');svg.setAttribute('fill','none');
svg.setAttribute('stroke','currentColor');svg.setAttribute('stroke-width','1.8');
const d=document.createElementNS('http://www.w3.org/2000/svg','path');
d.setAttribute('d','M6 3h9l4 4v14H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM14 3v5h5M8 12h8M8 16h8');
svg.appendChild(d);link.prepend(svg);grid.append(link);
}
// Rail et historique restes en DOM : les scripts des Listes et de la V1 les utilisent.
// Ne pas les supprimer au risque de casser les evenements ou le rafraichissement.
})();
@@ -0,0 +1,31 @@
/* Alfred Glass — aperçu Maison alimenté par les préférences de /config. */
(() => {
'use strict';
let rows=null;
function render(){
if(!rows)return;
const card=document.getElementById('avHomeSummary');
if(!card)return;
const ids=['avLights','avCovers','avIndoor'];
rows.slice(0,3).forEach((r,i)=>{
const node=document.getElementById(ids[i]);
if(!node)return;
node.textContent=`${r.label} : ${r.value}`;
node.closest('p').hidden=false;
});
for(let i=rows.length;i<ids.length;i++){
const node=document.getElementById(ids[i]);
if(node)node.closest('p').hidden=true;
}
}
window.alfredHomeCardCustomUpdate=render;
async function update(){
try {
const r=await fetch('/api/alfred/home-card',{cache:'no-store'});
if(!r.ok)throw new Error('HTTP '+r.status);
const data=await r.json();rows=Array.isArray(data.rows)?data.rows:null;
render();
}catch(e){console.warn('[ALFRED MAISON] Informations personnalisées indisponibles',e);}
}
update();setInterval(()=>{if(!document.hidden)update();},20000);
})();
@@ -0,0 +1,93 @@
/* ALFRED_IDENTITY_GLASS_V1 — dynamic user-facing assistant branding.
Technical keys, service names, tool IDs and logs remain unchanged. */
(() => {
'use strict';
let active='',activeWake='';
const replace=(el,oldName,newName)=>{
if(!el || !el.firstChild || el.firstChild.nodeType!==3)return;
const value=el.firstChild.nodeValue||'';
const next=value.replace(/\bAlfred\b/g,newName);
if(next!==value)el.firstChild.nodeValue=next;
};
function apply(n,wake){
if(typeof n!=='string' || n.trim().length<2 || n.length>30)return;
active=n.trim();
if(typeof wake==='string'&&wake.trim())activeWake=wake.trim();
const letter=Array.from(active)[0]?.toLocaleUpperCase('fr-FR')||'A';
const main=document.getElementById('alfredMainV1');
if(main){
const logo=main.querySelector('.av-logo');
if(logo)logo.textContent=letter;
const title=document.getElementById('avName');
if(title)title.textContent=active;
const info=document.getElementById('avVoiceSubtitle');
if(info&&/Dites\s+[«"]/.test(info.textContent||''))info.textContent=`Dites « ${activeWake||active} » puis votre demande`;
const system=main.querySelector('.av-circle.system')?.parentElement?.querySelector('small');
if(system)system.textContent='Mini-PC '+active;
replace(document.querySelector('#avMoreDialog h2'),'',active);
const moreTitle=document.querySelector('#avMoreDialog h2');if(moreTitle)moreTitle.textContent='Outils '+active;
const comfyDesc=document.querySelector('#avComfyDialog p');if(comfyDesc)comfyDesc.textContent='Décris l’image à générer. '+active+' transmettra ta demande à Hermes.';
const submit=document.getElementById('avComfySubmit');if(submit)submit.textContent='Envoyer à '+active;
const hero=main.querySelector('.av-voice-hero');if(hero)hero.setAttribute('aria-label','Commande vocale '+active);
const oldConfig=main.querySelector('a[href="/config"][aria-label^="Configuration d"]');if(oldConfig)oldConfig.setAttribute('aria-label','Configuration de '+active);
const nav=document.querySelector('#avMoreDialog .av-more-grid');
if(nav && !nav.querySelector('a[href="/identity"]')){
const link=document.createElement('a');link.href='/identity';link.textContent='Identité de '+active;nav.append(link);
}else if(nav){const link=nav.querySelector('a[href="/identity"]');if(link)link.textContent='Identité de '+active;}
const legacyStage=document.getElementById('stage');if(legacyStage)legacyStage.setAttribute('aria-label','Réponse de '+active);
const legacyCamera=document.getElementById('cameraFeed');if(legacyCamera)legacyCamera.alt='Vidéo en direct de la caméra de '+active;
document.title=active+' · Accueil';
}
const brand=document.querySelector('.admin-header .brand');
if(brand){
const b=brand.querySelector('strong'),o=brand.querySelector('.brand-orb');
if(b)b.textContent=active;if(o)o.textContent=letter;
const loginOrb=document.querySelector('.auth-box .brand-orb');if(loginOrb)loginOrb.textContent=letter;
const authIntro=document.querySelector('.auth-box p');if(authIntro)authIntro.textContent=document.body.dataset.page==='surveillance'?('Connecte-toi pour consulter l’état de '+active+'.'):('Connecte-toi pour modifier la configuration de '+active+'.');
const help=document.querySelector('.ag-help');if(help)help.textContent=help.textContent.replace(/redémarrer Alfred/g,'redémarrer '+active);
const restartInfo=document.querySelector('.restart-panel p');if(restartInfo)restartInfo.textContent='Redémarrer '+active+' pour appliquer les paramètres enregistrés. Action protégée par mot de passe administrateur.';
const configLabel=document.getElementById('ALFRED_NAME');if(configLabel)configLabel.placeholder=active;
const hint=document.getElementById('pendingNotice');
if(hint){const strong=hint.querySelector('strong');if(strong)strong.textContent='Redémarre '+active;}
const restartButton=document.getElementById('restartButton');if(restartButton)restartButton.textContent='Redémarrer '+active;
document.title=active+' · '+(document.body.dataset.page==='surveillance'?'Surveillance':'Configuration');
const nav=brand.closest('.admin-header')?.querySelector('nav');
if(nav && !nav.querySelector('a[href="/identity"]')){
const a=document.createElement('a');a.href='/identity';a.textContent='Identité';
const logout=nav.querySelector('button');if(logout)nav.insertBefore(a,logout);else nav.append(a);
}
const rail=document.querySelector('.ag-rail');
if(rail && !rail.querySelector('a[href="/identity"]')){
const a=document.createElement('a');a.href='/identity';a.innerHTML='<span>✎</span>Identité';rail.append(a);
}
}
const legacyAudio=document.querySelector('body > main .subtitle');
if(legacyAudio){legacyAudio.textContent=active+' · Paramètres audio';document.title=active+' — Configuration audio';
const hints=document.querySelectorAll('body > main p.hint');if(hints[0])hints[0].textContent='Désactive les réponses vocales de '+active+'.';if(hints[1])hints[1].textContent='Suspend l’écoute et la détection du mot « '+(activeWake||active)+' ».';}
const logsTitle=document.getElementById('lgName');
if(logsTitle){
logsTitle.textContent=active;
const letterEl=document.getElementById('lgLetter');if(letterEl)letterEl.textContent=letter;
const eyebrow=document.querySelector('.lg-eyebrow');if(eyebrow)eyebrow.textContent='OBSERVABILITÉ · '+active.toLocaleUpperCase('fr-FR');
const nav=document.querySelector('.lg-nav');
if(nav&&!nav.querySelector('a[href="/identity"]')){const a=document.createElement('a');a.href='/identity';a.textContent='Identité';nav.append(a);}
document.title='Journaux · '+active;
}
}
async function refresh(){
try{const r=await fetch('/api/alfred/identity/name',{cache:'no-store'});if(!r.ok)return;const d=await r.json();apply(d.name,d.wake_word);}catch(_){}
}
const subtitle=document.getElementById('avVoiceSubtitle');
if(subtitle){
const observer=new MutationObserver(()=>{
if(active&&/Dites\s+[«"]/.test(subtitle.textContent||'')){
const desired=`Dites « ${activeWake||active} » puis votre demande`;
if(subtitle.textContent!==desired)subtitle.textContent=desired;
}
});
observer.observe(subtitle,{childList:true,characterData:true,subtree:true});
}
refresh();window.addEventListener('focus',refresh);
document.addEventListener('visibilitychange',()=>{if(!document.hidden)refresh();});
setInterval(()=>{if(!document.hidden)refresh();},10000);
})();