poc: integrated AI settings into super admin page

This commit is contained in:
ievgenii vdovenko 2025-08-28 16:53:35 +02:00
parent a28f428516
commit 33be6804dc
6 changed files with 221 additions and 0 deletions

View file

@ -72,6 +72,7 @@ class NavEntry(object):
class NavigationRegistry(object):
_base_entries = [
NavEntry("global", _("Global"), "admin_settings_global"),
NavEntry("admin_settings_ai", _("Ai"), "admin_settings_ai"),
NavEntry("vcs", _("VCS"), "admin_settings_vcs"),
NavEntry("visual", _("Visual"), "admin_settings_visual"),
NavEntry("mapping", _("Remap and Rescan"), "admin_settings_mapping"),

View file

@ -28,6 +28,7 @@ def admin_routes(config):
"""
from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
from rhodecode.apps.admin.views.ai import AdminAiView
from rhodecode.apps.admin.views.automation import AdminAutomationView
from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
@ -147,6 +148,24 @@ def admin_routes(config):
renderer="rhodecode:templates/admin/settings/settings.mako",
)
config.add_route("admin_settings_ai", pattern="/settings/ai")
config.add_view(
AdminAiView,
attr="admin_settings_ai",
route_name="admin_settings_ai",
request_method="GET",
renderer="rhodecode:templates/admin/settings/settings.mako",
)
config.add_route(name="admin_settings_ai_update", pattern="/settings/ai/update")
config.add_view(
AdminAiView,
attr="admin_settings_ai_update",
route_name="admin_settings_ai_update",
request_method="POST",
renderer="rhodecode:templates/admin/settings/settings.mako",
)
config.add_route(name="admin_settings_vcs_svn_generate_cfg", pattern="/settings/vcs/svn_generate_cfg")
config.add_view(
AdminSvnConfigView,

View file

@ -0,0 +1,79 @@
import logging
import formencode
from pyramid.httpexceptions import HTTPFound
from rhodecode.apps._base import BaseAppView
from rhodecode.apps._base.navigation import navigation_list
from rhodecode.apps.ai_agents.ai_settings import AIModelName, GPTVersion, ClaudeVersion
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, CSRFRequired
from rhodecode.lib import helpers as h
from rhodecode.model.forms import AiSettingsForm
from rhodecode.model.settings import SettingsModel
from rhodecode.model.meta import Session
log = logging.getLogger(__name__)
class AdminAiView(BaseAppView):
def load_default_context(self):
c = self._get_local_tmpl_context()
return c
@LoginRequired()
@HasPermissionAllDecorator("hg.admin")
def admin_settings_ai(self):
c = self.load_default_context()
c.active = "ai"
c.navlist = navigation_list(self.request)
app_settings = c.rc_config
c.selected_ai_model = app_settings.get("rhodecode_ai_model", AIModelName.GPT.name)
c.selected_ai_model_version = app_settings.get("rhodecode_ai_model_version", GPTVersion.V5_nano.name)
c.api_key = app_settings.get("rhodecode_ai_api_key")
c.model_map = {
AIModelName.GPT.name: [v.name for v in GPTVersion],
}
return self._get_template_context(c)
@CSRFRequired()
@LoginRequired()
@HasPermissionAllDecorator("hg.admin")
def admin_settings_ai_update(self):
_ = self.request.translate
c = self.load_default_context()
c.active = "ai"
data = self._parse_form(_)
self._save_settings(_, data)
raise HTTPFound(h.route_path("admin_settings_ai"))
def _save_settings(self, _, data):
try:
settings = [
("ai_model", "rhodecode_ai_model", "unicode"),
("ai_model_version", "rhodecode_ai_model_version", "unicode"),
("ai_api_key", "rhodecode_ai_api_key", "unicode"),
]
for setting, form_key, type_ in settings:
sett = SettingsModel().create_or_update_setting(setting, data[form_key], type_)
Session().add(sett)
Session().commit()
SettingsModel().invalidate_settings_cache()
h.flash(_("AI settings saved"), category="success")
except Exception as e:
log.exception("Exception saving AI settings: %s", e)
h.flash(_("Error occurred during saving AI settings"), category="error")
def _parse_form(self, _):
try:
form = AiSettingsForm()()
data = form.to_python(self.request.POST)
except formencode.Invalid as errors:
log.exception("Failed to add new pattern")
error = errors
h.flash(_(f"Unknown error: {error}"), category="error")
raise HTTPFound(h.route_path("admin_settings_ai"))
return data

View file

@ -4,6 +4,11 @@ from dataclasses import dataclass
class AIModelName(enum.StrEnum):
GPT = enum.auto()
Claude = enum.auto()
class ClaudeVersion(enum.StrEnum):
Opus = "opus"
class GPTVersion(enum.StrEnum):

View file

@ -666,3 +666,13 @@ def IssueTrackerPatternsForm(localizer):
chained_validators = [v.ValidPattern(localizer)]
return _IssueTrackerPatternsForm
def AiSettingsForm():
class _AiSettingsForm(formencode.Schema):
allow_extra_fields = True
rhodecode_ai_model = v.UnicodeString(strip=True, required=True)
rhodecode_ai_model_version = v.UnicodeString(strip=True, required=True)
rhodecode_ai_api_key = v.UnicodeString(strip=True)
return _AiSettingsForm

View file

@ -0,0 +1,107 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Ai Configuration')}</h3>
</div>
<div class="panel-body">
${h.secure_form(h.route_path('admin_settings_ai_update'), request=request)}
<div class="fields">
<div class="field" id="model">
<div class="label label">
<label for="model">${_('Model')}</label>
</div>
<select id="rhodecode_ai_model" name="rhodecode_ai_model">
% for m in c.model_map:
<option value="${m}">${m}</option>
% endfor
</select>
</div>
<div id="model_version_slot"></div>
<div class="field" id="rhodecode_ai_api_key">
<div class="label label">
<label for="rhodecode_ai_api_key">${_('Api key')}</label>
</div>
<div class="input">
${h.text('rhodecode_ai_api_key', c.api_key, size=30)}
</div>
</div>
<div class="buttons">
${h.submit('save',_('Save'),class_="btn")}
</div>
${h.end_form()}
</div>
</div>
</div>
<%!
import json
%>
<script type="text/javascript">
$(document).ready(function () {
function selector(_id) {
$(_id).select2({
containerCssClass: 'drop-menu',
dropdownCssClass: 'drop-menu-dropdown',
dropdownAutoWidth: true,
minimumResultsForSearch: -1
});
}
const MODEL_MAP = ${json.dumps(c.model_map) | n};
const SELECTED_MODEL = ${json.dumps(getattr(c, 'selected_ai_model', None)) | n};
const SELECTED_VERSION = ${json.dumps(getattr(c, 'selected_ai_model_version', None)) | n};
const $model = $('#rhodecode_ai_model');
const $slot = $('#model_version_slot');
function renderVersionField(model) {
const versions = MODEL_MAP[model] || [];
$slot.empty();
if (!versions.length) {
return;
}
const html = `
<div class="field" id="model_version">
<div class="label label">
<label for="rhodecode_ai_model_version">${_('Model Version')}</label>
</div>
<select id="rhodecode_ai_model_version" name="rhodecode_ai_model_version"></select>
</div>
`;
$slot.append(html);
const $ver = $('#rhodecode_ai_model_version');
for (const v of versions) {
const isSelected = (model === SELECTED_MODEL) && (String(v) === String(SELECTED_VERSION));
$ver.append(new Option(String(v), String(v), isSelected, isSelected));
}
if (!$ver.find('option:selected').length && versions.length) {
$ver.prop('selectedIndex', 0);
}
selector('#rhodecode_ai_model_version');
$ver.trigger('change');
}
renderVersionField($model.val());
$model.on('change', function () {
renderVersionField(this.value);
});
selector('#rhodecode_ai_model');
if (SELECTED_MODEL && $model.find('option[value="' + SELECTED_MODEL.replace(/"/g, '\\"') + '"]').length) {
$model.val(SELECTED_MODEL).trigger('change.select2');
}
renderVersionField($model.val());
});
</script>