poc: adds AI user to the system, adds migration, changes AI settings to have switch enable\disable AI features

This commit is contained in:
ievgenii vdovenko 2025-09-01 12:51:26 +02:00
parent 60146345d3
commit b4aecb1e0b
24 changed files with 138 additions and 40 deletions

View file

@ -144,7 +144,7 @@ class BaseAppView(object):
if not user_obj:
return
if user_obj.username in [User.DEFAULT_USER, User.GHOST_USER]:
if user_obj.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER]:
return
now = time.time()
@ -270,7 +270,7 @@ class RepoAppView(BaseAppView):
def _prepare_and_set_clone_url(self, c):
username = ""
if self._rhodecode_user.username in [User.DEFAULT_USER, User.GHOST_USER]:
if self._rhodecode_user.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER]:
username = self._rhodecode_user.username
_def_clone_uri = c.clone_uri_tmpl
@ -558,7 +558,7 @@ class UserAppView(BaseAppView):
_ = self.request.translate
if not request.db_user_supports_default:
if self.db_user.username in [User.DEFAULT_USER, User.GHOST_USER]:
if self.db_user.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER]:
h.flash(
_("Editing user `{}` is disabled.".format(self.db_user.username)),
category="warning",

View file

@ -72,7 +72,7 @@ class NavEntry(object):
class NavigationRegistry(object):
_base_entries = [
NavEntry("global", _("Global"), "admin_settings_global"),
NavEntry("ai", _("Ai"), "admin_settings_ai"),
NavEntry("ai", _("AI features"), "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

@ -41,7 +41,9 @@ class TestAdminUsersView(TestController):
self.log_user()
response = self.app.get(route_path("users_data"), extra_environ=xhr_header)
all_users = User.query().filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER])).count()
all_users = (
User.query().filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER, User.AI_USER])).count()
)
assert response.json["recordsTotal"] == all_users
def test_show_users_data_filtered(self, xhr_header):
@ -50,7 +52,9 @@ class TestAdminUsersView(TestController):
route_path("users_data", params={"search[value]": "empty_search"}), extra_environ=xhr_header
)
all_users = User.query().filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER])).count()
all_users = (
User.query().filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER, User.AI_USER])).count()
)
assert response.json["recordsTotal"] == all_users
assert response.json["recordsFiltered"] == 0

View file

@ -9,9 +9,11 @@ from rhodecode.apps.ai_agents.ai_settings import AIModelName, GPTVersion, Claude
from rhodecode.apps.ai_agents.models.base import AIServiceBase
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, CSRFRequired
from rhodecode.lib import helpers as h
from rhodecode.model.db import User
from rhodecode.model.forms import AiSettingsForm
from rhodecode.model.settings import SettingsModel
from rhodecode.model.meta import Session
from rhodecode.model.user import UserModel
log = logging.getLogger(__name__)
@ -32,6 +34,7 @@ class AdminAiView(BaseAppView):
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.ai_features_enabled = app_settings.get("rhodecode_ai_features_enabled", False)
c.ai_instructions = app_settings.get("rhodecode_ai_code_review_instructions")
if not c.ai_instructions:
@ -59,6 +62,7 @@ class AdminAiView(BaseAppView):
def _save_settings(self, _, data):
try:
settings = [
("ai_features_enabled", "rhodecode_ai_features_enabled", "bool"),
("ai_model", "rhodecode_ai_model", "unicode"),
("ai_model_version", "rhodecode_ai_model_version", "unicode"),
("ai_api_key", "rhodecode_ai_api_key", "unicode"),
@ -66,6 +70,9 @@ class AdminAiView(BaseAppView):
]
for setting, form_key, type_ in settings:
sett = SettingsModel().create_or_update_setting(setting, data[form_key], type_)
if setting == "ai_features_enabled" and isinstance(data[form_key], bool):
self._activate_deactivate_ai_user(data, form_key)
Session().add(sett)
Session().commit()
@ -75,6 +82,12 @@ class AdminAiView(BaseAppView):
log.exception("Exception saving AI settings: %s", e)
h.flash(_("Error occurred during saving AI settings"), category="error")
def _activate_deactivate_ai_user(self, data, form_key):
log.debug("%s AI user" % "Activating" if data[form_key] else "Deactivating")
ai_user = User.get_ai_user()
ai_user.active = data[form_key]
Session().add(ai_user)
def _parse_form(self, _):
try:
form = AiSettingsForm()()

View file

@ -104,7 +104,7 @@ class AdminUsersView(BaseAppView, DataGridAppView):
def user_actions(user_id, username):
return _render("user_actions", user_id, username)
excluded_users = User.username.notin_([User.DEFAULT_USER, User.GHOST_USER])
excluded_users = User.username.notin_([User.DEFAULT_USER, User.GHOST_USER, User.AI_USER])
users_data_total_count = User.query().filter(excluded_users).count()
users_data_total_inactive_count = User.query().filter(excluded_users).filter(User.active != true()).count()

View file

@ -239,7 +239,7 @@ class HomeView(BaseAppView, DataGridAppView):
User.query()
.order_by(func.length(User.username))
.order_by(User.username)
.filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER]))
.filter(User.username.notin_([User.DEFAULT_USER, User.GHOST_USER, User.AI_USER]))
)
if name_contains:

View file

@ -53,7 +53,16 @@ class TestUsersController(TestController):
assert_response = response.assert_response()
assert_response.element_contains(edit_link_css, "Edit")
def test_user_profile_not_available(self, user_util):
@pytest.mark.parametrize(
"user",
[
User.DEFAULT_USER,
User.GHOST_USER,
User.AI_USER,
"unknown",
],
)
def test_user_profile_not_available(self, user_util, user):
user = user_util.create_user()
username = user.username
@ -64,11 +73,5 @@ class TestUsersController(TestController):
# after log-in show
self.app.get(route_path("user_profile", username=username), status=200)
# default user, not allowed to show it
# default user or system user, not allowed to show it
self.app.get(route_path("user_profile", username=User.DEFAULT_USER), status=404)
# ghost user, not allowed to show it
self.app.get(route_path("user_profile", username=User.GHOST_USER), status=404)
# actual 404
self.app.get(route_path("user_profile", username="unknown"), status=404)

View file

@ -40,7 +40,7 @@ class UserProfileView(BaseAppView):
username = self.request.matchdict.get("username")
c.user = UserModel().get_by_username(username)
if not c.user or c.user.username in [User.DEFAULT_USER, User.GHOST_USER]:
if not c.user or c.user.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER]:
raise HTTPNotFound()
return self._get_template_context(c)