V1
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Alfred — Configuration audio</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; font-family: Inter, system-ui, -apple-system, sans-serif; background:#15171a; color:#f3f4f6; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; min-height:100vh; display:grid; place-items:center; padding:28px; }
|
||||
main { width:min(100%,640px); }
|
||||
header { margin-bottom:30px; }
|
||||
h1 { font-size:1.8rem; font-weight:550; margin:0 0 8px; }
|
||||
.subtitle { color:#a5a9b0; margin:0; }
|
||||
.panel { background:#202328; border:1px solid #34383f; border-radius:18px; padding:26px; }
|
||||
.section { padding:20px 0; }
|
||||
.section:first-child { padding-top:0; }
|
||||
.section:last-child { padding-bottom:0; }
|
||||
.section + .section { border-top:1px solid #353940; }
|
||||
.line { display:flex; align-items:center; justify-content:space-between; gap:18px; }
|
||||
.label { display:flex; align-items:center; gap:12px; font-size:1.05rem; }
|
||||
.icon { width:24px; height:24px; color:#c9cdd3; flex-shrink:0; }
|
||||
.value { font-variant-numeric:tabular-nums; color:#c8cbd1; }
|
||||
input[type=range] { width:100%; margin:23px 0 2px; accent-color:#e5e7eb; cursor:pointer; }
|
||||
input[type=range]:disabled { opacity:.35; cursor:not-allowed; }
|
||||
.switch { position:relative; width:54px; height:30px; display:inline-block; flex-shrink:0; }
|
||||
.switch input { opacity:0; width:0; height:0; position:absolute; }
|
||||
.track { position:absolute; inset:0; background:#525760; border-radius:99px; transition:background .2s; cursor:pointer; }
|
||||
.track:before { content:""; position:absolute; width:22px; height:22px; top:4px; left:4px; background:white; border-radius:50%; transition:transform .2s; }
|
||||
.switch input:checked + .track { background:#ad5555; }
|
||||
.switch input:checked + .track:before { transform:translateX(24px); }
|
||||
.switch input:focus-visible + .track { outline:2px solid white; outline-offset:3px; }
|
||||
.hint { margin:9px 0 0 36px; color:#9298a2; font-size:.88rem; }
|
||||
#status { min-height:1.3em; margin:18px 0 0; color:#a5a9b0; font-size:.86rem; }
|
||||
a { color:#cdd2da; text-decoration:none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Configuration</h1>
|
||||
<p class="subtitle">Alfred · Paramètres audio</p>
|
||||
</header>
|
||||
<div class="panel">
|
||||
<section class="section">
|
||||
<div class="line">
|
||||
<label class="label" for="volume">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/><path d="M19 5a10 10 0 0 1 0 14"/></svg>
|
||||
Volume de sortie
|
||||
</label>
|
||||
<output class="value" id="volumeValue" for="volume">50 %</output>
|
||||
</div>
|
||||
<input id="volume" type="range" min="0" max="100" value="50" step="1" aria-label="Volume de sortie">
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="line">
|
||||
<span class="label" id="speakerLabel">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="m17 9 5 6m0-6-5 6"/></svg>
|
||||
Couper le haut-parleur
|
||||
</span>
|
||||
<label class="switch"><input type="checkbox" id="speakerMute" aria-labelledby="speakerLabel"><span class="track"></span></label>
|
||||
</div>
|
||||
<p class="hint">Désactive les réponses vocales d’Alfred.</p>
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="line">
|
||||
<span class="label" id="micLabel">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10a7 7 0 0 0 14 0M12 17v5m-4 0h8"/></svg>
|
||||
Couper le microphone
|
||||
</span>
|
||||
<label class="switch"><input type="checkbox" id="micMute" aria-labelledby="micLabel"><span class="track"></span></label>
|
||||
</div>
|
||||
<p class="hint">Suspend l’écoute et la détection du mot « Alfred ».</p>
|
||||
</section>
|
||||
</div>
|
||||
<p id="status" role="status" aria-live="polite"></p>
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
// API attendue côté Flask :
|
||||
// GET /api/audio/settings -> {"volume": 50, "speaker_muted": false, "microphone_muted": false}
|
||||
// PATCH /api/audio/settings <- mêmes clés, partielles acceptées.
|
||||
// Le navigateur n'agit PAS directement sur ALSA ou Vosk.
|
||||
const volume = document.getElementById('volume');
|
||||
const volumeValue = document.getElementById('volumeValue');
|
||||
const speakerMute = document.getElementById('speakerMute');
|
||||
const micMute = document.getElementById('micMute');
|
||||
const status = document.getElementById('status');
|
||||
let volumeTimer;
|
||||
function setStatus(message) { status.textContent = message; }
|
||||
function render(data) {
|
||||
if (typeof data.volume === 'number') volume.value = data.volume;
|
||||
if (typeof data.speaker_muted === 'boolean') speakerMute.checked = data.speaker_muted;
|
||||
if (typeof data.microphone_muted === 'boolean') micMute.checked = data.microphone_muted;
|
||||
volumeValue.textContent = `${volume.value} %`;
|
||||
}
|
||||
async function save(change) {
|
||||
try {
|
||||
const response = await fetch('/api/audio/settings', {
|
||||
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(change)
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
render(await response.json());
|
||||
setStatus('Paramètres appliqués.');
|
||||
} catch (error) {
|
||||
setStatus('Impossible d’appliquer le réglage : API Flask non disponible ou erreur serveur.');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
volume.addEventListener('input', () => {
|
||||
volumeValue.textContent = `${volume.value} %`;
|
||||
clearTimeout(volumeTimer);
|
||||
volumeTimer = setTimeout(() => save({ volume: Number(volume.value) }), 180);
|
||||
});
|
||||
speakerMute.addEventListener('change', () => save({ speaker_muted: speakerMute.checked }));
|
||||
micMute.addEventListener('change', () => save({ microphone_muted: micMute.checked }));
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/audio/settings');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
render(await response.json());
|
||||
setStatus('');
|
||||
} catch (_) {
|
||||
setStatus('Interface prête. Connexion à l’API audio Flask nécessaire pour contrôler le matériel.');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#202326">
|
||||
<title>{{ name }}</title>
|
||||
<link rel="stylesheet" href="/static/css/alfred.css">
|
||||
<link rel="stylesheet" href="/static/css/alfred-controls.css">
|
||||
<link rel="stylesheet" href="/static/css/alfred-views.css">
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="alfred-shell">
|
||||
<header class="topbar">
|
||||
<!-- Le statut remplace l'ancien titre « Alfred ». -->
|
||||
<div class="status" role="status" aria-live="polite">
|
||||
<span id="statusDot" class="status-dot" data-mode="idle" aria-hidden="true"></span>
|
||||
<span id="mode" data-mode="idle">En attente</span>
|
||||
</div>
|
||||
|
||||
<div class="header-actions" aria-label="Navigation et commandes audio">
|
||||
<button id="viewHome" class="icon-button" type="button" title="Accueil" aria-label="Accueil">⌂</button>
|
||||
<button id="viewCalendar" class="icon-button" type="button" title="Agenda" aria-label="Agenda">▦</button>
|
||||
<button id="viewWeather" class="icon-button" type="button" title="Météo Home Assistant" aria-label="Météo Home Assistant">☀</button>
|
||||
<button id="viewTemperatures" class="icon-button" type="button" title="Températures" aria-label="Températures">°</button>
|
||||
<button id="viewCameras" class="icon-button" type="button" title="Caméras Home Assistant" aria-label="Caméras Home Assistant">▣</button>
|
||||
<button id="audioSettingsButton" class="icon-button" type="button"
|
||||
aria-label="Réglages audio et caméra" aria-haspopup="dialog" aria-expanded="false"
|
||||
aria-controls="audioSettingsPanel" title="Réglages audio et caméra">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M4 7h16M4 17h16"/><circle cx="9" cy="7" r="2.5" fill="#272b30"/><circle cx="16" cy="17" r="2.5" fill="#272b30"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="micMute" class="icon-button" type="button"
|
||||
aria-label="Couper le microphone" aria-pressed="false" title="Couper le microphone">
|
||||
<svg class="mic-on-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="9" y="2.5" width="6" height="12" rx="3"/>
|
||||
<path d="M5.5 10.5v1.5a6.5 6.5 0 0 0 13 0v-1.5M12 18.5v3M8 21.5h8"/>
|
||||
</svg>
|
||||
<svg class="mic-off-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M9.2 5.3V5.5a3 3 0 0 1 5.5-1.4M15 10.6v.9c0 .5-.1 1-.3 1.5M9 9.5v2A3 3 0 0 0 13.6 14"/>
|
||||
<path d="M5.5 10.5V12a6.5 6.5 0 0 0 11.5 4.1M12 18.5v3M8 21.5h8M3 3l18 18"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="speakerMuteQuick" class="icon-button" type="button"
|
||||
aria-label="Couper le son" aria-pressed="false" title="Couper le son">
|
||||
<svg class="speaker-on-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m11 5-5 4H3v6h3l5 4V5Z"/><path d="M15 9a4 4 0 0 1 0 6M18 6a8 8 0 0 1 0 12"/>
|
||||
</svg>
|
||||
<svg class="speaker-off-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m11 5-5 4H3v6h3l5 4V5Z"/><path d="m16 9 5 6m0-6-5 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pop-up : commandes connectées à /api/audio/settings -->
|
||||
<div id="audioSettingsPanel" class="audio-popover" role="dialog"
|
||||
aria-label="Réglages audio et caméra" aria-modal="false" hidden>
|
||||
<div class="popover-heading">
|
||||
<strong>Réglages</strong>
|
||||
<button id="closeAudioSettings" class="icon-button mini" type="button" aria-label="Fermer les réglages">×</button>
|
||||
</div>
|
||||
<div class="settings-tabs" role="tablist" aria-label="Catégories de réglages">
|
||||
<button id="audioTab" class="settings-tab" type="button" role="tab" aria-selected="true" aria-controls="audioSettingsSection">Audio</button>
|
||||
<button id="cameraTab" class="settings-tab" type="button" role="tab" aria-selected="false" aria-controls="cameraSettingsSection">Caméra</button>
|
||||
</div>
|
||||
<div id="audioSettingsSection" role="tabpanel" aria-labelledby="audioTab">
|
||||
<div class="setting-row">
|
||||
<label for="volumeSlider">Volume du haut-parleur</label>
|
||||
<output id="volumeValue" for="volumeSlider">70 %</output>
|
||||
</div>
|
||||
<input id="volumeSlider" type="range" min="0" max="100" value="70" step="1">
|
||||
<div class="setting-row">
|
||||
<label for="micGainSlider">Sensibilité du microphone</label>
|
||||
<output id="micGainValue" for="micGainSlider">—</output>
|
||||
</div>
|
||||
<input id="micGainSlider" type="range" min="0" max="100" value="50" step="1">
|
||||
<p id="audioSettingsMessage" class="settings-message" role="status" aria-live="polite"></p>
|
||||
</div>
|
||||
<div id="cameraSettingsSection" role="tabpanel" aria-labelledby="cameraTab" hidden>
|
||||
<p class="camera-settings-note">Webcam locale · réglages matériels V4L2.<br>Tu peux voir le résultat dans l’aperçu « CAMÉRA » en bas à gauche.</p>
|
||||
<div id="cameraSettingsItems" class="camera-settings-items" aria-live="off"></div>
|
||||
<div class="camera-settings-actions">
|
||||
<button id="cameraSettingsReload" class="camera-settings-action" type="button">Actualiser</button>
|
||||
<button id="cameraSettingsReset" class="camera-settings-action reset" type="button">Valeurs d’usine</button>
|
||||
</div>
|
||||
<p id="cameraSettingsMessage" class="settings-message" role="status" aria-live="polite"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="stage" aria-label="Réponse d'Alfred">
|
||||
<div id="content"><h1>Bonjour.</h1><p>À votre écoute.</p></div>
|
||||
</section>
|
||||
|
||||
<section id="wave" aria-hidden="true"><canvas id="waveCanvas"></canvas></section>
|
||||
|
||||
<!-- Caméra locale : désactivée tant que l'utilisateur ne l'ouvre pas,
|
||||
pour éviter de monopoliser la webcam utilisée par le MCP visage. -->
|
||||
<aside class="camera-panel" aria-label="Prévisualisation caméra">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title"><span class="panel-indicator"></span> CAMÉRA</span>
|
||||
<button id="cameraToggle" class="panel-action" type="button" aria-pressed="false">Activer</button>
|
||||
</div>
|
||||
<div class="camera-viewport">
|
||||
<img id="cameraFeed" alt="Vidéo en direct de la caméra d'Alfred" hidden>
|
||||
<div id="cameraPlaceholder" class="camera-placeholder">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">
|
||||
<rect x="2.5" y="5" width="14" height="14" rx="3"/><path d="m16.5 10 5-3v10l-5-3"/>
|
||||
</svg>
|
||||
<span id="cameraMessage">Caméra désactivée</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Affiche les résultats partiels ET définitifs de la reconnaissance Vosk. -->
|
||||
<aside class="transcript-panel" aria-label="Transcription vocale">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title"><span class="panel-indicator stt-indicator"></span> RECONNAISSANCE VOCALE · VOSK</span>
|
||||
<span id="transcriptState" class="transcript-state">En attente</span>
|
||||
</div>
|
||||
<p id="transcriptText" class="transcript-text" aria-live="polite">La dernière phrase reconnue s'affichera ici.</p>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<script src="/static/js/alfred.js"></script>
|
||||
<script src="/static/js/alfred-views.js"></script>
|
||||
<script src="/static/js/alfred-controls.js"></script>
|
||||
<script src="/static/js/alfred-camera.js"></script>
|
||||
<script src="/static/js/alfred-camera-settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{name}}</title>
|
||||
<link rel="stylesheet" href="/static/css/alfred.css">
|
||||
<link rel="stylesheet" href="/static/css/alfred-controls.css">
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header><span id="name">{{name}}</span><span id="mode">repos</span></header>
|
||||
<section id="stage"><div id="content"><h1>Bonjour.</h1><p>À votre écoute.</p></div></section>
|
||||
<section id="wave"><canvas id="waveCanvas"></canvas></section>
|
||||
<footer class="alfred-controls" aria-label="Contrôles audio">
|
||||
<button id="speakerMute" class="audio-toggle" type="button" aria-label="Couper le haut-parleur" aria-pressed="false" title="Couper le haut-parleur">🔊</button>
|
||||
<label class="volume-label" for="volumeSlider">Volume</label>
|
||||
<input id="volumeSlider" type="range" min="0" max="100" step="1" value="70" aria-label="Volume sonore">
|
||||
<output id="volumeValue" for="volumeSlider">70 %</output>
|
||||
<span class="controls-separator" aria-hidden="true"></span>
|
||||
<label class="volume-label" for="micGainSlider">Sensibilité micro</label>
|
||||
<input id="micGainSlider" type="range" min="0" max="100" step="1" value="50" aria-label="Sensibilité du microphone">
|
||||
<output id="micGainValue" for="micGainSlider">—</output>
|
||||
<button id="micMute" class="audio-toggle" type="button" aria-label="Couper le microphone" aria-pressed="false" title="Couper le microphone">🎙️</button>
|
||||
</footer>
|
||||
</main>
|
||||
<script src="/static/js/alfred.js"></script>
|
||||
<script src="/static/js/alfred-controls.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
<!doctype html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{name}}</title><link rel="stylesheet" href="/static/css/alfred.css"><script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script></head>
|
||||
<body><main><header><span id="name">{{name}}</span><span id="mode">repos</span></header><section id="stage"><div id="content"><h1>Bonjour.</h1><p>À votre écoute.</p></div></section><section id="wave"><canvas id="waveCanvas"></canvas></section></main><script src="/static/js/alfred.js"></script></body></html>
|
||||
Reference in New Issue
Block a user