bluenevus commited on
Commit
4dbeeea
·
1 Parent(s): c5fd0c3

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +118 -107
app.py CHANGED
@@ -12,49 +12,43 @@ import threading
12
  import tempfile
13
  import shutil
14
  import logging
15
- from flask import request
16
  from dash.exceptions import PreventUpdate
17
  import PyPDF2
18
  import docx
19
  import chardet
20
 
21
- # --- Logging Setup ---
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger("maiko_matrix_app")
24
 
25
- # --- App and Session Setup ---
26
- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
27
- server = app.server
28
-
29
- openai.api_key = os.environ.get('OPENAI_API_KEY')
30
-
31
- # Session management globals
32
  SESSION_DATA = {}
33
  SESSION_LOCKS = {}
34
 
35
- def get_session_id():
36
- if 'session-id' in request.cookies:
37
- return request.cookies['session-id']
38
- # Generate new session id (uuid4) if not present
39
- session_id = str(uuid.uuid4())
40
- logger.info(f"Generated new session id: {session_id}")
41
- return session_id
 
 
 
42
 
43
  def get_session_data():
44
- session_id = get_session_id()
45
- if session_id not in SESSION_DATA:
46
- SESSION_DATA[session_id] = {
47
- 'uploaded_files': {}, # filename -> temp file path
48
- 'file_texts': {}, # filename -> extracted text
49
  'current_matrix': None,
50
  'matrix_type': None,
51
- 'temp_dir': tempfile.mkdtemp(prefix=f"maiko_{session_id}_"),
52
  }
53
- SESSION_LOCKS[session_id] = threading.Lock()
54
- return SESSION_DATA[session_id], SESSION_LOCKS[session_id]
55
 
56
  def cleanup_session_tempdirs():
57
- # Called optionally on server shutdown for cleanup
58
  for sess in SESSION_DATA.values():
59
  try:
60
  shutil.rmtree(sess['temp_dir'])
@@ -81,7 +75,102 @@ matrix_types = {
81
  "SWOT Matrix": "Create a matrix analyzing Strengths, Weaknesses, Opportunities, and Threats."
82
  }
83
 
