import gradio as gr import google.generativeai as genai from github import Github import gitlab import requests import tempfile import docx import os import logging # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def is_ui_file(filename): ui_extensions = ['.erb', '.haml', '.slim', '.php', '.aspx', '.jsp', '.ftl', '.twig', '.mustache', '.handlebars', '.ejs', '.pug', '.blade.php', '.xhtml', '.fxml', '.tsx', '.jsx', '.vue', '.html', '.cshtml', '.razor', '.xaml', '.jsx'] return any(filename.endswith(ext) for ext in ui_extensions) def get_file_contents(git_provider, repo_url, personal_access_token, exclude_folders): file_contents = [] logger.info(f"Fetching files from {git_provider} repository: {repo_url}") exclude_folders = [folder.strip() for folder in exclude_folders.split(',') if folder.strip()] if git_provider == "GitHub": g = Github(personal_access_token) repo = g.get_repo(repo_url) contents = repo.get_contents("") while contents: file_content = contents.pop(0) if file_content.type == "dir": if not any(file_content.path.startswith(folder) for folder in exclude_folders): contents.extend(repo.get_contents(file_content.path)) elif is_ui_file(file_content.name) and not any(file_content.path.startswith(folder) for folder in exclude_folders): logger.info(f"Found UI file: {file_content.path}") file_contents.append((file_content.path, file_content.decoded_content.decode('utf-8', errors='ignore'))) elif git_provider == "GitLab": gl = gitlab.Gitlab(url='https://gitlab.com', private_token=personal_access_token) project = gl.projects.get(repo_url) items = project.repository_tree(recursive=True) for item in items: if item['type'] == 'blob' and is_ui_file(item['name']) and not any(item['path'].startswith(folder) for folder in exclude_folders): logger.info(f"Found UI file: {item['path']}") file_content = project.files.get(item['path'], ref='main') file_contents.append((item['path'], file_content.decode().decode('utf-8', errors='ignore'))) elif git_provider == "Gitea": base_url = "https://gitea.com/api/v1" headers = {"Authorization": f"token {personal_access_token}"} def recursive_get_contents(path=""): response = requests.get(f"{base_url}/repos/{repo_url}/contents/{path}", headers=headers) response.raise_for_status() for item in response.json(): if item['type'] == 'file' and is_ui_file(item['name']) and not any(item['path'].startswith(folder) for folder in exclude_folders): logger.info(f"Found UI file: {item['path']}") file_content = requests.get(item['download_url']).text file_contents.append((item['path'], file_content)) elif item['type'] == 'dir' and not any(item['path'].startswith(folder) for folder in exclude_folders): recursive_get_contents(item['path']) recursive_get_contents() else: raise ValueError("Unsupported Git provider") logger.info(f"Total UI files found: {len(file_contents)}") return file_contents def generate_guide_section(file_path, file_content, guide_type, gemini_api_key): logger.info(f"Generating {guide_type} section for file: {file_path}") genai.configure(api_key=gemini_api_key) model = genai.GenerativeModel('gemini-2.0-flash-lite') if guide_type == "User Guide": prompt = f"""Based on the following UI-related code file, generate a section for a user guide: File: {file_path} Content: {file_content} Please focus on: 1. The specific features and functionality this UI component provides to the end users 2. Step-by-step instructions on how to use these features 3. Any user interactions or inputs required 4. Expected outcomes or results for the user Important formatting instructions: - The output should be in plain text no markdown for example do not use * or ** or # or ##. Instead use numbers like 1., 2. for bullets - Use clear section titles - Follow this numbering heirarchy (1.0, 1.1, 1.2), (2.0, 2.1, 2.2), (3.0, 3.1, 3.2) - Explain the purpose and benefit of each feature for non-technical users - This is an end user manual, not a system administration manual so focus on the end user components """ else: # Administration Guide prompt = f"""Based on the following UI-related code file, generate a section for an System guide: File: {file_path} Content: {file_content} Please focus on explaining what that component is and does: 1. Any configuration options or settings related to this UI component 2. Security considerations or access control related to this feature 3. How to monitor or troubleshoot issues with this component 4. Best practices for managing and maintaining this part of the system Important formatting instructions: - The output should be in plain text no markdown for example for example do not use * or ** or # or ##. Instead use numbers like 1., 2. for bullets - Use clear section titles - Use clear section titles that has the name of the file in parenthesis - Follow this numbering heirarchy (1.0, 1.1, 1.2), (2.0, 2.1, 2.2), (3.0, 3.1, 3.2) - Explain the purpose and implications of each component """ response = model.generate_content(prompt) logger.info(f"Generated {guide_type} section for {file_path}") return response.text def generate_guide(git_provider, repo_url, personal_access_token, gemini_api_key, guide_type, exclude_folders): try: logger.info(f"Starting guide generation for {repo_url}") file_contents = get_file_contents(git_provider, repo_url, personal_access_token, exclude_folders) guide_sections = [] for file_path, content in file_contents: section = generate_guide_section(file_path, content, guide_type, gemini_api_key) guide_sections.append(section) logger.info(f"Added section for {file_path}") full_guide = f"# {guide_type}\n\n" + "\n\n".join(guide_sections) logger.info("Creating DOCX file") doc = docx.Document() doc.add_heading(guide_type, 0) for line in full_guide.split('\n'): line = line.strip() if line.startswith('# '): doc.add_heading(line[2:], level=1) elif line.startswith('## '): doc.add_heading(line[3:], level=2) elif line.startswith('Step'): doc.add_paragraph(line, style='List Number') else: doc.add_paragraph(line) with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as temp_docx: doc.save(temp_docx.name) docx_path = temp_docx.name logger.info(f"DOCX file saved: {docx_path}") logger.info("Creating Markdown file") with tempfile.NamedTemporaryFile(delete=False, suffix='.md', mode='w', encoding='utf-8') as temp_md: temp_md.write(full_guide) md_path = temp_md.name logger.info(f"Markdown file saved: {md_path}") logger.info("Guide generation completed successfully") return full_guide, docx_path, md_path except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) return f"An error occurred: {str(e)}", None, None iface = gr.Interface( fn=generate_guide, 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.Radio(["User Guide", "Administration Guide"], label="Guide Type"), gr.Textbox(label="Exclude Folders", placeholder="folder1, folder2, folder3") ], outputs=[ gr.Textbox(label="Generated Guide"), gr.File(label="Download Guide (DOCX)"), gr.File(label="Download Guide (Markdown)") ], title="Automated Guide Generator", description="Generate a user guide or administration guide based on the UI-related code in a Git repository using Gemini AI. Select a Git provider, enter repository details, choose the guide type, and let AI create a comprehensive guide.", allow_flagging="never", theme="default", analytics_enabled=False, ) if __name__ == "__main__": iface.launch()