2026-06-09 00:41:27 +00:00
{% extends "base.html" %}
{% block content %}
< div x-data = "settingsPage()" x-init = "load()" >
< header class = "page-header settings-header" >
< div >
< h1 > Settings</ h1 >
< p class = "subtitle page-tip" > Configure integrations — Herman reads active settings from PostgreSQL.</ p >
</ div >
</ header >
< nav class = "settings-tabs" >
< a href = "/settings?tab=email" class = "settings-tab {% if active_tab == 'email' %}active{% endif %}" > Email</ a >
< a href = "/settings?tab=social" class = "settings-tab {% if active_tab == 'social' %}active{% endif %}" > Social API's</ a >
2026-06-23 10:04:23 +00:00
< a href = "/settings?tab=ai" class = "settings-tab {% if active_tab == 'ai' %}active{% endif %}" > AI / LLM</ a >
2026-06-09 00:41:27 +00:00
< a href = "/settings?tab=permissions" class = "settings-tab {% if active_tab == 'permissions' %}active{% endif %}" > Rechten Herman</ a >
< a href = "/settings?tab=general" class = "settings-tab {% if active_tab == 'general' %}active{% endif %}" > General</ a >
</ nav >
{% if active_tab == 'email' %}
< section class = "panel settings-panel" >
< div class = "settings-panel-head" >
< div >
< h2 > Email accounts</ h2 >
< p class = "page-tip" > Kies welk mailbox Herman gebruikt om te versturen. IMAP velden zijn voor latere inbox-sync.</ p >
</ div >
< button type = "button" class = "btn btn-primary" @ click = "openForm()" > + Nieuw account</ button >
</ div >
< div class = "settings-active-banner" x-show = "activeAccount" x-cloak >
< span class = "badge badge-positive" > Actief</ span >
< span x-text = "activeAccount ? activeAccount.label + ' · ' + activeAccount.email_address : ''" ></ span >
</ div >
< div class = "email-account-list" >
< template x-for = "acc in accounts" :key = "acc.id" >
< article class = "email-account-card" :class = "{ 'is-active': acc.is_active }" >
< div class = "email-account-card-head" >
< div >
< strong x-text = "acc.label" ></ strong >
< span class = "muted" x-text = "acc.email_address" ></ span >
</ div >
< span class = "badge" :class = "acc.is_active ? 'badge-positive' : 'badge-neutral'" x-text = "acc.is_active ? 'Actief' : acc.provider" ></ span >
</ div >
< div class = "email-account-meta" >
< span x-text = "'SMTP: ' + (acc.smtp_host || '—') + ':' + (acc.smtp_port || '')" ></ span >
< span x-text = "acc.smtp_password_set ? '🔒 wachtwoord opgeslagen' : '⚠️ geen SMTP wachtwoord'" ></ span >
< span x-show = "acc.last_test_status" x-text = "'Test: ' + acc.last_test_status" ></ span >
</ div >
< div class = "btn-group" >
< button type = "button" class = "btn btn-secondary btn-sm" @ click = "edit(acc)" > Bewerken</ button >
< button type = "button" class = "btn btn-primary btn-sm" @ click = "activate(acc.id)" x-show = "!acc.is_active" > Maak actief</ button >
< button type = "button" class = "btn btn-secondary btn-sm" @ click = "testAccount(acc.id)" > Test SMTP</ button >
< button type = "button" class = "btn btn-reject btn-sm" @ click = "remove(acc.id)" > Verwijder</ button >
</ div >
</ article >
</ template >
< p class = "empty-state" x-show = "!accounts.length && !loading" > Nog geen email account — voeg er een toe om Herman mail te laten versturen.</ p >
</ div >
</ section >
< div class = "modal" :class = "{ open: formOpen }" @ click . self = "formOpen=false" >
< div class = "modal-card settings-modal" >
< h3 x-text = "form.id ? 'Email account bewerken' : 'Nieuw email account'" ></ h3 >
< label class = "form-label" > Label
< input class = "form-input" x-model = "form.label" placeholder = "bv. Foodlinkk sales" />
</ label >
< label class = "form-label" > Email adres
< input class = "form-input" type = "email" x-model = "form.email_address" placeholder = "info@foodlinkk.nl" />
</ label >
< label class = "form-label" > Provider (vult SMTP/IMAP automatisch in)
< select class = "form-input" x-model = "form.provider" @ change = "applyProvider()" >
< option value = "custom" > Custom / eigen server</ option >
< option value = "gmail" > Gmail</ option >
< option value = "outlook" > Outlook / Microsoft 365</ option >
< option value = "zoho" > Zoho Mail</ option >
</ select >
</ label >
< div class = "settings-form-grid" >
< label class = "form-label" > SMTP host< input class = "form-input" x-model = "form.smtp_host" /></ label >
< label class = "form-label" > SMTP port< input class = "form-input" type = "number" x-model . number = "form.smtp_port" /></ label >
< label class = "form-label" > SMTP user< input class = "form-input" x-model = "form.smtp_user" /></ label >
< label class = "form-label" > SMTP wachtwoord
< input class = "form-input" type = "password" x-model = "form.smtp_password" :placeholder = "form.smtp_password_set ? '•••••••• (laat leeg om te behouden)' : 'App-wachtwoord'" />
</ label >
</ div >
< details class = "settings-details" >
< summary > IMAP (inbox sync — later)</ summary >
< div class = "settings-form-grid" >
< label class = "form-label" > IMAP host< input class = "form-input" x-model = "form.imap_host" /></ label >
< label class = "form-label" > IMAP port< input class = "form-input" type = "number" x-model . number = "form.imap_port" /></ label >
< label class = "form-label" > IMAP user< input class = "form-input" x-model = "form.imap_user" /></ label >
< label class = "form-label" > IMAP wachtwoord
< input class = "form-input" type = "password" x-model = "form.imap_password" :placeholder = "form.imap_password_set ? '••••••••' : 'App-wachtwoord'" />
</ label >
</ div >
< label class = "form-check" >
< input type = "checkbox" x-model = "form.sync_enabled" /> Inbox sync inschakelen (wanneer beschikbaar)
</ label >
</ details >
< label class = "form-check" >
< input type = "checkbox" x-model = "form.is_active" /> Direct als actief account instellen
</ label >
< div class = "btn-group" >
< button type = "button" class = "btn btn-primary" @ click = "save()" :disabled = "saving" > Opslaan</ button >
< button type = "button" class = "btn btn-secondary" @ click = "testForm()" :disabled = "saving" > Test SMTP</ button >
< button type = "button" class = "btn btn-secondary" @ click = "formOpen=false" > Annuleren</ button >
</ div >
</ div >
</ div >
2026-06-23 10:04:23 +00:00
{% elif active_tab == 'ai' %}
< section class = "panel settings-panel" x-data = "llmSettingsPage()" x-init = "load()" >
< div class = "settings-panel-head" >
< div >
< h2 > AI / LLM providers</ h2 >
< p class = "page-tip" > Koppel cloud API's (DeepSeek, Gemini, Groq, OpenRouter) naast lokale Ollama. Standaard provider wordt gebruikt in chat, Herman en documenten.</ p >
</ div >
< button type = "button" class = "btn btn-primary" @ click = "openForm()" > + Nieuwe provider</ button >
</ div >
< div class = "settings-active-banner" x-show = "defaultProvider" x-cloak >
< span class = "badge badge-positive" > Standaard</ span >
< span x-text = "defaultProvider ? defaultProvider.label + ' · ' + defaultProvider.model : ''" ></ span >
</ div >
< div class = "llm-preset-grid" style = "display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.65rem;margin-bottom:1.25rem" >
< template x-for = "pr in presets" :key = "pr.id" >
< div class = "perm-card" style = "cursor:pointer" @ click = "quickAdd(pr)" >
< strong x-text = "pr.label" ></ strong >
< p class = "page-tip" style = "margin:0.35rem 0" x-text = "pr.hint" ></ p >
< small x-text = "(pr.models||[]).slice(0,2).join(', ')" ></ small >
</ div >
</ template >
</ div >
< div class = "email-account-list" >
< template x-for = "p in providers" :key = "p.id" >
< article class = "email-account-card" :class = "{ 'is-active': p.is_default }" >
< div class = "email-account-card-head" >
< div >
< strong x-text = "p.label" ></ strong >
< span class = "muted" x-text = "p.preset_label + ' · ' + p.model" ></ span >
</ div >
< span class = "badge" :class = "p.is_default ? 'badge-positive' : (p.is_active ? 'badge-neutral' : 'badge-muted')"
x-text = "p.is_default ? 'Standaard' : (p.is_active ? 'Actief' : 'Inactief')" ></ span >
</ div >
< div class = "email-account-meta" >
< span x-text = "p.api_base_url || 'Ollama lokaal'" ></ span >
< span x-text = "p.api_key_set ? '🔒 API key opgeslagen' : (p.needs_key ? '⚠️ geen API key' : 'Geen key nodig')" ></ span >
< span x-show = "p.last_test_status" x-text = "'Test: ' + p.last_test_status" ></ span >
</ div >
< div class = "btn-group" >
< button type = "button" class = "btn btn-secondary btn-sm" @ click = "edit(p)" > Bewerken</ button >
< button type = "button" class = "btn btn-primary btn-sm" @ click = "activate(p.id)" x-show = "!p.is_default" > Maak standaard</ button >
< button type = "button" class = "btn btn-secondary btn-sm" @ click = "testProvider(p.id)" > Test</ button >
< button type = "button" class = "btn btn-reject btn-sm" @ click = "remove(p.id)" x-show = "p.provider_type !== 'ollama' || providers.length > 1" > Verwijder</ button >
</ div >
</ article >
</ template >
< p class = "empty-state" x-show = "!providers.length && !loading" > Nog geen LLM provider — voeg DeepSeek, Gemini of Groq toe.</ p >
</ div >
< div class = "modal" :class = "{ open: formOpen }" @ click . self = "formOpen=false" >
< div class = "modal-card settings-modal" >
< h3 x-text = "form.id ? 'Provider bewerken' : 'Nieuwe LLM provider'" ></ h3 >
< label class = "form-label" > Label
< input class = "form-input" x-model = "form.label" placeholder = "bv. DeepSeek productie" />
</ label >
< label class = "form-label" > Type
< select class = "form-input" x-model = "form.provider_type" @ change = "applyPreset()" >
< template x-for = "pr in presets" :key = "'fp'+pr.id" >
< option :value = "pr.id" x-text = "pr.label" ></ option >
</ template >
</ select >
</ label >
< label class = "form-label" > Model
< input class = "form-input" x-model = "form.model" list = "llm-model-list" placeholder = "deepseek-chat" />
< datalist id = "llm-model-list" >
< template x-for = "m in currentPresetModels()" :key = "m" >
< option :value = "m" ></ option >
</ template >
</ datalist >
</ label >
< label class = "form-label" x-show = "formNeedsKey()" > API base URL
< input class = "form-input" x-model = "form.api_base_url" placeholder = "https://api.deepseek.com/v1" />
</ label >
< label class = "form-label" x-show = "formNeedsKey()" > API key
< input class = "form-input" type = "password" x-model = "form.api_key"
:placeholder = "form.api_key_set ? '•••••••• (laat leeg om te behouden)' : 'sk-...'" />
</ label >
< p class = "page-tip" x-show = "presetHint()" x-text = "presetHint()" ></ p >
< p class = "page-tip" x-show = "presetSignup()" >
< a :href = "presetSignup()" target = "_blank" rel = "noopener" > → Gratis API key aanmaken</ a >
</ p >
< label class = "form-check" >
< input type = "checkbox" x-model = "form.is_default" /> Instellen als standaard provider
</ label >
< div class = "btn-group" >
< button type = "button" class = "btn btn-primary" @ click = "save()" :disabled = "saving" > Opslaan</ button >
< button type = "button" class = "btn btn-secondary" @ click = "testForm()" :disabled = "saving" > Test</ button >
< button type = "button" class = "btn btn-secondary" @ click = "formOpen=false" > Annuleren</ button >
</ div >
</ div >
</ div >
</ section >
2026-06-09 00:41:27 +00:00
{% elif active_tab == 'permissions' %}
< section class = "panel settings-panel hm-neo" x-data = "permissionsPage()" x-init = "loadPerms()" >
< div class = "settings-panel-head" >
< div >
< h2 > Herman — Module rechten</ h2 >
< p class = "page-tip" > Bepaal welke bedrijfsprocessen Herman mag benaderen. Grant all = volledige toegang.</ p >
</ div >
< button type = "button" class = "btn btn-pulse-green" @ click = "grantAll()" > 👑 Alles aan Herman</ button >
</ div >
< p >< span class = "live-badge" > Live</ span > < span x-text = "grantedCount + ' / ' + total + ' modules actief'" ></ span ></ p >
< div class = "perm-grid" >
< template x-for = "p in perms" :key = "p.module_key" >
< div class = "perm-card" :class = "p.granted ? 'granted' : ''" >
< div >
< strong x-text = "p.module_label" ></ strong >
< p style = "font-size:0.8rem;margin:0.25rem 0;color:#94a3b8" x-text = "p.description" ></ p >
< small x-text = "p.category" ></ small >
</ div >
< label class = "form-check" >
< input type = "checkbox" :checked = "p.granted" @ change = "toggle(p.module_key, $event.target.checked)" > Toegang
</ label >
</ div >
</ template >
</ div >
</ section >
{% elif active_tab == 'social' %}
< section class = "panel settings-panel" x-data = "socialSettingsPage()" x-init = "load()" >
< div class = "settings-panel-head" >
< div >
< h2 > Social API's</ h2 >
< p class = "page-tip" > Configureer later je API-sleutels per platform. Zonder keys worden posts in Automatisering overgeslagen (skip-modus).</ p >
</ div >
< a class = "btn btn-sm btn-pulse" href = "/marketing?tab=publish" > → Marketing Automatisering</ a >
</ div >
< div class = "social-platform-grid" >
< template x-for = "p in platforms" :key = "p.key" >
< div class = "perm-card" :class = "p.configured ? 'granted' : ''" >
< div class = "social-platform-head" >
< strong x-text = "p.label" ></ strong >
< span class = "badge" :class = "p.is_active && p.configured ? 'badge-positive' : 'badge-muted'"
x-text = "p.configured ? (p.is_active ? 'Actief' : 'Inactief') : 'Niet geconfigureerd'" ></ span >
</ div >
< p class = "page-tip" x-show = "p.last_test_status" x-text = "'Laatste test: ' + (p.last_test_status||'') + ' — ' + (p.last_test_message||'')" ></ p >
< template x-for = "field in p.fields" :key = "field.name" >
< label class = "form-label" x-text = "field.label" ></ label >
< input class = "form-input" :type = "field.secret ? 'password' : 'text'"
:placeholder = "field.placeholder"
x-model = "p.form[field.name]" />
< small x-show = "field.set && !p.form[field.name]" class = "muted" > Opgeslagen waarde blijft behouden</ small >
</ template >
< label class = "form-check" >
< input type = "checkbox" x-model = "p.form.is_active" /> Platform actief voor publicatie
</ label >
< div class = "btn-group" >
< button type = "button" class = "btn btn-primary btn-sm" @ click = "save(p)" :disabled = "p.saving" > Opslaan</ button >
< button type = "button" class = "btn btn-secondary btn-sm" @ click = "test(p)" :disabled = "p.saving || !p.configured" > Test</ button >
</ div >
</ div >
</ template >
</ div >
</ section >
{% elif active_tab == 'general' %}
< section class = "panel settings-panel" >
< h2 > General</ h2 >
< p class = "page-tip" > Dashboard: < a href = "http://10.4.7.18:8600" > http://10.4.7.18:8600</ a > · Telegram: @klaploper_bot · Ochtend briefing: 07:00</ p >
< p class = "page-tip" > Meer instellingen (Telegram tijd, timezone) komen hier later.</ p >
</ section >
{% endif %}
</ div >
{% endblock %}
{% block scripts %}
< script >
function socialSettingsPage () {
const META = {
twitter : { label : 'X (Twitter)' , fields : [
{ name : 'api_key' , label : 'API Key' , secret : true },
{ name : 'api_secret' , label : 'API Secret' , secret : true },
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'access_secret' , label : 'Access Secret' , secret : true },
]},
linkedin : { label : 'LinkedIn' , fields : [
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'person_urn' , label : 'Person URN' , secret : false },
]},
instagram : { label : 'Instagram' , fields : [
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'page_id' , label : 'Page / Business ID' , secret : false },
]},
facebook : { label : 'Facebook' , fields : [
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'page_id' , label : 'Page ID' , secret : false },
]},
tiktok : { label : 'TikTok' , fields : [
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'open_id' , label : 'Open ID' , secret : false },
]},
pinterest : { label : 'Pinterest' , fields : [
{ name : 'access_token' , label : 'Access Token' , secret : true },
{ name : 'board_id' , label : 'Board ID' , secret : false },
]},
};
return {
platforms : [],
async load () {
try {
const r = await Cockpit . api ( '/api/settings/social' );
const items = r . items || [];
const byKey = {};
items . forEach ( i => { byKey [ i . platform ] = i ; });
this . platforms = ( r . platforms || Object . keys ( META )). map ( key => {
const row = byKey [ key ] || {};
const cfg = row . config || {};
const meta = META [ key ] || { label : key , fields : [] };
const form = { is_active : !! row . is_active };
meta . fields . forEach ( f => {
form [ f . name ] = '' ;
f . set = !! cfg [ f . name + '_set' ];
f . placeholder = f . set ? '••••••••' : '' ;
});
const configured = meta . fields . every ( f => f . set || form [ f . name ]);
return {
key , label : meta . label , fields : meta . fields , form ,
is_active : !! row . is_active ,
configured : meta . fields . every ( f => cfg [ f . name + '_set' ]),
last_test_status : row . last_test_status ,
last_test_message : row . last_test_message ,
saving : false ,
};
});
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
async save ( p ) {
p . saving = true ;
try {
const body = { is_active : p . form . is_active };
p . fields . forEach ( f => {
if ( p . form [ f . name ]) body [ f . name ] = p . form [ f . name ];
});
await Cockpit . api ( '/api/settings/social/' + p . key , { method : 'PUT' , body : JSON . stringify ( body ) });
Cockpit . toast ( p . label + ' opgeslagen' , 'success' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
p . saving = false ;
},
async test ( p ) {
p . saving = true ;
try {
const r = await Cockpit . api ( '/api/settings/social/' + p . key + '/test' , { method : 'POST' });
Cockpit . toast ( r . message || ( r . ok ? 'OK' : 'Mislukt' ), r . ok ? 'success' : 'error' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
p . saving = false ;
},
};
}
function permissionsPage () {
return {
perms : [], grantedCount : 0 , total : 0 ,
async loadPerms () {
const r = await Cockpit . api ( '/api/settings/permissions' );
this . perms = r . items || [];
this . grantedCount = r . granted_count || 0 ;
this . total = r . total || 0 ;
},
async toggle ( key , granted ) {
await Cockpit . api ( '/api/settings/permissions/' + key , { method : 'PUT' , body : JSON . stringify ({ granted }) });
await this . loadPerms ();
},
async grantAll () {
const r = await Cockpit . api ( '/api/settings/permissions/grant-all' , { method : 'POST' });
Cockpit . toast ( r . message || 'Alle rechten toegekend' , 'success' );
await this . loadPerms ();
}
};
}
</ script >
{% if active_tab == 'email' %}
< script >
const PROVIDERS = {
gmail : { smtp_host : 'smtp.gmail.com' , smtp_port : 587 , imap_host : 'imap.gmail.com' , imap_port : 993 },
outlook : { smtp_host : 'smtp.office365.com' , smtp_port : 587 , imap_host : 'outlook.office365.com' , imap_port : 993 },
zoho : { smtp_host : 'smtp.zoho.eu' , smtp_port : 587 , imap_host : 'imap.zoho.eu' , imap_port : 993 },
custom : { smtp_host : '' , smtp_port : 587 , imap_host : '' , imap_port : 993 },
};
function settingsPage () {
return {
accounts : [],
activeAccount : null ,
loading : true ,
formOpen : false ,
saving : false ,
form : defaultForm (),
async load () {
this . loading = true ;
try {
const r = await Cockpit . api ( '/api/settings/email' );
this . accounts = r . accounts || [];
this . activeAccount = this . accounts . find ( a => a . is_active ) || null ;
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . loading = false ;
},
defaultForm () { return defaultForm (); },
openForm () {
this . form = defaultForm ();
this . formOpen = true ;
},
edit ( acc ) {
this . form = Object . assign ( defaultForm (), acc , { smtp_password : '' , imap_password : '' });
this . formOpen = true ;
},
applyProvider () {
const p = PROVIDERS [ this . form . provider ] || PROVIDERS . custom ;
this . form . smtp_host = p . smtp_host ;
this . form . smtp_port = p . smtp_port ;
this . form . imap_host = p . imap_host ;
this . form . imap_port = p . imap_port ;
if ( this . form . email_address && ! this . form . smtp_user ) {
this . form . smtp_user = this . form . email_address ;
this . form . imap_user = this . form . email_address ;
}
},
bodyFromForm () {
const b = {
label : this . form . label ,
email_address : this . form . email_address ,
provider : this . form . provider ,
is_active : this . form . is_active ,
smtp_host : this . form . smtp_host ,
smtp_port : this . form . smtp_port ,
smtp_user : this . form . smtp_user ,
imap_host : this . form . imap_host ,
imap_port : this . form . imap_port ,
imap_user : this . form . imap_user ,
sync_enabled : this . form . sync_enabled ,
};
if ( this . form . smtp_password ) b . smtp_password = this . form . smtp_password ;
if ( this . form . imap_password ) b . imap_password = this . form . imap_password ;
return b ;
},
async save () {
this . saving = true ;
try {
const body = this . bodyFromForm ();
if ( this . form . id ) {
await Cockpit . api ( '/api/settings/email/' + this . form . id , { method : 'PUT' , body : JSON . stringify ( body ) });
} else {
await Cockpit . api ( '/api/settings/email' , { method : 'POST' , body : JSON . stringify ( body ) });
}
Cockpit . toast ( 'Opgeslagen' , 'success' );
this . formOpen = false ;
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . saving = false ;
},
async activate ( id ) {
try {
await Cockpit . api ( '/api/settings/email/' + id + '/activate' , { method : 'POST' });
Cockpit . toast ( 'Actief account gewijzigd' , 'success' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
async testAccount ( id ) {
try {
Cockpit . toast ( 'SMTP test…' , 'info' );
const r = await Cockpit . api ( '/api/settings/email/' + id + '/test' , { method : 'POST' });
Cockpit . toast ( r . message || 'SMTP OK' , r . ok ? 'success' : 'error' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
async testForm () {
this . saving = true ;
try {
const r = await Cockpit . api ( '/api/settings/email/test' , {
method : 'POST' ,
body : JSON . stringify ( this . bodyFromForm ()),
});
Cockpit . toast ( r . message || 'SMTP OK' , r . ok ? 'success' : 'error' );
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . saving = false ;
},
async remove ( id ) {
if ( ! confirm ( 'Email account verwijderen?' )) return ;
try {
await Cockpit . api ( '/api/settings/email/' + id , { method : 'DELETE' });
Cockpit . toast ( 'Verwijderd' , 'success' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
};
}
function defaultForm () {
return {
id : null , label : '' , email_address : '' , provider : 'gmail' , is_active : true ,
smtp_host : 'smtp.gmail.com' , smtp_port : 587 , smtp_user : '' , smtp_password : '' , smtp_password_set : false ,
imap_host : 'imap.gmail.com' , imap_port : 993 , imap_user : '' , imap_password : '' , imap_password_set : false ,
sync_enabled : false ,
};
}
</ script >
2026-06-23 10:04:23 +00:00
{% elif active_tab == 'ai' %}
< script >
function llmDefaultForm () {
return {
id : null , label : '' , provider_type : 'deepseek' , api_base_url : '' , api_key : '' ,
model : 'deepseek-chat' , is_active : true , is_default : false , api_key_set : false ,
};
}
function llmSettingsPage () {
return {
providers : [],
presets : [],
defaultProvider : null ,
loading : true ,
formOpen : false ,
saving : false ,
form : llmDefaultForm (),
async load () {
this . loading = true ;
try {
const r = await Cockpit . api ( '/api/settings/llm' );
this . providers = r . providers || [];
this . presets = r . presets || [];
this . defaultProvider = r . default || this . providers . find ( p => p . is_default ) || null ;
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . loading = false ;
},
openForm () {
this . form = llmDefaultForm ();
this . applyPreset ();
this . formOpen = true ;
},
quickAdd ( pr ) {
this . form = llmDefaultForm ();
this . form . provider_type = pr . id ;
this . form . label = pr . label ;
this . applyPreset ();
this . formOpen = true ;
},
edit ( p ) {
this . form = Object . assign ( llmDefaultForm (), p , { api_key : '' });
this . formOpen = true ;
},
applyPreset () {
const pr = this . presets . find ( x => x . id === this . form . provider_type ) || {};
if ( pr . api_base_url ) this . form . api_base_url = pr . api_base_url ;
if (( pr . models || []). length && ! this . form . id ) this . form . model = pr . models [ 0 ];
if ( ! this . form . label ) this . form . label = pr . label || this . form . provider_type ;
},
currentPresetModels () {
const pr = this . presets . find ( x => x . id === this . form . provider_type );
return ( pr && pr . models ) ? pr . models : [];
},
formNeedsKey () {
const pr = this . presets . find ( x => x . id === this . form . provider_type );
return pr ? !! pr . needs_key : true ;
},
presetHint () {
const pr = this . presets . find ( x => x . id === this . form . provider_type );
return pr ? ( pr . hint || '' ) : '' ;
},
presetSignup () {
const pr = this . presets . find ( x => x . id === this . form . provider_type );
return pr ? ( pr . signup_url || '' ) : '' ;
},
bodyFromForm () {
const b = {
label : this . form . label ,
provider_type : this . form . provider_type ,
api_base_url : this . form . api_base_url ,
model : this . form . model ,
is_active : true ,
is_default : this . form . is_default ,
extra_config : {},
};
if ( this . form . api_key ) b . api_key = this . form . api_key ;
return b ;
},
async save () {
this . saving = true ;
try {
const body = this . bodyFromForm ();
if ( this . form . id ) {
await Cockpit . api ( '/api/settings/llm/' + this . form . id , { method : 'PUT' , body : JSON . stringify ( body ) });
} else {
await Cockpit . api ( '/api/settings/llm' , { method : 'POST' , body : JSON . stringify ( body ) });
}
Cockpit . toast ( 'LLM provider opgeslagen' , 'success' );
this . formOpen = false ;
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
this . saving = false ;
},
async testForm () {
if ( ! this . form . id ) {
Cockpit . toast ( 'Sla eerst op, daarna testen' , 'info' );
return ;
}
await this . testProvider ( this . form . id );
},
async testProvider ( id ) {
try {
const r = await Cockpit . api ( '/api/settings/llm/' + id + '/test' , { method : 'POST' });
Cockpit . toast ( r . message || 'OK' , 'success' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
async activate ( id ) {
try {
await Cockpit . api ( '/api/settings/llm/' + id + '/activate' , { method : 'POST' });
Cockpit . toast ( 'Standaard LLM bijgewerkt' , 'success' );
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
async remove ( id ) {
if ( ! confirm ( 'Deze LLM provider verwijderen?' )) return ;
try {
await Cockpit . api ( '/api/settings/llm/' + id , { method : 'DELETE' });
await this . load ();
} catch ( e ) { Cockpit . toast ( e . message , 'error' ); }
},
};
}
</ script >
2026-06-09 00:41:27 +00:00
{% endif %}
{% endblock %}