84
- # --- UI Layout ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  app.layout = dbc.Container([
86
  dbc.Row([
87
  dbc.Col([
@@ -166,83 +255,6 @@ app.layout = dbc.Container([
166
  ])
167
  ], fluid=True, style={'padding': '0'})
168
 
169
-
170
- # --- File Parsing ---
171
- def parse_file_content(file_path, filename):
172
- try:
173
- with open(file_path, "rb") as f:
174
- decoded = f.read()
175
- if filename.endswith('.pdf'):
176
- with io.BytesIO(decoded) as pdf_file:
177
- reader = PyPDF2.PdfReader(pdf_file)
178
- return ' '.join([page.extract_text() or "" for page in reader.pages])
179
- elif filename.endswith('.docx'):
180
- with io.BytesIO(decoded) as docx_file:
181
- doc = docx.Document(docx_file)
182
- return ' '.join([para.text for para in doc.paragraphs])
183
- elif filename.endswith('.txt') or filename.endswith('.rtf'):
184
- encoding = chardet.detect(decoded)['encoding']
185
- return decoded.decode(encoding)
186
- else:
187
- return "Unsupported file format"
188
- except Exception as e:
189
- logger.exception(f"Error processing file {filename}: {str(e)}")
190
- return "Error processing file"
191
-
192
- def truncate_filename(filename, max_length=24):
193
- if len(filename) <= max_length:
194
- return filename
195
- else:
196
- return filename[:max_length - 3] + '...'
197
-
198
- def get_file_cards(file_dict):
199
- cards = []
200
- for name in file_dict:
201
- cards.append(
202
- dbc.Card(
203
- dbc.CardBody(
204
- dbc.Row([
205
- dbc.Col(
206
- html.Span(
207
- truncate_filename(name),
208
- title=name,
209
- style={
210
- 'display': 'inline-block',
211
- 'overflow': 'hidden',
212
- 'textOverflow': 'ellipsis',
213
- 'whiteSpace': 'nowrap',
214
- 'maxWidth': '90%',
215
- 'verticalAlign': 'middle',
216
- }
217
- ),
218
- width='auto',
219
- style={'display': 'flex', 'alignItems': 'center', 'padding': '0'}
220
- ),
221
- dbc.Col(
222
- dbc.Button(
223
- "Delete",
224
- id={'type': 'remove-file', 'index': name},
225
- color="danger",
226
- size="sm",
227
- style={'marginLeft': 'auto', 'float': 'right'}
228
- ),
229
- width='auto',
230
- style={'display': 'flex', 'alignItems': 'center', 'justifyContent': 'flex-end', 'padding': '0'}
231
- ),
232
- ],
233
- justify="between",
234
- align="center",
235
- style={"margin": "0", "padding": "0"}
236
- ),
237
- style={'padding': '6px 8px', 'margin': '0', 'display': 'flex', 'alignItems': 'center', 'background': 'none', 'boxShadow': 'none'}
238
- ),
239
- style={'border': 'none', 'boxShadow': 'none', 'background': 'none', 'marginBottom': '2px'}
240
- )
241
- )
242
- return cards
243
-
244
- # --- Callbacks ---
245
-
246
  @app.callback(
247
  Output('file-list', 'children'),
248
  Input('upload-files', 'contents'),
@@ -269,12 +281,11 @@ def update_output(list_of_contents, list_of_names):
269
  @app.callback(
270
  Output('file-list', 'children', allow_duplicate=True),
271
  Input({'type': 'remove-file', 'index': ALL}, 'n_clicks'),
272
- State('file-list', 'children'),
273
  prevent_initial_call=True
274
  )
275
- def remove_file(n_clicks, existing_cards):
276
- session_data, lock = get_session_data()
277
  ctx = dash.callback_context
 
278
  if not ctx.triggered:
279
  raise PreventUpdate
280
  triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
@@ -352,7 +363,7 @@ Now, generate the {matrix_type}:
352
  ]
353
  )
354
  matrix_text = response.choices[0].message.content.strip()
355
- logger.info(f"Raw matrix text from GPT: {matrix_text[:200]}...") # log only first 200 chars
356
  lines = [line.strip() for line in matrix_text.split('\n') if '|' in line]
357
  data = [line.split('|') for line in lines]
358
  data = [[cell.strip() for cell in row] for row in data]
 
12
  import tempfile
13
  import shutil
14
  import logging
15
+ from flask import request, make_response, g
16
  from dash.exceptions import PreventUpdate
17
  import PyPDF2
18
  import docx
19
  import chardet
20
 
 
21
  logging.basicConfig(level=logging.INFO)
22
  logger = logging.getLogger("maiko_matrix_app")
23
 
 
 
 
 
 
 
 
24
  SESSION_DATA = {}
25
  SESSION_LOCKS = {}
26
 
27
+ def get_session_id(set_cookie=False):
28
+ # Try to get session id from browser cookies
29
+ sid = request.cookies.get('session-id')
30
+ if sid and sid in SESSION_DATA:
31
+ return sid
32
+ # If not present, generate a new one
33
+ sid = str(uuid.uuid4())
34
+ if set_cookie:
35
+ g.set_cookie_sid = sid
36
+ return sid
37
 
38
  def get_session_data():
39
+ sid = get_session_id()
40
+ if sid not in SESSION_DATA:
41
+ SESSION_DATA[sid] = {
42
+ 'uploaded_files': {},
43
+ 'file_texts': {},
44
  'current_matrix': None,
45
  'matrix_type': None,
46
+ 'temp_dir': tempfile.mkdtemp(prefix=f"maiko_{sid}_"),
47
  }
48
+ SESSION_LOCKS[sid] = threading.Lock()
49
+ return SESSION_DATA[sid], SESSION_LOCKS[sid]
50
 
51
  def cleanup_session_tempdirs():
 
52
  for sess in SESSION_DATA.values():
53
  try:
54
  shutil.rmtree(sess['temp_dir'])
 
75
  "SWOT Matrix": "Create a matrix analyzing Strengths, Weaknesses, Opportunities, and Threats."
76
  }
77
 
