Merge pull request !2951 from rhodecode-enterprise-ce feature/RCCE-386_add_ai_code_review_checkbox

feature: implements AI code review PR checkbox; adds AI PR settings
This commit is contained in:
Andrii Verbytskyi 2026-01-22 12:00:55 +00:00
commit e1a67b7306
12 changed files with 184 additions and 48 deletions

View file

@ -0,0 +1,29 @@
.. _ai-code-review:
AI Code review
=================
To start AI code review, first complete the setup in :ref:`ai-config`.
You can configure AI code review in several places:
1. Global (admin)
Navigate to :menuselection:`Admin --> Authentication --> Settings --> AI Features` and enable `Turn on AI code review by default for new PRs`.
This sets the default for all newly created pull requests. Repository admins and PR authors can override this setting at the repository and pull request level.
2. Repository-level setting (repository admin)
Navigate to :menuselection:`Repository Settings --> VCS --> Pull Request Settings` and toggle `Enable AI review for pull requests`.
3. During pull request creation (any user)
When creating a pull request, enable the `Run AI code review` checkbox to run the review for that PR.
4. In an existing pull request (any user)
In an existing pull request, use the button in the right-side menu to start an AI code review.
.. note::
At the moment, AI code review can only be run once per pull request.
After the review finishes, you wont be able to restart it. In addition,
you can view the AI code review status in the audit logs.

View file

@ -0,0 +1,16 @@
.. _ai-config:
AI Configuration
=================
To start using AI features in the |RCE|, run through the below steps:
1. Navigate to :menuselection:`Admin --> Authentication --> Settings --> AI Features` - and check `Enable AI features` checkbox.
2. Insert your API key into the `Api key` input.
3. Select `AI Model` from the dropdown. Currently |RCE| supports 3 providers: GPT, Gemini, Claude.
4. Hit `Update model versions` button. This will download model versions for selected model.
5. Hit `Save` button.

View file

@ -47,6 +47,13 @@ and commit files and |repos| while managing their security permissions.
RhodeCode RCstack Installer <https://docs.rhodecode.com/rcstack/>
.. toctree::
:maxdepth: 1
:caption: AI Documentation
ai/ai-configuration
ai/ai-code-review
.. toctree::
:maxdepth: 1
:caption: Admin Documentation

View file

