Skip to content

Commit 4e2af2f

Browse files
committed
Merge branch 'main_upstream' into feature/i18n
# Conflicts: # api.py # biz/event/event_manager.py # biz/utils/im/im_notifier.py # biz/utils/im/wecom.py # doc/faq.md
2 parents 5373bfb + f64f4db commit 4e2af2f

File tree

9 files changed

+118
-23
lines changed

9 files changed

+118
-23
lines changed

api.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ def handle_webhook():
160160
else:
161161
return jsonify({'message': _('Invalid data format')}), 400
162162

163+
def transform_gitlab_url(url):
164+
# 去掉 http:// 或 https://
165+
if url.startswith("http://"):
166+
url = url[len("http://"):]
167+
elif url.startswith("https://"):
168+
url = url[len("https://"):]
169+
170+
if url.endswith('/'):
171+
url = url[:-1]
172+
173+
# 替换 . 和 / 为 _
174+
transformed_url = url.replace('.', '_').replace('/', '_')
175+
176+
return transformed_url
163177

164178
def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
165179
try:
@@ -179,6 +193,7 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
179193
changes = filter_changes(changes)
180194
if not changes:
181195
logger.info(_('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。'))
196+
return
182197
review_result = _("关注的文件没有修改")
183198

184199
if len(changes) > 0:
@@ -188,6 +203,8 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
188203
# 将review结果提交到Gitlab的 notes
189204
handler.add_push_notes(_('Auto Review Result: \n{}').format(review_result))
190205

206+
url_base = transform_gitlab_url(gitlab_url)
207+
191208
event_manager['push_reviewed'].send(PushReviewEntity(
192209
project_name=webhook_data['project']['name'],
193210
author=webhook_data['user_username'],
@@ -196,6 +213,7 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
196213
commits=commits,
197214
score=score,
198215
review_result=review_result,
216+
gitlab_url = url_base,
199217
))
200218

201219
except Exception as e:
@@ -236,9 +254,14 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
236254
commits_text = ';'.join(commit['title'] for commit in commits)
237255
review_result = review_code(str(changes), commits_text)
238256

257+
if "COT ABORT!" in review_result:
258+
logger.error('COT ABORT!')
259+
return
260+
239261
# 将review结果提交到Gitlab的 notes
240262
handler.add_merge_request_notes(_('Auto Review Result: \n{}').format(review_result))
241263

264+
url_base = transform_gitlab_url(gitlab_url)
242265
# dispatch merge_request_reviewed event
243266
event_manager['merge_request_reviewed'].send(
244267
MergeRequestReviewEntity(
@@ -250,7 +273,8 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
250273
commits=commits,
251274
score=CodeReviewer.parse_review_score(review_text=review_result),
252275
url=webhook_data['object_attributes']['url'],
253-
review_result=review_result
276+
review_result=review_result,
277+
gitlab_url = url_base,
254278
)
255279
)
256280

biz/entity/review_entity.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class MergeRequestReviewEntity:
22
def __init__(self, project_name: str, author: str, source_branch: str, target_branch: str, updated_at: int,
3-
commits: list, score: float, url: str, review_result: str):
3+
commits: list, score: float, url: str, review_result: str, gitlab_url: str):
44
self.project_name = project_name
55
self.author = author
66
self.source_branch = source_branch
@@ -10,6 +10,7 @@ def __init__(self, project_name: str, author: str, source_branch: str, target_br
1010
self.score = score
1111
self.url = url
1212
self.review_result = review_result
13+
self.gitlab_url = gitlab_url
1314

1415
@property
1516
def commit_messages(self):
@@ -19,14 +20,15 @@ def commit_messages(self):
1920

2021
class PushReviewEntity:
2122
def __init__(self, project_name: str, author: str, branch: str, updated_at: int, commits: list, score: float,
22-
review_result: str):
23+
review_result: str, gitlab_url: str):
2324
self.project_name = project_name
2425
self.author = author
2526
self.branch = branch
2627
self.updated_at = updated_at
2728
self.commits = commits
2829
self.score = score
2930
self.review_result = review_result
31+
self.gitlab_url = gitlab_url
3032

3133
@property
3234
def commit_messages(self):

biz/event/event_manager.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity):
4444
review_result=mr_review_entity.review_result
4545
)
4646
im_notifier.send_notification(content=im_msg, msg_type='markdown', title=_('Merge Request Review'),
47-
project_name=mr_review_entity.project_name)
47+
project_name=mr_review_entity.project_name, url_base=mr_review_entity.gitlab_url)
4848

4949
# 记录到数据库
5050
ReviewService().insert_mr_review_log(mr_review_entity)
@@ -76,7 +76,8 @@ def on_push_reviewed(entity: PushReviewEntity):
7676
im_msg += _("#### AI Review 结果: \n {review_result}\n\n").format(review_result=entity.review_result)
7777
im_notifier.send_notification(content=im_msg, msg_type='markdown',
7878
title=_("{project_name} Push Event").format(project_name=entity.project_name),
79-
project_name=entity.project_name)
79+
project_name=entity.project_name,
80+
url_base=entity.gitlab_url)
8081

