bluenevus's picture
Update app.py
faefb38 verified
raw
history blame
12.6 kB
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
import base64
import io
import pandas as pd
import openai
import os
import time
from dash.exceptions import PreventUpdate
import PyPDF2
import docx
import chardet
# Initialize the Dash app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
# Get OpenAI API key from Hugging Face Spaces environment variable
openai.api_key = os.environ.get('OPENAI_API_KEY')
# Global variables
uploaded_files = {}
current_matrix = None
matrix_type = None
# Matrix types and their descriptions
matrix_types = {
"Project Plan Matrix": "Generate a project management matrix outlining all aspects of project management that summarizes components such as scope, schedule, budget, quality, resources, communications, risk, procurement, and stakeholder management.",
"Communications Plan Matrix": "Create a matrix showing stakeholders, communication methods, frequency, and responsibilities.",
"Project Kick-off Matrix": "Generate a matrix outlining key project details, goals, team roles, and initial timelines.",
"Decision Matrix": "Develop a matrix for evaluating options against criteria, with weighted scores analysis of alternatives style.",
"Lessons Learned Matrix": "Create a matrix capturing project experiences, challenges, solutions, and recommendations.",
"Key Performance Indicator Matrix": "Generate a matrix of KPIs, their measurable targets, actual performance, and status.",
"Prioritization Matrix": "Develop a matrix for ranking tasks or features based on importance and urgency.",
"Risk Matrix": "Create a matrix identifying tasks with potential risks, their likelihood, impact, and mitigation strategies.",
"RACI Matrix": "Generate a matrix showing team members and their roles (Responsible, Accountable, Consulted, Informed) for each task.",
"Project Schedule Matrix": "Develop a matrix showing project phases, tasks, durations, and dependencies.",
"Quality Control Matrix": "Create a matrix outlining measurable quality standards, testing methods, and acceptance criteria.",
"Requirements Traceability Matrix": "Generate a matrix linking requirements to their sources, test cases, and status.",
"Sprint Planning Matrix": "Develop a matrix for sprint nubmer, sprint tasks in that sprint number, story points, assignees, and status.",
"Test Traceability Matrix": "Create a matrix linking test cases to requirements, execution status, and results.",
"Sprint Backlog": "Generate a matrix of user stories, tasks, estimates, and priorities for the sprint.",
"Sprint Retrospective": "Develop a matrix capturing what went well, what didn't, and action items from the sprint.",
"SWOT Matrix": "Create a matrix analyzing Strengths, Weaknesses, Opportunities, and Threats."
}
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H4("Project Artifacts", className="mt-3 mb-4"),
dcc.Upload(
id='upload-files',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px 0'
},
multiple=True
),
html.Div(id='file-list'),
html.Hr(),
html.Div([
dbc.Button(
matrix_type,
id=f'btn-{matrix_type.lower().replace(" ", "-")}',
color="primary",
className="mb-2 w-100",
style={'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'}
) for matrix_type in matrix_types.keys()
])
], width=3),
dbc.Col([
html.Div(style={"height": "20px"}), # Added small gap
dcc.Loading(
id="loading-indicator",
type="dot",
children=[html.Div(id="loading-output")]
),
html.Div(id='matrix-preview', className="border p-3 mb-3"),
dbc.Button("Download Matrix", id="btn-download", color="success", className="mt-3"),
dcc.Download(id="download-matrix"),
html.Hr(),
html.Div(style={"height": "20px"}), # Added small gap
dcc.Loading(
id="chat-loading",
type="dot",
children=[
dbc.Input(id="chat-input", type="text", placeholder="Chat with GPT to update matrix...", className="mb-2"),
dbc.Button("Send", id="btn-send-chat", color="primary", className="mb-3"),
html.Div(id="chat-output")
]
)
], width=9)
])
], fluid=True)
def parse_file_content(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if filename.endswith('.pdf'):
with io.BytesIO(decoded) as pdf_file:
reader = PyPDF2.PdfReader(pdf_file)
return ' '.join([page.extract_text() for page in reader.pages])
elif filename.endswith('.docx'):
with io.BytesIO(decoded) as docx_file:
doc = docx.Document(docx_file)
return ' '.join([para.text for para in doc.paragraphs])
elif filename.endswith('.txt') or filename.endswith('.rtf'):
encoding = chardet.detect(decoded)['encoding']
return decoded.decode(encoding)
else:
return "Unsupported file format"
except Exception as e:
print(f"Error processing file {filename}: {str(e)}")
return "Error processing file"
@app.callback(
Output('file-list', 'children'),
Input('upload-files', 'contents'),
State('upload-files', 'filename'),
State('file-list', 'children')
)
def update_output(list_of_contents, list_of_names, existing_files):
global uploaded_files
if list_of_contents is not None:
new_files = []
for i, (content, name) in enumerate(zip(list_of_contents, list_of_names)):
file_content = parse_file_content(content, name)
uploaded_files[name] = file_content
new_files.append(html.Div([
html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
html.Span(name)
]))
if existing_files is None:
existing_files = []
return existing_files + new_files
return existing_files
@app.callback(
Output('file-list', 'children', allow_duplicate=True),
Input({'type': 'remove-file', 'index': dash.ALL}, 'n_clicks'),
State('file-list', 'children'),
prevent_initial_call=True
)
def remove_file(n_clicks, existing_files):
global uploaded_files
ctx = dash.callback_context
if not ctx.triggered:
raise PreventUpdate
removed_file = ctx.triggered[0]['prop_id'].split(',')[0].split(':')[-1].strip('}')
uploaded_files.pop(removed_file, None)
return [file for file in existing_files if file['props']['children'][1]['props']['children'] != removed_file]
def generate_matrix_with_gpt(matrix_type, file_contents):
prompt = f"""Generate a {matrix_type} based on the following project artifacts:
{' '.join(file_contents)}
Instructions:
1. Create the {matrix_type} as a table.
2. Use ONLY pipe symbols (|) to separate columns.
3. Do NOT include any introductory text, descriptions, or explanations.
4. Do NOT use any dashes (-) or other formatting characters.
5. The first row should be the column headers.
6. Start the output immediately with the column headers.
7. Each subsequent row should represent a single item in the matrix.
Example format:
Header1|Header2|Header3
Item1A|Item1B|Item1C
Item2A|Item2B|Item2C
Now, generate the {matrix_type}:
"""
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
messages=[
{"role": "system", "content": "You are a precise matrix generator that outputs only the requested matrix without any additional text."},
{"role": "user", "content": prompt}
]
)
matrix_text = response.choices[0].message.content.strip()
print("Raw matrix text from GPT:", matrix_text) # For debugging
lines = [line.strip() for line in matrix_text.split('\n') if '|' in line]
data = [line.split('|') for line in lines]
data = [[cell.strip() for cell in row] for row in data]
headers = data[0]
data = data[1:]
return pd.DataFrame(data, columns=headers)
@app.callback(
Output('matrix-preview', 'children'),
Output('loading-output', 'children'),
[Input(f'btn-{matrix_type.lower().replace(" ", "-")}', 'n_clicks') for matrix_type in matrix_types.keys()],
prevent_initial_call=True
)
def generate_matrix(*args):
global current_matrix, matrix_type
ctx = dash.callback_context
if not ctx.triggered:
raise PreventUpdate
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
matrix_type = button_id.replace('btn-', '').replace('-', ' ').title()
if not uploaded_files:
return html.Div("Please upload project artifacts before generating a matrix."), ""
file_contents = list(uploaded_files.values())
try:
current_matrix = generate_matrix_with_gpt(matrix_type, file_contents)
return dbc.Table.from_dataframe(current_matrix, striped=True, bordered=True, hover=True), f"{matrix_type} generated"
except Exception as e:
print(f"Error generating matrix: {str(e)}")
return html.Div(f"Error generating matrix: {str(e)}"), "Error"
@app.callback(
Output('chat-output', 'children'),
Output('matrix-preview', 'children', allow_duplicate=True),
Input('btn-send-chat', 'n_clicks'),
State('chat-input', 'value'),
prevent_initial_call=True
)
def update_matrix_via_chat(n_clicks, chat_input):
global current_matrix, matrix_type
if not chat_input or current_matrix is None:
raise PreventUpdate
prompt = f"""Update the following {matrix_type} based on this instruction: {chat_input}
Current matrix:
{current_matrix.to_string(index=False)}
Instructions:
1. Provide ONLY the updated matrix as a table.
2. Use ONLY pipe symbols (|) to separate columns.
3. Do NOT include any introductory text, descriptions, or explanations.
4. Do NOT use any dashes (-) or other formatting characters.
5. The first row should be the column headers.
6. Start the output immediately with the column headers.
7. Each subsequent row should represent a single item in the matrix.
Now, provide the updated {matrix_type}:
"""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a precise matrix updater that outputs only the requested matrix without any additional text."},
{"role": "user", "content": prompt}
]
)
updated_matrix_text = response.choices[0].message.content.strip()
print("Raw updated matrix text from GPT:", updated_matrix_text) # For debugging
lines = [line.strip() for line in updated_matrix_text.split('\n') if '|' in line]
data = [line.split('|') for line in lines]
data = [[cell.strip() for cell in row] for row in data]
headers = data[0]
data = data[1:]
current_matrix = pd.DataFrame(data, columns=headers)
return f"Matrix updated based on: {chat_input}", dbc.Table.from_dataframe(current_matrix, striped=True, bordered=True, hover=True)
@app.callback(
Output("download-matrix", "data"),
Input("btn-download", "n_clicks"),
prevent_initial_call=True
)
def download_matrix(n_clicks):
global current_matrix, matrix_type
if current_matrix is None:
raise PreventUpdate
# Create an in-memory Excel file
output = io.BytesIO()
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
current_matrix.to_excel(writer, sheet_name='Sheet1', index=False)
return dcc.send_bytes(output.getvalue(), f"{matrix_type}.xlsx")
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.")