feat: add optional ai and mcp automation

This commit is contained in:
Christian Krakau-Louis
2026-05-23 20:45:19 +02:00
parent e566a2d1e9
commit 0d15c16e6a
14 changed files with 1183 additions and 8 deletions
+109
View File
@@ -778,6 +778,90 @@
{% endcall %}
{% endcall %}
<!-- ── Optional AI and MCP ────────────────────────────────────────────── -->
{% call card() %}
{% call card_header() %}
{% call card_title() %}AI and Agent Automation{% endcall %}
{% call card_description() %}Opt-in summaries, read-only MCP access, and human-confirmed proposal controls{% endcall %}
{% endcall %}
{% call card_content() %}
<form @submit.prevent="saveAutomationSettings()" class="space-y-4">
<div class="alert alert-info">
<span>AI and MCP features are disabled by default. Safe context redacts secrets, tokens, long opaque values, and email local-parts before it can be shared with model or agent surfaces.</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
:checked="s['ai.enabled'] === 'true'"
@change="s['ai.enabled'] = $event.target.checked ? 'true' : 'false'"
class="checkbox checkbox-primary" />
<span class="label-text font-medium">Enable AI assistance</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
:checked="s['mcp.enabled'] === 'true'"
@change="s['mcp.enabled'] = $event.target.checked ? 'true' : 'false'"
class="checkbox checkbox-primary" />
<span class="label-text font-medium">Enable read-only MCP endpoint</span>
</label>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-control w-full">
<label class="label"><span class="label-text font-medium">Provider</span></label>
<select x-model="s['ai.provider']" class="input input-bordered w-full">
<option value="template">Template</option>
<option value="local">Local</option>
<option value="remote">Remote</option>
</select>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text font-medium">Model</span></label>
<input type="text" x-model="s['ai.model']" class="input input-bordered w-full" placeholder="Optional model name" />
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text font-medium">Redaction</span></label>
<select x-model="s['ai.redaction_mode']" class="input input-bordered w-full">
<option value="strict">Strict</option>
<option value="balanced">Balanced</option>
</select>
</div>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text font-medium">Remote Base URL</span></label>
<input type="url" x-model="s['ai.remote_base_url']" class="input input-bordered w-full" placeholder="https://provider.example/v1" />
<label class="label"><span class="label-text-alt text-muted-foreground">Credentials are not stored here; inject provider secrets through the deployment environment.</span></label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
:checked="s['ai.action_tools_enabled'] === 'true'"
@change="s['ai.action_tools_enabled'] = $event.target.checked ? 'true' : 'false'"
class="checkbox checkbox-primary" />
<span class="label-text font-medium">Allow human-confirmed action proposals</span>
</label>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-default btn-md" :disabled="saving">
<template x-if="!saving">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
</template>
<template x-if="saving"><span class="loading loading-spinner loading-xs mr-2"></span></template>
Save Automation Settings
</button>
</div>
</form>
{% endcall %}
{% endcall %}
<!-- ── Mail Sources shortcut ──────────────────────────────────────────── -->
{% call card() %}
{% call card_header() %}
@@ -903,6 +987,31 @@ function settingsApp() {
}
},
async saveAutomationSettings() {
this.saving = true;
const keys = Object.keys(this.s).filter(k => k.startsWith('ai.') || k.startsWith('mcp.'));
const settings = {};
keys.forEach(k => { settings[k] = String(this.s[k] ?? ''); });
try {
const res = await fetch('/api/v1/settings/bulk', {
method: 'POST',
headers: this.apiHeaders(),
body: JSON.stringify({ settings }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
this.showFlash('Save failed: ' + (data.detail || res.statusText), false);
} else {
data.forEach(r => { this.s[r.key] = r.value ?? ''; });
this.showFlash('Automation settings saved.', true);
}
} catch (err) {
this.showFlash('Error saving automation settings: ' + err.message, false);
} finally {
this.saving = false;
}
},
async sendTestNotification() {
this.testingNotification = true;
try {