8182
# 记录到数据库
8283
ReviewService().insert_push_review_log(entity)

biz/utils/im/dingtalk.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, webhook_url=None):
1313
self.enabled = os.environ.get('DINGTALK_ENABLED', '0') == '1'
1414
self.default_webhook_url = webhook_url or os.environ.get('DINGTALK_WEBHOOK_URL')
1515

16-
def _get_webhook_url(self, project_name=None):
16+
def _get_webhook_url(self, project_name=None, url_base=None):
1717
"""
1818
获取项目对应的 Webhook URL
1919
:param project_name: 项目名称
@@ -32,6 +32,12 @@ def _get_webhook_url(self, project_name=None):
3232
for env_key, env_value in os.environ.items():
3333
if env_key.upper() == target_key:
3434
return env_value # 找到匹配项,直接返回
35+
36+
# url_base 优先级次之
37+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
38+
for env_key, env_value in os.environ.items():
39+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
40+
return env_value # 找到匹配项,直接返回
3541

3642
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
3743
if self.default_webhook_url:
@@ -40,13 +46,13 @@ def _get_webhook_url(self, project_name=None):
4046
# 如果既未找到匹配项,也没有默认值,抛出异常
4147
raise ValueError(_("未找到项目 '{}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。").format(project_name))
4248

43-
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None):
49+
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None, url_base = None):
4450
if not self.enabled:
4551
logger.info(_("钉钉推送未启用"))
4652
return
4753

4854
try:
49-
post_url = self._get_webhook_url(project_name=project_name)
55+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
5056
headers = {
5157
"Content-Type": "application/json",
5258
"Charset": "UTF-8"

biz/utils/im/feishu.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, webhook_url=None):
1313
self.default_webhook_url = webhook_url or os.environ.get('FEISHU_WEBHOOK_URL', '')
1414
self.enabled = os.environ.get('FEISHU_ENABLED', '0') == '1'
1515

16-
def _get_webhook_url(self, project_name=None):
16+
def _get_webhook_url(self, project_name=None, url_base=None):
1717
"""
1818
获取项目对应的 Webhook URL
1919
:param project_name: 项目名称
@@ -32,6 +32,12 @@ def _get_webhook_url(self, project_name=None):
3232
for env_key, env_value in os.environ.items():
3333
if env_key.upper() == target_key:
3434
return env_value # 找到匹配项,直接返回
35+
36+
# url_base 优先级次之
37+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
38+
for env_key, env_value in os.environ.items():
39+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
40+
return env_value # 找到匹配项,直接返回
3541

3642
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
3743
if self.default_webhook_url:
@@ -40,7 +46,7 @@ def _get_webhook_url(self, project_name=None):
4046
# 如果既未找到匹配项,也没有默认值,抛出异常
4147
raise ValueError(_("未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 Webhook URL。"))
4248

43-
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None):
49+
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_base=None):
4450
"""
4551
发送飞书消息
4652
:param content: 消息内容
@@ -54,7 +60,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
5460
return
5561

5662
try:
57-
post_url = self._get_webhook_url(project_name=project_name)
63+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
5864
if msg_type == 'markdown':
5965
data = {
6066
"msg_type": "interactive",

biz/utils/im/im_notifier.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1+
from biz.utils.i18n import get_translator
12
from biz.utils.im.dingtalk import DingTalkNotifier
23
from biz.utils.im.feishu import FeishuNotifier
34
from biz.utils.im.wecom import WeComNotifier
4-
from biz.utils.i18n import get_translator
5+
56
_ = get_translator()
67

7-
def send_notification(content, msg_type='text', title=_("通知"), is_at_all=False, project_name=None):
8+
9+
def send_notification(content, msg_type='text', title=_("通知"), is_at_all=False, project_name=None, url_base=None):
810
"""
911
发送通知消息到配置的平台(钉钉和企业微信)
1012
:param content: 消息内容
1113
:param msg_type: 消息类型,支持text和markdown
1214
:param title: 消息标题(markdown类型时使用)
1315
:param is_at_all: 是否@所有人
16+
:param url_base: gitlab服务器的url地址 http://www.gitlab.com 传递进来自动移除http和https,转换成 www_gitlab_com
1417
"""
1518
# 钉钉推送
1619
notifier = DingTalkNotifier()
1720
notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
18-
project_name=project_name)
21+
project_name=project_name, url_base=url_base)
1922

2023
# 企业微信推送
2124
wecom_notifier = WeComNotifier()
2225
wecom_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
23-
project_name=project_name)
26+
project_name=project_name, url_base=url_base)
2427

2528
# 飞书推送
2629
feishu_notifier = FeishuNotifier()
2730
feishu_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
28-
project_name=project_name)
31+
project_name=project_name, url_base=url_base)

biz/utils/im/wecom.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, webhook_url=None):
1515
self.default_webhook_url = webhook_url or os.environ.get('WECOM_WEBHOOK_URL', '')
1616
self.enabled = os.environ.get('WECOM_ENABLED', '0') == '1'
1717

18-
def _get_webhook_url(self, project_name=None):
18+
def _get_webhook_url(self, project_name=None, url_base=None):
1919
"""
2020
获取项目对应的 Webhook URL
2121
:param project_name: 项目名称
@@ -31,8 +31,16 @@ def _get_webhook_url(self, project_name=None):
3131

