feat: add optional ai and mcp automation
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user