@ -36,6 +36,7 @@ class AdminAiView(BaseAppView):
c.selected_ai_model_version = app_settings.get("rhodecode_ai_model_version")
c.api_key = app_settings.get("rhodecode_ai_api_key")
c.ai_features_enabled = app_settings.get("rhodecode_ai_features_enabled", False)
c.ai_default_code_review = app_settings.get("rhodecode_ai_default_code_review", False)
c.ai_instructions = app_settings.get("rhodecode_ai_code_review_instructions")
if not c.ai_instructions:
@ -112,6 +113,7 @@ class AdminAiView(BaseAppView):
try:
settings = [
("ai_features_enabled", "rhodecode_ai_features_enabled", "bool"),
("ai_default_code_review", "rhodecode_ai_default_code_review", "bool"),
("ai_model", "rhodecode_ai_model", "unicode"),
("ai_model_version", "rhodecode_ai_model_version", "unicode"),
("ai_api_key", "rhodecode_ai_api_key", "unicode"),

View file

@ -913,6 +913,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
self.assure_not_empty_repo()
source_repo = self.db_repo
app_settings = c.rc_config
c.ai_enabled = app_settings.get("rhodecode_ai_features_enabled", False)
commit_id = self.request.GET.get("commit")
branch_ref = self.request.GET.get("branch")
bookmark_ref = self.request.GET.get("bookmark")
@ -971,6 +974,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
# Get security scan setting for pre-checking the checkbox
c.pr_security_scan_enabled = self._get_repo_setting(source_repo, "rhodecode_pr_security_scan_enabled", False)
c.ai_pr_review = self._get_repo_setting(
source_repo, "rhodecode_ai_default_code_review", app_settings.get("rhodecode_ai_default_code_review", False)
)
return self._get_template_context(c)
@LoginRequired()
@ -1311,6 +1317,11 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
except ImportError:
log.warning("EE security audit tasks not available, skipping")
rc_settings = SettingsModel()
ai_enabled = rc_settings.get_setting_by_name("ai_features_enabled")
if ai_enabled and _form.get("run_ai_codereview", False):
self._try_start_ai_code_review(_, pull_request, add_existing_users=True)
Session().commit()
h.flash(_("Successfully opened new pull request"), category="success")
@ -1373,62 +1384,46 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
log.warning("Code review already finished.")
return False
admin_permissions = (
"repository.admin",
"hg.admin",
)
is_admin = h.HasRepoPermissionAny(*admin_permissions)(
user=self._rhodecode_user, repo_name=pull_request.target_repo.repo_name
)
ai_user = User.get_ai_user()
log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id)
if is_admin:
ai_user = User.get_ai_user()
log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id)
reviewers_data = []
reviewers_data = []
if add_existing_users:
current_reviewers = PullRequestReviewers.get_pull_request_reviewers(
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER
)
for existing_reviewer in current_reviewers:
reviewers_data.append(
(
existing_reviewer.user_id,
existing_reviewer.reasons,
existing_reviewer.mandatory,
existing_reviewer.role,
existing_reviewer.rule_data,
)
if add_existing_users:
current_reviewers = PullRequestReviewers.get_pull_request_reviewers(
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER
)
for existing_reviewer in current_reviewers:
reviewers_data.append(
(
existing_reviewer.user_id,
existing_reviewer.reasons,
existing_reviewer.mandatory,
existing_reviewer.role,
existing_reviewer.rule_data,
)
reviewers_data.append(
(
ai_user.user_id,
["adds system ai user for review"],
False,
PullRequestReviewers.ROLE_REVIEWER,
pull_request.reviewer_data,
)
reviewers_data.append(
(
ai_user.user_id,
["adds system ai user for review"],
False,
PullRequestReviewers.ROLE_REVIEWER,
pull_request.reviewer_data,
)
)
PullRequestModel().update_reviewers(pull_request, reviewers_data, self._rhodecode_db_user)
PullRequestModel().update_reviewers(pull_request, reviewers_data, self._rhodecode_db_user)
Session().commit()
Session().commit()
run_task(tasks.start_ai_code_review, pull_request.pull_request_id)
self._set_ai_pr_state_pending(pull_request)
run_task(tasks.start_ai_code_review, pull_request.pull_request_id)
self._set_ai_pr_state_pending(pull_request)
msg = _("AI code review has started. The review will be ready in about 13 minutes.")
h.flash(msg, category="success")
msg = _("AI code review has started. The review will be ready in about 13 minutes.")
h.flash(msg, category="success")
else:
log.debug("AI code review canceled, only admin can initiate AI code review.")
msg = _("AI code review is not available. This feature is in BETA and can only be initiated by an admin.")
h.flash(
msg,
category="warning",
)
return False
return True
@LoginRequired()

View file

@ -71,7 +71,18 @@ class RepoSettingsVcsView(RepoAppView):
c = self.load_default_context()
model = VcsSettingsModel(repo=self.db_repo_name)
app_settings = c.rc_config
c.active = "vcs"
c.repo_vcs_settings = True
c.ai_features_enabled = app_settings.get("rhodecode_ai_features_enabled", False)
c.ai_default_code_review = self._get_repo_setting(
self.db_repo,
"rhodecode_ai_default_code_review",
app_settings.get("rhodecode_ai_default_code_review", False),
)
c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
c.svn_branch_patterns = model.get_repo_svn_branch_patterns()

View file

@ -444,6 +444,7 @@ class _BaseVcsSettingsForm(formencode.Schema):
rhodecode_auto_merge_enabled = v.StringBoolean(if_missing=False)
rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
rhodecode_pr_security_scan_enabled = v.StringBoolean(if_missing=False)
rhodecode_ai_default_code_review = v.StringBoolean(if_missing=False)
# hg
extensions_largefiles = v.StringBoolean(if_missing=False)
@ -673,6 +674,7 @@ def PullRequestForm(localizer, repo_id):
description_renderer = v.UnicodeString(strip=True, required=False)
close_branch_before_merging = v.StringBoolean(if_missing=False)
run_security_audit = v.StringBoolean(if_missing=False)
run_ai_codereview = v.StringBoolean(if_missing=False)
return _PullRequestForm
@ -692,6 +694,7 @@ def AiSettingsForm():
class _AiSettingsForm(formencode.Schema):
allow_extra_fields = True
rhodecode_ai_features_enabled = v.StringBoolean(if_missing=False)
rhodecode_ai_default_code_review = v.StringBoolean(if_missing=False)
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)

View file

@ -457,6 +457,7 @@ class VcsSettingsModel:
"pr_merge_enabled",
"auto_merge_enabled",
"pr_security_scan_enabled",
"ai_default_code_review",
"hg_use_rebase_for_merging",
"hg_close_branch_before_merging",
"hg_merge_strategy_selector",

View file

@ -1,3 +1,9 @@
<style>
.field > .label {
min-width: 300px;
}
</style>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Ai Configuration')}</h3>
@ -15,6 +21,16 @@
</div>
</div>
<div class="field" id="ai-default-review-toggle" >
<div class="label label-checkbox">
<label for="rhodecode_ai_default_code_review">${_('Turn on AI code review by default for new PRs')}:</label>
<strong>Warning</strong>: can potentially spend a lot of tokens
</div>
<div class="checkboxes">
${h.checkbox('rhodecode_ai_default_code_review',value=True, checked=c.ai_default_code_review==True)}
</div>
</div>
<div class="field" id="model" style="display: flex;">
<div class="label label">
<label for="model">${_('Model')}</label>
@ -79,6 +95,7 @@ import json
const $model = $('#rhodecode_ai_model');
const $slot = $('#model_version_slot');
const $enable = $('#rhodecode_ai_features_enabled');
const $codeReview = $('#rhodecode_ai_default_code_review');
const $form = $('#ai_features_form');
const $updateModelsBtn = $('#model_version_button_update');
@ -181,7 +198,8 @@ import json
data: {
'rhodecode_ai_model': $model.val(),
'rhodecode_ai_api_key': $apiKey.val(),
'rhodecode_ai_features_enabled': $enable.is(':checked')
'rhodecode_ai_features_enabled': $enable.is(':checked'),
'rhodecode_ai_default_code_review': $codeReview.is(':checked')
},
success: function (response) {
$.Topic('/notifications').publish({

View file

@ -279,6 +279,34 @@
<span class="help-block">${_('When enabled, the security scan checkbox will be pre-selected when creating pull requests. This scans the PR diff for exposed secrets and credentials.')}</span>
</div>
%endif
%if hasattr(c, 'repo_vcs_settings') and c.repo_vcs_settings:
%if c.rhodecode_edition_id != 'EE':
<div class="checkbox">
<input type="checkbox" disabled>
<label>${_('Enable AI review for pull requests')}</label>
</div>
<div class="label">
<span class="help-block">${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</span>
</div>
%else:
<div class="checkbox">
%if c.ai_features_enabled:
${h.checkbox('rhodecode_ai_default_code_review' + suffix, 'True', checked=c.ai_default_code_review, **kwargs)}
<label for="rhodecode_ai_default_code_review${suffix}">${_('Enable AI review for pull requests')}</label>
%else:
<span class="tooltip" title="${_('AI features disabled, enable AI features to activate this checkbox.')}">
${h.checkbox('rhodecode_ai_default_code_review' + suffix, 'True', checked=c.ai_default_code_review, disabled=True, **kwargs)}
<label for="rhodecode_ai_default_code_review${suffix}">${_('Enable AI review for pull requests')}</label>
</span>
%endif
</div>
<div class="label">
<span class="help-block">${_('When enabled, the AI code review checkbox will be pre-selected when creating pull requests. This will schedule AI code review for the PR.')}</span>
</div>
%endif
%endif
</div>
</div>
% endif

View file

@ -247,6 +247,31 @@
</label>
</div>
% endif
% if c.ai_enabled and c.rhodecode_edition_id == 'EE':
<div class="pull-request-settings" style="margin-left: 220px">
${h.checkbox('run_ai_codereview', checked=c.ai_pr_review, value=True)}
<label for="run_ai_codereview">
${h.literal(_('Run run AI code review'))}
</label>
</div>
% elif not c.ai_enabled:
<div class="pull-request-settings" style="margin-left: 220px">
${h.checkbox('run_ai_codereview', checked=False, value=True, disabled=True, **{'class': 'disabled-checkbox'})}
<label for="run_ai_codereview" class="disabled-label">
${h.literal(_('Run AI code review'))}
<span class="tooltip" title="${_('Enable AI features to use this checkbox')}">[?]</span>
</label>
</div>
% elif c.rhodecode_edition_id != 'EE':
<div class="pull-request-settings" style="margin-left: 220px">
${h.checkbox('run_ai_codereview', checked=False, value=True, disabled=True, **{'class': 'disabled-checkbox'})}
<label for="run_ai_codereview" class="disabled-label">
${h.literal(_('Run AI code review'))}
<span class="ee-feature-label">[EE Only]</span>
</label>
</div>
% endif
</div>
</div>
</div>

View file

@ -37,6 +37,7 @@ GENERAL_FORM_DATA = {
"rhodecode_auto_merge_enabled": True,
"rhodecode_use_outdated_comments": True,
"rhodecode_pr_security_scan_enabled": True,
"rhodecode_ai_default_code_review": True,
"rhodecode_hg_use_rebase_for_merging": True,
"rhodecode_hg_close_branch_before_merging": True,
"rhodecode_hg_merge_strategy_selector": True,