{% extends "base.twig" %}
{% block title %}{{ t('ui.admin.bbs_settings.ai.page_title', {}, 'common') }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{{ t('ui.admin.bbs_settings.ai.heading', {}, 'common') }}</h1>
</div>
<div class="row g-4">
<div class="col-lg-6">
<div class="card shadow h-100">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.admin.bbs_settings.ai.status_title', {}, 'common') }}</h6>
</div>
<div class="card-body">
{% for provider in provider_status %}
<div class="{% if not loop.last %}mb-3 pb-3 border-bottom{% endif %}">
<div class="d-flex align-items-center mb-2">
<strong class="me-2">{{ provider.display_name }}</strong>
{% if provider.configured and provider.name == system_default_provider %}
<span class="badge bg-primary me-1">{{ t('ui.admin.bbs_settings.ai.system_default', {}, 'common') }}</span>
{% endif %}
{% if provider.configured %}
<span class="badge bg-success">{{ t('ui.admin.bbs_settings.ai.status_enabled', {}, 'common') }}</span>
{% else %}
<span class="badge bg-secondary">{{ t('ui.admin.bbs_settings.ai.status_disabled', {}, 'common') }}</span>
{% endif %}
</div>
{% if provider.configured %}
<table class="table table-sm table-borderless mb-2">
<tbody>
<tr>
<td class="text-muted ps-0 py-1" style="width:40%">{{ t('ui.admin.bbs_settings.ai.status_default_model', {}, 'common') }}</td>
<td class="py-1"><code>{{ provider.default_model }}</code></td>
</tr>
<tr>
<td class="text-muted ps-0 py-1">{{ t('ui.admin.bbs_settings.ai.status_tools', {}, 'common') }}</td>
<td class="py-1">
{% if provider.supports_tools %}
<span class="text-success"><i class="fas fa-check me-1"></i>{{ t('ui.admin.bbs_settings.ai.status_tools_yes', {}, 'common') }}</span>
{% else %}
<span class="text-muted">{{ t('ui.admin.bbs_settings.ai.status_tools_no', {}, 'common') }}</span>
{% endif %}
</td>
</tr>
</tbody>
</table>
{% if provider.power_cost is not null %}
<div>
<small class="text-muted fw-semibold">{{ t('ui.admin.bbs_settings.ai.power_cost_title', {}, 'common') }}</small><br>
{% if provider.power_cost.gpu_watts > 0 and provider.power_cost.per_kwh > 0 %}
<small>{{ t('ui.admin.bbs_settings.ai.power_cost_configured', {watts: provider.power_cost.gpu_watts, per_kwh: provider.power_cost.per_kwh, per_hour: provider.power_cost.cost_per_hour}, 'common') }}</small>
{% else %}
<small class="text-muted">{{ t('ui.admin.bbs_settings.ai.power_cost_not_configured', {}, 'common') }}</small>
{% endif %}
</div>
{% else %}
<div>
<small class="text-muted fw-semibold">{{ t('ui.admin.bbs_settings.ai.pricing_title', {}, 'common') }}</small><br>
{% if provider.pricing.has_pricing %}
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted ps-0 py-0" style="width:50%"><small>{{ t('ui.admin.bbs_settings.ai.pricing_input', {}, 'common') }}</small></td>
<td class="py-0"><small>${{ provider.pricing.input }}</small></td>
</tr>
<tr>
<td class="text-muted ps-0 py-0"><small>{{ t('ui.admin.bbs_settings.ai.pricing_output', {}, 'common') }}</small></td>
<td class="py-0"><small>${{ provider.pricing.output }}</small></td>
</tr>
{% if provider.pricing.cached_input > 0 %}
<tr>
<td class="text-muted ps-0 py-0"><small>{{ t('ui.admin.bbs_settings.ai.pricing_cached', {}, 'common') }}</small></td>
<td class="py-0"><small>${{ provider.pricing.cached_input }}</small></td>
</tr>
{% endif %}
{% if provider.pricing.cache_write > 0 %}
<tr>
<td class="text-muted ps-0 py-0"><small>{{ t('ui.admin.bbs_settings.ai.pricing_cache_write', {}, 'common') }}</small></td>
<td class="py-0"><small>${{ provider.pricing.cache_write }}</small></td>
</tr>
{% endif %}
</tbody>
</table>
{% else %}
<small class="text-muted">{{ t('ui.admin.bbs_settings.ai.pricing_not_configured', {}, 'common') }}</small>
{% endif %}
</div>
{% endif %}
{% else %}
<small class="text-muted">{{ t('ui.admin.bbs_settings.ai.status_configure_hint', {env_var: provider.env_var_hint}, 'common') }}</small>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow h-100">
<div class="card-header">
<h6 class="mb-0">{{ t('ui.admin.bbs_settings.ai.features_title', {}, 'common') }}</h6>
</div>
<div class="card-body">
{% if not ai_available %}
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>{{ t('ui.admin.bbs_settings.ai.no_provider_warning', {}, 'common') }}
</div>
{% endif %}
<div id="aiSettingsAlert"></div>
<form id="aiSettingsForm">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="aiAssistantEnabled">
<label class="form-check-label" for="aiAssistantEnabled">{{ t('ui.admin.bbs_settings.features.enable_ai_assistant', {}, 'common') }}</label>
<div><small class="text-muted">{{ t('ui.admin.bbs_settings.features.ai_assistant_help', {}, 'common') }}</small></div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="aiShareSummaryEnabled">
<label class="form-check-label" for="aiShareSummaryEnabled">{{ t('ui.admin.bbs_settings.ai.enable_share_summary', {}, 'common') }}</label>
<div><small class="text-muted">{{ t('ui.admin.bbs_settings.ai.share_summary_help', {}, 'common') }}</small></div>
</div>
<div class="mb-3">
<label for="aiShareSummaryPrompt" class="form-label">{{ t('ui.admin.bbs_settings.ai.share_summary_prompt_label', {}, 'common') }}</label>
<textarea class="form-control" id="aiShareSummaryPrompt" rows="5" placeholder="{{ share_summary_default_prompt }}"></textarea>
<small class="text-muted">{{ t('ui.admin.bbs_settings.ai.share_summary_prompt_help', {}, 'common') }}</small>
</div>
<button type="button" class="btn btn-primary" onclick="saveAiSettings()">{{ t('ui.admin.bbs_settings.ai.save', {}, 'common') }}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function uiT(key, fallback, params = {}) {
if (window.t) {
return window.t(key, params, fallback);
}
return fallback;
}
function apiError(payload, fallback) {
if (window.getApiErrorMessage) {
return window.getApiErrorMessage(payload, fallback);
}
return payload && payload.error ? payload.error : fallback;
}
function showAiSettingsAlert(message, type) {
const container = document.getElementById('aiSettingsAlert');
container.innerHTML = `
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
}
function loadAiSettings() {
fetch('/admin/api/bbs-settings')
.then(r => r.json())
.then(data => {
if (!data.success) throw new Error(apiError(data, uiT('ui.admin.bbs_settings.load_failed', 'Failed to load settings')));
const ai = data.config?.ai_assistant || {};
document.getElementById('aiAssistantEnabled').checked = ai.enabled === true;
document.getElementById('aiShareSummaryEnabled').checked = ai.share_summary_enabled === true;
document.getElementById('aiShareSummaryPrompt').value = ai.share_summary_prompt || '';
})
.catch(err => showAiSettingsAlert(err.message || uiT('ui.admin.bbs_settings.load_failed', 'Failed to load settings'), 'danger'));
}
function saveAiSettings() {
const payload = {
ai_assistant: {
enabled: document.getElementById('aiAssistantEnabled').checked,
share_summary_enabled: document.getElementById('aiShareSummaryEnabled').checked,
share_summary_prompt: document.getElementById('aiShareSummaryPrompt').value,
}
};
fetch('/admin/api/ai-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(r => r.json())
.then(data => {
if (!data.success) throw new Error(apiError(data, uiT('ui.admin.bbs_settings.ai.save_failed', 'Failed to save AI settings')));
const msg = window.getApiMessage
? window.getApiMessage(data, uiT('ui.admin.bbs_settings.ai.saved_success', 'AI settings saved.'))
: uiT('ui.admin.bbs_settings.ai.saved_success', 'AI settings saved.');
showAiSettingsAlert(msg, 'success');
})
.catch(err => showAiSettingsAlert(err.message || uiT('ui.admin.bbs_settings.ai.save_failed', 'Failed to save AI settings'), 'danger'));
}
document.addEventListener('DOMContentLoaded', function() {
loadI18nNamespaces(['common', 'errors']).then(function() {
loadAiSettings();
});
});
</script>
{% endblock %}
|