78
+ app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
79
+ server = app.server
80
+
81
+ openai.api_key = os.environ.get('OPENAI_API_KEY')
82
+
83
+ @server.before_request
84
+ def ensure_session_cookie():
85
+ # Set session cookie if not present
86
+ sid = request.cookies.get('session-id')
87
+ if not sid or sid not in SESSION_DATA:
88
+ sid_new = str(uuid.uuid4())
89
+ g.set_cookie_sid = sid_new
90
+ get_session_id(set_cookie=True)
91
+ else:
92
+ g.set_cookie_sid = None
93
+
94
+ @server.after_request
95
+ def set_session_cookie(response):
96
+ sid = getattr(g, 'set_cookie_sid', None)
97
+ if sid:
98
+ response.set_cookie('session-id', sid, max_age=60*60*48, httponly=True)
99
+ return response
100
+
101
+ def parse_file_content(file_path, filename):
102
+ try:
103
+ with open(file_path, "rb") as f:
104
+ decoded = f.read()
105
+ if filename.endswith('.pdf'):
106
+ with io.BytesIO(decoded) as pdf_file:
107
+ reader = PyPDF2.PdfReader(pdf_file)
108
+ return ' '.join([page.extract_text() or "" for page in reader.pages])
109
+ elif filename.endswith('.docx'):
110
+ with io.BytesIO(decoded) as docx_file:
111
+ doc = docx.Document(docx_file)
112
+ return ' '.join([para.text for para in doc.paragraphs])
113
+ elif filename.endswith('.txt') or filename.endswith('.rtf'):
114
+ encoding = chardet.detect(decoded)['encoding']
115
+ return decoded.decode(encoding)
116
+ else:
117
+ return "Unsupported file format"
118
+ except Exception as e:
119
+ logger.exception(f"Error processing file {filename}: {str(e)}")
120
+ return "Error processing file"
121
+
122
+ def truncate_filename(filename, max_length=24):
123
+ if len(filename) <= max_length:
124
+ return filename
125
+ else:
126
+ return filename[:max_length - 3] + '...'
127
+
128
+ def get_file_cards(file_dict):
129
+ cards = []
130
+ for name in file_dict:
131
+ cards.append(
132
+ dbc.Card(
133
+ dbc.CardBody(
134
+ dbc.Row([
135
+ dbc.Col(
136
+ html.Span(
137
+ truncate_filename(name),
138
+ title=name,
139
+ style={
140
+ 'display': 'inline-block',
141
+ 'overflow': 'hidden',
142
+ 'textOverflow': 'ellipsis',
143
+ 'whiteSpace': 'nowrap',
144
+ 'maxWidth': '90%',
145
+ 'verticalAlign': 'middle',
146
+ }
147
+ ),
148
+ width='auto',
149
+ style={'display': 'flex', 'alignItems': 'center', 'padding': '0'}
150
+ ),
151
+ dbc.Col(
152
+ dbc.Button(
153
+ "Delete",
154
+ id={'type': 'remove-file', 'index': name},
155
+ color="danger",
156
+ size="sm",
157
+ style={'marginLeft': 'auto', 'float': 'right'}
158
+ ),
159
+ width='auto',
160
+ style={'display': 'flex', 'alignItems': 'center', 'justifyContent': 'flex-end', 'padding': '0'}
161
+ ),
162
+ ],
163
+ justify="between",
164
+ align="center",
165
+ style={"margin": "0", "padding": "0"}
166
+ ),
167
+ style={'padding': '6px 8px', 'margin': '0', 'display': 'flex', 'alignItems': 'center', 'background': 'none', 'boxShadow': 'none'}
168
+ ),
169
+ style={'border': 'none', 'boxShadow': 'none', 'background': 'none', 'marginBottom': '2px'}
170
+ )
171
+ )
172
+ return cards
173
+
174
  app.layout = dbc.Container([
175
  dbc.Row([
176
  dbc.Col([
 
255
  ])
256
  ], fluid=True, style={'padding': '0'})
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  @app.callback(
259
  Output('file-list', 'children'),
260
  Input('upload-files', 'contents'),
 
281
  @app.callback(
282
  Output('file-list', 'children', allow_duplicate=True),
283
  Input({'type': 'remove-file', 'index': ALL}, 'n_clicks'),
 
284
  prevent_initial_call=True
285
  )
286
+ def remove_file(n_clicks):
 
287
  ctx = dash.callback_context
288
+ session_data, lock = get_session_data()
289
  if not ctx.triggered:
290
  raise PreventUpdate
291
  triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
 
363
  ]
364
  )
365
  matrix_text = response.choices[0].message.content.strip()
366
+ logger.info(f"Raw matrix text from GPT: {matrix_text[:200]}...")
367
  lines = [line.strip() for line in matrix_text.split('\n') if '|' in line]
368
  data = [line.split('|') for line in lines]
369
  data = [[cell.strip() for cell in row] for row in data]