bluenevus's picture
Update app.py
4edfbd8 verified
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
from datetime import datetime, timedelta
import google.generativeai as genai
from github import Github, GithubException
import gitlab
import docx
import tempfile
import requests
import os
import threading
import io
# Initialize the Dash app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
# Hugging Face variables
HF_GEMINI_API_KEY = os.environ.get('HF_GEMINI_API_KEY')
HF_GITHUB_TOKEN = os.environ.get('HF_GITHUB_TOKEN')
# Global variables to store generated files and PR URL
generated_file = None
pr_url = None
def generate_release_notes(git_provider, repo_url, start_date, end_date, folder_location):
global generated_file
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(HF_GITHUB_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=HF_GITHUB_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 {HF_GITHUB_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
commit_text = "\n".join(commit_messages)
if not commit_text:
return "No commits found in the specified date range.", None
genai.configure(api_key=HF_GEMINI_API_KEY)
model = genai.GenerativeModel('gemini-2.0-flash-lite')
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
# Create Markdown file
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"
# Generate file name based on the current date
file_name = f"{datetime.now().strftime('%m-%d-%Y')}.md"
# Store the generated file content
generated_file = io.BytesIO(markdown_content.encode())
generated_file.seek(0)
return release_notes, file_name
except Exception as e:
return f"An error occurred: {str(e)}", None
def update_summary_and_create_pr(repo_url, folder_location, start_date, end_date, markdown_content):
global pr_url
try:
g = Github(HF_GITHUB_TOKEN)
repo = g.get_repo(repo_url)
# Generate file name based on end date
file_name = f"{end_date}.md"
# Determine SUMMARY.md location
summary_folder = '/'.join(folder_location.split('/')[:-1])
summary_path = f"{summary_folder}/SUMMARY.md"
# Get the current content of SUMMARY.md
try:
summary_file = repo.get_contents(summary_path)
summary_content = summary_file.decoded_content.decode()
except GithubException as e:
if e.status == 404:
summary_content = "* [Releases](README.md)\n"
else:
raise
# Add new file link at the top of the Releases section
new_entry = f" * [{end_date}](rel/{file_name})\n"
releases_index = summary_content.find("* [Releases]")
if releases_index != -1:
insert_position = summary_content.find("\n", releases_index) + 1
updated_summary = (summary_content[:insert_position] + new_entry +
summary_content[insert_position:])
else:
updated_summary = summary_content + f"* [Releases](README.md)\n{new_entry}"
# Create a new branch for the PR
base_branch = repo.default_branch
new_branch = f"update-release-notes-{datetime.now().strftime('%Y%m%d%H%M%S')}"
ref = repo.get_git_ref(f"heads/{base_branch}")
repo.create_git_ref(ref=f"refs/heads/{new_branch}", sha=ref.object.sha)
# Update SUMMARY.md in the new branch
repo.update_file(
summary_path,
f"Update SUMMARY.md with new release notes {file_name}",
updated_summary,
summary_file.sha if 'summary_file' in locals() else None,
branch=new_branch
)
# Create the new release notes file
new_file_path = f"{folder_location}/{file_name}"
repo.create_file(
new_file_path,
f"Add release notes {file_name}",
markdown_content,
branch=new_branch
)
# Create a pull request
pr = repo.create_pull(
title=f"Add release notes {file_name} and update SUMMARY.md",
body="Automatically generated PR to add new release notes and update SUMMARY.md.",
head=new_branch,
base=base_branch
)
pr_url = pr.html_url
return f"Pull request created: {pr_url}"
except Exception as e:
print(f"Error: {str(e)}")
return f"Error creating PR: {str(e)}"
# App layout
app.layout = dbc.Container([
html.H1("Automated Release Notes Generator", className="mb-4"),
dbc.Card([
dbc.CardBody([
dbc.Form([
dbc.Row([
dbc.Col([
dcc.Dropdown(
id='git-provider',
options=[
{'label': 'GitHub', 'value': 'GitHub'},
{'label': 'GitLab', 'value': 'GitLab'},
{'label': 'Gitea', 'value': 'Gitea'}
],
placeholder="Select Git Provider"
)
], width=12, className="mb-3"),
]),
dbc.Row([
dbc.Col([
dbc.Input(id='repo-url', placeholder="Repository URL (e.g., MicroHealthLLC/maiko-assistant)", type="text")
], width=12, className="mb-3"),
]),
dbc.Row([
dbc.Col([
dbc.Input(id='start-date', placeholder="Start Date (YYYY-MM-DD)", type="text")
], width=12, className="mb-3"),
]),
dbc.Row([
dbc.Col([
dbc.Input(id='end-date', placeholder="End Date (YYYY-MM-DD)", type="text")
], width=12, className="mb-3"),
]),
dbc.Row([
dbc.Col([
dbc.Input(id='folder-location', placeholder="Folder Location (e.g., documentation/releases/rel)", type="text")
], width=12, className="mb-3"),
]),
dbc.Row([
dbc.Col([
dbc.Button("Generate Release Notes", id="generate-button", color="primary", className="me-2 mb-2"),
], width=4),
dbc.Col([
dbc.Button("Download Markdown", id="download-button", color="secondary", className="me-2 mb-2", disabled=True),
], width=4),
dbc.Col([
dbc.Button("Create PR", id="pr-button", color="info", className="mb-2", disabled=True),
], width=4),
]),
dbc.Row([
dbc.Col([
dcc.Loading(
id="pr-loading",
type="circle",
children=[html.Div(id="pr-output")]
)
], width=12, className="mb-3"),
]),
]),
])
], className="mb-4"),
dbc.Card([
dbc.CardBody([
html.H4("Generated Release Notes"),
dcc.Loading(
id="loading-output",
type="circle",
children=[html.Pre(id="output-notes", style={"white-space": "pre-wrap"})]
)
])
]),
dcc.Download(id="download-markdown")
])
@app.callback(
[Output("output-notes", "children"),
Output("download-button", "disabled"),
Output("pr-button", "disabled"),
Output("download-markdown", "data"),
Output("pr-button", "children"),
Output("pr-output", "children")],
[Input("generate-button", "n_clicks"),
Input("download-button", "n_clicks"),
Input("pr-button", "n_clicks")],
[State("git-provider", "value"),
State("repo-url", "value"),
State("start-date", "value"),
State("end-date", "value"),
State("folder-location", "value")]
)
def handle_all_actions(generate_clicks, download_clicks, pr_clicks,
git_provider, repo_url, start_date, end_date, folder_location):
global generated_file, pr_url
ctx = dash.callback_context
if not ctx.triggered:
return "", True, True, None, "Create PR", ""
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == "generate-button":
notes, file_name = generate_release_notes(git_provider, repo_url, start_date, end_date, folder_location)
return notes, False, False, None, "Create PR", ""
elif button_id == "download-button":
if generated_file is None:
return dash.no_update, dash.no_update, dash.no_update, None, dash.no_update, ""
return (dash.no_update, dash.no_update, dash.no_update,
dcc.send_bytes(generated_file.getvalue(), f"release_notes_{datetime.now().strftime('%Y%m%d%H%M%S')}.md"),
dash.no_update, "")
elif button_id == "pr-button":
if generated_file is None:
return dash.no_update, dash.no_update, dash.no_update, None, "Error: No file generated", "No file generated"
markdown_content = generated_file.getvalue().decode()
result = update_summary_and_create_pr(repo_url, folder_location, start_date, end_date, markdown_content)
if pr_url:
return dash.no_update, dash.no_update, True, None, f"PR Created", f"PR Created: {pr_url}"
else:
return dash.no_update, dash.no_update, False, None, "PR Creation Failed", result
return dash.no_update, dash.no_update, dash.no_update, None, dash.no_update, ""
if __name__ == '__main__':
print("Starting the Dash application...")
app.run(debug=True, host='0.0.0.0', port=7860)
print("Dash application has finished running.")