2026-06-09 00:41:27 +00:00
{% extends "base.html" %}
{% block content %}
< div x-data = "hermanChat()" class = "chat-layout" >
< header class = "page-header" >
< h1 > Herman</ h1 >
2026-06-09 10:41:13 +00:00
< p class = "subtitle page-tip" > Co-CEO orchestrator — delegeert naar specialist agents (packaging, design, …).</ p >
2026-06-09 00:41:27 +00:00
</ header >
2026-06-09 10:41:13 +00:00
< div class = "card card-compact" style = "margin-bottom:1rem;" >
< h2 style = "font-size:1rem;margin:0 0 .5rem;" > Verpakking via Packaging agent</ h2 >
< p class = "page-tip" style = "margin:0 0 .5rem;" > Start met < code > maak verpakking:</ code > — de agent genereert het design, slaat op op NAS en rapporteert aan Herman.</ p >
< p class = "page-tip" style = "margin:0;font-size:0.78rem" > Voorbeeld: < em > maak verpakking: pouch 100× 140 voor Halal Kip Curry project #5 halal-badge</ em ></ p >
</ div >
2026-06-09 00:41:27 +00:00
< div class = "card card-compact" style = "margin-bottom:1rem;" >
< h2 style = "font-size:1rem;margin:0 0 .5rem;" > Foto genereren (ComfyUI)</ h2 >
< p class = "page-tip" style = "margin:0 0 .75rem;" > Beschrijf wat je wilt zien. Op CPU duurt dit 1– 3 minuten.</ p >
< form class = "chat-input" @ submit . prevent = "generateImage" >
< input class = "form-input" x-model = "imagePrompt" placeholder = "bijv. halal kipnuggets productfoto, studio licht" autocomplete = "off" />
< button class = "btn btn-primary" type = "submit" :disabled = "generating" >
< span x-show = "!generating" > Genereer</ span >
< span x-show = "generating" > Bezig…</ span >
</ button >
</ form >
< div x-show = "generating" class = "page-tip" style = "margin-top:.5rem;" > Ik ga even de gegevens ophalen — afbeelding wordt gemaakt…</ div >
< div x-show = "lastImage" style = "margin-top:1rem;" >
< img :src = "lastImage" alt = "Gegenereerde afbeelding" style = "max-width:100%;border-radius:8px;border:1px solid var(--border);" />
</ div >
</ div >
< div class = "chat-messages" id = "chat-log" >
{% if user_message %}< div class = "chat-msg user" >< p > {{ user_message }}</ p ></ div > {% endif %}
{% if last_reply %}< div class = "chat-msg agent" >< span class = "badge" > {{ last_agent }}</ span >< p > {{ last_reply }}</ p ></ div > {% endif %}
{% for h in history[:10] %}
< div class = "chat-msg agent" >< small > {{ h.created_at }}</ small >< p > {{ h.title or h.body }}</ p ></ div >
{% endfor %}
</ div >
< form class = "chat-input" @ submit . prevent = "send" >
2026-06-09 10:41:13 +00:00
< input class = "form-input" x-model = "message" placeholder = "Message Herman… (bijv. maak verpakking: doos 120× 80× 40 voor Chai N Masala project #3)" autocomplete = "off" />
2026-06-09 00:41:27 +00:00
< button class = "btn btn-primary" type = "submit" :disabled = "loading" > Send</ button >
</ form >
</ div >
{% endblock %}
{% block scripts %}
< script >
function esc ( s ) { const d = document . createElement ( 'div' ); d . textContent = s || '' ; return d . innerHTML ; }
function hermanChat () {
return {
message : '' ,
imagePrompt : '' ,
loading : false ,
generating : false ,
lastImage : '' ,
async send () {
if ( ! this . message . trim ()) return ;
this . loading = true ;
const msg = this . message ;
try {
const r = await Cockpit . api ( '/api/herman/chat' , { method : 'POST' , body : JSON . stringify ({ message : msg }) });
const log = document . getElementById ( 'chat-log' );
log . innerHTML += '<div class="chat-msg user"><p>' + esc ( msg ) + '</p></div>' ;
let badge = esc ( r . agent_label || 'Herman' );
if ( r . delegated_agents && r . delegated_agents . length > 1 ) {
badge += ' · ' + esc ( r . delegated_agents . join ( ', ' ));
}
let html = '<div class="chat-msg agent"><span class="badge">' + badge + '</span><p>' + esc ( r . reply || '' ) + '</p>' ;
if ( r . image_url ) {
html += '<img src="' + r . image_url + '" alt="Gegenereerd" style="max-width:100%;margin-top:.5rem;border-radius:8px;" />' ;
this . lastImage = r . image_url ;
}
2026-06-09 10:41:13 +00:00
if ( r . packaging_studio_url ) {
html += '<p style="margin-top:.5rem"><a class="btn btn-sm" href="' + esc ( r . packaging_studio_url ) + '">📦 Open Packaging Studio</a>' ;
if ( r . packaging_pdf_url ) html += ' <a class="btn btn-sm btn-ghost" href="' + esc ( r . packaging_pdf_url ) + '" target="_blank">PDF</a>' ;
html += '</p>' ;
}
2026-06-09 00:41:27 +00:00
html += '</div>' ;
log . innerHTML += html ;
this . message = '' ;
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . loading = false ;
},
async generateImage () {
if ( ! this . imagePrompt . trim ()) return ;
this . generating = true ;
this . lastImage = '' ;
try {
const r = await Cockpit . api ( '/api/ai/generate-image' , {
method : 'POST' ,
body : JSON . stringify ({ prompt : this . imagePrompt , width : 512 , height : 512 , steps : 15 })
});
if ( r . proxy_url ) {
this . lastImage = r . proxy_url ;
const log = document . getElementById ( 'chat-log' );
log . innerHTML += '<div class="chat-msg agent"><span class="badge">Design</span><p>Afbeelding klaar: ' + esc ( this . imagePrompt ) + '</p><img src="' + r . proxy_url + '" alt="Gegenereerd" style="max-width:100%;margin-top:.5rem;border-radius:8px;" /></div>' ;
}
Cockpit . toast ( 'Afbeelding gegenereerd' , 'success' );
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . generating = false ;
}
};
}
</ script >
{% endblock %}