bluenevus commited on
Commit
a2ec7dc
·
1 Parent(s): 87e9e53

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +190 -86
app.py CHANGED
@@ -3,11 +3,17 @@ import io
3
  import base64
4
  import threading
5
  import logging
6
- from dash import Dash, dcc, html, Input, Output, State
 
 
 
 
 
 
7
  import dash_bootstrap_components as dbc
8
  import openai
 
9
  from google import generativeai as genai
10
- from anthropic import Anthropic
11
  import requests
12
  from diagrams import Diagram, Cluster
13
  from diagrams.aws.compute import EC2
@@ -20,10 +26,8 @@ from diagrams.onprem.compute import Server
20
  from diagrams.onprem.database import PostgreSQL
21
  from diagrams.onprem.network import Nginx
22
 
23
- # Configure logging
24
  logging.basicConfig(level=logging.INFO)
25
 
26
- # Initialize API clients
27
  openai.api_key = os.getenv("OPENAI_API_KEY")
28
  if not openai.api_key:
29
  logging.warning("OPENAI_API_KEY not set. GPT-3.5 model will not be available.")
@@ -40,46 +44,74 @@ else:
40
  logging.warning("GOOGLE_API_KEY not set. Gemini model will not be available.")
41
 
42
  anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
43
- if anthropic_api_key:
44
- try:
45
- anthropic = Anthropic(api_key=anthropic_api_key)
46
- except Exception as e:
47
- logging.error(f"Failed to initialize Anthropic client: {e}")
48
- anthropic = None
49
- else:
50
- anthropic = None
51
  logging.warning("ANTHROPIC_API_KEY not set. Claude model will not be available.")
52
 
53
  grok_api_key = os.getenv("GROK_API_KEY")
54
  if not grok_api_key:
55
  logging.warning("GROK_API_KEY not set. Groq model will not be available.")
56
 
57
- app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Global variable to store the generated diagram
60
- generated_diagram = None
 
 
61
 
62
- def generate_diagram(description):
63
- global generated_diagram
64
- # This is a placeholder function. In a real implementation, you would parse the AI model's
65
- # output and create a diagram based on that. For now, we'll create a simple diagram.
66
- with Diagram("Architecture", show=False) as diag:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  with Cluster("Cloud"):
68
  lb = ELB("Load Balancer")
69
  with Cluster("Web Tier"):
70
- web = [EC2("Web Server 1"),
71
- EC2("Web Server 2")]
72
  with Cluster("Database Tier"):
73
  db_master = RDS("Master")
74
  db_slave = RDS("Slave")
75
-
76
  lb >> web
77
  web >> db_master
78
  db_master - db_slave
79
-
80
- img_bytes = diag.dot.pipe(format='png')
81
- generated_diagram = base64.b64encode(img_bytes).decode('utf-8')
82
- return generated_diagram
83
 
84
  def get_ai_response(model, prompt):
85
  if model == 'gpt-3.5-turbo':
@@ -89,17 +121,25 @@ def get_ai_response(model, prompt):
89
  )
90
  return response.choices[0].message.content
91
  elif model == 'gemini-1.5-flash-latest':
92
- model = genai.GenerativeModel('gemini-1.5-pro')
93
- response = model.generate_content(prompt)
 
 
94
  return response.text
95
  elif model == 'claude-3-5-haiku-20241022':
96
- response = anthropic.messages.create(
 
 
 
97
  model="claude-3-5-haiku-20241022",
98
- max_tokens=2000,
 
99
  messages=[{"role": "user", "content": prompt}]
100
  )
101
  return response.content[0].text
102
  elif model == 'grok-3-mini-fast-beta':
 
 
103
  groq_url = "https://api.groq.com/openai/v1/chat/completions"
