Download app.py from MicroHealth/github-release-notes: direct link, hf CLI and curl.
- Browser
- Download file 12.2 kB
-
https://huggingface.co/spaces/MicroHealth/github-release-notes/resolve/main/app.py
- Command line
-
hf download hf://spaces/MicroHealth/github-release-notes/app.py
-
curl -L -o app.py https://huggingface.co/spaces/MicroHealth/github-release-notes/resolve/main/app.py
12.2 kB
| 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") | |
| ]) | |
| 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.") |