File size: 8,924 Bytes
e60c00e
 
 
525d347
bd5de4f
 
e60c00e
 
cb6077d
e60c00e
229097a
 
0a1b210
9a27157
e60c00e
 
 
 
0a1b210
 
419e509
e60c00e
cb6077d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e60c00e
 
5ec2a94
b011df6
 
 
 
 
 
5ec2a94
 
 
b011df6
5ec2a94
 
 
 
 
e60c00e
cb6077d
e60c00e
a134b3b
cb6077d
e60c00e
cb6077d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e60c00e
 
cb6077d
 
 
 
e60c00e
cb6077d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a1b210
cb6077d
 
14fb127
 
cb6077d
 
 
 
d96a160
cb6077d
 
 
 
 
 
 
c3e6348
cb6077d
 
d96a160
cb6077d
c3e6348
8e99391
cb6077d
 
8e99391
cb6077d
 
46fa047
 
cb6077d
 
 
 
 
46fa047
cb6077d
 
 
 
 
 
 
 
 
 
 
 
 
 
46fa047
cb6077d
 
46fa047
cb6077d
46fa047
 
cb6077d
 
 
46fa047
cb6077d
 
 
 
46fa047
cb6077d
8d5114f
cb6077d
229097a
cb6077d
 
 
ea14fe6
cb6077d
46fa047
 
 
cb6077d
46fa047
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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)}"

@app.callback(
    Output('file-list', 'children'),
    Input('upload-document', 'contents'),
    State('upload-document', '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 = 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

@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 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

@app.callback(
    Output('document-preview', 'children'),
    Output('loading-output', 'children'),
    [Input(f'btn-{doc_type.lower().replace(" ", "-")}', 'n_clicks') for doc_type in document_types.keys()],
    prevent_initial_call=True
)
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"

@app.callback(
    Output('chat-output', 'children'),
    Output('document-preview', 'children', allow_duplicate=True),
    Input('btn-send-chat', 'n_clicks'),
    State('chat-input', 'value'),
    prevent_initial_call=True
)
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)

@app.callback(
    Output("download-document", "data"),
    Input("btn-download", "n_clicks"),
    prevent_initial_call=True
)
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.")