v2 - STT via Whisper for more precision and if nor available, twist to Vox
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/* ALFRED_TIMERS_CONTEXT_V1 — chronos/minuteurs dans un coin, illustrations en popup. */
|
||||
(() => {
|
||||
'use strict';
|
||||
const root = document.getElementById('alfredTimersDock');
|
||||
const list = document.getElementById('alfredTimersList');
|
||||
const context = document.getElementById('alfredContextPopup');
|
||||
const contextImage = document.getElementById('alfredContextImage');
|
||||
const contextTitle = document.getElementById('alfredContextTitle');
|
||||
const contextText = document.getElementById('alfredContextText');
|
||||
const contextSource = document.getElementById('alfredContextSource');
|
||||
const contextClose = document.getElementById('alfredContextClose');
|
||||
const toast = document.getElementById('alfredTimerToast');
|
||||
if (!root || !list) return;
|
||||
let items = [];
|
||||
let anchorMs = Date.now();
|
||||
let lastSnapshotClientMs = Date.now();
|
||||
let toastTimeout = null;
|
||||
|
||||
function format(seconds) {
|
||||
const s = Math.max(0, Math.floor(seconds));
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor(s % 3600 / 60);
|
||||
const sec = s % 60;
|
||||
return h > 0 ? `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}` :
|
||||
`${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`;
|
||||
}
|
||||
function textNode(tag, cls, text) {
|
||||
const el = document.createElement(tag);
|
||||
if (cls) el.className = cls;
|
||||
if (text !== undefined) el.textContent = text;
|
||||
return el;
|
||||
}
|
||||
async function action(op, id, seconds) {
|
||||
const payload = {action:op, id};
|
||||
if (seconds !== undefined) payload.seconds = seconds;
|
||||
const r = await fetch('/api/timers/action', {method:'POST',
|
||||
headers:{'Content-Type':'application/json', 'X-Alfred-Request':'1'},
|
||||
body:JSON.stringify(payload)});
|
||||
const data = await r.json();
|
||||
if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`);
|
||||
update(data.items || []);
|
||||
}
|
||||
function update(next) {
|
||||
items = Array.isArray(next) ? next : [];
|
||||
lastSnapshotClientMs = Date.now();
|
||||
list.replaceChildren();
|
||||
root.hidden = !items.length;
|
||||
for (const item of items) {
|
||||
const card = textNode('div','alfred-time-card');
|
||||
card.dataset.itemId = item.id;
|
||||
const label = textNode('span','alfred-time-label', (item.kind === 'timer' ? '⌛ ' : '⏱ ') + item.label);
|
||||
const counter = textNode('strong','alfred-time-value','00:00');
|
||||
counter.dataset.kind = item.kind;
|
||||
card.append(label,counter);
|
||||
const actions = textNode('div','alfred-time-actions');
|
||||
const pause = textNode('button','alfred-time-icon', item.state === 'paused' ? '▶' : 'Ⅱ');
|
||||
pause.type='button'; pause.title=item.state === 'paused' ? 'Reprendre':'Mettre en pause';
|
||||
pause.setAttribute('aria-label',pause.title);
|
||||
pause.onclick=()=>action(item.state==='paused'?'resume':'pause',item.id).catch(showError);
|
||||
const stop=textNode('button','alfred-time-icon','×');
|
||||
stop.type='button'; stop.title='Annuler'; stop.setAttribute('aria-label','Annuler '+item.label);
|
||||
stop.onclick=()=>action('cancel',item.id).catch(showError);
|
||||
actions.append(pause,stop);card.append(actions);list.append(card);
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
function redraw() {
|
||||
const delta = Math.max(0, (Date.now()-lastSnapshotClientMs)/1000);
|
||||
for (const item of items) {
|
||||
const node=[...list.querySelectorAll('[data-item-id]')].find(el=>el.dataset.itemId===item.id);
|
||||
if (!node) continue;
|
||||
let elapsed=Number(item.elapsed)||0;
|
||||
if(item.state==='running') elapsed+=delta;
|
||||
const seconds = item.kind==='timer' ? Math.max(0,(Number(item.duration)||0)-elapsed) :
|
||||
elapsed;
|
||||
const counter=node.querySelector('.alfred-time-value');
|
||||
counter.textContent=format(seconds);
|
||||
counter.classList.toggle('is-paused',item.state==='paused');
|
||||
if(item.kind==='stopwatch' && item.duration!==null)
|
||||
counter.title='Limite '+format(item.duration);
|
||||
}
|
||||
}
|
||||
function notify(message) {
|
||||
if (!toast) return;
|
||||
toast.textContent=message;toast.hidden=false;
|
||||
clearTimeout(toastTimeout);
|
||||
toastTimeout=setTimeout(()=>{toast.hidden=true;},9000);
|
||||
}
|
||||
function showError(error) {notify(error.message || String(error));}
|
||||
async function refresh() {
|
||||
try {
|
||||
const r=await fetch('/api/timers',{cache:'no-store'});
|
||||
if(!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const data=await r.json(); update(data.items || []);
|
||||
} catch (err) { console.warn('[ALFRED TIMERS]',err); }
|
||||
}
|
||||
const socket = window.alfredSocket;
|
||||
socket?.on('alfred_timers_changed', data => update(data.items || []));
|
||||
socket?.on('alfred_timer_finished', data => {
|
||||
notify(`⏰ ${data.label} : terminé !`);
|
||||
refresh();
|
||||
});
|
||||
socket?.on('connect', refresh);
|
||||
refresh();
|
||||
setInterval(redraw, 250);
|
||||
setInterval(()=>{if(!document.hidden)refresh();}, 15000);
|
||||
|
||||
// Illustration des demandes « qui est ... ? » : source = Wikipédia, réponse = Hermes.
|
||||
function closeContext() {
|
||||
if (context) context.hidden=true;
|
||||
if (contextImage) contextImage.removeAttribute('src');
|
||||
}
|
||||
if (context && contextImage && contextTitle) {
|
||||
socket?.on('alfred_context_image', data => {
|
||||
try {
|
||||
const image = new URL(data.image_url);
|
||||
const source = new URL(data.source_url);
|
||||
if (image.protocol !== 'https:' || !['upload.wikimedia.org','commons.wikimedia.org'].includes(image.hostname))return;
|
||||
if (source.protocol !== 'https:' || source.hostname !== 'fr.wikipedia.org')return;
|
||||
contextTitle.textContent = String(data.title || 'Illustration');
|
||||
contextText.textContent = String(data.text || '').slice(0,500);
|
||||
contextImage.src = image.href;
|
||||
contextSource.href = source.href;
|
||||
contextSource.textContent = String(data.credit || 'Source : Wikipédia');
|
||||
context.hidden=false;
|
||||
} catch (err) { console.warn('[ALFRED CONTEXT]',err); }
|
||||
});
|
||||
contextClose?.addEventListener('click', closeContext);
|
||||
context.addEventListener('pointerdown', e => {if(e.target === context)closeContext();});
|
||||
document.addEventListener('keydown', e=>{if(e.key === 'Escape' && !context.hidden)closeContext();});
|
||||
}
|
||||
|
||||
// Masque uniquement les anciennes fenêtres intitulées exactement « Activité Hermes ».
|
||||
// N'interfère ni avec la session Hermes ni avec ses réponses dans la scène.
|
||||
function hideLegacyHermesActivity() {
|
||||
const titlePattern=/^activit[eé]\s+hermes$/i;
|
||||
for (const el of document.querySelectorAll('h1,h2,h3,h4,[role="heading"],.popup-title,.modal-title')) {
|
||||
if (!titlePattern.test((el.textContent || '').trim()))continue;
|
||||
const popup = el.closest('[role="dialog"],.modal,.popup,.popover,.overlay,.hermes-activity-panel,.hermes-activity-popup');
|
||||
if (popup && popup !== document.body) {
|
||||
popup.hidden = true;
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
for (const el of document.querySelectorAll('[aria-label="Activité Hermes"],[aria-label="Activite Hermes"],#hermesActivityPopup,#hermesActivityModal')) {
|
||||
el.hidden=true;el.style.display='none';
|
||||
}
|
||||
}
|
||||
hideLegacyHermesActivity();
|
||||
let pending=false;
|
||||
const observer = new MutationObserver(() => {
|
||||
if(pending)return;
|
||||
pending=true;
|
||||
queueMicrotask(()=>{pending=false;hideLegacyHermesActivity();});
|
||||
});
|
||||
observer.observe(document.body,{childList:true,subtree:true});
|
||||
})();
|
||||
Reference in New Issue
Block a user