Spaces:
Paused
Paused
| import base64 | |
| import io | |
| import os | |
| import pandas as pd | |
| from docx import Document | |
| from io import BytesIO | |
| import dash | |
| import dash_bootstrap_components as dbc | |
| from dash import html, dcc, Input, Output, State, callback_context | |
| import google.generativeai as genai | |
| from docx.shared import Pt | |
| from docx.enum.style import WD_STYLE_TYPE | |
| from PyPDF2 import PdfReader | |
| from io import StringIO | |
| # Initialize Dash app | |
| app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) | |
| # Configure Gemini AI | |
| genai.configure(api_key=os.environ["GEMINI_API_KEY"]) | |
| model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25') | |
| # Global variables | |
| uploaded_files = {} | |
| current_document = None | |
| document_type = None | |
| # Document types and their descriptions | |
| document_types = { | |
| "Shred": "Generate an outline of the Project Work Statement (PWS)", | |
| "Pink": "Create a Pink Team document based on the PWS outline", | |
| "P.Review": "Evaluate compliance of the Pink Team document", | |
| "Red": "Generate a Red Team document based on the P.Review", | |
| "R.Review": "Evaluate compliance of the Red Team document", | |
| "G.Review": "Perform a final compliance review", | |
| "LOE": "Generate a Level of Effort (LOE) breakdown" | |
| } | |
| app.layout = dbc.Container([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| html.H4("Proposal Documents", className="mt-3 mb-4"), | |
| dcc.Upload( | |
| id='upload-document', | |
| 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( | |
| doc_type, | |
| id=f'btn-{doc_type.lower().replace(" ", "-")}', | |
| color="link", | |
| className="mb-2 w-100 text-left custom-button", | |
| style={'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'} | |
| ) for doc_type in document_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='document-preview', className="border p-3 mb-3"), | |
| dbc.Button("Download Document", id="btn-download", color="success", className="mt-3"), | |
| dcc.Download(id="download-document"), | |
| 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 AI to update document...", 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 process_document(contents, filename): | |
| content_type, content_string = contents.split(',') | |
| decoded = base64.b64decode(content_string) | |
| try: | |
| if filename.lower().endswith('.docx'): | |
| doc = Document(BytesIO(decoded)) | |
| text = "\n".join([para.text for para in doc.paragraphs]) | |
| return text | |
| elif filename.lower().endswith('.pdf'): | |
| pdf = PdfReader(BytesIO(decoded)) | |
| text = "" | |
| for page in pdf.pages: | |
| text += page.extract_text() | |
| return text | |
| else: | |
| return f"Unsupported file format: {filename}. Please upload a PDF or DOCX file." | |
| except Exception as e: | |
| return f"Error processing document: {str(e)}" | |
| 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 = process_document(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 | |
| def remove_file(n_clicks, existing_files): | |
| global uploaded_files | |
| ctx = dash.callback_context | |
| if not ctx.triggered: | |
| raise dash.exceptions.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_document(document_type, file_contents): | |
| prompt = f"""Generate a {document_type} based on the following project artifacts: | |
| {' '.join(file_contents)} | |
| Instructions: | |
| 1. Create the {document_type} as a detailed document. | |
| 2. Use proper formatting and structure. | |
| 3. Include all necessary sections and details. | |
| 4. Start the output immediately with the document content. | |
| Now, generate the {document_type}: | |
| """ | |
| response = model.generate_content(prompt) | |
| return response.text | |
| def generate_document_preview(*args): | |
| global current_document, document_type | |
| ctx = dash.callback_context | |
| if not ctx.triggered: | |
| raise dash.exceptions.PreventUpdate | |
| button_id = ctx.triggered[0]['prop_id'].split('.')[0] | |
| document_type = button_id.replace('btn-', '').replace('-', ' ').title() | |
| if not uploaded_files: | |
| return html.Div("Please upload project artifacts before generating a document."), "" | |
| file_contents = list(uploaded_files.values()) | |
| try: | |
| current_document = generate_document(document_type, file_contents) | |
| return dcc.Markdown(current_document), f"{document_type} generated" | |
| except Exception as e: | |
| print(f"Error generating document: {str(e)}") | |
| return html.Div(f"Error generating document: {str(e)}"), "Error" | |
| def update_document_via_chat(n_clicks, chat_input): | |
| global current_document, document_type | |
| if not chat_input or current_document is None: | |
| raise dash.exceptions.PreventUpdate | |
| prompt = f"""Update the following {document_type} based on this instruction: {chat_input} | |
| Current document: | |
| {current_document} | |
| Instructions: | |
| 1. Provide the updated document content. | |
| 2. Maintain proper formatting and structure. | |
| 3. Incorporate the requested changes seamlessly. | |
| Now, provide the updated {document_type}: | |
| """ | |
| response = model.generate_content(prompt) | |
| current_document = response.text | |
| return f"Document updated based on: {chat_input}", dcc.Markdown(current_document) | |
| def download_document(n_clicks): | |
| global current_document, document_type | |
| if current_document is None: | |
| raise dash.exceptions.PreventUpdate | |
| # Create an in-memory Word document | |
| doc = Document() | |
| doc.add_paragraph(current_document) | |
| # Save the document to a BytesIO object | |
| output = BytesIO() | |
| doc.save(output) | |
| return dcc.send_bytes(output.getvalue(), f"{document_type}.docx") | |
| if __name__ == '__main__': | |
| print("Starting the Dash application...") | |
| app.run(debug=False, host='0.0.0.0', port=7860) | |
| print("Dash application has finished running.") |