Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2d637b935 | |||
| 96e61a4a92 | |||
| e42c1b6da8 | |||
| 387bba093e | |||
| 123cf9cb11 |
@@ -34,7 +34,7 @@
|
|||||||
<a href="https://github.com/AstrBotDevs/AstrBot/issues">问题提交</a>
|
<a href="https://github.com/AstrBotDevs/AstrBot/issues">问题提交</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
AstrBot 是一个开源的一站式 Agent 聊天机器人平台,可接入主流即时通讯软件,为个人、开发者和团队打造可靠、可扩展的对话式智能基础设施。无论是个人 AI 伙伴、智能客服、自动化助手,还是企业知识库,AstrBot 都能在你的即时通讯软件平台的工作流中快速构建生产可用的 AI 应用。
|
AstrBot 是一个开源的一站式 Agentic 个人和群聊助手,可在 QQ、Telegram、企业微信、飞书、钉钉、Slack、等数十款主流即时通讯软件上部署,此外还内置类似 OpenWebUI 的轻量化 ChatUI,为个人、开发者和团队打造可靠、可扩展的对话式智能基础设施。无论是个人 AI 伙伴、智能客服、自动化助手,还是企业知识库,AstrBot 都能在你的即时通讯软件平台的工作流中快速构建 AI 应用。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -50,6 +50,25 @@ AstrBot 是一个开源的一站式 Agent 聊天机器人平台,可接入主
|
|||||||
7. 🌈 Web ChatUI 支持,ChatUI 内置代理沙盒、网页搜索等。
|
7. 🌈 Web ChatUI 支持,ChatUI 内置代理沙盒、网页搜索等。
|
||||||
8. 🌐 国际化(i18n)支持。
|
8. 🌐 国际化(i18n)支持。
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<table align="center">
|
||||||
|
<tr align="center">
|
||||||
|
<th>💙 角色扮演 & 情感陪伴</th>
|
||||||
|
<th>✨ 主动式 Agent</th>
|
||||||
|
<th>🚀 通用 Agentic 能力</th>
|
||||||
|
<th>🧩 900+ 社区插件</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><p align="center"><img width="984" height="1746" alt="99b587c5d35eea09d84f33e6cf6cfd4f" src="https://github.com/user-attachments/assets/89196061-3290-458d-b51f-afa178049f84" /></p></td>
|
||||||
|
<td align="center"><p align="center"><img width="976" height="1612" alt="c449acd838c41d0915cc08a3824025b1" src="https://github.com/user-attachments/assets/f75368b4-e022-41dc-a9e0-131c3e73e32e" /></p></td>
|
||||||
|
<td align="center"><p align="center"><img width="974" height="1732" alt="image" src="https://github.com/user-attachments/assets/e22a3968-87d7-4708-a7cd-e7f198c7c32e" /></p></td>
|
||||||
|
<td align="center"><p align="center"><img width="976" height="1734" alt="image" src="https://github.com/user-attachments/assets/0952b395-6b4a-432a-8a50-c294b7f89750" /></p></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
陪伴与能力**从来不应该是**对立面。我们希望创造的是一个既能理解情绪、给予陪伴,也能可靠完成工作的机器人——致敬[ATRI](https://zh.wikipedia.org/zh-cn/ATRI_-My_Dear_Moments-)。
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
#### Docker 部署(推荐 🥳)
|
#### Docker 部署(推荐 🥳)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "4.13.2"
|
__version__ = "4.14.0"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
|
|||||||
|
|
||||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||||
|
|
||||||
VERSION = "4.13.2"
|
VERSION = "4.14.0"
|
||||||
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||||
|
|
||||||
WEBHOOK_SUPPORTED_PLATFORMS = [
|
WEBHOOK_SUPPORTED_PLATFORMS = [
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ class CustomFilter(HandlerFilter, metaclass=CustomFilterMeta):
|
|||||||
class CustomFilterOr(CustomFilter):
|
class CustomFilterOr(CustomFilter):
|
||||||
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
|
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if not isinstance(filter1, CustomFilter | CustomFilterAnd | CustomFilterOr):
|
if not isinstance(filter1, (CustomFilter, CustomFilterAnd, CustomFilterOr)):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"CustomFilter lass can only operate with other CustomFilter.",
|
"CustomFilter class can only operate with other CustomFilter.",
|
||||||
)
|
)
|
||||||
self.filter1 = filter1
|
self.filter1 = filter1
|
||||||
self.filter2 = filter2
|
self.filter2 = filter2
|
||||||
@@ -51,7 +51,7 @@ class CustomFilterOr(CustomFilter):
|
|||||||
class CustomFilterAnd(CustomFilter):
|
class CustomFilterAnd(CustomFilter):
|
||||||
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
|
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if not isinstance(filter1, CustomFilter | CustomFilterAnd | CustomFilterOr):
|
if not isinstance(filter1, (CustomFilter, CustomFilterAnd, CustomFilterOr)):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"CustomFilter lass can only operate with other CustomFilter.",
|
"CustomFilter lass can only operate with other CustomFilter.",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ def register_custom_filter(custom_type_filter, *args, **kwargs):
|
|||||||
if args:
|
if args:
|
||||||
raise_error = args[0]
|
raise_error = args[0]
|
||||||
|
|
||||||
if not isinstance(custom_filter, CustomFilterAnd | CustomFilterOr):
|
if not isinstance(custom_filter, (CustomFilterAnd, CustomFilterOr)):
|
||||||
custom_filter = custom_filter(raise_error)
|
custom_filter = custom_filter(raise_error)
|
||||||
|
|
||||||
def decorator(awaitable):
|
def decorator(awaitable):
|
||||||
|
|||||||
@@ -315,6 +315,17 @@ class PluginRoute(Route):
|
|||||||
"display_name": plugin.display_name,
|
"display_name": plugin.display_name,
|
||||||
"logo": f"/api/file/{logo_url}" if logo_url else None,
|
"logo": f"/api/file/{logo_url}" if logo_url else None,
|
||||||
}
|
}
|
||||||
|
# 检查是否为全空的幽灵插件
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
|
plugin.name,
|
||||||
|
plugin.author,
|
||||||
|
plugin.desc,
|
||||||
|
plugin.version,
|
||||||
|
plugin.display_name,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
continue
|
||||||
_plugin_resp.append(_t)
|
_plugin_resp.append(_t)
|
||||||
return (
|
return (
|
||||||
Response()
|
Response()
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
## What's Changed - BIG AND BEAUTIFUL VERSION
|
||||||
|
|
||||||
|
> 如果在之前版本使用了 Skill,这次更新之后**需要重新配置** Skill Runtime 相关选项。
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- 🔥 新增未来任务系统(Future Tasks)。给 AstrBot 布置的未来任务,让 AstrBot 能够在某一时刻自动唤醒,帮你完成任务。详见 [主动任务](https://docs.astrbot.app/use/proactive-agent.html) 。(实验性) ([#4697](https://github.com/AstrBotDevs/AstrBot/issues/4831))
|
||||||
|
- 🔥 新增子代理(SubAgent)编排器。(实验性)([#4697](https://github.com/AstrBotDevs/AstrBot/issues/4831))
|
||||||
|
- 🔥 AstrBot 目前可以直接通过调用 tool 将图片 / 文件推送给用户,大大提高交互效果。
|
||||||
|
- 新增 Computer Use 运行时配置,以融合 Skill 和 Sandbox 配置 ([#4831](https://github.com/AstrBotDevs/AstrBot/issues/4831))
|
||||||
|
- 新增主题自定义功能,可设置主色与辅色
|
||||||
|
- 支持在配置页下人格对话框的编辑人格 ([#4826](https://github.com/AstrBotDevs/AstrBot/issues/4826))
|
||||||
|
- 支持开关 “追踪” 功能;支持在系统配置中设置是否将日志写入 log 文件 ([#4822](https://github.com/AstrBotDevs/AstrBot/issues/4822))
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- ‼️ 修复 ChatUI 图片、思考等显示异常问题。
|
||||||
|
- ‼️ 修复 Skill 上传到 Sandbox 后未自动解压导致 Agent 无法读取的问题。
|
||||||
|
- ‼️ 修复配置特定插件集时 MCP 工具被过滤的问题 ([#4825](https://github.com/AstrBotDevs/AstrBot/issues/4825))
|
||||||
|
- ‼️ 移除 ChatUI 自带的让 LLM 最后提出问题的 prompt ([#4824](https://github.com/AstrBotDevs/AstrBot/issues/4824))
|
||||||
|
- ‼️ 修复 WebUI 在上传 Skill 失败后仍显示成功消息的 bug ([#4768](https://github.com/AstrBotDevs/AstrBot/issues/4768))
|
||||||
|
- 修复 MCP 服务器无法重命名的问题 ([#4766](https://github.com/AstrBotDevs/AstrBot/issues/4766))
|
||||||
|
- 修复插件的 tool 无法在 WebUI 管理行为中看到来源的问题 ([#4776](https://github.com/AstrBotDevs/AstrBot/issues/4776))
|
||||||
|
- ‼️ 修复 skill-like 的 tool 模式下,调用 tool 失败的问题 ([#4775](https://github.com/AstrBotDevs/AstrBot/issues/4775))
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- WebUI 整体 UI 效果优化
|
||||||
|
- 部分 Dialog 标题样式统一
|
||||||
|
|
||||||
|
## What's Changed (EN)
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- Introduce CronJob system with one-time tasks and enhanced dashboard management
|
||||||
|
- Add theme customization with primary & secondary color options
|
||||||
|
- Add computer-use runtime config for skills sandbox execution ([#4831](https://github.com/AstrBotDevs/AstrBot/issues/4831))
|
||||||
|
- Add edit button to persona selector dialog ([#4826](https://github.com/AstrBotDevs/AstrBot/issues/4826))
|
||||||
|
- Add trace logging toggle and configuration UI ([#4822](https://github.com/AstrBotDevs/AstrBot/issues/4822))
|
||||||
|
- Add proactive-messaging capability with cron-tool trigger
|
||||||
|
- Implement SubAgent orchestrator with configurable tool-management policies
|
||||||
|
- Support resolving sandbox file paths and auto-download when necessary
|
||||||
|
- Add embedded image & audio styles in MessagePartsRenderer
|
||||||
|
- Introduce i18n foundation
|
||||||
|
- Persist agent-interaction history
|
||||||
|
- Add user notifications for file-download success/removal
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Improve ghost-plugin detection accuracy
|
||||||
|
- Add error handling to prevent ghost-plugin crashes
|
||||||
|
- Prevent skills bundle from overwriting existing files
|
||||||
|
- Fix skills bundle unzip failure inside sandbox
|
||||||
|
- Fix MCP tools being filtered when specific plugin set configured ([#4825](https://github.com/AstrBotDevs/AstrBot/issues/4825))
|
||||||
|
- Merge ChatUI persona pop-up into default persona ([#4824](https://github.com/AstrBotDevs/AstrBot/issues/4824))
|
||||||
|
- Fix reasoning block style
|
||||||
|
- Add missing comma in truncate_and_compress hint
|
||||||
|
- Fix frontend still showing success message ([#4768](https://github.com/AstrBotDevs/AstrBot/issues/4768))
|
||||||
|
- Fix unable to rename MCP server ([#4766](https://github.com/AstrBotDevs/AstrBot/issues/4766))
|
||||||
|
- Remove leftover sandbox runtime handling in skill upload ([#4798](https://github.com/AstrBotDevs/AstrBot/issues/4798))
|
||||||
|
- Fix handler module path construction ([#4776](https://github.com/AstrBotDevs/AstrBot/issues/4776))
|
||||||
|
- Fix skill-like tool invocation error ([#4775](https://github.com/AstrBotDevs/AstrBot/issues/4775))
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- Runtime hints & refined UI in skills management
|
||||||
|
- Performance and UX improvements on cron-job page
|
||||||
|
- General WebUI performance boost
|
||||||
|
- Group tools by plugin in dropdown
|
||||||
|
- Consistent dialog titles with padding and text styles
|
||||||
|
- Code formatting unified (ruff format)
|
||||||
|
- Bump version to 4.13.2
|
||||||
|
|
||||||
|
### Others
|
||||||
|
- Remove obsolete reminder code
|
||||||
|
- Extract main-agent module for better architecture
|
||||||
|
- Merge AstrBot_skill branch changes
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"markstream-vue": "^0.0.6",
|
"markstream-vue": "^0.0.6",
|
||||||
"mermaid": "^11.12.2",
|
"mermaid": "^11.12.2",
|
||||||
"monaco-editor": "^0.55.1",
|
"monaco-editor": "^0.52.2",
|
||||||
"pinia": "2.1.6",
|
"pinia": "2.1.6",
|
||||||
"pinyin-pro": "^3.26.0",
|
"pinyin-pro": "^3.26.0",
|
||||||
"remixicon": "3.5.0",
|
"remixicon": "3.5.0",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"cron": "Cron",
|
"cron": "Cron",
|
||||||
|
"session": "Session ID",
|
||||||
"nextRun": "Next Run",
|
"nextRun": "Next Run",
|
||||||
"lastRun": "Last Run",
|
"lastRun": "Last Run",
|
||||||
"note": "Note",
|
"note": "Note",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"name": "名称",
|
"name": "名称",
|
||||||
"type": "类型",
|
"type": "类型",
|
||||||
"cron": "Cron",
|
"cron": "Cron",
|
||||||
|
"session": "会话 ID",
|
||||||
"nextRun": "下一次执行",
|
"nextRun": "下一次执行",
|
||||||
"lastRun": "最近执行",
|
"lastRun": "最近执行",
|
||||||
"note": "说明",
|
"note": "说明",
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
"nameHint": "建议使用英文小写+下划线,且全局唯一",
|
"nameHint": "建议使用英文小写+下划线,且全局唯一",
|
||||||
"providerLabel": "Chat Provider(可选)",
|
"providerLabel": "Chat Provider(可选)",
|
||||||
"providerHint": "留空表示跟随全局默认 provider。",
|
"providerHint": "留空表示跟随全局默认 provider。",
|
||||||
"personaLabel": "选择 Persona",
|
"personaLabel": "选择人格设定",
|
||||||
"personaHint": "SubAgent 将直接继承所选 Persona 的系统设定与工具。",
|
"personaHint": "SubAgent 将直接继承所选 Persona 的系统设定与工具。在人格设定页管理和新建人格。",
|
||||||
"descriptionLabel": "对主 LLM 的描述(用于决定是否 handoff)",
|
"descriptionLabel": "对主 LLM 的描述(用于决定是否 handoff)",
|
||||||
"descriptionHint": "这段会作为 transfer_to_* 工具的描述给主 LLM 看,建议简短明确。"
|
"descriptionHint": "这段会作为 transfer_to_* 工具的描述给主 LLM 看,建议简短明确。"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,9 @@
|
|||||||
<div class="text-caption text-medium-emphasis">{{ item.timezone || tm('table.timezoneLocal') }}</div>
|
<div class="text-caption text-medium-emphasis">{{ item.timezone || tm('table.timezoneLocal') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #item.session="{ item }">
|
||||||
|
<div>{{ item.session || tm('table.notAvailable') }}</div>
|
||||||
|
</template>
|
||||||
<template #item.next_run_time="{ item }">{{ formatTime(item.next_run_time) }}</template>
|
<template #item.next_run_time="{ item }">{{ formatTime(item.next_run_time) }}</template>
|
||||||
<template #item.last_run_at="{ item }">{{ formatTime(item.last_run_at) }}</template>
|
<template #item.last_run_at="{ item }">{{ formatTime(item.last_run_at) }}</template>
|
||||||
<template #item.note="{ item }">{{ item.note || tm('table.notAvailable') }}</template>
|
<template #item.note="{ item }">{{ item.note || tm('table.notAvailable') }}</template>
|
||||||
@@ -129,6 +132,7 @@ const headers = computed(() => [
|
|||||||
{ title: tm('table.headers.name'), key: 'name', minWidth: '200px' },
|
{ title: tm('table.headers.name'), key: 'name', minWidth: '200px' },
|
||||||
{ title: tm('table.headers.type'), key: 'type', width: 110 },
|
{ title: tm('table.headers.type'), key: 'type', width: 110 },
|
||||||
{ title: tm('table.headers.cron'), key: 'cron_expression', minWidth: '160px' },
|
{ title: tm('table.headers.cron'), key: 'cron_expression', minWidth: '160px' },
|
||||||
|
{ title: tm('table.headers.session'), key: 'session', minWidth: '200px' },
|
||||||
{ title: tm('table.headers.nextRun'), key: 'next_run_time', minWidth: '160px' },
|
{ title: tm('table.headers.nextRun'), key: 'next_run_time', minWidth: '160px' },
|
||||||
{ title: tm('table.headers.lastRun'), key: 'last_run_at', minWidth: '160px' },
|
{ title: tm('table.headers.lastRun'), key: 'last_run_at', minWidth: '160px' },
|
||||||
{ title: tm('table.headers.note'), key: 'note', minWidth: '220px' },
|
{ title: tm('table.headers.note'), key: 'note', minWidth: '220px' },
|
||||||
@@ -163,7 +167,11 @@ async function loadJobs() {
|
|||||||
try {
|
try {
|
||||||
const res = await axios.get('/api/cron/jobs')
|
const res = await axios.get('/api/cron/jobs')
|
||||||
if (res.data.status === 'ok') {
|
if (res.data.status === 'ok') {
|
||||||
jobs.value = Array.isArray(res.data.data) ? res.data.data : []
|
const data = Array.isArray(res.data.data) ? res.data.data : []
|
||||||
|
jobs.value = data.map((job: any) => ({
|
||||||
|
...job,
|
||||||
|
session: job?.payload?.session || job?.session || ''
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
toast(res.data.message || tm('messages.loadFailed'), 'error')
|
toast(res.data.message || tm('messages.loadFailed'), 'error')
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "AstrBot"
|
name = "AstrBot"
|
||||||
version = "4.13.2"
|
version = "4.14.0"
|
||||||
description = "Easy-to-use multi-platform LLM chatbot and development framework"
|
description = "Easy-to-use multi-platform LLM chatbot and development framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Executable
+253
@@ -0,0 +1,253 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Auto-generate changelog from git commits using LLM.
|
||||||
|
Usage: python scripts/generate_changelog.py [--version VERSION]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_tag():
|
||||||
|
"""Get the latest git tag."""
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "describe", "--tags", "--abbrev=0"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_commits_since_tag(tag):
|
||||||
|
"""Get all commit messages since the specified tag."""
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "log", f"{tag}..HEAD", "--pretty=format:%H|%s|%b"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
commits = []
|
||||||
|
for line in result.stdout.strip().split("\n"):
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
parts = line.split("|", 2)
|
||||||
|
if len(parts) >= 2:
|
||||||
|
commit_hash = parts[0]
|
||||||
|
subject = parts[1]
|
||||||
|
body = parts[2] if len(parts) > 2 else ""
|
||||||
|
commits.append({"hash": commit_hash[:7], "subject": subject, "body": body})
|
||||||
|
return commits
|
||||||
|
|
||||||
|
|
||||||
|
def extract_issue_number(text):
|
||||||
|
"""Extract issue number from commit message."""
|
||||||
|
# Match #1234 or (#1234)
|
||||||
|
match = re.search(r"#(\d+)", text)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
|
||||||
|
def call_llm_for_changelog(commits, version):
|
||||||
|
"""Call LLM to generate changelog from commits."""
|
||||||
|
try:
|
||||||
|
# Try to use OpenAI API or other LLM providers
|
||||||
|
import openai
|
||||||
|
|
||||||
|
# Build prompt
|
||||||
|
commits_text = "\n".join([f"- {c['subject']}" for c in commits])
|
||||||
|
|
||||||
|
prompt = f"""Based on the following git commit messages, generate a changelog document in BOTH Chinese and English.
|
||||||
|
|
||||||
|
Commit messages:
|
||||||
|
{commits_text}
|
||||||
|
|
||||||
|
Please organize the changes into these categories:
|
||||||
|
- 新增 (New Features)
|
||||||
|
- 修复 (Bug Fixes)
|
||||||
|
- 优化 (Improvements)
|
||||||
|
- 其他 (Others)
|
||||||
|
|
||||||
|
Format requirements:
|
||||||
|
1. Start with Chinese version under "## What's Changed"
|
||||||
|
2. Follow with English version under "## What's Changed (EN)"
|
||||||
|
3. Use markdown format with proper bullet points
|
||||||
|
4. Keep descriptions concise and user-friendly
|
||||||
|
5. If a commit mentions an issue number (#1234), include it in the format ([#1234](https://github.com/AstrBotDevs/AstrBot/issues/1234))
|
||||||
|
|
||||||
|
Example format:
|
||||||
|
## What's Changed
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- 支持某某功能 ([#1234](https://github.com/AstrBotDevs/AstrBot/issues/1234))
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- 修复某某问题
|
||||||
|
|
||||||
|
## What's Changed (EN)
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- Add support for something ([#1234](https://github.com/AstrBotDevs/AstrBot/issues/1234))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fix something
|
||||||
|
"""
|
||||||
|
|
||||||
|
client = openai.OpenAI(
|
||||||
|
api_key=os.getenv("OPENAI_API_KEY"),
|
||||||
|
base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model=os.getenv("OPENAI_MODEL", "gpt-4"),
|
||||||
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a helpful assistant that generates well-structured changelogs.",
|
||||||
|
},
|
||||||
|
{"role": "user", "content": prompt},
|
||||||
|
],
|
||||||
|
temperature=0.3,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.choices[0].message.content
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print(
|
||||||
|
"Warning: openai package not installed. Install it with: pip install openai"
|
||||||
|
)
|
||||||
|
return generate_simple_changelog(commits)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Failed to call LLM API: {e}")
|
||||||
|
print("Falling back to simple changelog generation...")
|
||||||
|
return generate_simple_changelog(commits)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_simple_changelog(commits):
|
||||||
|
"""Generate a simple changelog without LLM."""
|
||||||
|
sections = {
|
||||||
|
"feat": ("新增", "New Features", []),
|
||||||
|
"fix": ("修复", "Bug Fixes", []),
|
||||||
|
"perf": ("优化", "Improvements", []),
|
||||||
|
"docs": ("文档", "Documentation", []),
|
||||||
|
"refactor": ("重构", "Refactoring", []),
|
||||||
|
"test": ("测试", "Tests", []),
|
||||||
|
"chore": ("其他", "Chore", []),
|
||||||
|
"other": ("其他", "Others", []),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Categorize commits by conventional commit type
|
||||||
|
for commit in commits:
|
||||||
|
subject = commit["subject"]
|
||||||
|
issue_num = extract_issue_number(subject)
|
||||||
|
issue_link = (
|
||||||
|
f" ([#{issue_num}](https://github.com/AstrBotDevs/AstrBot/issues/{issue_num}))"
|
||||||
|
if issue_num
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Detect conventional commit type
|
||||||
|
matched = False
|
||||||
|
for prefix in ["feat", "fix", "perf", "docs", "refactor", "test", "chore"]:
|
||||||
|
if subject.lower().startswith(f"{prefix}:") or subject.lower().startswith(
|
||||||
|
f"{prefix}("
|
||||||
|
):
|
||||||
|
# Remove prefix for display
|
||||||
|
clean_subject = re.sub(
|
||||||
|
r"^[a-z]+(\([^)]+\))?:\s*", "", subject, flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
sections[prefix][2].append(f"- {clean_subject}{issue_link}")
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not matched:
|
||||||
|
sections["other"][2].append(f"- {subject}{issue_link}")
|
||||||
|
|
||||||
|
# Build Chinese version
|
||||||
|
changelog_zh = "## What's Changed\n\n"
|
||||||
|
for section_key in ["feat", "fix", "perf", "docs", "refactor", "test", "other"]:
|
||||||
|
zh_title, _, items = sections[section_key]
|
||||||
|
if items:
|
||||||
|
changelog_zh += f"### {zh_title}\n\n"
|
||||||
|
changelog_zh += "\n".join(items) + "\n\n"
|
||||||
|
|
||||||
|
# Build English version
|
||||||
|
changelog_en = "## What's Changed (EN)\n\n"
|
||||||
|
for section_key in ["feat", "fix", "perf", "docs", "refactor", "test", "other"]:
|
||||||
|
_, en_title, items = sections[section_key]
|
||||||
|
if items:
|
||||||
|
changelog_en += f"### {en_title}\n\n"
|
||||||
|
changelog_en += "\n".join(items) + "\n\n"
|
||||||
|
|
||||||
|
return changelog_zh + changelog_en
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Generate changelog from git commits")
|
||||||
|
parser.add_argument(
|
||||||
|
"--version", help="Version number for the changelog (e.g., v4.13.3)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--use-llm",
|
||||||
|
action="store_true",
|
||||||
|
help="Use LLM to generate changelog (requires OpenAI API key)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Get latest tag
|
||||||
|
try:
|
||||||
|
latest_tag = get_latest_tag()
|
||||||
|
print(f"Latest tag: {latest_tag}")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Error: No tags found in repository")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Get commits since tag
|
||||||
|
commits = get_commits_since_tag(latest_tag)
|
||||||
|
if not commits:
|
||||||
|
print(f"No commits found since {latest_tag}")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print(f"Found {len(commits)} commits since {latest_tag}")
|
||||||
|
|
||||||
|
# Determine version
|
||||||
|
if args.version:
|
||||||
|
version = args.version
|
||||||
|
else:
|
||||||
|
# Auto-increment patch version
|
||||||
|
match = re.match(r"v(\d+)\.(\d+)\.(\d+)", latest_tag)
|
||||||
|
if match:
|
||||||
|
major, minor, patch = map(int, match.groups())
|
||||||
|
version = f"v{major}.{minor}.{patch + 1}"
|
||||||
|
else:
|
||||||
|
print(f"Warning: Could not parse version from tag {latest_tag}")
|
||||||
|
version = "vX.X.X"
|
||||||
|
|
||||||
|
print(f"Generating changelog for {version}...")
|
||||||
|
|
||||||
|
# Generate changelog
|
||||||
|
if args.use_llm:
|
||||||
|
changelog_content = call_llm_for_changelog(commits, version)
|
||||||
|
else:
|
||||||
|
changelog_content = generate_simple_changelog(commits)
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
changelog_dir = Path(__file__).parent.parent / "changelogs"
|
||||||
|
changelog_dir.mkdir(exist_ok=True)
|
||||||
|
changelog_file = changelog_dir / f"{version}.md"
|
||||||
|
|
||||||
|
with open(changelog_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(changelog_content)
|
||||||
|
|
||||||
|
print(f"\n✓ Changelog generated: {changelog_file}")
|
||||||
|
print("\nPreview:")
|
||||||
|
print("=" * 80)
|
||||||
|
print(changelog_content)
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user