poc: integrated AI settings into super admin page
This commit is contained in:
parent
a28f428516
commit
33be6804dc
6 changed files with 221 additions and 0 deletions
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
79
rhodecode/apps/admin/views/ai.py
Normal file
79
rhodecode/apps/admin/views/ai.py
Normal 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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
107
rhodecode/templates/admin/settings/settings_ai.mako
Normal file
107
rhodecode/templates/admin/settings/settings_ai.mako
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue