|
import dash |
|
from dash import dcc, html, Input, Output, State, callback |
|
import dash_bootstrap_components as dbc |
|
from dash.exceptions import PreventUpdate |
|
import google.generativeai as genai |
|
from github import Github |
|
import gitlab |
|
import requests |
|
import tempfile |
|
import docx |
|
import os |
|
import logging |
|
import threading |
|
import base64 |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) |
|
|
|
|
|
|
|
|
|
app.layout = dbc.Container([ |
|
html.H1("Automated Guide Generator", className="my-4"), |
|
html.P("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."), |
|
|
|
dbc.Card([ |
|
dbc.CardBody([ |
|
dcc.Dropdown( |
|
id='git-provider', |
|
options=[ |
|
{'label': 'GitHub', 'value': 'GitHub'}, |
|
{'label': 'GitLab', 'value': 'GitLab'}, |
|
{'label': 'Gitea', 'value': 'Gitea'} |
|
], |
|
placeholder="Select Git Provider" |
|
), |
|
dbc.Input(id='repo-url', placeholder="Repository URL (owner/repo)", type="text", className="mt-3"), |
|
dcc.RadioItems( |
|
id='guide-type', |
|
options=[ |
|
{'label': 'User Guide', 'value': 'User Guide'}, |
|
{'label': 'Administration Guide', 'value': 'Administration Guide'} |
|
], |
|
className="mt-3" |
|
), |
|
dbc.Input(id='exclude-folders', placeholder="Exclude Folders (comma-separated)", type="text", className="mt-3"), |
|
dbc.Button("Generate Guide", id="generate-button", color="primary", className="mt-3"), |
|
]) |
|
], className="mb-4"), |
|
|
|
dbc.Spinner( |
|
dcc.Loading( |
|
id="loading-output", |
|
children=[ |
|
html.Div(id="output-area"), |
|
dcc.Download(id="download-docx"), |
|
dcc.Download(id="download-md") |
|
], |
|
type="default", |
|
) |
|
), |
|
|
|
dcc.Store(id='guide-store'), |
|
]) |
|
|
|
@app.callback( |
|
Output('guide-store', 'data'), |
|
Output('output-area', 'children'), |
|
Input('generate-button', 'n_clicks'), |
|
State('git-provider', 'value'), |
|
State('repo-url', 'value'), |
|
State('guide-type', 'value'), |
|
State('exclude-folders', 'value'), |
|
prevent_initial_call=True |
|
) |
|
def generate_guide_callback(n_clicks, git_provider, repo_url, guide_type, exclude_folders): |
|
if not all([git_provider, repo_url, guide_type]): |
|
raise PreventUpdate |
|
|
|
def generate_guide_thread(): |
|
try: |
|
guide_text, docx_path, md_path = generate_guide( |
|
git_provider, repo_url, "", "", guide_type, exclude_folders |
|
) |
|
|
|
with open(docx_path, 'rb') as docx_file, open(md_path, 'rb') as md_file: |
|
docx_content = base64.b64encode(docx_file.read()).decode('utf-8') |
|
md_content = base64.b64encode(md_file.read()).decode('utf-8') |
|
|
|
os.unlink(docx_path) |
|
os.unlink(md_path) |
|
|
|
return { |
|
'guide_text': guide_text, |
|
'docx_content': docx_content, |
|
'md_content': md_content |
|
} |
|
except Exception as e: |
|
logger.error(f"An error occurred: {str(e)}", exc_info=True) |
|
return {'error': str(e)} |
|
|
|
thread = threading.Thread(target=generate_guide_thread) |
|
thread.start() |
|
thread.join() |
|
|
|
result = thread.result() if hasattr(thread, 'result') else None |
|
|
|
if result and 'error' not in result: |
|
output = [ |
|
html.H3("Generated Guide"), |
|
html.Pre(result['guide_text']), |
|
dbc.Button("Download DOCX", id="btn-download-docx", color="secondary", className="me-2"), |
|
dbc.Button("Download Markdown", id="btn-download-md", color="secondary") |
|
] |
|
return result, output |
|
else: |
|
error_message = result['error'] if result and 'error' in result else "An unknown error occurred" |
|
return None, html.Div(f"Error: {error_message}", style={'color': 'red'}) |
|
|
|
@app.callback( |
|
Output("download-docx", "data"), |
|
Input("btn-download-docx", "n_clicks"), |
|
State('guide-store', 'data'), |
|
prevent_initial_call=True |
|
) |
|
def download_docx(n_clicks, data): |
|
if data and 'docx_content' in data: |
|
return dict(content=data['docx_content'], filename="guide.docx", base64=True) |
|
raise PreventUpdate |
|
|
|
@app.callback( |
|
Output("download-md", "data"), |
|
Input("btn-download-md", "n_clicks"), |
|
State('guide-store', 'data'), |
|
prevent_initial_call=True |
|
) |
|
def download_md(n_clicks, data): |
|
if data and 'md_content' in data: |
|
return dict(content=data['md_content'], filename="guide.md", base64=True) |
|
raise PreventUpdate |
|
|
|
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.") |