wangzerui commited on
Commit
eb8d8ce
·
1 Parent(s): b8df1ad
Files changed (2) hide show
  1. app.py +445 -105
  2. gradio_app.py +0 -484
app.py CHANGED
@@ -1,144 +1,484 @@
1
  #!/usr/bin/env python3
2
  """
3
- Hugging Face Spaces deployment for Residential Architecture Assistant
4
- Standalone version that completely avoids LangGraph imports until needed
5
  """
6
 
 
7
  import gradio as gr
 
 
 
 
 
 
8
 
9
- def create_interface():
10
- """Create interface that works reliably in HF Spaces"""
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- def handle_chat(message, history, api_key):
13
- if not api_key or not api_key.strip():
14
- error_msg = "❌ Please provide your OpenAI API key to start using the Architecture Assistant."
15
- history.append([message, error_msg])
16
- return history, ""
17
 
18
- if not message.strip():
19
- return history, ""
 
 
 
 
 
 
 
20
 
21
  try:
22
- # Only import when actually used
23
- from graph import ArchitectureAssistant
 
 
 
 
 
 
24
 
25
- assistant = ArchitectureAssistant(
26
- openai_api_key=api_key.strip(),
27
- user_id=f"hf_user_{len(history)}"
28
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- response = assistant.chat(message)
31
  history.append([message, response])
32
 
33
- except ImportError as e:
34
- error_msg = f"""❌ **Import Error**: {str(e)}
35
-
36
- **The system modules couldn't be loaded.** This might be due to:
37
- 1. Missing dependencies in the Hugging Face Space
38
- 2. Package compatibility issues
39
-
40
- **This should be the Residential Architecture Assistant** with:
41
- - 🏛️ Architecture design guidance
42
- - 💰 Montreal market cost analysis
43
- - 📐 Professional floorplan generation
44
- - 📋 Building codes & permit requirements
45
-
46
- Please contact the space administrator to resolve the import issues."""
47
 
 
 
 
 
48
  history.append([message, error_msg])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  except Exception as e:
51
- error_msg = f"""**System Error**: {str(e)}
52
-
53
- **Troubleshooting:**
54
- 1. **Check your OpenAI API key** - Ensure it's valid and has credits
55
- 2. **Try a simpler question** - Start with basic architecture questions
56
- 3. **Wait and retry** - There might be temporary connectivity issues
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- **Expected functionality:**
59
- - Multi-agent architecture consultation
60
- - Montreal-specific building analysis
61
- - Professional floorplan generation
62
- - Building code guidance
63
 
64
- Please try again or contact support if issues persist."""
65
-
66
- history.append([message, error_msg])
67
-
68
- return history, ""
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- with gr.Blocks(title="🏠 Architecture Assistant") as interface:
 
 
 
 
 
71
  gr.HTML("""
72
- <div style="text-align: center; padding: 20px;">
73
  <h1>🏠 Residential Architecture Assistant</h1>
74
- <p><strong>Multi-Agent LangGraph System for Professional Architecture Consultation</strong></p>
75
- <p>✨ 7 AI Specialists | 📐 Professional Floorplans | 💰 Montreal Market Analysis | 📋 Building Codes</p>
76
  </div>
77
  """)
78
 
79
- api_key = gr.Textbox(
80
- label="🔑 OpenAI API Key",
81
- type="password",
82
- placeholder="Enter your OpenAI API key (sk-...)",
83
- info="Required for AI functionality. Not stored or logged."
84
- )
 
 
 
 
 
85
 
86
- chatbot = gr.Chatbot(
87
- label="💬 Architecture Consultation",
88
- height=400
89
  )
90
 
91
- msg = gr.Textbox(
92
- label="Your Message",
93
- placeholder="Ask about home design, budgets, floorplans, Montreal building codes...",
94
- lines=2
95
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
 
97
  with gr.Row():
98
- send_btn = gr.Button("📤 Send", variant="primary")
99
- clear_btn = gr.Button("🔄 Clear", variant="secondary")
100
 
101
- gr.HTML("""
102
- <div style="background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 8px;">
103
- <h4>�� Example Questions:</h4>
104
- <ul style="margin: 10px 0;">
105
- <li>"I want to design a home but don't know where to start"</li>
106
- <li>"I have a $800,000 budget for Montreal - is that realistic?"</li>
107
- <li>"We're a family of 4, what size house do we need?"</li>
108
- <li>"Can you generate a floorplan for a 2500 sq ft house?"</li>
109
- <li>"What building permits do I need in Montreal?"</li>
110
- </ul>
111
-
112
- <h4>🤖 Our AI Specialists:</h4>
113
- <ul style="margin: 10px 0;">
114
- <li><strong>RouterAgent:</strong> Intelligent conversation routing</li>
115
- <li><strong>GeneralDesignAgent:</strong> Architecture principles & design guidance</li>
116
- <li><strong>BudgetAnalysisAgent:</strong> Montreal market cost analysis</li>
117
- <li><strong>FloorplanAgent:</strong> Spatial planning & requirements</li>
118
- <li><strong>FloorplanGeneratorAgent:</strong> Detailed architectural specifications</li>
119
- <li><strong>DetailedBudgetAgent:</strong> Comprehensive cost breakdowns</li>
120
- <li><strong>RegulationAgent:</strong> Montreal building codes & permits</li>
121
- </ul>
122
- </div>
123
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  # Event handlers
126
- msg.submit(handle_chat, [msg, chatbot, api_key], [chatbot, msg])
127
- send_btn.click(handle_chat, [msg, chatbot, api_key], [chatbot, msg])
128
- clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg])
129
 
130
- gr.HTML("""
131
- <div style="text-align: center; padding: 20px; border-top: 1px solid #ddd; margin-top: 20px;">
132
- <p><strong>🏠 Residential Architecture Assistant v3.0</strong></p>
133
- <p>Built with <a href="https://langchain.ai/langgraph">LangGraph</a> •
134
- Powered by <a href="https://openai.com">OpenAI</a> •
135
- Interface by <a href="https://gradio.app">Gradio</a></p>
136
- <p><em>Professional architecture consultation from concept to construction</em></p>
137
- </div>
138
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  return interface
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  if __name__ == "__main__":
143
- demo = create_interface()
144
- demo.launch()
 
1
  #!/usr/bin/env python3
2
  """
3
+ Final Enhanced Gradio Web UI with Auto-Loading User Conversations
 
4
  """
5
 
6
+ import os
7
  import gradio as gr
8
+ from dotenv import load_dotenv
9
+ from graph import ArchitectureAssistant
10
+ from user_state_manager import user_state_manager
11
+ import json
12
+ import hashlib
13
+ from datetime import datetime
14
 
15
+
16
+ class GradioArchitectureApp:
17
+ def __init__(self):
18
+ load_dotenv()
19
+ api_key = os.getenv("OPENAI_API_KEY")
20
+ if not api_key:
21
+ raise ValueError("Please set OPENAI_API_KEY in .env file")
22
+
23
+ self.api_key = api_key
24
+ self.assistant = None
25
+ self.current_user_id = None
26
+ self.conversation_history = []
27
+ self.state_manager = user_state_manager
28
 
29
+ def initialize_user_session(self, user_identifier: str = None):
30
+ """Initialize user session with persistent state"""
31
+ if not user_identifier:
32
+ # Generate user ID from timestamp for demo
33
+ user_identifier = hashlib.md5(str(datetime.now()).encode()).hexdigest()[:12]
34
 
35
+ self.current_user_id = user_identifier
36
+ self.assistant = ArchitectureAssistant(self.api_key, user_identifier)
37
+
38
+ return user_identifier
39
+
40
+ def change_user_id(self, new_user_id: str):
41
+ """Change user ID and auto-load their conversation history"""
42
+ if not new_user_id.strip():
43
+ return [], "❌ Please enter a valid User ID", "", "Start chatting to see your requirements here!"
44
 
45
  try:
46
+ # Set new user ID
47
+ self.current_user_id = new_user_id.strip()
48
+
49
+ # Create new assistant instance for this user
50
+ self.assistant = ArchitectureAssistant(self.api_key, self.current_user_id)
51
+
52
+ # Try to load their latest conversation
53
+ history = self.state_manager.get_user_history(self.current_user_id)
54
 
55
+ if history:
56
+ # Load most recent conversation
57
+ latest_session = history[0]["session_id"]
58
+ success = self.assistant.load_previous_state(latest_session)
59
+
60
+ if success:
61
+ # Convert state messages to chat history
62
+ chat_history = []
63
+ messages = self.assistant.state.get("messages", [])
64
+
65
+ current_pair = [None, None]
66
+ for msg in messages:
67
+ if msg["role"] == "user":
68
+ current_pair = [msg["content"], None]
69
+ elif msg["role"] == "assistant":
70
+ if current_pair[0] is not None:
71
+ current_pair[1] = msg["content"]
72
+ chat_history.append(current_pair)
73
+ current_pair = [None, None]
74
+
75
+ status_msg = f"✅ Loaded {len(history)} previous conversation(s) for user {new_user_id}"
76
+ summary = self.get_conversation_summary()
77
+
78
+ return chat_history, status_msg, new_user_id, summary
79
+ else:
80
+ status_msg = f"✅ User {new_user_id} found but couldn't load conversation. Starting fresh."
81
+ summary = self.get_conversation_summary()
82
+ return [], status_msg, new_user_id, summary
83
+ else:
84
+ # New user
85
+ status_msg = f"✅ New user {new_user_id} - starting fresh conversation"
86
+ summary = self.get_conversation_summary()
87
+ return [], status_msg, new_user_id, summary
88
+
89
+ except Exception as e:
90
+ return [], f"❌ Error loading user {new_user_id}: {str(e)}", "", "Error loading user"
91
+
92
+ def chat_with_assistant(self, message, history, user_id_input=""):
93
+ """Process user message and return response with state saving"""
94
+ if not message.strip():
95
+ return history, "", user_id_input, self.get_conversation_summary()
96
+
97
+ # Ensure we have an assistant instance
98
+ if not self.assistant:
99
+ if user_id_input.strip():
100
+ self.current_user_id = user_id_input.strip()
101
+ self.assistant = ArchitectureAssistant(self.api_key, self.current_user_id)
102
+ else:
103
+ user_id = self.initialize_user_session()
104
+ return history, "", user_id, self.get_conversation_summary()
105
+
106
+ # Handle special commands
107
+ if message.lower() == 'reset':
108
+ self.assistant.reset_conversation()
109
+ self.conversation_history = []
110
+ status_msg = "🔄 Conversation reset! New session started."
111
+ return [[status_msg, ""]], "", self.current_user_id or "", self.get_conversation_summary()
112
+
113
+ # Get response from assistant
114
+ try:
115
+ response = self.assistant.chat(message)
116
 
117
+ # Add response to history
118
  history.append([message, response])
119
 
120
+ # Add to conversation history
121
+ self.conversation_history.append({"user": message, "assistant": response})
122
+
123
+ # Get updated summary
124
+ summary = self.get_conversation_summary()
 
 
 
 
 
 
 
 
 
125
 
126
+ return history, "", self.current_user_id or "", summary
127
+
128
+ except Exception as e:
129
+ error_msg = f"❌ Error: {str(e)}"
130
  history.append([message, error_msg])
131
+ return history, "", self.current_user_id or "", self.get_conversation_summary()
132
+
133
+ def get_conversation_summary(self):
134
+ """Get formatted conversation summary"""
135
+ if not self.assistant:
136
+ return "Enter a User ID above to start or resume a conversation!"
137
+
138
+ summary = self.assistant.get_conversation_summary()
139
+
140
+ summary_text = f"📋 **CONVERSATION SUMMARY**\n"
141
+ summary_text += f"👤 **User ID:** {self.current_user_id or 'Not set'}\n\n"
142
+
143
+ # User requirements
144
+ reqs = summary["user_requirements"]
145
+ summary_text += "**USER REQUIREMENTS:**\n"
146
+ if reqs["budget"]:
147
+ summary_text += f"• Budget: ${reqs['budget']:,.0f}\n"
148
+ if reqs["location"]:
149
+ summary_text += f"• Location: {reqs['location']}\n"
150
+ if reqs["family_size"]:
151
+ summary_text += f"• Family size: {reqs['family_size']}\n"
152
+ if reqs["lifestyle_preferences"]:
153
+ summary_text += f"• Preferences: {', '.join(reqs['lifestyle_preferences'])}\n"
154
+
155
+ # Floorplan requirements
156
+ floor_reqs = summary["floorplan_requirements"]
157
+ summary_text += "\n**FLOORPLAN REQUIREMENTS:**\n"
158
+ if floor_reqs["num_floors"]:
159
+ summary_text += f"• Floors: {floor_reqs['num_floors']}\n"
160
+ if floor_reqs["total_sqft"]:
161
+ summary_text += f"• Total sq ft: {floor_reqs['total_sqft']}\n"
162
+ if floor_reqs["lot_shape"]:
163
+ summary_text += f"• Lot shape: {floor_reqs['lot_shape']}\n"
164
+ if floor_reqs["lot_dimensions"]:
165
+ summary_text += f"• Lot dimensions: {floor_reqs['lot_dimensions']}\n"
166
+ if floor_reqs["rooms"]:
167
+ rooms_str = ", ".join([f"{r['count']}x {r['type']}" for r in floor_reqs["rooms"]])
168
+ summary_text += f"• Rooms: {rooms_str}\n"
169
+
170
+ # Project progress
171
+ agent_memory = self.assistant.state.get("agent_memory", {})
172
+ completed_phases = []
173
+ if self.assistant.state.get("detailed_floorplan", {}).get("detailed_rooms"):
174
+ completed_phases.append("Architectural Design")
175
+ if self.assistant.state.get("budget_breakdown", {}).get("total_construction_cost"):
176
+ completed_phases.append("Budget Analysis")
177
+ if agent_memory.get("structural_analysis"):
178
+ completed_phases.append("Structural Analysis")
179
+ if agent_memory.get("sustainability"):
180
+ completed_phases.append("Sustainability Review")
181
+ if agent_memory.get("permits"):
182
+ completed_phases.append("Permit Planning")
183
+ if agent_memory.get("interior_design"):
184
+ completed_phases.append("Interior Design")
185
+
186
+ summary_text += f"\n**PROJECT PROGRESS:**\n"
187
+ summary_text += f"• Completed: {len(completed_phases)}/6 phases\n"
188
+ if completed_phases:
189
+ summary_text += f"• Phases: {', '.join(completed_phases)}\n"
190
+ summary_text += f"• Total messages: {summary['total_messages']}\n"
191
+
192
+ return summary_text
193
+
194
+ def get_user_history_display(self, user_id: str):
195
+ """Get formatted user history for display"""
196
+ if not user_id.strip():
197
+ return "Please enter a User ID to view history."
198
+
199
+ try:
200
+ history = self.state_manager.get_user_history(user_id.strip())
201
+
202
+ if not history:
203
+ return f"No conversation history found for user: {user_id}"
204
+
205
+ display_text = f"📚 **CONVERSATION HISTORY FOR USER: {user_id}**\n\n"
206
+
207
+ for i, conv in enumerate(history, 1):
208
+ timestamp = conv["timestamp"]
209
+ session_id = conv["session_id"]
210
+ summary = conv.get("summary", {})
211
+
212
+ display_text += f"**#{i} - {timestamp}**\n"
213
+ display_text += f"Session ID: `{session_id}`\n"
214
+
215
+ if summary.get("total_messages", 0) > 0:
216
+ display_text += f"Messages: {summary['total_messages']}\n"
217
+
218
+ if summary.get("user_requirements"):
219
+ reqs = summary["user_requirements"]
220
+ if reqs.get("budget"):
221
+ display_text += f"Budget: {reqs['budget']}\n"
222
+ if reqs.get("location"):
223
+ display_text += f"Location: {reqs['location']}\n"
224
+ if reqs.get("family_size"):
225
+ display_text += f"Family: {reqs['family_size']} people\n"
226
+
227
+ if summary.get("floorplan_status"):
228
+ floor_status = summary["floorplan_status"]
229
+ if floor_status.get("size"):
230
+ display_text += f"House Size: {floor_status['size']}\n"
231
+ if floor_status.get("rooms"):
232
+ display_text += f"Rooms: {floor_status['rooms']}\n"
233
+
234
+ if summary.get("project_progress"):
235
+ progress = summary["project_progress"]
236
+ completed = progress.get("completed_phases", [])
237
+ percentage = progress.get("completion_percentage", 0)
238
+ display_text += f"Progress: {percentage}% - {', '.join(completed)}\n"
239
+
240
+ display_text += "\n---\n\n"
241
+
242
+ return display_text
243
 
244
  except Exception as e:
245
+ return f"❌ Error retrieving history: {str(e)}"
246
+
247
+ def get_all_users_summary(self):
248
+ """Get summary of all users"""
249
+ try:
250
+ users = self.state_manager.get_all_users()
251
+
252
+ if not users:
253
+ return "No users found in the system."
254
+
255
+ display_text = f"👥 **ALL USERS SUMMARY** ({len(users)} users)\n\n"
256
+
257
+ for i, user in enumerate(users, 1):
258
+ user_id = user["user_id"]
259
+ total_conversations = user["total_conversations"]
260
+ last_activity = user["last_activity"]
261
+ latest_summary = user.get("latest_summary", {})
262
+
263
+ display_text += f"**#{i} User: {user_id}**\n"
264
+ display_text += f"Conversations: {total_conversations}\n"
265
+ display_text += f"Last Activity: {last_activity}\n"
266
+
267
+ if latest_summary.get("project_progress"):
268
+ progress = latest_summary["project_progress"]
269
+ percentage = progress.get("completion_percentage", 0)
270
+ display_text += f"Latest Progress: {percentage}%\n"
271
+
272
+ display_text += "\n---\n\n"
273
+
274
+ return display_text
275
+
276
+ except Exception as e:
277
+ return f"❌ Error retrieving users summary: {str(e)}"
278
 
 
 
 
 
 
279
 
280
+ def create_gradio_interface():
281
+ """Create and return the final enhanced Gradio interface"""
282
+ app = GradioArchitectureApp()
283
+
284
+ # Custom CSS for better styling
285
+ css = """
286
+ .gradio-container {
287
+ max-width: 1400px !important;
288
+ }
289
+ .chat-message {
290
+ border-radius: 10px !important;
291
+ }
292
+ .user-id-input {
293
+ background-color: #e3f2fd !important;
294
+ font-weight: bold !important;
295
+ }
296
+ """
297
 
298
+ with gr.Blocks(
299
+ title="🏠 Architecture Assistant - Multi-User",
300
+ theme=gr.themes.Soft(),
301
+ css=css
302
+ ) as interface:
303
+
304
  gr.HTML("""
305
+ <div style="text-align: center; margin-bottom: 20px;">
306
  <h1>🏠 Residential Architecture Assistant</h1>
307
+ <p>Multi-user system with automatic conversation loading and precise floorplan specifications</p>
 
308
  </div>
309
  """)
310
 
311
+ # User ID input at the top
312
+ with gr.Row():
313
+ with gr.Column(scale=4):
314
+ user_id_input = gr.Textbox(
315
+ placeholder="Enter your User ID (e.g., 'john_smith_house') - automatically loads your conversation history",
316
+ label="👤 User ID",
317
+ elem_classes=["user-id-input"],
318
+ interactive=True
319
+ )
320
+ with gr.Column(scale=1):
321
+ load_user_btn = gr.Button("Load User", variant="primary")
322
 
323
+ status_display = gr.Markdown(
324
+ label="📊 Status",
325
+ value="Enter a User ID above to start or resume your architectural consultation."
326
  )
327
 
328
+ # Main interface
329
+ with gr.Row():
330
+ with gr.Column(scale=2):
331
+ chatbot = gr.Chatbot(
332
+ label="💬 Chat with your Architecture Assistant",
333
+ height=500,
334
+ show_copy_button=True,
335
+ bubble_full_width=False
336
+ )
337
+
338
+ with gr.Row():
339
+ msg = gr.Textbox(
340
+ placeholder="Ask about home design, budget, or floorplan planning...",
341
+ container=False,
342
+ scale=4,
343
+ label="Your message"
344
+ )
345
+ send_btn = gr.Button("Send", variant="primary", scale=1)
346
+
347
+ with gr.Row():
348
+ clear_btn = gr.Button("🔄 Reset Conversation", variant="secondary")
349
+
350
+ gr.HTML("""
351
+ <div style="margin-top: 15px; padding: 10px; background-color: #f0f0f0; border-radius: 5px;">
352
+ <h4>💡 How to use:</h4>
353
+ <ul>
354
+ <li><strong>New User:</strong> Enter any User ID above and start chatting</li>
355
+ <li><strong>Returning User:</strong> Enter your previous User ID - conversation automatically loads</li>
356
+ <li><strong>Multiple Users:</strong> Each User ID has completely separate conversations</li>
357
+ <li><strong>Floorplans:</strong> Get precise room dimensions for drawing actual plans</li>
358
+ </ul>
359
+ </div>
360
+ """)
361
+
362
+ with gr.Column(scale=1):
363
+ summary_display = gr.Markdown(
364
+ label="📋 Current Project Summary",
365
+ value="Enter a User ID above to start or resume a conversation!"
366
+ )
367
+
368
+ gr.HTML("""
369
+ <div style="margin-top: 20px; padding: 10px; background-color: #e8f4f8; border-radius: 5px;">
370
+ <h4>🤖 Available Core Specialists:</h4>
371
+ <p><strong>🧭 Router:</strong> Intelligent conversation routing</p>
372
+ <p><strong>🏛️ General Design:</strong> Architecture principles & design guidance</p>
373
+ <p><strong>💰 Budget Analysis:</strong> Montreal market costs & feasibility</p>
374
+ <p><strong>📐 Floorplan:</strong> Layout planning with exact dimensions</p>
375
+ <p><strong>📋 Regulation:</strong> Montreal building codes & permit requirements</p>
376
+ <br>
377
+ </div>
378
+ """)
379
 
380
+ # Example prompts
381
  with gr.Row():
382
+ gr.HTML("<h3>🚀 Try these examples:</h3>")
 
383
 
384
+ with gr.Row():
385
+ example1 = gr.Button("What are key home design principles?", size="sm")
386
+ example2 = gr.Button("I have $800k budget for Montreal - realistic?", size="sm")
387
+ example3 = gr.Button("Need 3BR/2BA house, 2500 sqft - help floorplan design", size="sm")
388
+
389
+ with gr.Row():
390
+ example4 = gr.Button("Show me a detailed budget breakdown", size="sm")
391
+ example5 = gr.Button("What permits do I need for my house design in Montreal?", size="sm")
392
+ example6 = gr.Button("Help me with Montreal building codes", size="sm")
393
+
394
+ # History tab
395
+ with gr.Accordion("📚 User History & Management", open=False):
396
+ with gr.Row():
397
+ with gr.Column():
398
+ gr.HTML("<h4>📋 View User History</h4>")
399
+
400
+ with gr.Row():
401
+ history_user_id = gr.Textbox(
402
+ placeholder="Enter User ID to view history",
403
+ label="User ID for History",
404
+ scale=3
405
+ )
406
+ view_history_btn = gr.Button("View History", variant="primary", scale=1)
407
+
408
+ user_history_display = gr.Markdown(
409
+ label="User History",
410
+ value="Enter a User ID to view their conversation history."
411
+ )
412
+
413
+ with gr.Column():
414
+ gr.HTML("<h4>👥 All Users Overview</h4>")
415
+
416
+ view_all_btn = gr.Button("View All Users", variant="secondary")
417
+
418
+ all_users_display = gr.Markdown(
419
+ label="All Users Summary",
420
+ value="Click 'View All Users' to see system overview."
421
+ )
422
 
423
  # Event handlers
424
+ def send_message(message, history, user_id):
425
+ return app.chat_with_assistant(message, history, user_id)
 
426
 
427
+ def change_user(user_id):
428
+ return app.change_user_id(user_id)
429
+
430
+ def view_user_history(user_id):
431
+ return app.get_user_history_display(user_id)
432
+
433
+ def view_all_users():
434
+ return app.get_all_users_summary()
435
+
436
+ # Wire up the events
437
+ load_user_btn.click(change_user, [user_id_input], [chatbot, status_display, user_id_input, summary_display])
438
+
439
+ msg.submit(send_message, [msg, chatbot, user_id_input], [chatbot, msg, user_id_input, summary_display])
440
+ send_btn.click(send_message, [msg, chatbot, user_id_input], [chatbot, msg, user_id_input, summary_display])
441
+
442
+ clear_btn.click(lambda: app.assistant.reset_conversation() if app.assistant else None, outputs=[chatbot, summary_display])
443
+
444
+ # History management events
445
+ view_history_btn.click(view_user_history, [history_user_id], [user_history_display])
446
+ view_all_btn.click(view_all_users, outputs=[all_users_display])
447
+
448
+ # Example button handlers
449
+ example1.click(lambda: "What are key home design principles?", outputs=msg)
450
+ example2.click(lambda: "I have $800k budget for Montreal - is that realistic?", outputs=msg)
451
+ example3.click(lambda: "I need a 3 bedroom, 2 bathroom house with about 2500 square feet. Can you help me plan the layout?", outputs=msg)
452
+ example4.click(lambda: "Can you show me a detailed budget breakdown for my house design?", outputs=msg)
453
+ example5.click(lambda: "What permits do I need for my house design in Montreal?", outputs=msg)
454
+ example6.click(lambda: "Help me understand Montreal building codes and regulations", outputs=msg)
455
 
456
  return interface
457
 
458
+
459
+ def main():
460
+ """Launch the final enhanced Gradio interface"""
461
+ try:
462
+ interface = create_gradio_interface()
463
+ print("🏠 Starting Final Architecture Assistant Web UI...")
464
+ print("👥 Features: Multi-user support, auto-loading conversations, precise floorplans")
465
+ print("📝 Make sure you have set OPENAI_API_KEY in your .env file")
466
+ print("🔧 Fixed issues: User conversation resumption, exact floorplan dimensions, SVG generation")
467
+
468
+ interface.launch(
469
+ server_name="0.0.0.0",
470
+ server_port=7861,
471
+ share=False,
472
+ show_error=True,
473
+ quiet=False
474
+ )
475
+
476
+ except ValueError as e:
477
+ print(f"❌ Configuration Error: {e}")
478
+ print("Please copy .env.example to .env and add your OpenAI API key")
479
+ except Exception as e:
480
+ print(f"❌ Error starting the application: {e}")
481
+
482
+
483
  if __name__ == "__main__":
484
+ main()
 
gradio_app.py DELETED
@@ -1,484 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Final Enhanced Gradio Web UI with Auto-Loading User Conversations
4
- """
5
-
6
- import os
7
- import gradio as gr
8
- from dotenv import load_dotenv
9
- from graph import ArchitectureAssistant
10
- from user_state_manager import user_state_manager
11
- import json
12
- import hashlib
13
- from datetime import datetime
14
-
15
-
16
- class GradioArchitectureApp:
17
- def __init__(self):
18
- load_dotenv()
19
- api_key = os.getenv("OPENAI_API_KEY")
20
- if not api_key:
21
- raise ValueError("Please set OPENAI_API_KEY in .env file")
22
-
23
- self.api_key = api_key
24
- self.assistant = None
25
- self.current_user_id = None
26
- self.conversation_history = []
27
- self.state_manager = user_state_manager
28
-
29
- def initialize_user_session(self, user_identifier: str = None):
30
- """Initialize user session with persistent state"""
31
- if not user_identifier:
32
- # Generate user ID from timestamp for demo
33
- user_identifier = hashlib.md5(str(datetime.now()).encode()).hexdigest()[:12]
34
-
35
- self.current_user_id = user_identifier
36
- self.assistant = ArchitectureAssistant(self.api_key, user_identifier)
37
-
38
- return user_identifier
39
-
40
- def change_user_id(self, new_user_id: str):
41
- """Change user ID and auto-load their conversation history"""
42
- if not new_user_id.strip():
43
- return [], "❌ Please enter a valid User ID", "", "Start chatting to see your requirements here!"
44
-
45
- try:
46
- # Set new user ID
47
- self.current_user_id = new_user_id.strip()
48
-
49
- # Create new assistant instance for this user
50
- self.assistant = ArchitectureAssistant(self.api_key, self.current_user_id)
51
-
52
- # Try to load their latest conversation
53
- history = self.state_manager.get_user_history(self.current_user_id)
54
-
55
- if history:
56
- # Load most recent conversation
57
- latest_session = history[0]["session_id"]
58
- success = self.assistant.load_previous_state(latest_session)
59
-
60
- if success:
61
- # Convert state messages to chat history
62
- chat_history = []
63
- messages = self.assistant.state.get("messages", [])
64
-
65
- current_pair = [None, None]
66
- for msg in messages:
67
- if msg["role"] == "user":
68
- current_pair = [msg["content"], None]
69
- elif msg["role"] == "assistant":
70
- if current_pair[0] is not None:
71
- current_pair[1] = msg["content"]
72
- chat_history.append(current_pair)
73
- current_pair = [None, None]
74
-
75
- status_msg = f"✅ Loaded {len(history)} previous conversation(s) for user {new_user_id}"
76
- summary = self.get_conversation_summary()
77
-
78
- return chat_history, status_msg, new_user_id, summary
79
- else:
80
- status_msg = f"✅ User {new_user_id} found but couldn't load conversation. Starting fresh."
81
- summary = self.get_conversation_summary()
82
- return [], status_msg, new_user_id, summary
83
- else:
84
- # New user
85
- status_msg = f"✅ New user {new_user_id} - starting fresh conversation"
86
- summary = self.get_conversation_summary()
87
- return [], status_msg, new_user_id, summary
88
-
89
- except Exception as e:
90
- return [], f"❌ Error loading user {new_user_id}: {str(e)}", "", "Error loading user"
91
-
92
- def chat_with_assistant(self, message, history, user_id_input=""):
93
- """Process user message and return response with state saving"""
94
- if not message.strip():
95
- return history, "", user_id_input, self.get_conversation_summary()
96
-
97
- # Ensure we have an assistant instance
98
- if not self.assistant:
99
- if user_id_input.strip():
100
- self.current_user_id = user_id_input.strip()
101
- self.assistant = ArchitectureAssistant(self.api_key, self.current_user_id)
102
- else:
103
- user_id = self.initialize_user_session()
104
- return history, "", user_id, self.get_conversation_summary()
105
-
106
- # Handle special commands
107
- if message.lower() == 'reset':
108
- self.assistant.reset_conversation()
109
- self.conversation_history = []
110
- status_msg = "🔄 Conversation reset! New session started."
111
- return [[status_msg, ""]], "", self.current_user_id or "", self.get_conversation_summary()
112
-
113
- # Get response from assistant
114
- try:
115
- response = self.assistant.chat(message)
116
-
117
- # Add response to history
118
- history.append([message, response])
119
-
120
- # Add to conversation history
121
- self.conversation_history.append({"user": message, "assistant": response})
122
-
123
- # Get updated summary
124
- summary = self.get_conversation_summary()
125
-
126
- return history, "", self.current_user_id or "", summary
127
-
128
- except Exception as e:
129
- error_msg = f"❌ Error: {str(e)}"
130
- history.append([message, error_msg])
131
- return history, "", self.current_user_id or "", self.get_conversation_summary()
132
-
133
- def get_conversation_summary(self):
134
- """Get formatted conversation summary"""
135
- if not self.assistant:
136
- return "Enter a User ID above to start or resume a conversation!"
137
-
138
- summary = self.assistant.get_conversation_summary()
139
-
140
- summary_text = f"📋 **CONVERSATION SUMMARY**\n"
141
- summary_text += f"👤 **User ID:** {self.current_user_id or 'Not set'}\n\n"
142
-
143
- # User requirements
144
- reqs = summary["user_requirements"]
145
- summary_text += "**USER REQUIREMENTS:**\n"
146
- if reqs["budget"]:
147
- summary_text += f"• Budget: ${reqs['budget']:,.0f}\n"
148
- if reqs["location"]:
149
- summary_text += f"• Location: {reqs['location']}\n"
150
- if reqs["family_size"]:
151
- summary_text += f"• Family size: {reqs['family_size']}\n"
152
- if reqs["lifestyle_preferences"]:
153
- summary_text += f"• Preferences: {', '.join(reqs['lifestyle_preferences'])}\n"
154
-
155
- # Floorplan requirements
156
- floor_reqs = summary["floorplan_requirements"]
157
- summary_text += "\n**FLOORPLAN REQUIREMENTS:**\n"
158
- if floor_reqs["num_floors"]:
159
- summary_text += f"• Floors: {floor_reqs['num_floors']}\n"
160
- if floor_reqs["total_sqft"]:
161
- summary_text += f"• Total sq ft: {floor_reqs['total_sqft']}\n"
162
- if floor_reqs["lot_shape"]:
163
- summary_text += f"• Lot shape: {floor_reqs['lot_shape']}\n"
164
- if floor_reqs["lot_dimensions"]:
165
- summary_text += f"• Lot dimensions: {floor_reqs['lot_dimensions']}\n"
166
- if floor_reqs["rooms"]:
167
- rooms_str = ", ".join([f"{r['count']}x {r['type']}" for r in floor_reqs["rooms"]])
168
- summary_text += f"• Rooms: {rooms_str}\n"
169
-
170
- # Project progress
171
- agent_memory = self.assistant.state.get("agent_memory", {})
172
- completed_phases = []
173
- if self.assistant.state.get("detailed_floorplan", {}).get("detailed_rooms"):
174
- completed_phases.append("Architectural Design")
175
- if self.assistant.state.get("budget_breakdown", {}).get("total_construction_cost"):
176
- completed_phases.append("Budget Analysis")
177
- if agent_memory.get("structural_analysis"):
178
- completed_phases.append("Structural Analysis")
179
- if agent_memory.get("sustainability"):
180
- completed_phases.append("Sustainability Review")
181
- if agent_memory.get("permits"):
182
- completed_phases.append("Permit Planning")
183
- if agent_memory.get("interior_design"):
184
- completed_phases.append("Interior Design")
185
-
186
- summary_text += f"\n**PROJECT PROGRESS:**\n"
187
- summary_text += f"• Completed: {len(completed_phases)}/6 phases\n"
188
- if completed_phases:
189
- summary_text += f"• Phases: {', '.join(completed_phases)}\n"
190
- summary_text += f"• Total messages: {summary['total_messages']}\n"
191
-
192
- return summary_text
193
-
194
- def get_user_history_display(self, user_id: str):
195
- """Get formatted user history for display"""
196
- if not user_id.strip():
197
- return "Please enter a User ID to view history."
198
-
199
- try:
200
- history = self.state_manager.get_user_history(user_id.strip())
201
-
202
- if not history:
203
- return f"No conversation history found for user: {user_id}"
204
-
205
- display_text = f"📚 **CONVERSATION HISTORY FOR USER: {user_id}**\n\n"
206
-
207
- for i, conv in enumerate(history, 1):
208
- timestamp = conv["timestamp"]
209
- session_id = conv["session_id"]
210
- summary = conv.get("summary", {})
211
-
212
- display_text += f"**#{i} - {timestamp}**\n"
213
- display_text += f"Session ID: `{session_id}`\n"
214
-
215
- if summary.get("total_messages", 0) > 0:
216
- display_text += f"Messages: {summary['total_messages']}\n"
217
-
218
- if summary.get("user_requirements"):
219
- reqs = summary["user_requirements"]
220
- if reqs.get("budget"):
221
- display_text += f"Budget: {reqs['budget']}\n"
222
- if reqs.get("location"):
223
- display_text += f"Location: {reqs['location']}\n"
224
- if reqs.get("family_size"):
225
- display_text += f"Family: {reqs['family_size']} people\n"
226
-
227
- if summary.get("floorplan_status"):
228
- floor_status = summary["floorplan_status"]
229
- if floor_status.get("size"):
230
- display_text += f"House Size: {floor_status['size']}\n"
231
- if floor_status.get("rooms"):
232
- display_text += f"Rooms: {floor_status['rooms']}\n"
233
-
234
- if summary.get("project_progress"):
235
- progress = summary["project_progress"]
236
- completed = progress.get("completed_phases", [])
237
- percentage = progress.get("completion_percentage", 0)
238
- display_text += f"Progress: {percentage}% - {', '.join(completed)}\n"
239
-
240
- display_text += "\n---\n\n"
241
-
242
- return display_text
243
-
244
- except Exception as e:
245
- return f"❌ Error retrieving history: {str(e)}"
246
-
247
- def get_all_users_summary(self):
248
- """Get summary of all users"""
249
- try:
250
- users = self.state_manager.get_all_users()
251
-
252
- if not users:
253
- return "No users found in the system."
254
-
255
- display_text = f"👥 **ALL USERS SUMMARY** ({len(users)} users)\n\n"
256
-
257
- for i, user in enumerate(users, 1):
258
- user_id = user["user_id"]
259
- total_conversations = user["total_conversations"]
260
- last_activity = user["last_activity"]
261
- latest_summary = user.get("latest_summary", {})
262
-
263
- display_text += f"**#{i} User: {user_id}**\n"
264
- display_text += f"Conversations: {total_conversations}\n"
265
- display_text += f"Last Activity: {last_activity}\n"
266
-
267
- if latest_summary.get("project_progress"):
268
- progress = latest_summary["project_progress"]
269
- percentage = progress.get("completion_percentage", 0)
270
- display_text += f"Latest Progress: {percentage}%\n"
271
-
272
- display_text += "\n---\n\n"
273
-
274
- return display_text
275
-
276
- except Exception as e:
277
- return f"❌ Error retrieving users summary: {str(e)}"
278
-
279
-
280
- def create_gradio_interface():
281
- """Create and return the final enhanced Gradio interface"""
282
- app = GradioArchitectureApp()
283
-
284
- # Custom CSS for better styling
285
- css = """
286
- .gradio-container {
287
- max-width: 1400px !important;
288
- }
289
- .chat-message {
290
- border-radius: 10px !important;
291
- }
292
- .user-id-input {
293
- background-color: #e3f2fd !important;
294
- font-weight: bold !important;
295
- }
296
- """
297
-
298
- with gr.Blocks(
299
- title="🏠 Architecture Assistant - Multi-User",
300
- theme=gr.themes.Soft(),
301
- css=css
302
- ) as interface:
303
-
304
- gr.HTML("""
305
- <div style="text-align: center; margin-bottom: 20px;">
306
- <h1>🏠 Residential Architecture Assistant</h1>
307
- <p>Multi-user system with automatic conversation loading and precise floorplan specifications</p>
308
- </div>
309
- """)
310
-
311
- # User ID input at the top
312
- with gr.Row():
313
- with gr.Column(scale=4):
314
- user_id_input = gr.Textbox(
315
- placeholder="Enter your User ID (e.g., 'john_smith_house') - automatically loads your conversation history",
316
- label="👤 User ID",
317
- elem_classes=["user-id-input"],
318
- interactive=True
319
- )
320
- with gr.Column(scale=1):
321
- load_user_btn = gr.Button("Load User", variant="primary")
322
-
323
- status_display = gr.Markdown(
324
- label="📊 Status",
325
- value="Enter a User ID above to start or resume your architectural consultation."
326
- )
327
-
328
- # Main interface
329
- with gr.Row():
330
- with gr.Column(scale=2):
331
- chatbot = gr.Chatbot(
332
- label="💬 Chat with your Architecture Assistant",
333
- height=500,
334
- show_copy_button=True,
335
- bubble_full_width=False
336
- )
337
-
338
- with gr.Row():
339
- msg = gr.Textbox(
340
- placeholder="Ask about home design, budget, or floorplan planning...",
341
- container=False,
342
- scale=4,
343
- label="Your message"
344
- )
345
- send_btn = gr.Button("Send", variant="primary", scale=1)
346
-
347
- with gr.Row():
348
- clear_btn = gr.Button("🔄 Reset Conversation", variant="secondary")
349
-
350
- gr.HTML("""
351
- <div style="margin-top: 15px; padding: 10px; background-color: #f0f0f0; border-radius: 5px;">
352
- <h4>💡 How to use:</h4>
353
- <ul>
354
- <li><strong>New User:</strong> Enter any User ID above and start chatting</li>
355
- <li><strong>Returning User:</strong> Enter your previous User ID - conversation automatically loads</li>
356
- <li><strong>Multiple Users:</strong> Each User ID has completely separate conversations</li>
357
- <li><strong>Floorplans:</strong> Get precise room dimensions for drawing actual plans</li>
358
- </ul>
359
- </div>
360
- """)
361
-
362
- with gr.Column(scale=1):
363
- summary_display = gr.Markdown(
364
- label="📋 Current Project Summary",
365
- value="Enter a User ID above to start or resume a conversation!"
366
- )
367
-
368
- gr.HTML("""
369
- <div style="margin-top: 20px; padding: 10px; background-color: #e8f4f8; border-radius: 5px;">
370
- <h4>🤖 Available Core Specialists:</h4>
371
- <p><strong>🧭 Router:</strong> Intelligent conversation routing</p>
372
- <p><strong>🏛️ General Design:</strong> Architecture principles & design guidance</p>
373
- <p><strong>💰 Budget Analysis:</strong> Montreal market costs & feasibility</p>
374
- <p><strong>📐 Floorplan:</strong> Layout planning with exact dimensions</p>
375
- <p><strong>📋 Regulation:</strong> Montreal building codes & permit requirements</p>
376
- <br>
377
- </div>
378
- """)
379
-
380
- # Example prompts
381
- with gr.Row():
382
- gr.HTML("<h3>🚀 Try these examples:</h3>")
383
-
384
- with gr.Row():
385
- example1 = gr.Button("What are key home design principles?", size="sm")
386
- example2 = gr.Button("I have $800k budget for Montreal - realistic?", size="sm")
387
- example3 = gr.Button("Need 3BR/2BA house, 2500 sqft - help floorplan design", size="sm")
388
-
389
- with gr.Row():
390
- example4 = gr.Button("Show me a detailed budget breakdown", size="sm")
391
- example5 = gr.Button("What permits do I need for my house design in Montreal?", size="sm")
392
- example6 = gr.Button("Help me with Montreal building codes", size="sm")
393
-
394
- # History tab
395
- with gr.Accordion("📚 User History & Management", open=False):
396
- with gr.Row():
397
- with gr.Column():
398
- gr.HTML("<h4>📋 View User History</h4>")
399
-
400
- with gr.Row():
401
- history_user_id = gr.Textbox(
402
- placeholder="Enter User ID to view history",
403
- label="User ID for History",
404
- scale=3
405
- )
406
- view_history_btn = gr.Button("View History", variant="primary", scale=1)
407
-
408
- user_history_display = gr.Markdown(
409
- label="User History",
410
- value="Enter a User ID to view their conversation history."
411
- )
412
-
413
- with gr.Column():
414
- gr.HTML("<h4>👥 All Users Overview</h4>")
415
-
416
- view_all_btn = gr.Button("View All Users", variant="secondary")
417
-
418
- all_users_display = gr.Markdown(
419
- label="All Users Summary",
420
- value="Click 'View All Users' to see system overview."
421
- )
422
-
423
- # Event handlers
424
- def send_message(message, history, user_id):
425
- return app.chat_with_assistant(message, history, user_id)
426
-
427
- def change_user(user_id):
428
- return app.change_user_id(user_id)
429
-
430
- def view_user_history(user_id):
431
- return app.get_user_history_display(user_id)
432
-
433
- def view_all_users():
434
- return app.get_all_users_summary()
435
-
436
- # Wire up the events
437
- load_user_btn.click(change_user, [user_id_input], [chatbot, status_display, user_id_input, summary_display])
438
-
439
- msg.submit(send_message, [msg, chatbot, user_id_input], [chatbot, msg, user_id_input, summary_display])
440
- send_btn.click(send_message, [msg, chatbot, user_id_input], [chatbot, msg, user_id_input, summary_display])
441
-
442
- clear_btn.click(lambda: app.assistant.reset_conversation() if app.assistant else None, outputs=[chatbot, summary_display])
443
-
444
- # History management events
445
- view_history_btn.click(view_user_history, [history_user_id], [user_history_display])
446
- view_all_btn.click(view_all_users, outputs=[all_users_display])
447
-
448
- # Example button handlers
449
- example1.click(lambda: "What are key home design principles?", outputs=msg)
450
- example2.click(lambda: "I have $800k budget for Montreal - is that realistic?", outputs=msg)
451
- example3.click(lambda: "I need a 3 bedroom, 2 bathroom house with about 2500 square feet. Can you help me plan the layout?", outputs=msg)
452
- example4.click(lambda: "Can you show me a detailed budget breakdown for my house design?", outputs=msg)
453
- example5.click(lambda: "What permits do I need for my house design in Montreal?", outputs=msg)
454
- example6.click(lambda: "Help me understand Montreal building codes and regulations", outputs=msg)
455
-
456
- return interface
457
-
458
-
459
- def main():
460
- """Launch the final enhanced Gradio interface"""
461
- try:
462
- interface = create_gradio_interface()
463
- print("🏠 Starting Final Architecture Assistant Web UI...")
464
- print("👥 Features: Multi-user support, auto-loading conversations, precise floorplans")
465
- print("📝 Make sure you have set OPENAI_API_KEY in your .env file")
466
- print("🔧 Fixed issues: User conversation resumption, exact floorplan dimensions, SVG generation")
467
-
468
- interface.launch(
469
- server_name="0.0.0.0",
470
- server_port=7861,
471
- share=False,
472
- show_error=True,
473
- quiet=False
474
- )
475
-
476
- except ValueError as e:
477
- print(f"❌ Configuration Error: {e}")
478
- print("Please copy .env.example to .env and add your OpenAI API key")
479
- except Exception as e:
480
- print(f"❌ Error starting the application: {e}")
481
-
482
-
483
- if __name__ == "__main__":
484
- main()