Platform bundle: marketing publish, IT ops, packaging, agents mesh.

Volledige Foodlinkk Command Center uitbreiding met social automatisering,
reclamefolder filters, Proxmox monitoring en documentatie.
This commit is contained in:
Aissa
2026-06-09 00:41:27 +00:00
commit 5d60d33db1
212 changed files with 30044 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
{% extends "base.html" %}
{% block title %}AI Studio · ComfyUI{% endblock %}
{% block extra_head %}
<style>
.studio-grid { display: grid; grid-template-columns: 1fr 340px; gap: 1rem; }
@media (max-width: 1000px) { .studio-grid { grid-template-columns: 1fr; } }
.studio-preview {
min-height: 420px; display: flex; align-items: center; justify-content: center;
background: rgba(0,0,0,.35); border: 1px solid rgba(148,163,184,.15);
border-radius: 12px; overflow: hidden;
}
.studio-preview img { max-width: 100%; max-height: 70vh; border-radius: 8px; }
.progress-bar {
height: 8px; background: rgba(148,163,184,.15); border-radius: 999px; overflow: hidden;
margin: .75rem 0;
}
.progress-fill {
height: 100%; width: 0%; background: linear-gradient(90deg, #22d3ee, #a855f7);
transition: width .4s ease;
}
.event-log {
max-height: 280px; overflow-y: auto; font-family: ui-monospace, monospace;
font-size: .78rem; background: rgba(0,0,0,.35); border-radius: 8px; padding: .6rem;
border: 1px solid rgba(148,163,184,.12);
}
.event-log div { padding: .15rem 0; color: var(--text-secondary); }
.quality-pills { display: flex; gap: .5rem; flex-wrap: wrap; margin: .75rem 0; }
.quality-pill {
padding: .35rem .75rem; border-radius: 999px; cursor: pointer; font-size: .85rem;
border: 1px solid rgba(148,163,184,.2); background: rgba(0,0,0,.2);
}
.quality-pill.active { border-color: var(--accent-cyan); color: var(--accent-cyan); }
.studio-links { display: flex; gap: .75rem; flex-wrap: wrap; margin-top: .75rem; }
</style>
{% endblock %}
{% block content %}
<div x-data="aiStudio()" x-init="init()">
<header class="page-header">
<h1>AI Studio</h1>
<p class="subtitle page-tip">ComfyUI — prompt, live voortgang, HD export. CPU: 310 min per HD foto.</p>
</header>
<div class="studio-grid">
<section class="panel">
<h2 style="font-size:1rem;margin:0 0 .75rem;">Positieve prompt</h2>
<textarea class="form-input" rows="3" x-model="prompt"
placeholder="bijv. premium halal kipnuggets productfoto, studio softbox, witte achtergrond, 8k commercial"></textarea>
<h2 style="font-size:1rem;margin:1rem 0 .75rem;">Negatieve prompt</h2>
<textarea class="form-input" rows="2" x-model="negativePrompt"
placeholder="blurry, low quality, watermark, text, ugly, deformed, bad anatomy"></textarea>
<div class="quality-pills">
<button type="button" class="quality-pill" :class="{active: quality==='fast'}" @click="quality='fast'">Snel · 512px</button>
<button type="button" class="quality-pill" :class="{active: quality==='hd'}" @click="quality='hd'">HD · 1024px</button>
<button type="button" class="quality-pill" :class="{active: quality==='ultra'}" @click="quality='ultra'">Ultra · 1024px+</button>
</div>
<div style="display:flex;gap:.5rem;flex-wrap:wrap;">
<button class="btn btn-primary" @click="start()" :disabled="running || !prompt.trim()">
<span x-show="!running">Genereer HD foto</span>
<span x-show="running">Bezig…</span>
</button>
<a class="btn btn-ghost" href="http://10.4.7.18:8188" target="_blank" rel="noopener">ComfyUI backend ↗</a>
</div>
<div x-show="running || percent > 0" style="margin-top:1rem;">
<div style="display:flex;justify-content:space-between;font-size:.85rem;">
<span x-text="statusLabel"></span>
<span x-text="percent + '%'"></span>
</div>
<div class="progress-bar"><div class="progress-fill" :style="'width:' + percent + '%'"></div></div>
<p class="page-tip" x-text="statusMessage" style="margin:0;"></p>
</div>
</section>
<aside class="panel">
<h2 style="font-size:1rem;margin:0 0 .75rem;">Live log</h2>
<div class="event-log">
<template x-for="(ev, i) in events" :key="i">
<div x-text="ev"></div>
</template>
<div x-show="!events.length" class="page-tip">Start een generatie om ComfyUI live te volgen.</div>
</div>
<div class="studio-links" x-show="imageUrl">
<a class="btn btn-ghost" :href="imageUrl" target="_blank" download>Download</a>
</div>
</aside>
</div>
<div class="panel" style="margin-top:1rem;">
<div class="studio-preview">
<img x-show="imageUrl" :src="imageUrl" alt="Gegenereerde afbeelding" />
<p x-show="!imageUrl && !running" class="page-tip">Nog geen afbeelding — vul prompt in en klik Genereer.</p>
<p x-show="running && !imageUrl" class="page-tip">ComfyUI werkt op de achtergrond…</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function aiStudio() {
return {
prompt: '',
negativePrompt: 'blurry, low quality, watermark, text, ugly, deformed, bad anatomy, oversaturated',
quality: 'hd',
running: false,
promptId: null,
pollTimer: null,
percent: 0,
statusLabel: '',
statusMessage: '',
events: [],
imageUrl: '',
init() {},
async start() {
if (!this.prompt.trim() || this.running) return;
this.running = true;
this.percent = 0;
this.events = [];
this.imageUrl = '';
this.statusLabel = 'Starten…';
this.statusMessage = 'ComfyUI queue';
try {
const r = await Cockpit.api('/api/ai/generate-image/start', {
method: 'POST',
body: JSON.stringify({ prompt: this.prompt.trim(), negative_prompt: this.negativePrompt.trim(), quality: this.quality })
});
this.promptId = r.prompt_id;
this.events = r.events || ['Gestart'];
this.statusLabel = r.quality_label || this.quality;
this.poll();
} catch (e) {
Cockpit.toast(e.message, 'error');
this.running = false;
}
},
poll() {
if (this.pollTimer) clearInterval(this.pollTimer);
this.pollTimer = setInterval(async () => {
if (!this.promptId) return;
try {
const j = await Cockpit.api('/api/ai/generate-image/progress/' + this.promptId);
this.percent = j.percent || 0;
this.statusMessage = j.message || j.status || '';
this.statusLabel = (j.quality || this.quality) + ' · ' + (j.status || '');
if (j.events && j.events.length) this.events = j.events;
if (j.status === 'done' && j.proxy_url) {
this.imageUrl = j.proxy_url;
this.percent = 100;
this.running = false;
clearInterval(this.pollTimer);
Cockpit.toast('HD foto klaar', 'success');
}
if (j.status === 'error') {
this.running = false;
clearInterval(this.pollTimer);
Cockpit.toast(j.error || 'ComfyUI fout', 'error');
}
} catch (e) {
this.running = false;
clearInterval(this.pollTimer);
Cockpit.toast(e.message, 'error');
}
}, 1500);
}
};
}
</script>
{% endblock %}