104
  headers = {
105
  "Authorization": f"Bearer {grok_api_key}",
@@ -112,62 +152,121 @@ def get_ai_response(model, prompt):
112
  response = requests.post(groq_url, json=data, headers=headers)
113
  return response.json()['choices'][0]['message']['content']
114
  else:
115
- return "Error: Invalid model selected."
116
-
117
- app.layout = dbc.Container([
118
- html.H1("Architecture Diagram Generator", className="my-4"),
119
- dbc.Row([
120
- dbc.Col([
121
- dcc.Dropdown(
122
- id='model-dropdown',
123
- options=[
124
- {'label': 'GPT-3.5 Turbo', 'value': 'gpt-3.5-turbo'},
125
- {'label': 'Gemini 1.5 Flash', 'value': 'gemini-1.5-flash-latest'},
126
- {'label': 'Claude 3.5 Haiku', 'value': 'claude-3-5-haiku-20241022'},
127
- {'label': 'Grok 3 Mini Fast', 'value': 'grok-3-mini-fast-beta'}
128
- ],
129
- value='gpt-3.5-turbo',
130
- className="mb-3"
131
- ),
132
- dbc.Textarea(id='description-input', placeholder="Enter architecture description here...", style={'height': '200px'}, className="mb-3"),
133
- dbc.Button("Generate Diagram", id='generate-button', color="primary", className="mb-3"),
134
- dbc.Button("Download Diagram", id='download-button', color="secondary", className="mb-3", disabled=True),
135
- dcc.Download(id="download-diagram"),
136
- html.Div(id='status-message', className="mb-3")
137
- ], width=4),
138
- dbc.Col([
139
- html.Img(id='diagram-output', style={'width': '100%'})
140
- ], width=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  ])
142
- ], fluid=True)
 
 
 
 
 
 
 
 
143
 
144
  @app.callback(
145
- [Output('diagram-output', 'src'),
146
- Output('status-message', 'children'),
147
- Output('download-button', 'disabled')],
148
- [Input('generate-button', 'n_clicks')],
149
- [State('description-input', 'value'),
150
- State('model-dropdown', 'value')],
151
  prevent_initial_call=True
152
  )
153
  def update_diagram(n_clicks, description, model):
 
 
154
  if n_clicks is None:
155
- return dash.no_update, dash.no_update, True
156
-
157
  if not description:
158
- return dash.no_update, "Please enter a description.", True
159
-
160
- try:
161
- # Get AI response
162
- ai_response = get_ai_response(model, description)
163
-
164
- # Generate diagram based on AI response
165
- diagram_base64 = generate_diagram(ai_response)
166
-
167
- return f'data:image/png;base64,{diagram_base64}', "Diagram generated successfully!", False
168
- except Exception as e:
169
- logging.error(f"Error generating diagram: {str(e)}")
170
- return dash.no_update, f"Error: {str(e)}", True
 
 
171
 
172
  @app.callback(
173
  Output("download-diagram", "data"),
@@ -175,15 +274,20 @@ def update_diagram(n_clicks, description, model):
175
  prevent_initial_call=True
176
  )
177
  def download_diagram(n_clicks):
 
 
178
  if n_clicks is None:
179
- return dash.no_update
180
-
181
- if generated_diagram is None:
182
- return dash.no_update
183
-
184
- return dcc.send_bytes(base64.b64decode(generated_diagram), "architecture_diagram.png")
 
 
 
185
 
186
  if __name__ == '__main__':
187
  print("Starting the Dash application...")
188
- app.run(debug=True, host='0.0.0.0', port=7860)
189
  print("Dash application has finished running.")
 
3
  import base64
4
  import threading
5
  import logging
6
+ import tempfile
7
+ import shutil
8
+ import uuid
9
+ import json
10
+ from pathlib import Path
11
+ from flask import request
12
+ from dash import Dash, dcc, html, Input, Output, State, ctx, no_update
13
  import dash_bootstrap_components as dbc
14
  import openai
15
+ import anthropic
16
  from google import generativeai as genai
 
17
  import requests
18
  from diagrams import Diagram, Cluster
19
  from diagrams.aws.compute import EC2
 
26
  from diagrams.onprem.database import PostgreSQL
27
  from diagrams.onprem.network import Nginx
28
 
 
29
  logging.basicConfig(level=logging.INFO)
30
 
 
31
  openai.api_key = os.getenv("OPENAI_API_KEY")
32
  if not openai.api_key:
33
  logging.warning("OPENAI_API_KEY not set. GPT-3.5 model will not be available.")
 
44
  logging.warning("GOOGLE_API_KEY not set. Gemini model will not be available.")
45
 
46
  anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
47
+ if not anthropic_api_key:
 
 
 
 
 
 
 
48
  logging.warning("ANTHROPIC_API_KEY not set. Claude model will not be available.")
49
 
50
  grok_api_key = os.getenv("GROK_API_KEY")
51
  if not grok_api_key:
52
  logging.warning("GROK_API_KEY not set. Groq model will not be available.")
53
 
54
+ # Session management globals
55
+ SESSION_DIR = Path(tempfile.gettempdir()) / "arch_diagram_sessions"
56
+ SESSION_DIR.mkdir(parents=True, exist_ok=True)
57
+ session_locks = {}
58
+ session_data = {}
59
+
60
+ def get_or_create_session_id():
61
+ sid = request.cookies.get("sid")
62
+ if not sid or not isinstance(sid, str) or len(sid) < 8:
63
+ sid = str(uuid.uuid4())
64
+ return sid
65
 
66
+ def get_session_dir(sid):
67
+ p = SESSION_DIR / sid
68
+ p.mkdir(parents=True, exist_ok=True)
69
+ return p
70
 
71
+ def get_session_lock(sid):
72
+ if sid not in session_locks:
73
+ session_locks[sid] = threading.Lock()
74
+ return session_locks[sid]
75
+
76
+ def get_session_data(sid):
77
+ if sid not in session_data:
78
+ session_data[sid] = {"diagram_base64": None, "description": "", "model": "gpt-3.5-turbo"}
79
+ return session_data[sid]
80
+
81
+ def save_session_state(sid, data):
82
+ session_data[sid] = data
83
+ sdir = get_session_dir(sid)
84
+ with open(sdir / "session.json", "w") as f:
85
+ json.dump(data, f)
86
+
87
+ def load_session_state(sid):
88
+ sdir = get_session_dir(sid)
89
+ sf = sdir / "session.json"
90
+ if sf.exists():
91
+ try:
92
+ with open(sf) as f:
93
+ session_data[sid] = json.load(f)
94
+ except Exception as e:
95
+ logging.error(f"Session load error: {e}")
96
+
97
+ def generate_diagram(description, sid):
98
+ sdir = get_session_dir(sid)
99
+ img_path = sdir / "diagram.png"
100
+ with Diagram("Architecture", show=False, outformat="png", filename=str(img_path.with_suffix(""))):
101
  with Cluster("Cloud"):
102
  lb = ELB("Load Balancer")
103
  with Cluster("Web Tier"):
104
+ web = [EC2("Web Server 1"), EC2("Web Server 2")]
 
105
  with Cluster("Database Tier"):
106
  db_master = RDS("Master")
107
  db_slave = RDS("Slave")
 
108
  lb >> web
109
  web >> db_master
110
  db_master - db_slave
111
+ with open(img_path, "rb") as f:
112
+ img_bytes = f.read()
113
+ diagram_base64 = base64.b64encode(img_bytes).decode('utf-8')
114
+ return diagram_base64
115
 
116
  def get_ai_response(model, prompt):
117
  if model == 'gpt-3.5-turbo':
 
121
  )
122
  return response.choices[0].message.content
123
  elif model == 'gemini-1.5-flash-latest':
124
+ if genai is None:
125
+ raise Exception("Gemini API not configured.")
126
+ model_obj = genai.GenerativeModel('gemini-1.5-pro-latest')
127
+ response = model_obj.generate_content(prompt)
128
  return response.text
129
  elif model == 'claude-3-5-haiku-20241022':
130
+ if not anthropic_api_key:
131
+ raise Exception("Anthropic API not configured.")
132
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
133
+ response = client.messages.create(
134
  model="claude-3-5-haiku-20241022",
135
+ system=None,
136
+ max_tokens=2048,
137
  messages=[{"role": "user", "content": prompt}]
138
  )
139
  return response.content[0].text
140
  elif model == 'grok-3-mini-fast-beta':
141
+ if not grok_api_key:
142
+ raise Exception("Groq API not configured.")
143
  groq_url = "https://api.groq.com/openai/v1/chat/completions"
144
  headers = {
145
  "Authorization": f"Bearer {grok_api_key}",
 
152
  response = requests.post(groq_url, json=data, headers=headers)
153
  return response.json()['choices'][0]['message']['content']
154
  else:
155
+ raise Exception("Error: Invalid model selected.")
156
+
157
+ app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
158
+
159
+ def serve_layout():
160
+ sid = get_or_create_session_id()
161
+ load_session_state(sid)
162
+ sdata = get_session_data(sid)
163
+ diagram = sdata.get("diagram_base64", None)
164
+ resp = dbc.Container(fluid=True, children=[
165
+ dbc.Row([
166
+ dbc.Col([
167
+ html.H1("Architecture Diagram Generator", className="mb-4"),
168
+ ], width=12)
169
+ ], className="mt-2"),
170
+ dbc.Row([
171
+ dbc.Col([
172
+ dbc.Card([
173
+ dbc.CardHeader("Controls"),
174
+ dbc.CardBody([
175
+ html.Div([
176
+ dcc.Dropdown(
177
+ id='model-dropdown',
178
+ options=[
179
+ {'label': 'GPT-3.5 Turbo', 'value': 'gpt-3.5-turbo'},
180
+ {'label': 'Gemini 1.5 Flash', 'value': 'gemini-1.5-flash-latest'},
181
+ {'label': 'Claude 3.5 Haiku', 'value': 'claude-3-5-haiku-20241022'},
182
+ {'label': 'Grok 3 Mini Fast', 'value': 'grok-3-mini-fast-beta'}
183
+ ],
184
+ value=sdata.get("model", "gpt-3.5-turbo"),
185
+ className="mb-3"
186
+ ),
187
+ dbc.Textarea(
188
+ id='description-input',
189
+ placeholder="Enter architecture description here...",
190
+ value=sdata.get("description", ""),
191
+ style={'height': '200px', 'whiteSpace': 'pre-wrap', 'wordWrap': 'break-word'},
192
+ className="mb-3"
193
+ ),
194
+ dbc.Button("Generate Diagram",
195
+ id='generate-button',
196
+ color="primary",
197
+ className="me-2 mb-2"),
198
+ dbc.Button("Download Diagram",
199
+ id='download-button',
200
+ color="secondary",
201
+ className="mb-2",
202
+ disabled=(diagram is None)),
203
+ dcc.Download(id="download-diagram"),
204
+ html.Div(id='status-message', className="mb-3"),
205
+ ]),
206
+ ])
207
+ ])
208
+ ], width=4, style={'backgroundColor': '#f8f9fa', 'minHeight': '100vh'}),
209
+ dbc.Col([
210
+ dbc.Card([
211
+ dbc.CardHeader("Diagram"),
212
+ dbc.CardBody([
213
+ dcc.Loading(
214
+ id="loading",
215
+ type="default",
216
+ fullscreen=False,
217
+ children=[
218
+ html.Img(
219
+ id='diagram-output',
220
+ src=f'data:image/png;base64,{diagram}' if diagram else "",
221
+ style={'width': '100%', 'minHeight': '300px'}
222
+ ),
223
+ ]
224
+ )
225
+ ])
226
+ ]),
227
+ ], width=8, style={'backgroundColor': '#fff', 'minHeight': '100vh'}),
228
+ ])
229
  ])
230
+ return resp
231
+
232
+ app.layout = serve_layout
233
+
234
+ @app.server.after_request
235
+ def set_cookie(response):
236
+ sid = get_or_create_session_id()
237
+ response.set_cookie("sid", sid, max_age=60*60*24*30, httponly=False, samesite="Lax")
238
+ return response
239
 
240
  @app.callback(
241
+ Output('diagram-output', 'src'),
242
+ Output('status-message', 'children'),
243
+ Output('download-button', 'disabled'),
244
+ Input('generate-button', 'n_clicks'),
245
+ State('description-input', 'value'),
246
+ State('model-dropdown', 'value'),
247
  prevent_initial_call=True
248
  )
249
  def update_diagram(n_clicks, description, model):
250
+ sid = get_or_create_session_id()
251
+ lock = get_session_lock(sid)
252
  if n_clicks is None:
253
+ return no_update, no_update, True
 
254
  if not description:
255
+ return no_update, "Please enter a description.", True
256
+ with lock:
257
+ try:
258
+ ai_response = get_ai_response(model, description)
259
+ diagram_base64 = generate_diagram(ai_response, sid)
260
+ sdata = get_session_data(sid)
261
+ sdata["diagram_base64"] = diagram_base64
262
+ sdata["description"] = description
263
+ sdata["model"] = model
264
+ save_session_state(sid, sdata)
265
+ logging.info(f"Session {sid}: Diagram generated successfully.")
266
+ return f'data:image/png;base64,{diagram_base64}', "Diagram generated successfully!", False
267
+ except Exception as e:
268
+ logging.error(f"Session {sid}: Error generating diagram: {str(e)}")
269
+ return no_update, f"Error: {str(e)}", True
270
 
271
  @app.callback(
272
  Output("download-diagram", "data"),
 
274
  prevent_initial_call=True
275
  )
276
  def download_diagram(n_clicks):
277
+ sid = get_or_create_session_id()
278
+ lock = get_session_lock(sid)
279
  if n_clicks is None:
280
+ return no_update
281
+ with lock:
282
+ sdata = get_session_data(sid)
283
+ diagram_base64 = sdata.get("diagram_base64", None)
284
+ if not diagram_base64:
285
+ return no_update
286
+ diagram_bytes = base64.b64decode(diagram_base64)
287
+ logging.info(f"Session {sid}: Diagram downloaded.")
288
+ return dcc.send_bytes(diagram_bytes, "architecture_diagram.png")
289
 
290
  if __name__ == '__main__':
291
  print("Starting the Dash application...")
292
+ app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
293
  print("Dash application has finished running.")