feature: implements AI code review PR checkbox; adds AI PR settings

This commit is contained in:
ievgenii vdovenko 2026-01-21 11:59:03 +01:00
parent bf9cd8455f
commit c71db28d3f
9 changed files with 132 additions and 48 deletions

View file

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

View file

@ -913,6 +913,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
self.assure_not_empty_repo() self.assure_not_empty_repo()
source_repo = self.db_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") commit_id = self.request.GET.get("commit")
branch_ref = self.request.GET.get("branch") branch_ref = self.request.GET.get("branch")
bookmark_ref = self.request.GET.get("bookmark") bookmark_ref = self.request.GET.get("bookmark")
@ -971,6 +974,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
# Get security scan setting for pre-checking the checkbox # 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.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) return self._get_template_context(c)
@LoginRequired() @LoginRequired()
@ -1311,6 +1317,11 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
except ImportError: except ImportError:
log.warning("EE security audit tasks not available, skipping") 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() Session().commit()
h.flash(_("Successfully opened new pull request"), category="success") h.flash(_("Successfully opened new pull request"), category="success")
@ -1373,15 +1384,6 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
log.warning("Code review already finished.") log.warning("Code review already finished.")
return False 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
)
if is_admin:
ai_user = User.get_ai_user() ai_user = User.get_ai_user()
log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id) log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id)
@ -1421,14 +1423,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
msg = _("AI code review has started. The review will be ready in about 1–3 minutes.") msg = _("AI code review has started. The review will be ready in about 1–3 minutes.")
h.flash(msg, category="success") 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 return True
@LoginRequired() @LoginRequired()

View file

@ -71,7 +71,18 @@ class RepoSettingsVcsView(RepoAppView):
c = self.load_default_context() c = self.load_default_context()
model = VcsSettingsModel(repo=self.db_repo_name) model = VcsSettingsModel(repo=self.db_repo_name)
app_settings = c.rc_config
c.active = "vcs" 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_branch_patterns = model.get_global_svn_branch_patterns()
c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
c.svn_branch_patterns = model.get_repo_svn_branch_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_auto_merge_enabled = v.StringBoolean(if_missing=False)
rhodecode_use_outdated_comments = 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_pr_security_scan_enabled = v.StringBoolean(if_missing=False)
rhodecode_ai_default_code_review = v.StringBoolean(if_missing=False)
# hg # hg
extensions_largefiles = v.StringBoolean(if_missing=False) extensions_largefiles = v.StringBoolean(if_missing=False)
@ -673,6 +674,7 @@ def PullRequestForm(localizer, repo_id):
description_renderer = v.UnicodeString(strip=True, required=False) description_renderer = v.UnicodeString(strip=True, required=False)
close_branch_before_merging = v.StringBoolean(if_missing=False) close_branch_before_merging = v.StringBoolean(if_missing=False)
run_security_audit = v.StringBoolean(if_missing=False) run_security_audit = v.StringBoolean(if_missing=False)
run_ai_codereview = v.StringBoolean(if_missing=False)
return _PullRequestForm return _PullRequestForm
@ -692,6 +694,7 @@ def AiSettingsForm():
class _AiSettingsForm(formencode.Schema): class _AiSettingsForm(formencode.Schema):
allow_extra_fields = True allow_extra_fields = True
rhodecode_ai_features_enabled = v.StringBoolean(if_missing=False) 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 = v.UnicodeString(strip=True, required=True)
rhodecode_ai_model_version = v.UnicodeString(strip=True, required=True) rhodecode_ai_model_version = v.UnicodeString(strip=True, required=True)
rhodecode_ai_api_key = v.UnicodeString(strip=True) rhodecode_ai_api_key = v.UnicodeString(strip=True)

View file

@ -457,6 +457,7 @@ class VcsSettingsModel:
"pr_merge_enabled", "pr_merge_enabled",
"auto_merge_enabled", "auto_merge_enabled",
"pr_security_scan_enabled", "pr_security_scan_enabled",
"ai_default_code_review",
"hg_use_rebase_for_merging", "hg_use_rebase_for_merging",
"hg_close_branch_before_merging", "hg_close_branch_before_merging",
"hg_merge_strategy_selector", "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 panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">${_('Ai Configuration')}</h3> <h3 class="panel-title">${_('Ai Configuration')}</h3>
@ -15,6 +21,16 @@
</div> </div>
</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="field" id="model" style="display: flex;">
<div class="label label"> <div class="label label">
<label for="model">${_('Model')}</label> <label for="model">${_('Model')}</label>
@ -79,6 +95,7 @@ import json
const $model = $('#rhodecode_ai_model'); const $model = $('#rhodecode_ai_model');
const $slot = $('#model_version_slot'); const $slot = $('#model_version_slot');
const $enable = $('#rhodecode_ai_features_enabled'); const $enable = $('#rhodecode_ai_features_enabled');
const $codeReview = $('#rhodecode_ai_default_code_review');
const $form = $('#ai_features_form'); const $form = $('#ai_features_form');
const $updateModelsBtn = $('#model_version_button_update'); const $updateModelsBtn = $('#model_version_button_update');
@ -181,7 +198,8 @@ import json
data: { data: {
'rhodecode_ai_model': $model.val(), 'rhodecode_ai_model': $model.val(),
'rhodecode_ai_api_key': $apiKey.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) { success: function (response) {
$.Topic('/notifications').publish({ $.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> <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> </div>
%endif %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>
</div> </div>
% endif % endif

View file

@ -247,6 +247,31 @@
</label> </label>
</div> </div>
% endif % 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> </div>
</div> </div>

View file

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