| | import gradio as gr |
| | from datetime import datetime, timedelta |
| | import google.generativeai as genai |
| | from github import Github, GithubException |
| | import gitlab |
| | import docx |
| | import tempfile |
| | import requests |
| |
|
| | def generate_release_notes(git_provider, repo_url, personal_access_token, gemini_api_key, start_date, end_date): |
| | try: |
| | start_date = datetime.strptime(start_date, "%Y-%m-%d") |
| | end_date = datetime.strptime(end_date, "%Y-%m-%d") |
| |
|
| | if git_provider == "GitHub": |
| | g = Github(personal_access_token) |
| | repo = g.get_repo(repo_url) |
| | commits = list(repo.get_commits(since=start_date, until=end_date)) |
| | commit_messages = [commit.commit.message for commit in commits] |
| | elif git_provider == "GitLab": |
| | gl = gitlab.Gitlab(url='https://gitlab.com', private_token=personal_access_token) |
| | project = gl.projects.get(repo_url) |
| | commits = project.commits.list(since=start_date.isoformat(), until=end_date.isoformat()) |
| | commit_messages = [commit.message for commit in commits] |
| | elif git_provider == "Gitea": |
| | base_url = "https://gitea.com/api/v1" |
| | headers = {"Authorization": f"token {personal_access_token}"} |
| | response = requests.get(f"{base_url}/repos/{repo_url}/commits", headers=headers, params={ |
| | "since": start_date.isoformat(), |
| | "until": end_date.isoformat() |
| | }) |
| | response.raise_for_status() |
| | commits = response.json() |
| | commit_messages = [commit['commit']['message'] for commit in commits] |
| | else: |
| | return "Unsupported Git provider", None, None |
| |
|
| | commit_text = "\n".join(commit_messages) |
| | |
| | if not commit_text: |
| | return "No commits found in the specified date range.", None, None |
| | |
| | genai.configure(api_key=gemini_api_key) |
| | model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25') |
| | |
| | prompt = f"""Based on the following commit messages, generate comprehensive release notes: |
| | |
| | {commit_text} |
| | |
| | Please organize the release notes into sections such as: |
| | 1. New Features |
| | 2. Bug Fixes |
| | 3. Improvements |
| | 4. Breaking Changes (if any) |
| | |
| | Provide a concise summary for each item. Do not include any links, but keep issue numbers if present. |
| | |
| | Important formatting instructions: |
| | - The output should be plain text without any markdown or "-" for post processing |
| | - Use section titles followed by a colon (e.g., "New Features:") |
| | - Start each item on a new line |
| | - Be sure to briefly explain the why and benefits of the change for average users that are non-technical |
| | """ |
| |
|
| | response = model.generate_content(prompt) |
| | release_notes = response.text |
| | |
| | |
| | doc = docx.Document() |
| | doc.add_heading('Release Notes', 0) |
| | |
| | current_section = None |
| | for line in release_notes.split('\n'): |
| | line = line.strip() |
| | if line.endswith(':'): |
| | doc.add_heading(line, level=1) |
| | current_section = None |
| | elif line: |
| | if current_section is None: |
| | current_section = doc.add_paragraph().style |
| | current_section.name = 'List Bullet' |
| | doc.add_paragraph(line, style=current_section) |
| | |
| | with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as temp_docx: |
| | doc.save(temp_docx.name) |
| | docx_path = temp_docx.name |
| |
|
| | |
| | markdown_content = "# Release Notes\n\n" |
| | for line in release_notes.split('\n'): |
| | line = line.strip() |
| | if line.endswith(':'): |
| | markdown_content += f"\n## {line}\n\n" |
| | elif line: |
| | markdown_content += f"- {line}\n" |
| |
|
| | with tempfile.NamedTemporaryFile(delete=False, suffix='.md', mode='w', encoding='utf-8') as temp_md: |
| | temp_md.write(markdown_content) |
| | md_path = temp_md.name |
| | |
| | return release_notes, docx_path, md_path |
| | |
| | except Exception as e: |
| | return f"An error occurred: {str(e)}", None, None |
| |
|
| | default_end_date = datetime.now() |
| | default_start_date = default_end_date - timedelta(days=30) |
| |
|
| | iface = gr.Interface( |
| | fn=generate_release_notes, |
| | inputs=[ |
| | gr.Dropdown( |
| | choices=["GitHub", "GitLab", "Gitea"], |
| | label="Git Provider" |
| | ), |
| | gr.Textbox(label="Repository URL", placeholder="owner/repo"), |
| | gr.Textbox(label="Personal Access Token", type="password"), |
| | gr.Textbox(label="Gemini API Key", type="password"), |
| | gr.Textbox( |
| | label="Start Date", |
| | placeholder="YYYY-MM-DD", |
| | value=default_start_date.strftime("%Y-%m-%d"), |
| | ), |
| | gr.Textbox( |
| | label="End Date", |
| | placeholder="YYYY-MM-DD", |
| | value=default_end_date.strftime("%Y-%m-%d"), |
| | ) |
| | ], |
| | outputs=[ |
| | gr.Textbox(label="Generated Release Notes"), |
| | gr.File(label="Download Release Notes (DOCX)"), |
| | gr.File(label="Download Release Notes (Markdown)") |
| | ], |
| | title="Automated Release Notes Generator", |
| | description="Generate release notes based on Git commits using Gemini AI. Select a Git provider, enter repository details, and specify the date range for commits.", |
| | allow_flagging="never", |
| | theme="default", |
| | analytics_enabled=False, |
| | ) |
| |
|
| | iface.launch() |