2026-08-16 11:01:58 +00:00
<!DOCTYPE html>
< html lang = "nl" >
2026-08-16 15:49:16 +00:00
< script >( function (){ try { var t = localStorage . getItem ( 'flk-theme' ); if ( t ) document . documentElement . setAttribute ( 'data-theme' , t );} catch ( e ){}})();</ script >
2026-08-16 11:01:58 +00:00
< head >
< meta charset = "UTF-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1" />
< title > War Room · Foodlinkk</ title >
< link rel = "stylesheet" href = "/static/css/palantir-theme.css?v=2" />
2026-08-16 15:49:16 +00:00
< link rel = "stylesheet" href = "/static/css/flk-themes.css?v=1" />
2026-08-16 11:01:58 +00:00
< link rel = "stylesheet" href = "/static/css/pulse-theme.css?v=2" />
< link rel = "stylesheet" href = "/static/css/agent-characters.css?v=4" />
2026-08-16 15:49:16 +00:00
< link rel = "stylesheet" href = "/static/css/agents-warroom.css?v=18" />
< script defer src = "/static/js/agents-warroom.js?v=18" ></ script >
2026-08-16 11:01:58 +00:00
< script defer src = "/static/js/alpine.min.js?v=2" ></ script >
</ head >
< body class = "wr-popup-body" >
< div class = "wr-popup-shell" x-data = "warRoomPopup()" x-init = "init()" >
< div class = "wr-layout" >
< aside class = "wr-roster" >
< h3 > Kanalen</ h3 >
< button type = "button" class = "wr-room-btn" :class = "{active: room && room.kind==='warroom'}" @ click = "openWarroom()" >
< span > ⚔️</ span >< strong > #war-room</ strong >
</ button >
< h3 style = "margin-top:0.85rem" > Agents</ h3 >
< template x-for = "s in souls" :key = "'p-'+s.agent_key" >
< button type = "button" class = "wr-agent-btn"
:class = "{active: room && room.kind==='dm' && room.agent_key===s.agent_key}"
2026-08-16 15:49:16 +00:00
@ click = "selectAgent(s.agent_key)"
@ dblclick . prevent = "openDm(s.agent_key)"
title = "Klik = @tag · dubbelklik = DM" >
2026-08-16 11:01:58 +00:00
< div class = "agent-char-wrap"
:data-agent-avatar = "s.agent_key"
:data-agent-color = "s.avatar_color || '#1e3a5f'"
x-init = "$nextTick(() => window.AgentAvatars && AgentAvatars.paint($el, s.agent_key, s.avatar_color))" ></ div >
< div class = "wr-agent-meta" >
< strong x-text = "s.display_name" ></ strong >
< small x-text = "'@'+s.agent_key" ></ small >
</ div >
</ button >
</ template >
</ aside >
< div class = "wr-main" >
< div class = "wr-head" >
< div >
< h2 x-text = "room ? (room.title || 'War Room') : 'War Room'" ></ h2 >
< small class = "muted" x-show = "status" x-text = "status" ></ small >
</ div >
< div class = "wr-head-actions" >
2026-08-16 15:49:16 +00:00
< label class = "btn btn-sm" style = "display:inline-flex;align-items:center;gap:0.3rem;cursor:pointer;margin:0" title = "Agents praten onderling door" >< input type = "checkbox" x-model = "autoDebate" @ change = "autoDebate && scheduleContinue(1500)" style = "margin:0" /> A2A</ label >
< button type = "button" class = "btn btn-sm btn-pulse" @ click = "openVoiceRoom()" :disabled = "!room" title = "Spraakmeeting met agents" > 🎙️ Voice Room</ button >< button type = "button" class = "btn btn-sm" :class = "{'is-recording': voiceRecording}" @ mousedown . prevent = "startVoiceTalk($event)" @ mouseup . prevent = "stopVoiceTalk($event)" @ touchstart . prevent = "startVoiceTalk($event)" @ touchend . prevent = "stopVoiceTalk($event)" :disabled = "voiceBusy || busy || !room" title = "Snel praten" > 🎤</ button >
2026-08-16 11:01:58 +00:00
< a class = "btn btn-sm" href = "/agents?tab=warroom" target = "_blank" > In cockpit</ a >
2026-08-16 15:49:16 +00:00
< button type = "button" class = "btn btn-sm" @ click = "room && openRoom(room.id)" :disabled = "busy" > ↻</ button >
2026-08-16 11:01:58 +00:00
</ div >
</ div >
< div class = "wr-log" x-ref = "wrLog" >
2026-08-16 15:49:16 +00:00
< p class = "wr-empty" x-show = "!messages.length && !typing" > Nog geen berichten.</ p >
2026-08-16 11:01:58 +00:00
< template x-for = "m in messages" :key = "m.id" >
< div class = "wr-bubble"
:class = "{'is-user': m.sender_type==='user', 'is-agent': m.sender_type==='agent', 'is-system': m.sender_type==='system'}" >
< div class = "wr-avatar" x-show = "m.sender_type!=='system'" >
< template x-if = "m.sender_type==='agent'" >
< div class = "agent-char-wrap"
:data-agent-avatar = "m.agent_key"
x-init = "$nextTick(() => window.AgentAvatars && AgentAvatars.paint($el, m.agent_key))" ></ div >
</ template >
< template x-if = "m.sender_type==='user' && (m.sender_avatar_url || me.avatar_url)" >
< img :src = "m.sender_avatar_url || me.avatar_url" alt = "" />
</ template >
< template x-if = "m.sender_type==='user' && !(m.sender_avatar_url || me.avatar_url)" >
< span class = "wr-avatar-fallback" x-text = "(m.username||me.username||'?').slice(0,2).toUpperCase()" ></ span >
</ template >
</ div >
< div class = "wr-bubble-body" >
2026-08-16 15:49:16 +00:00
< div class = "wr-bubble-meta" >
< span x-text = "labelForMessage(m)" ></ span >
< button type = "button" class = "wr-msg-del" title = "Verwijderen" @ click . stop = "deleteMessage(m)" > 🗑</ button >
</ div >
2026-08-16 11:01:58 +00:00
< div class = "wr-bubble-text" x-text = "m.content" ></ div >
2026-08-16 15:49:16 +00:00
< div class = "wr-attach-list" x-show = "attachmentsOf(m).length" >
< template x-for = "(a, ai) in attachmentsOf(m)" :key = "m.id+'-pa-'+ai" >
< a class = "wr-attach-card" :href = "a.url" target = "_blank" rel = "noopener" >
< img x-show = "isImage(a)" :src = "a.url" :alt = "a.name" />
< span x-text = "a.name" ></ span >
</ a >
</ template >
</ div >
2026-08-16 11:01:58 +00:00
</ div >
</ div >
</ template >
2026-08-16 15:49:16 +00:00
< div class = "wr-bubble is-agent is-typing" x-show = "typing" x-cloak >
< div class = "wr-avatar" >
< div class = "agent-char-wrap"
:data-agent-avatar = "typing && typing.agent_key"
x-effect = "if (typing && window.AgentAvatars) AgentAvatars.paint($el, typing.agent_key)" ></ div >
</ div >
< div class = "wr-bubble-body" >
< div class = "wr-bubble-meta" >< span x-text = "(typing && typing.display_name) || 'Agent'" ></ span >< span > · typt</ span ></ div >
< div class = "wr-bubble-text wr-typing-cursor" x-text = "(typing && typing.text) || '…'" ></ div >
</ div >
</ div >
2026-08-16 11:01:58 +00:00
</ div >
< div class = "wr-composer" >
2026-08-16 15:49:16 +00:00
< div class = "wr-composer-tools" >
< button type = "button" class = "btn btn-sm" :class = "{'is-recording': voiceRecording}" @ mousedown . prevent = "startVoiceTalk($event)" @ mouseup . prevent = "stopVoiceTalk($event)" @ touchstart . prevent = "startVoiceTalk($event)" @ touchend . prevent = "stopVoiceTalk($event)" :disabled = "voiceBusy || busy" title = "Houd in om te praten" > 🎤</ button >
< button type = "button" class = "btn btn-sm" @ click = "emojiOpen=!emojiOpen" :disabled = "busy" > 😊</ button >
< label class = "btn btn-sm" style = "cursor:pointer;margin:0" >
📎
< input type = "file" hidden multiple @ change = "onPickFiles($event)"
accept = ".png,.jpg,.jpeg,.gif,.webp,.pdf,.txt,.csv,.xlsx,.xls,.doc,.docx,.zip,.json,.md,image/*" />
</ label >
< div class = "wr-emoji-pop" x-show = "emojiOpen" @ click . outside = "emojiOpen=false" x-cloak >
< template x-for = "em in emojis" :key = "em" >
< button type = "button" @ click = "insertEmoji(em)" x-text = "em" ></ button >
</ template >
</ div >
</ div >
< div class = "wr-pending" x-show = "pendingFiles.length" >
< template x-for = "(f, fi) in pendingFiles" :key = "f.url" >
< span class = "wr-pending-chip" >
< span x-text = "f.name" ></ span >
< button type = "button" @ click = "removePending(fi)" > × </ button >
</ span >
</ template >
</ div >
< div class = "wr-composer-wrap" >
< div class = "wr-mention-pop" x-show = "mentionOpen && mentionSuggestions().length" x-cloak >
< template x-for = "(s, si) in mentionSuggestions()" :key = "'pm-'+s.agent_key" >
< button type = "button" class = "wr-mention-item"
:class = "{active: si===mentionIndex}"
@ mousedown . prevent = "pickMention(s)" >
< strong x-text = "s.display_name" ></ strong >
< small x-text = "'@'+s.agent_key" ></ small >
</ button >
</ template >
</ div >
< textarea class = "form-input" rows = "2" x-ref = "wrInput" x-model = "draft"
@ input = "onDraftInput()" @ keydown = "onKey($event)"
placeholder = "Typ @bizdev… antwoord komt in deze War Room"
:disabled = "!room" ></ textarea >
</ div >
< button type = "button" class = "btn btn-pulse-green" @ click = "send()" :disabled = "!canSend()" > Stuur</ button >
2026-08-16 11:01:58 +00:00
</ div >
2026-08-16 15:49:16 +00:00
< div class = "wr-voice-overlay" :class = "{ 'is-open': voiceRoomOpen }" x-show = "voiceRoomOpen" x-cloak @ keydown . escape . window = "closeVoiceRoom()" >
< div class = "wr-voice-panel" @ click . stop >
< div class = "wr-voice-head" >
< div >
< h3 > 🎙️ Foodlinkk Voice Room</ h3 >
< small class = "muted" > Jij + agents in één gesprek (spraak)</ small >
</ div >
< div class = "wr-voice-head-actions" >
< label class = "btn btn-sm" style = "display:inline-flex;align-items:center;gap:0.25rem;margin:0;cursor:pointer" >
< input type = "checkbox" x-model = "speakReplies" style = "margin:0" /> 🔊 Voorlezen
</ label >
< button type = "button" class = "btn btn-sm btn-pulse" @ click = "closeVoiceRoom()" > Sluiten</ button >
</ div >
</ div >
< div class = "wr-voice-status" x-text = "status || (voiceRecording ? 'Luisteren…' : (voiceSpeakingKey ? (voiceSpeakingKey + ' spreekt…') : 'Houd de microfoon in om te praten'))" ></ div >
< div class = "wr-voice-grid" >
< div class = "wr-voice-card is-human" :class = "{'is-live': voiceRecording}" >
< div class = "wr-voice-avatar wr-voice-avatar-human" x-text = "(me.username||'Jij').slice(0,2).toUpperCase()" ></ div >
< strong x-text = "me.username || 'Jij'" ></ strong >
< small x-show = "voiceRecording" > 🎤 aan het woord</ small >
< small x-show = "!voiceRecording" class = "muted" > mens</ small >
</ div >
< template x-for = "s in voiceAgentSouls()" :key = "'vr-'+s.agent_key" >
< div class = "wr-voice-card is-agent"
:class = "{'is-live': voiceSpeakingKey===s.agent_key, 'is-off': !isVoiceAgent(s.agent_key)}"
@ click = "toggleVoiceAgent(s.agent_key)"
:title = "isVoiceAgent(s.agent_key) ? 'Klik = uit meeting' : 'Klik = in meeting'" >
< div class = "wr-voice-avatar agent-char-wrap"
:data-agent-avatar = "s.agent_key"
x-init = "$nextTick(() => window.AgentAvatars && AgentAvatars.paint($el, s.agent_key))" ></ div >
< strong x-text = "s.display_name || s.agent_key" ></ strong >
< small x-show = "voiceSpeakingKey===s.agent_key" > 🔊 spreekt</ small >
< small x-show = "voiceSpeakingKey!==s.agent_key" class = "muted" x-text = "isVoiceAgent(s.agent_key) ? ('@'+s.agent_key) : 'niet in meeting'" ></ small >
</ div >
</ template >
</ div >
< p class = "wr-voice-hint" x-show = "voiceLastHeard" > Laatst gehoord: < span x-text = "voiceLastHeard" ></ span ></ p >
< div class = "wr-voice-ptt" >
< button type = "button" class = "wr-ptt-btn"
:class = "{'is-recording': voiceRecording, 'is-busy': voiceBusy}"
@ mousedown . prevent = "startVoiceTalk($event)" @ mouseup . prevent = "stopVoiceTalk($event)"
@ touchstart . prevent = "startVoiceTalk($event)" @ touchend . prevent = "stopVoiceTalk($event)"
@ mouseleave = "voiceRecording && stopVoiceTalk($event)"
:disabled = "voiceBusy || busy" >
< span x-show = "!voiceRecording && !voiceBusy" > 🎤 Ingedrukt houden</ span >
< span x-show = "voiceRecording" > ● Luisteren… laat los</ span >
< span x-show = "voiceBusy && !voiceRecording" > ⏳ Bezig…</ span >
</ button >
</ div >
< div class = "wr-voice-foot" >
< span class = "muted" > Klik agents om ze in/uit de meeting te zetten. Standaard praat @herman; 🔊 leest antwoorden voor.</ span >
< a class = "btn btn-sm" href = "https://meet.ffmuc.net/FoodlinkkWR1" :href = "callHref()" target = "_blank" rel = "noopener" > Alleen-mensen video ↗</ a >
</ div >
</ div >
</ div >
2026-08-16 11:01:58 +00:00
</ div >
</ div >
</ div >
2026-08-16 15:49:16 +00:00
2026-08-16 11:01:58 +00:00
< script src = "/static/js/cockpit.js?v=3" ></ script >
< script src = "/static/js/agent-avatars.js?v=4" ></ script >
2026-08-16 15:49:16 +00:00
<!-- agents-warroom loaded in head (before alpine) -->
2026-08-16 11:01:58 +00:00
</ body >
</ html >