bluenevus commited on
Commit
ba33407
·
verified ·
1 Parent(s): 22e4390

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +239 -110
app.py CHANGED
@@ -1,5 +1,5 @@
1
  import dash
2
- from dash import dcc, html, Input, Output, State, callback
3
  import dash_bootstrap_components as dbc
4
  from dash.exceptions import PreventUpdate
5
  import google.generativeai as genai
@@ -11,136 +11,265 @@ import docx
11
  import os
12
  import logging
13
  import threading
14
- import base64
15
 
16
  # Configure logging
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
 
20
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
21
 
22
- # Helper functions (is_ui_file, get_file_contents, generate_guide_section, generate_guide)
23
- # ... (Keep these functions as they are in the original code)
24
 
25
- app.layout = dbc.Container([
26
- html.H1("Automated Guide Generator", className="my-4"),
27
- 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."),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- dbc.Card([
30
- dbc.CardBody([
31
- dcc.Dropdown(
32
- id='git-provider',
33
- options=[
34
- {'label': 'GitHub', 'value': 'GitHub'},
35
- {'label': 'GitLab', 'value': 'GitLab'},
36
- {'label': 'Gitea', 'value': 'Gitea'}
37
- ],
38
- placeholder="Select Git Provider"
39
- ),
40
- dbc.Input(id='repo-url', placeholder="Repository URL (owner/repo)", type="text", className="mt-3"),
41
- dcc.RadioItems(
42
- id='guide-type',
43
- options=[
44
- {'label': 'User Guide', 'value': 'User Guide'},
45
- {'label': 'Administration Guide', 'value': 'Administration Guide'}
46
- ],
47
- className="mt-3"
48
- ),
49
- dbc.Input(id='exclude-folders', placeholder="Exclude Folders (comma-separated)", type="text", className="mt-3"),
50
- dbc.Button("Generate Guide", id="generate-button", color="primary", className="mt-3"),
51
- ])
52
- ], className="mb-4"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- dbc.Spinner(
55
- dcc.Loading(
56
- id="loading-output",
57
- children=[
58
- html.Div(id="output-area"),
59
- dcc.Download(id="download-docx"),
60
- dcc.Download(id="download-md")
61
- ],
62
- type="default",
63
- )
 
 
 
 
 
 
 
 
 
 
 
 
64
  ),
65
 
66
- dcc.Store(id='guide-store'),
67
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  @app.callback(
70
- Output('guide-store', 'data'),
71
- Output('output-area', 'children'),
72
- Input('generate-button', 'n_clicks'),
73
- State('git-provider', 'value'),
74
- State('repo-url', 'value'),
75
- State('guide-type', 'value'),
76
- State('exclude-folders', 'value'),
77
- prevent_initial_call=True
78
  )
79
- def generate_guide_callback(n_clicks, git_provider, repo_url, guide_type, exclude_folders):
80
- if not all([git_provider, repo_url, guide_type]):
81
  raise PreventUpdate
82
-
83
  def generate_guide_thread():
84
- try:
85
- guide_text, docx_path, md_path = generate_guide(
86
- git_provider, repo_url, "", "", guide_type, exclude_folders
87
- )
88
-
89
- with open(docx_path, 'rb') as docx_file, open(md_path, 'rb') as md_file:
90
- docx_content = base64.b64encode(docx_file.read()).decode('utf-8')
91
- md_content = base64.b64encode(md_file.read()).decode('utf-8')
92
-
93
- os.unlink(docx_path)
94
- os.unlink(md_path)
95
-
96
- return {
97
- 'guide_text': guide_text,
98
- 'docx_content': docx_content,
99
- 'md_content': md_content
100
- }
101
- except Exception as e:
102
- logger.error(f"An error occurred: {str(e)}", exc_info=True)
103
- return {'error': str(e)}
104
-
105
  thread = threading.Thread(target=generate_guide_thread)
106
  thread.start()
107
  thread.join()
108
-
109
- result = thread.result() if hasattr(thread, 'result') else None
110
-
111
- if result and 'error' not in result:
112
- output = [
113
- html.H3("Generated Guide"),
114
- html.Pre(result['guide_text']),
115
- dbc.Button("Download DOCX", id="btn-download-docx", color="secondary", className="me-2"),
116
- dbc.Button("Download Markdown", id="btn-download-md", color="secondary")
117
- ]
118
- return result, output
119
  else:
120
- error_message = result['error'] if result and 'error' in result else "An unknown error occurred"
121
- return None, html.Div(f"Error: {error_message}", style={'color': 'red'})
122
-
123
- @app.callback(
124
- Output("download-docx", "data"),
125
- Input("btn-download-docx", "n_clicks"),
126
- State('guide-store', 'data'),
127
- prevent_initial_call=True
128
- )
129
- def download_docx(n_clicks, data):
130
- if data and 'docx_content' in data:
131
- return dict(content=data['docx_content'], filename="guide.docx", base64=True)
132
- raise PreventUpdate
133
-
134
- @app.callback(
135
- Output("download-md", "data"),
136
- Input("btn-download-md", "n_clicks"),
137
- State('guide-store', 'data'),
138
- prevent_initial_call=True
139
- )
140
- def download_md(n_clicks, data):
141
- if data and 'md_content' in data:
142
- return dict(content=data['md_content'], filename="guide.md", base64=True)
143
- raise PreventUpdate
144
 
145
  if __name__ == '__main__':
146
  print("Starting the Dash application...")
 
1
  import dash
2
+ from dash import dcc, html, Input, Output, State
3
  import dash_bootstrap_components as dbc
4
  from dash.exceptions import PreventUpdate
5
  import google.generativeai as genai
 
11
  import os
12
  import logging
13
  import threading
14
+ from huggingface_hub import HfApi
15
 
16
  # Configure logging
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
+ # Initialize Dash app
21
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
22
 
23
+ # Hugging Face API setup
24
+ hf_api = HfApi()
25
 
26
+ # Get Hugging Face variables
27
+ GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
28
+ GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
29
+
30
+ def is_ui_file(filename):
31
+ 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']
32
+ return any(filename.endswith(ext) for ext in ui_extensions)
33
+
34
+ def get_file_contents(git_provider, repo_url, exclude_folders):
35
+ file_contents = []
36
+ logger.info(f"Fetching files from {git_provider} repository: {repo_url}")
37
+ exclude_folders = [folder.strip() for folder in exclude_folders.split(',') if folder.strip()]
38
+ if git_provider == "GitHub":
39
+ g = Github(GITHUB_TOKEN)
40
+ repo = g.get_repo(repo_url)
41
+ contents = repo.get_contents("")
42
+ while contents:
43
+ file_content = contents.pop(0)
44
+ if file_content.type == "dir":
45
+ if not any(file_content.path.startswith(folder) for folder in exclude_folders):
46
+ contents.extend(repo.get_contents(file_content.path))
47
+ elif is_ui_file(file_content.name) and not any(file_content.path.startswith(folder) for folder in exclude_folders):
48
+ logger.info(f"Found UI file: {file_content.path}")
49
+ file_contents.append((file_content.path, file_content.decoded_content.decode('utf-8', errors='ignore')))
50
+ elif git_provider == "GitLab":
51
+ gl = gitlab.Gitlab(url='https://gitlab.com', private_token=GITHUB_TOKEN)
52
+ project = gl.projects.get(repo_url)
53
+ items = project.repository_tree(recursive=True)
54
+ for item in items:
55
+ if item['type'] == 'blob' and is_ui_file(item['name']) and not any(item['path'].startswith(folder) for folder in exclude_folders):
56
+ logger.info(f"Found UI file: {item['path']}")
57
+ file_content = project.files.get(item['path'], ref='main')
58
+ file_contents.append((item['path'], file_content.decode().decode('utf-8', errors='ignore')))
59
+ elif git_provider == "Gitea":
60
+ base_url = "https://gitea.com/api/v1"
61
+ headers = {"Authorization": f"token {GITHUB_TOKEN}"}
62
+ def recursive_get_contents(path=""):
63
+ response = requests.get(f"{base_url}/repos/{repo_url}/contents/{path}", headers=headers)
64
+ response.raise_for_status()
65
+ for item in response.json():
66
+ if item['type'] == 'file' and is_ui_file(item['name']) and not any(item['path'].startswith(folder) for folder in exclude_folders):
67
+ logger.info(f"Found UI file: {item['path']}")
68
+ file_content = requests.get(item['download_url']).text
69
+ file_contents.append((item['path'], file_content))
70
+ elif item['type'] == 'dir' and not any(item['path'].startswith(folder) for folder in exclude_folders):
71
+ recursive_get_contents(item['path'])
72
+ recursive_get_contents()
73
+ else:
74
+ raise ValueError("Unsupported Git provider")
75
+ logger.info(f"Total UI files found: {len(file_contents)}")
76
+ return file_contents
77
+
78
+ def generate_guide_section(file_path, file_content, guide_type):
79
+ logger.info(f"Generating {guide_type} section for file: {file_path}")
80
+ genai.configure(api_key=GEMINI_API_KEY)
81
+ model = genai.GenerativeModel('gemini-2.0-flash-lite')
82
 
83
+ if guide_type == "User Guide":
84
+ prompt = f"""Based on the following UI-related code file, generate a section for a user guide:
85
+
86
+ File: {file_path}
87
+ Content:
88
+ {file_content}
89
+
90
+ Please focus on:
91
+ 1. The specific features and functionality this UI component provides to the end users
92
+ 2. Step-by-step instructions on how to use these features
93
+ 3. Any user interactions or inputs required
94
+ 4. Expected outcomes or results for the user
95
+
96
+ Important formatting instructions:
97
+ - 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
98
+ - Use clear section titles
99
+ - Follow this numbering heirarchy (1.0, 1.1, 1.2), (2.0, 2.1, 2.2), (3.0, 3.1, 3.2)
100
+ - Explain the purpose and benefit of each feature for non-technical users
101
+ - This is an end user manual, not a system administration manual so focus on the end user components
102
+ """
103
+ else: # Administration Guide
104
+ prompt = f"""Based on the following UI-related code file, generate a section for an System guide:
105
+
106
+ File: {file_path}
107
+ Content:
108
+ {file_content}
109
+
110
+ Please focus on explaining what that component is and does:
111
+ 1. Any configuration options or settings related to this UI component
112
+ 2. Security considerations or access control related to this feature
113
+ 3. How to monitor or troubleshoot issues with this component
114
+ 4. Best practices for managing and maintaining this part of the system
115
+
116
+ Important formatting instructions:
117
+ - 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
118
+ - Use clear section titles
119
+ - Use clear section titles that has the name of the file in parenthesis
120
+ - Follow this numbering heirarchy (1.0, 1.1, 1.2), (2.0, 2.1, 2.2), (3.0, 3.1, 3.2)
121
+ - Explain the purpose and implications of each component
122
+ """
123
+
124
+ response = model.generate_content(prompt)
125
+ logger.info(f"Generated {guide_type} section for {file_path}")
126
+ return response.text
127
+
128
+ def generate_guide(git_provider, repo_url, guide_type, exclude_folders):
129
+ try:
130
+ logger.info(f"Starting guide generation for {repo_url}")
131
+ file_contents = get_file_contents(git_provider, repo_url, exclude_folders)
132
+
133
+ guide_sections = []
134
+ for file_path, content in file_contents:
135
+ section = generate_guide_section(file_path, content, guide_type)
136
+ guide_sections.append(section)
137
+ logger.info(f"Added section for {file_path}")
138
+
139
+ full_guide = f"# {guide_type}\n\n" + "\n\n".join(guide_sections)
140
+
141
+ logger.info("Creating DOCX file")
142
+ doc = docx.Document()
143
+ doc.add_heading(guide_type, 0)
144
+
145
+ for line in full_guide.split('\n'):
146
+ line = line.strip()
147
+ if line.startswith('# '):
148
+ doc.add_heading(line[2:], level=1)
149
+ elif line.startswith('## '):
150
+ doc.add_heading(line[3:], level=2)
151
+ elif line.startswith('Step'):
152
+ doc.add_paragraph(line, style='List Number')
153
+ else:
154
+ doc.add_paragraph(line)
155
+
156
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as temp_docx:
157
+ doc.save(temp_docx.name)
158
+ docx_path = temp_docx.name
159
+ logger.info(f"DOCX file saved: {docx_path}")
160
+
161
+ logger.info("Creating Markdown file")
162
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.md', mode='w', encoding='utf-8') as temp_md:
163
+ temp_md.write(full_guide)
164
+ md_path = temp_md.name
165
+ logger.info(f"Markdown file saved: {md_path}")
166
+
167
+ logger.info("Guide generation completed successfully")
168
+ return full_guide, docx_path, md_path
169
 
170
+ except Exception as e:
171
+ logger.error(f"An error occurred: {str(e)}", exc_info=True)
172
+ return f"An error occurred: {str(e)}", None, None
173
+
174
+ # App layout
175
+ app.layout = dbc.Container([
176
+ dbc.Navbar(
177
+ dbc.Container([
178
+ html.A(
179
+ dbc.Row([
180
+ dbc.Col(html.Img(src="/assets/logo.png", height="30px")),
181
+ dbc.Col(dbc.NavbarBrand("Automated Guide Generator", className="ms-2")),
182
+ ],
183
+ align="center",
184
+ className="g-0",
185
+ ),
186
+ href="/",
187
+ style={"textDecoration": "none"},
188
+ )
189
+ ]),
190
+ color="primary",
191
+ dark=True,
192
  ),
193
 
194
+ dbc.Row([
195
+ dbc.Col([
196
+ html.H1("Automated Guide Generator", className="text-center my-4"),
197
+ 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.", className="text-center mb-4"),
198
+
199
+ dbc.Card([
200
+ dbc.CardBody([
201
+ dbc.Form([
202
+ dbc.Select(
203
+ id="git-provider",
204
+ options=[
205
+ {"label": "GitHub", "value": "GitHub"},
206
+ {"label": "GitLab", "value": "GitLab"},
207
+ {"label": "Gitea", "value": "Gitea"}
208
+ ],
209
+ placeholder="Select Git Provider",
210
+ ),
211
+ dbc.Input(id="repo-url", type="text", placeholder="Repository URL (owner/repo)"),
212
+ dbc.RadioItems(
213
+ id="guide-type",
214
+ options=[
215
+ {"label": "User Guide", "value": "User Guide"},
216
+ {"label": "Administration Guide", "value": "Administration Guide"}
217
+ ],
218
+ inline=True,
219
+ ),
220
+ dbc.Input(id="exclude-folders", type="text", placeholder="Exclude Folders (comma-separated)"),
221
+ dbc.Button("Generate Guide", id="generate-button", color="primary", className="mt-3"),
222
+ ])
223
+ ])
224
+ ], className="mb-4"),
225
+
226
+ dbc.Spinner(
227
+ dbc.Card([
228
+ dbc.CardBody([
229
+ html.H4("Generated Guide", className="card-title"),
230
+ html.Div(id="generated-guide", style={"whiteSpace": "pre-wrap"}),
231
+ html.Div([
232
+ dbc.Button("Download DOCX", id="download-docx", color="secondary", className="me-2"),
233
+ dbc.Button("Download Markdown", id="download-md", color="secondary"),
234
+ ], className="mt-3")
235
+ ])
236
+ ], className="mt-4"),
237
+ color="primary",
238
+ ),
239
+ ], width=12)
240
+ ])
241
+ ], fluid=True)
242
 
243
  @app.callback(
244
+ [Output("generated-guide", "children"),
245
+ Output("download-docx", "href"),
246
+ Output("download-md", "href")],
247
+ [Input("generate-button", "n_clicks")],
248
+ [State("git-provider", "value"),
249
+ State("repo-url", "value"),
250
+ State("guide-type", "value"),
251
+ State("exclude-folders", "value")]
252
  )
253
+ def update_output(n_clicks, git_provider, repo_url, guide_type, exclude_folders):
254
+ if n_clicks is None:
255
  raise PreventUpdate
256
+
257
  def generate_guide_thread():
258
+ nonlocal guide_text, docx_path, md_path
259
+ guide_text, docx_path, md_path = generate_guide(git_provider, repo_url, guide_type, exclude_folders)
260
+
261
+ guide_text, docx_path, md_path = None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  thread = threading.Thread(target=generate_guide_thread)
263
  thread.start()
264
  thread.join()
265
+
266
+ if docx_path and md_path:
267
+ docx_url = f"/download?path={docx_path}"
268
+ md_url = f"/download?path={md_path}"
 
 
 
 
 
 
 
269
  else:
270
+ docx_url = md_url = "#"
271
+
272
+ return guide_text, docx_url, md_url
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
  if __name__ == '__main__':
275
  print("Starting the Dash application...")