3232
# 遍历所有环境变量(忽略大小写),找到项目对应的 Webhook URL
3333
target_key = f"WECOM_WEBHOOK_URL_{project_name.upper()}"
34+
35+
# 仓库名称优先级高,先匹配
36+
for env_key, env_value in os.environ.items():
37+
if env_key.upper() == target_key :
38+
return env_value # 找到匹配项,直接返回
39+
40+
# url_base 优先级次之
41+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
3442
for env_key, env_value in os.environ.items():
35-
if env_key.upper() == target_key:
43+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
3644
return env_value # 找到匹配项,直接返回
3745

3846
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
@@ -61,7 +69,7 @@ def format_markdown_content(self, content, title=None):
6169
formatted_content += content
6270
return formatted_content
6371

64-
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None):
72+
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_base=None):
6573
"""
6674
发送企业微信消息
6775
:param content: 消息内容
@@ -74,7 +82,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
7482
return
7583

7684
try:
77-
post_url = self._get_webhook_url(project_name=project_name)
85+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
7886
if msg_type == 'markdown':
7987
formatted_content = self.format_markdown_content(content, title)
8088
data = {
@@ -100,6 +108,30 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
100108

101109
if response.status_code != 200:
102110
logger.error(_("企业微信消息发送失败! webhook_url:{}, error_msg:{}").format(post_url, response.text))
111+
112+
try:
113+
data = json.loads(response.text)
114+
errmsg = data.get("errmsg")
115+
if errmsg and "markdown.content exceed max length" in errmsg:
116+
# markdown渲染过长了,尝试text发送
117+
data = {
118+
"msgtype": "text",
119+
"text": {
120+
"content": content,
121+
"mentioned_list": ["@all"] if is_at_all else []
122+
}
123+
}
124+
125+
response = requests.post(
126+
url=post_url,
127+
json=data,
128+
headers={'Content-Type': 'application/json'}
129+
)
130+
131+
except json.JSONDecodeError as e:
132+
print(f"JSON 解析失败: {e}")
133+
except Exception as e:
134+
logger.error(_("企业微信消息发送失败! {}").format(e))
103135
return
104136

105137
result = response.json()

core/llm/client/ollama_client.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ def _extract_content(self, content: str) -> str:
2727
Returns:
2828
str: 提取后的内容。
2929
"""
30-
if re.search(r'<think>.*?</think>', content, re.DOTALL):
30+
if "<think>" in content and "</think>" not in content:
31+
# 大模型回复的时候,思考链有可能截断,那么果断忽略回复,返回空
32+
return "COT ABORT!"
33+
elif "<think>" not in content and "</think>" in content:
34+
return content.split("</think>", 1)[1].strip()
35+
elif re.search(r'<think>.*?</think>', content, re.DOTALL):
3136
return re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
3237
return content
3338

doc/faq.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,26 @@ OLLAMA_API_BASE_URL=http://127.0.0.1:11434 # 错误
6969
OLLAMA_API_BASE_URL=http://{宿主机/外网IP地址}:11434 # 正确
7070
```
7171

72-
#### 5. 翻译
72+
#### 5.如何支持多个git服务器及对应的不通webhook地址
73+
74+
在项目的 .env 文件中,配置不同GIT项目对应的群机器人的 Webhook 地址。替换
75+
以 DingTalk 为例,配置如下:
76+
77+
```
78+
DINGTALK_ENABLED=1
79+
# git服务器A实际地址 http://192.168.30.164/
80+
DINGTALK_WEBHOOK_192_168_30_164=https://oapi.dingtalk.com/robot/send?access_token={access_token_of_project_a}
81+
# git服务器A实际地址 http://192.168.35.164/
82+
DINGTALK_WEBHOOK_192_168_35_164=https://oapi.dingtalk.com/robot/send?access_token={access_token_of_project_a}
83+
```
84+
85+
飞书和企业微信的配置方式类似,GIT服务器的群机器人优先级,仓库名字>特定服务器地址>默认服务器地址
86+
87+
#### 6. 翻译
88+
7389
更改翻译后无法正确应用,我该怎么办?
7490
该应用程序使用 python 原生 gettext 实现来加载各种语言的翻译。如果您更改了翻译文件,但是应用程序没有正确应用新的翻译,您可以尝试以下步骤:
7591
1. 更新翻译: `bash translations_update.sh`
7692
2. 通过向具有空 msgstr 的新行添加值来手动调整 `locales/<locale>/LC_Messages/messages.po` 中缺失的翻译
7793
3. 编译翻译:`bash translations_compile.sh`
78-
4. 重新启动您的应用程序
94+
4. 重新启动您的应用程序

0 commit comments

Comments
 (0)