From ba0623119a54ce25304d98f65c3da47191dce006 Mon Sep 17 00:00:00 2001 From: DjangoPeng Date: Tue, 30 Jul 2024 05:33:29 +0000 Subject: [PATCH] Refactor command handling and subscription logic, export commits in daily progress, and enhance report generation with GPT-3.5 --- .gitignore | 4 ++ requirements.txt | 1 + src/command_handler.py | 74 +++++++++++++++++++++++++++++ src/config.py | 2 +- src/github_client.py | 57 +++++++++++++++++++---- src/llm.py | 26 +++++++++++ src/main.py | 93 +++++++++---------------------------- src/report_generator.py | 44 ++++++++++++++---- src/scheduler.py | 22 +++++---- src/subscription_manager.py | 4 +- subscriptions.json | 5 +- 11 files changed, 228 insertions(+), 104 deletions(-) create mode 100644 src/command_handler.py create mode 100644 src/llm.py diff --git a/.gitignore b/.gitignore index 82f92755..f9b6345e 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,7 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# User-defined +daily_progress/* + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f2293605..ceeef96a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ requests +openai \ No newline at end of file diff --git a/src/command_handler.py b/src/command_handler.py new file mode 100644 index 00000000..cdeb7013 --- /dev/null +++ b/src/command_handler.py @@ -0,0 +1,74 @@ +# src/command_handler.py + +import argparse + +class CommandHandler: + def __init__(self, github_client, subscription_manager, report_generator): + self.github_client = github_client + self.subscription_manager = subscription_manager + self.report_generator = report_generator + self.parser = self.create_parser() + + def create_parser(self): + parser = argparse.ArgumentParser( + description='GitHub Sentinel Command Line Interface', + formatter_class=argparse.RawTextHelpFormatter + ) + subparsers = parser.add_subparsers(title='Commands', dest='command') + + parser_add = subparsers.add_parser('add', help='Add a subscription') + parser_add.add_argument('repo', type=str, help='The repository to subscribe to (e.g., owner/repo)') + parser_add.set_defaults(func=self.add_subscription) + + parser_remove = subparsers.add_parser('remove', help='Remove a subscription') + parser_remove.add_argument('repo', type=str, help='The repository to unsubscribe from (e.g., owner/repo)') + parser_remove.set_defaults(func=self.remove_subscription) + + parser_list = subparsers.add_parser('list', help='List all subscriptions') + parser_list.set_defaults(func=self.list_subscriptions) + + parser_fetch = subparsers.add_parser('fetch', help='Fetch updates immediately') + parser_fetch.set_defaults(func=self.fetch_updates) + + parser_export = subparsers.add_parser('export', help='Export daily progress') + parser_export.add_argument('repo', type=str, help='The repository to export progress from (e.g., owner/repo)') + parser_export.set_defaults(func=self.export_daily_progress) + + parser_generate = subparsers.add_parser('generate', help='Generate daily report from markdown file') + parser_generate.add_argument('file', type=str, help='The markdown file to generate report from') + parser_generate.set_defaults(func=self.generate_daily_report) + + parser_help = subparsers.add_parser('help', help='Show help message') + parser_help.set_defaults(func=self.print_help) + + return parser + + def add_subscription(self, args): + self.subscription_manager.add_subscription(args.repo) + print(f"Added subscription for repository: {args.repo}") + + def remove_subscription(self, args): + self.subscription_manager.remove_subscription(args.repo) + print(f"Removed subscription for repository: {args.repo}") + + def list_subscriptions(self, args): + subscriptions = self.subscription_manager.list_subscriptions() + print("Current subscriptions:") + for sub in subscriptions: + print(f" - {sub}") + + def fetch_updates(self, args): + updates = self.github_client.fetch_updates() + for update in updates: + print(update) + + def export_daily_progress(self, args): + self.github_client.export_daily_progress(args.repo) + print(f"Exported daily progress for repository: {args.repo}") + + def generate_daily_report(self, args): + self.report_generator.generate_daily_report(args.file) + print(f"Generated daily report from file: {args.file}") + + def print_help(self, args=None): + self.parser.print_help() diff --git a/src/config.py b/src/config.py index 9094ea8f..f93bb9a1 100644 --- a/src/config.py +++ b/src/config.py @@ -10,4 +10,4 @@ def load_config(self): self.github_token = config.get('github_token') self.notification_settings = config.get('notification_settings') self.subscriptions_file = config.get('subscriptions_file') - self.update_interval = config.get('update_interval', 24 * 60 * 60) # Default to 24 hours + self.update_interval = config.get('update_interval', 24 * 60 * 60) # Default to 24 hours \ No newline at end of file diff --git a/src/github_client.py b/src/github_client.py index cc155180..a79ac624 100644 --- a/src/github_client.py +++ b/src/github_client.py @@ -1,16 +1,55 @@ +# src/github_client.py + import requests +import datetime class GitHubClient: def __init__(self, token): self.token = token - - def fetch_updates(self, subscriptions): - headers = { - 'Authorization': f'token {self.token}' + self.headers = {'Authorization': f'token {self.token}'} + + def fetch_updates(self, repo): + # 获取特定 repo 的更新(commits, issues, pull requests) + updates = { + 'commits': self.fetch_commits(repo), + 'issues': self.fetch_issues(repo), + 'pull_requests': self.fetch_pull_requests(repo) } - updates = {} - for repo in subscriptions: - response = requests.get(f'https://api.github.com/repos/{repo}/releases/latest', headers=headers) - if response.status_code == 200: - updates[repo] = response.json() return updates + + def fetch_commits(self, repo): + url = f'https://api.github.com/repos/{repo}/commits' + response = requests.get(url, headers=self.headers) + response.raise_for_status() + return response.json() + + def fetch_issues(self, repo): + url = f'https://api.github.com/repos/{repo}/issues' + response = requests.get(url, headers=self.headers) + response.raise_for_status() + return response.json() + + def fetch_pull_requests(self, repo): + url = f'https://api.github.com/repos/{repo}/pulls' + response = requests.get(url, headers=self.headers) + response.raise_for_status() + return response.json() + + + def export_daily_progress(self, repo): + date_str = datetime.datetime.now().strftime('%Y-%m-%d') + issues = self.fetch_issues(repo) + pull_requests = self.fetch_pull_requests(repo) + filename = f'daily_progress/{repo.replace("/", "_")}_{date_str}.md' + with open(filename, 'w') as f: + f.write(f"# {repo} Daily Progress - {date_str}\n\n") + f.write("## Issues\n") + for issue in issues: + f.write(f"- {issue['title']} #{issue['number']}\n") + f.write("\n## Pull Requests\n") + for pr in pull_requests: + f.write(f"- {pr['title']} #{pr['number']}\n") + + print(f"Exported daily progress to {filename}") + + return filename \ No newline at end of file diff --git a/src/llm.py b/src/llm.py new file mode 100644 index 00000000..3fccb645 --- /dev/null +++ b/src/llm.py @@ -0,0 +1,26 @@ +# src/llm.py + +import os +from openai import OpenAI + +class LLM: + def __init__(self): + self.client = OpenAI() + + def generate_daily_report(self, markdown_content, dry_run=False): + prompt = f"以下是项目的最新进展,根据功能合并同类项,形成一份简报,至少包含:1)新增功能;2)主要改进;3)修复问题;:\n\n{markdown_content}" + if dry_run: + with open("daily_progress/prompt.txt", "w+") as f: + f.write(prompt) + return "DRY RUN" + + print("Before call GPT") + response = self.client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": prompt} + ] + ) + print("After call GPT") + print(response) + return response.choices[0].message.content diff --git a/src/main.py b/src/main.py index 2705b4ab..6ff9c4ce 100644 --- a/src/main.py +++ b/src/main.py @@ -1,58 +1,30 @@ -import argparse +# src/main.py + +import threading +import shlex + +from argparse import ArgumentError + from config import Config from scheduler import Scheduler from github_client import GitHubClient from notifier import Notifier from report_generator import ReportGenerator +from llm import LLM from subscription_manager import SubscriptionManager -import threading -import shlex +from command_handler import CommandHandler def run_scheduler(scheduler): scheduler.start() -def add_subscription(args, subscription_manager): - subscription_manager.add_subscription(args.repo) - print(f"Added subscription: {args.repo}") - -def remove_subscription(args, subscription_manager): - subscription_manager.remove_subscription(args.repo) - print(f"Removed subscription: {args.repo}") - -def list_subscriptions(subscription_manager): - subscriptions = subscription_manager.get_subscriptions() - print("Current subscriptions:") - for sub in subscriptions: - print(f"- {sub}") - -def fetch_updates(github_client, subscription_manager, report_generator): - subscriptions = subscription_manager.get_subscriptions() - updates = github_client.fetch_updates(subscriptions) - report = report_generator.generate(updates) - print("Updates fetched:") - print(report) - -def print_help(): - help_text = """ -GitHub Sentinel Command Line Interface - -Available commands: - add Add a subscription (e.g., owner/repo) - remove Remove a subscription (e.g., owner/repo) - list List all subscriptions - fetch Fetch updates immediately - help Show this help message - exit Exit the tool - quit Exit the tool -""" - print(help_text) - def main(): config = Config() github_client = GitHubClient(config.github_token) notifier = Notifier(config.notification_settings) - report_generator = ReportGenerator() + llm = LLM() + report_generator = ReportGenerator(llm) subscription_manager = SubscriptionManager(config.subscriptions_file) + command_handler = CommandHandler(github_client, subscription_manager, report_generator) scheduler = Scheduler( github_client=github_client, @@ -66,42 +38,23 @@ def main(): scheduler_thread.daemon = True scheduler_thread.start() - parser = argparse.ArgumentParser(description='GitHub Sentinel Command Line Interface') - subparsers = parser.add_subparsers(title='Commands', dest='command') - - parser_add = subparsers.add_parser('add', help='Add a subscription') - parser_add.add_argument('repo', type=str, help='The repository to subscribe to (e.g., owner/repo)') - parser_add.set_defaults(func=lambda args: add_subscription(args, subscription_manager)) - - parser_remove = subparsers.add_parser('remove', help='Remove a subscription') - parser_remove.add_argument('repo', type=str, help='The repository to unsubscribe from (e.g., owner/repo)') - parser_remove.set_defaults(func=lambda args: remove_subscription(args, subscription_manager)) - - parser_list = subparsers.add_parser('list', help='List all subscriptions') - parser_list.set_defaults(func=lambda args: list_subscriptions(subscription_manager)) - - parser_fetch = subparsers.add_parser('fetch', help='Fetch updates immediately') - parser_fetch.set_defaults(func=lambda args: fetch_updates(github_client, subscription_manager, report_generator)) - - parser_help = subparsers.add_parser('help', help='Show this help message') - parser_help.set_defaults(func=lambda args: print_help()) - - # Print help on startup - print_help() + parser = command_handler.parser + command_handler.print_help() while True: try: user_input = input("GitHub Sentinel> ") - if user_input in ["exit", "quit"]: - print("Exiting GitHub Sentinel...") + if user_input in ['exit', 'quit']: break - args = parser.parse_args(shlex.split(user_input)) - if args.command is not None: + try: + args = parser.parse_args(shlex.split(user_input)) + if args.command is None: + continue args.func(args) - else: - parser.print_help() + except SystemExit as e: + print("Invalid command. Type 'help' to see the list of available commands.") except Exception as e: - print(f"Error: {e}") + print(f"Unexpected error: {e}") -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/src/report_generator.py b/src/report_generator.py index a9922d14..11136795 100644 --- a/src/report_generator.py +++ b/src/report_generator.py @@ -1,11 +1,35 @@ +# src/report_generator.py + +import os +from datetime import date + class ReportGenerator: - def generate(self, updates): - report = "Latest Release Information:\n\n" - for repo, release in updates.items(): - report += f"Repository: {repo}\n" - report += f"Latest Version: {release['tag_name']}\n" - report += f"Release Name: {release['name']}\n" - report += f"Published at: {release['published_at']}\n" - report += f"Release Notes:\n{release['body']}\n" - report += "-" * 40 + "\n" - return report + def __init__(self, llm): + self.llm = llm + + def export_daily_progress(self, repo, updates): + file_path = f'daily_progress/{repo.replace("/", "_")}_{date.today()}.md' + with open(file_path, 'w') as file: + file.write(f"# Daily Progress for {repo} ({date.today()})\n\n") + file.write("## Commits\n") + for commit in updates['commits']: + file.write(f"- {commit}\n") + file.write("\n## Issues\n") + for issue in updates['issues']: + file.write(f"- {issue}\n") + file.write("\n## Pull Requests\n") + for pr in updates['pull_requests']: + file.write(f"- {pr}\n") + return file_path + + def generate_daily_report(self, markdown_file_path): + with open(markdown_file_path, 'r') as file: + markdown_content = file.read() + + report = self.llm.generate_daily_report(markdown_content) + + report_file_path = os.path.splitext(markdown_file_path)[0] + "_report.md" + with open(report_file_path, 'w+') as report_file: + report_file.write(report) + + print(f"Generated report saved to {report_file_path}") diff --git a/src/scheduler.py b/src/scheduler.py index 43c50f3b..7c333a1f 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -1,21 +1,23 @@ +# src/scheduler.py + import time -import threading class Scheduler: - def __init__(self, github_client, notifier, report_generator, subscription_manager, interval): + def __init__(self, github_client, notifier, report_generator, subscription_manager, interval=86400): self.github_client = github_client self.notifier = notifier self.report_generator = report_generator self.subscription_manager = subscription_manager self.interval = interval - + def start(self): + self.run() + + def run(self): while True: - self.run() + subscriptions = self.subscription_manager.get_subscriptions() + for repo in subscriptions: + updates = self.github_client.fetch_updates(repo) + markdown_file_path = self.report_generator.export_daily_progress(repo, updates) + self.report_generator.generate_daily_report(markdown_file_path) time.sleep(self.interval) - - def run(self): - subscriptions = self.subscription_manager.get_subscriptions() - updates = self.github_client.fetch_updates(subscriptions) - report = self.report_generator.generate(updates) - self.notifier.notify(report) diff --git a/src/subscription_manager.py b/src/subscription_manager.py index 8ff5aec3..04698747 100644 --- a/src/subscription_manager.py +++ b/src/subscription_manager.py @@ -13,7 +13,7 @@ def save_subscriptions(self): with open(self.subscriptions_file, 'w') as f: json.dump(self.subscriptions, f, indent=4) - def get_subscriptions(self): + def list_subscriptions(self): return self.subscriptions def add_subscription(self, repo): @@ -24,4 +24,4 @@ def add_subscription(self, repo): def remove_subscription(self, repo): if repo in self.subscriptions: self.subscriptions.remove(repo) - self.save_subscriptions() + self.save_subscriptions() \ No newline at end of file diff --git a/subscriptions.json b/subscriptions.json index 174479d6..96386f29 100644 --- a/subscriptions.json +++ b/subscriptions.json @@ -1,3 +1,4 @@ [ - "langchain-ai/langchain" -] + "langchain-ai/langchain", + "DjangoPeng/openai-quickstart" +] \ No newline at end of file