|
|
import os |
|
|
import gradio as gr |
|
|
from langchain_openai import ChatOpenAI |
|
|
from crewai import Agent, Task, Crew, LLM |
|
|
from crewai_tools import SerperDevTool |
|
|
from typing import List, Tuple |
|
|
import time |
|
|
|
|
|
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") |
|
|
SERPER_API_KEY = os.getenv("SERPER_API_KEY") |
|
|
|
|
|
if not OPENROUTER_API_KEY: |
|
|
raise ValueError("Missing OPENROUTER_API_KEY environment variable") |
|
|
if not SERPER_API_KEY: |
|
|
raise ValueError("Missing SERPER_API_KEY environment variable") |
|
|
|
|
|
|
|
|
crew_llm = LLM( |
|
|
model="openrouter/deepseek/deepseek-r1", |
|
|
base_url="https://openrouter.ai/api/v1", |
|
|
api_key=OPENROUTER_API_KEY, |
|
|
temperature=0.7 |
|
|
) |
|
|
|
|
|
|
|
|
direct_llm = ChatOpenAI( |
|
|
model="deepseek/deepseek-r1", |
|
|
openai_api_key=OPENROUTER_API_KEY, |
|
|
openai_api_base="https://openrouter.ai/api/v1", |
|
|
temperature=0.7, |
|
|
max_tokens=2000 |
|
|
) |
|
|
|
|
|
class ConstructionChatbot: |
|
|
def __init__(self): |
|
|
self.memory: List[Tuple[str, str]] = [] |
|
|
self.setup_tools() |
|
|
self.setup_crew() |
|
|
|
|
|
def setup_tools(self): |
|
|
"""Set up web search tools""" |
|
|
try: |
|
|
self.search_tool = SerperDevTool() |
|
|
print("β
Web search tool initialized successfully") |
|
|
except Exception as e: |
|
|
self.search_tool = None |
|
|
print(f"β οΈ Warning: Could not initialize web search tool: {e}") |
|
|
|
|
|
def setup_crew(self): |
|
|
"""Set up CrewAI agents and tasks""" |
|
|
|
|
|
tools = [] |
|
|
if self.search_tool: |
|
|
tools.append(self.search_tool) |
|
|
|
|
|
|
|
|
self.construction_agent = Agent( |
|
|
role='Construction Expert Assistant', |
|
|
goal='Provide accurate construction-related information ONLY. Reject all non-construction queries.', |
|
|
backstory="""You are a specialized construction industry expert with deep knowledge in: |
|
|
- Building safety and regulations |
|
|
- Fire safety codes and compliance |
|
|
- Construction materials and costs |
|
|
- Project management methodologies |
|
|
- Heavy machinery and equipment |
|
|
- Civil engineering principles |
|
|
- Structural design and analysis |
|
|
- Site management and safety protocols |
|
|
|
|
|
IMPORTANT: You MUST ONLY respond to construction-related questions. |
|
|
If a user asks about anything not related to construction, building, |
|
|
engineering, safety, materials, or project management, you must respond |
|
|
with EXACTLY: "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics." |
|
|
|
|
|
When you need current information about construction topics, use the search tool.""", |
|
|
llm=crew_llm, |
|
|
tools=tools, |
|
|
verbose=True, |
|
|
allow_delegation=False, |
|
|
max_iter=3, |
|
|
max_execution_time=45 |
|
|
) |
|
|
|
|
|
|
|
|
if self.search_tool: |
|
|
self.research_agent = Agent( |
|
|
role='Construction Research Specialist', |
|
|
goal='Search and gather current construction-related information from the internet ONLY', |
|
|
backstory="""You are a specialized researcher focused exclusively on construction industry topics. |
|
|
You search for the most current information about: |
|
|
- Construction practices and regulations |
|
|
- Building costs and material prices |
|
|
- Safety standards and compliance requirements |
|
|
- Industry trends and new technologies |
|
|
- Engineering standards and best practices |
|
|
|
|
|
You ONLY research construction-related topics. If asked to research non-construction |
|
|
topics, decline politely and redirect to construction subjects.""", |
|
|
llm=crew_llm, |
|
|
tools=[self.search_tool], |
|
|
verbose=True, |
|
|
allow_delegation=False, |
|
|
max_iter=2, |
|
|
max_execution_time=30 |
|
|
) |
|
|
else: |
|
|
self.research_agent = None |
|
|
|
|
|
def add_to_memory(self, user_query: str, response: str): |
|
|
"""Add interaction to rolling memory window""" |
|
|
self.memory.append((user_query, response)) |
|
|
if len(self.memory) > 5: |
|
|
self.memory.pop(0) |
|
|
|
|
|
def get_chat_history(self) -> str: |
|
|
"""Format chat history for prompt""" |
|
|
if not self.memory: |
|
|
return "No previous conversation." |
|
|
|
|
|
history = "" |
|
|
for i, (user_msg, bot_msg) in enumerate(self.memory, 1): |
|
|
history += f"Message {i}:\nUser: {user_msg}\nAssistant: {bot_msg}\n\n" |
|
|
return history.strip() |
|
|
|
|
|
def is_construction_related(self, query: str) -> bool: |
|
|
"""Simple check if query is construction-related""" |
|
|
construction_keywords = [ |
|
|
'construction', 'building', 'concrete', 'steel', 'foundation', 'safety', |
|
|
'project management', 'engineering', 'structure', 'material', 'cost', |
|
|
'regulation', 'fire safety', 'osha', 'machinery', 'equipment', 'site', |
|
|
'contractor', 'cement', 'rebar', 'excavation', 'blueprint', 'architect', |
|
|
'electrical', 'plumbing', 'hvac', 'roofing', 'insulation', 'drywall' |
|
|
] |
|
|
|
|
|
query_lower = query.lower() |
|
|
return any(keyword in query_lower for keyword in construction_keywords) |
|
|
|
|
|
def generate_response_with_crew(self, user_query: str) -> str: |
|
|
"""Generate response using CrewAI with web search capabilities""" |
|
|
|
|
|
if not self.is_construction_related(user_query): |
|
|
response = "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics." |
|
|
self.add_to_memory(user_query, response) |
|
|
return response |
|
|
|
|
|
chat_history = self.get_chat_history() |
|
|
|
|
|
try: |
|
|
|
|
|
search_keywords = ['current', 'latest', 'recent', 'today', '2024', '2025', 'price', 'cost', 'regulation', 'new', 'trend'] |
|
|
needs_search = any(keyword in user_query.lower() for keyword in search_keywords) |
|
|
|
|
|
if needs_search and self.research_agent: |
|
|
|
|
|
research_task = Task( |
|
|
description=f"""Search for current construction-related information about: {user_query} |
|
|
|
|
|
Focus on finding: |
|
|
- Latest construction industry data |
|
|
- Current material prices and costs |
|
|
- Recent regulations and safety updates |
|
|
- New construction technologies and methods |
|
|
- Industry trends and market information |
|
|
|
|
|
Search query should be concise and focused on construction industry information. |
|
|
""", |
|
|
expected_output="Current, accurate construction industry information and data", |
|
|
agent=self.research_agent |
|
|
) |
|
|
|
|
|
|
|
|
response_task = Task( |
|
|
description=f"""Based on research findings and chat history, provide a comprehensive response to: {user_query} |
|
|
|
|
|
Chat history: {chat_history} |
|
|
|
|
|
Guidelines: |
|
|
- Use the research data to provide accurate, current information |
|
|
- Focus on construction industry expertise |
|
|
- Provide practical, actionable advice |
|
|
- Include specific details like prices, regulations, or technical specifications when available |
|
|
- Structure the response clearly and professionally |
|
|
""", |
|
|
expected_output="Detailed, informative construction industry response with current data", |
|
|
agent=self.construction_agent, |
|
|
context=[research_task] |
|
|
) |
|
|
|
|
|
|
|
|
crew = Crew( |
|
|
agents=[self.research_agent, self.construction_agent], |
|
|
tasks=[research_task, response_task], |
|
|
verbose=False |
|
|
) |
|
|
|
|
|
else: |
|
|
|
|
|
response_task = Task( |
|
|
description=f"""Provide expert construction advice for: {user_query} |
|
|
|
|
|
Chat history: {chat_history} |
|
|
|
|
|
Guidelines: |
|
|
- Draw from your construction industry expertise |
|
|
- Provide detailed, accurate information |
|
|
- Include relevant safety considerations |
|
|
- Suggest best practices and standards |
|
|
- Structure the response professionally |
|
|
""", |
|
|
expected_output="Expert construction industry advice and information", |
|
|
agent=self.construction_agent |
|
|
) |
|
|
|
|
|
|
|
|
crew = Crew( |
|
|
agents=[self.construction_agent], |
|
|
tasks=[response_task], |
|
|
verbose=False |
|
|
) |
|
|
|
|
|
|
|
|
result = crew.kickoff() |
|
|
response = str(result).strip() |
|
|
|
|
|
|
|
|
if not response or len(response) < 10: |
|
|
response = "I apologize, but I'm having trouble generating a proper response. Could you please rephrase your construction-related question?" |
|
|
|
|
|
|
|
|
self.add_to_memory(user_query, response) |
|
|
return response |
|
|
|
|
|
except Exception as e: |
|
|
print(f"CrewAI Error: {e}") |
|
|
|
|
|
return self.generate_response_direct(user_query) |
|
|
|
|
|
def generate_response_direct(self, user_query: str) -> str: |
|
|
"""Fallback method using direct LLM with construction filtering""" |
|
|
if not self.is_construction_related(user_query): |
|
|
response = "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics." |
|
|
self.add_to_memory(user_query, response) |
|
|
return response |
|
|
|
|
|
chat_history = self.get_chat_history() |
|
|
|
|
|
prompt = f"""You are a specialized construction industry AI assistant with expertise in building, safety, materials, project management, and engineering. |
|
|
|
|
|
Chat history: {chat_history} |
|
|
|
|
|
User question: {user_query} |
|
|
|
|
|
Provide a detailed, professional response focusing on construction industry knowledge. Include specific information about safety standards, building codes, material specifications, cost estimates, or project management advice as relevant to the question. |
|
|
|
|
|
Response:""" |
|
|
|
|
|
try: |
|
|
response = direct_llm.invoke(prompt) |
|
|
if hasattr(response, 'content'): |
|
|
response_text = response.content |
|
|
else: |
|
|
response_text = str(response) |
|
|
|
|
|
self.add_to_memory(user_query, response_text) |
|
|
return response_text |
|
|
|
|
|
except Exception as e: |
|
|
fallback_response = f"""I apologize, but I'm experiencing technical difficulties. However, I can still help with construction-related questions about safety, materials, project management, and engineering. Please try rephrasing your question. |
|
|
|
|
|
Technical error: {str(e)[:100]}...""" |
|
|
|
|
|
self.add_to_memory(user_query, fallback_response) |
|
|
return fallback_response |
|
|
|
|
|
def generate_response(self, user_query: str) -> str: |
|
|
"""Main response generation method""" |
|
|
try: |
|
|
return self.generate_response_with_crew(user_query) |
|
|
except Exception as e: |
|
|
print(f"Crew method failed, using direct method: {e}") |
|
|
return self.generate_response_direct(user_query) |
|
|
|
|
|
|
|
|
chatbot = ConstructionChatbot() |
|
|
|
|
|
custom_css = """ |
|
|
/* Construction-themed color palette */ |
|
|
:root { |
|
|
--construction-orange: #ff6b1a; |
|
|
--construction-yellow: #ffc107; |
|
|
--construction-blue: #1e88e5; |
|
|
--construction-green: #4caf50; |
|
|
--construction-red: #f44336; |
|
|
--construction-dark: #2d3436; |
|
|
--construction-darker: #1a1a1a; |
|
|
--construction-light-gray: #ecf0f1; |
|
|
--construction-medium-gray: #95a5a6; |
|
|
--construction-steel: #34495e; |
|
|
} |
|
|
|
|
|
/* Main container styling */ |
|
|
.gradio-container { |
|
|
background: linear-gradient(135deg, var(--construction-dark) 0%, var(--construction-darker) 100%); |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
min-height: 100vh; |
|
|
} |
|
|
|
|
|
/* Header styling */ |
|
|
.construction-header { |
|
|
background: linear-gradient(45deg, var(--construction-orange), var(--construction-yellow)); |
|
|
color: #000; |
|
|
text-align: center; |
|
|
padding: 25px; |
|
|
font-weight: bold; |
|
|
font-size: 28px; |
|
|
border-radius: 15px; |
|
|
margin-bottom: 25px; |
|
|
box-shadow: 0 8px 25px rgba(255, 107, 26, 0.4); |
|
|
border: 3px solid var(--construction-yellow); |
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
/* Chat container - Custom styling */ |
|
|
.chatbot { |
|
|
border: 3px solid var(--construction-orange) !important; |
|
|
border-radius: 15px !important; |
|
|
background: linear-gradient(135deg, var(--construction-steel), #2c3e50) !important; |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3) !important; |
|
|
} |
|
|
|
|
|
/* Message bubbles styling */ |
|
|
.message-wrap { |
|
|
margin: 15px 0 !important; |
|
|
} |
|
|
|
|
|
.message-wrap.svelte-1lcyrx4.user { |
|
|
justify-content: flex-end !important; |
|
|
} |
|
|
|
|
|
.message-wrap.svelte-1lcyrx4.bot { |
|
|
justify-content: flex-start !important; |
|
|
} |
|
|
|
|
|
/* User message styling */ |
|
|
.message-wrap.user .message { |
|
|
background: linear-gradient(135deg, var(--construction-orange), var(--construction-yellow)) !important; |
|
|
color: #000 !important; |
|
|
border-radius: 20px 20px 5px 20px !important; |
|
|
padding: 15px 20px !important; |
|
|
max-width: 75% !important; |
|
|
font-weight: 500 !important; |
|
|
box-shadow: 0 4px 15px rgba(255, 107, 26, 0.3) !important; |
|
|
border: 2px solid var(--construction-yellow) !important; |
|
|
} |
|
|
|
|
|
/* Bot message styling */ |
|
|
.message-wrap.bot .message { |
|
|
background: linear-gradient(135deg, var(--construction-blue), #1565c0) !important; |
|
|
color: #ffffff !important; |
|
|
border-radius: 20px 20px 20px 5px !important; |
|
|
padding: 15px 20px !important; |
|
|
max-width: 75% !important; |
|
|
box-shadow: 0 4px 15px rgba(30, 136, 229, 0.3) !important; |
|
|
border-left: 5px solid var(--construction-green) !important; |
|
|
} |
|
|
|
|
|
/* Input styling */ |
|
|
.input-container { |
|
|
background: linear-gradient(135deg, var(--construction-steel), #2c3e50); |
|
|
border-radius: 25px; |
|
|
padding: 15px; |
|
|
border: 3px solid var(--construction-orange); |
|
|
margin-top: 20px; |
|
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
/* Textbox styling */ |
|
|
.textbox { |
|
|
background: var(--construction-dark) !important; |
|
|
border: 2px solid var(--construction-orange) !important; |
|
|
border-radius: 20px !important; |
|
|
color: var(--construction-light-gray) !important; |
|
|
padding: 15px 20px !important; |
|
|
font-size: 16px !important; |
|
|
} |
|
|
|
|
|
.textbox:focus { |
|
|
border-color: var(--construction-yellow) !important; |
|
|
box-shadow: 0 0 15px rgba(255, 193, 7, 0.3) !important; |
|
|
} |
|
|
|
|
|
/* Button styling */ |
|
|
.btn-primary { |
|
|
background: linear-gradient(135deg, var(--construction-orange), var(--construction-yellow)) !important; |
|
|
color: #000 !important; |
|
|
border: none !important; |
|
|
border-radius: 20px !important; |
|
|
padding: 15px 25px !important; |
|
|
font-weight: bold !important; |
|
|
font-size: 16px !important; |
|
|
cursor: pointer !important; |
|
|
transition: all 0.3s ease !important; |
|
|
box-shadow: 0 4px 15px rgba(255, 107, 26, 0.3) !important; |
|
|
} |
|
|
|
|
|
.btn-primary:hover { |
|
|
background: linear-gradient(135deg, var(--construction-yellow), #ffeb3b) !important; |
|
|
transform: translateY(-2px) !important; |
|
|
box-shadow: 0 6px 20px rgba(255, 193, 7, 0.4) !important; |
|
|
} |
|
|
|
|
|
.btn-secondary { |
|
|
background: linear-gradient(135deg, var(--construction-steel), #34495e) !important; |
|
|
color: var(--construction-light-gray) !important; |
|
|
border: 2px solid var(--construction-medium-gray) !important; |
|
|
border-radius: 15px !important; |
|
|
padding: 12px 20px !important; |
|
|
font-weight: bold !important; |
|
|
transition: all 0.3s ease !important; |
|
|
} |
|
|
|
|
|
.btn-secondary:hover { |
|
|
background: linear-gradient(135deg, var(--construction-blue), #1976d2) !important; |
|
|
border-color: var(--construction-blue) !important; |
|
|
transform: translateY(-2px) !important; |
|
|
} |
|
|
|
|
|
/* Status and info styling */ |
|
|
.status-info { |
|
|
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(30, 136, 229, 0.1)); |
|
|
border: 2px solid var(--construction-green); |
|
|
border-radius: 12px; |
|
|
padding: 15px; |
|
|
margin: 15px 0; |
|
|
color: var(--construction-light-gray); |
|
|
font-size: 14px; |
|
|
box-shadow: 0 3px 10px rgba(76, 175, 80, 0.2); |
|
|
} |
|
|
|
|
|
/* Warning/info boxes */ |
|
|
.warning-box { |
|
|
background: linear-gradient(135deg, rgba(244, 67, 54, 0.1), rgba(255, 107, 26, 0.1)); |
|
|
border: 2px solid var(--construction-red); |
|
|
border-radius: 12px; |
|
|
padding: 20px; |
|
|
margin: 20px 0; |
|
|
color: var(--construction-light-gray); |
|
|
box-shadow: 0 4px 15px rgba(244, 67, 54, 0.2); |
|
|
} |
|
|
|
|
|
.info-box { |
|
|
background: linear-gradient(135deg, rgba(30, 136, 229, 0.1), rgba(255, 193, 7, 0.1)); |
|
|
border: 2px solid var(--construction-blue); |
|
|
border-radius: 12px; |
|
|
padding: 20px; |
|
|
margin: 20px 0; |
|
|
color: var(--construction-light-gray); |
|
|
box-shadow: 0 4px 15px rgba(30, 136, 229, 0.2); |
|
|
} |
|
|
|
|
|
.success-box { |
|
|
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(255, 193, 7, 0.1)); |
|
|
border: 2px solid var(--construction-green); |
|
|
border-radius: 12px; |
|
|
padding: 20px; |
|
|
margin: 20px 0; |
|
|
color: var(--construction-light-gray); |
|
|
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2); |
|
|
} |
|
|
|
|
|
/* Headings */ |
|
|
h1, h2, h3, h4 { |
|
|
color: var(--construction-orange) !important; |
|
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3) !important; |
|
|
font-weight: bold !important; |
|
|
} |
|
|
|
|
|
/* Links */ |
|
|
a { |
|
|
color: var(--construction-yellow) !important; |
|
|
text-decoration: none !important; |
|
|
font-weight: bold !important; |
|
|
} |
|
|
|
|
|
a:hover { |
|
|
color: var(--construction-orange) !important; |
|
|
text-decoration: underline !important; |
|
|
} |
|
|
|
|
|
/* Code styling */ |
|
|
code { |
|
|
background: var(--construction-darker) !important; |
|
|
color: var(--construction-yellow) !important; |
|
|
padding: 2px 6px !important; |
|
|
border-radius: 4px !important; |
|
|
font-family: 'Courier New', monospace !important; |
|
|
} |
|
|
|
|
|
/* Lists */ |
|
|
ul, ol { |
|
|
color: var(--construction-light-gray); |
|
|
} |
|
|
|
|
|
li { |
|
|
margin: 8px 0; |
|
|
padding-left: 5px; |
|
|
border-left: 3px solid var(--construction-orange); |
|
|
padding-left: 10px; |
|
|
margin-left: 10px; |
|
|
} |
|
|
|
|
|
/* Status indicators */ |
|
|
.status-indicator { |
|
|
display: inline-block; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
border-radius: 50%; |
|
|
margin-right: 10px; |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
.status-online { |
|
|
background: var(--construction-green); |
|
|
box-shadow: 0 0 15px rgba(76, 175, 80, 0.6); |
|
|
} |
|
|
|
|
|
.status-offline { |
|
|
background: var(--construction-red); |
|
|
box-shadow: 0 0 15px rgba(244, 67, 54, 0.6); |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { opacity: 1; } |
|
|
50% { opacity: 0.5; } |
|
|
100% { opacity: 1; } |
|
|
} |
|
|
|
|
|
/* Badge styling */ |
|
|
.badge { |
|
|
background: linear-gradient(135deg, var(--construction-blue), #1565c0); |
|
|
color: white; |
|
|
padding: 6px 12px; |
|
|
border-radius: 15px; |
|
|
font-size: 12px; |
|
|
font-weight: bold; |
|
|
display: inline-block; |
|
|
margin: 2px; |
|
|
box-shadow: 0 2px 8px rgba(30, 136, 229, 0.3); |
|
|
} |
|
|
|
|
|
/* Scrollbar styling */ |
|
|
::-webkit-scrollbar { |
|
|
width: 10px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-track { |
|
|
background: var(--construction-darker); |
|
|
border-radius: 5px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb { |
|
|
background: linear-gradient(135deg, var(--construction-orange), var(--construction-yellow)); |
|
|
border-radius: 5px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
|
background: linear-gradient(135deg, var(--construction-yellow), #ffeb3b); |
|
|
} |
|
|
|
|
|
/* Responsive design */ |
|
|
@media (max-width: 768px) { |
|
|
.construction-header { |
|
|
font-size: 20px; |
|
|
padding: 20px 15px; |
|
|
} |
|
|
|
|
|
.message { |
|
|
max-width: 90% !important; |
|
|
padding: 12px 15px !important; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Animation for new messages */ |
|
|
.message-wrap { |
|
|
animation: slideIn 0.3s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes slideIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(20px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
def create_interface(): |
|
|
"""Create the Gradio interface with construction theme""" |
|
|
|
|
|
with gr.Blocks(css=custom_css, title="ποΈ Construction AI Assistant - DeepSeek R1 ποΈ") as interface: |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="construction-header"> |
|
|
ποΈ CONSTRUCTION COMPANY AI ASSISTANT ποΈ |
|
|
<br> |
|
|
<small style="font-size: 18px; font-weight: normal; opacity: 0.9;"> |
|
|
Powered by <span class="badge">DeepSeek R1</span> | Expert Construction Guidance with Live Web Search |
|
|
</small> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
def get_status_info(): |
|
|
search_status = "π Web Search: ONLINE" if chatbot.search_tool else "π Web Search: OFFLINE" |
|
|
search_class = "status-online" if chatbot.search_tool else "status-offline" |
|
|
memory_count = len(chatbot.memory) |
|
|
|
|
|
return f""" |
|
|
<div class="status-info"> |
|
|
<span class="status-indicator {search_class}"></span> |
|
|
<strong>π§ DeepSeek R1 ACTIVE</strong> | |
|
|
<strong>{search_status}</strong> | |
|
|
<strong>π Memory: {memory_count}/5</strong> | |
|
|
<strong>ποΈ Construction Expert Ready</strong> |
|
|
<br> |
|
|
<small>π Real-time construction data | π Current prices & regulations | β‘ Lightning fast responses</small> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
memory_status = gr.HTML(get_status_info()) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
chatbot_interface = gr.Chatbot( |
|
|
[], |
|
|
height=500, |
|
|
show_label=False, |
|
|
container=True, |
|
|
avatar_images=("π€", "ποΈ"), |
|
|
bubble_full_width=False, |
|
|
type="messages", |
|
|
show_copy_button=True, |
|
|
elem_classes=["chatbot"] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=8): |
|
|
msg_input = gr.Textbox( |
|
|
placeholder="ποΈ Ask me about construction, safety, materials, project management, engineering, costs, regulations... (Non-construction queries will be declined)", |
|
|
show_label=False, |
|
|
container=False, |
|
|
lines=2, |
|
|
max_lines=4, |
|
|
elem_classes=["textbox"] |
|
|
) |
|
|
with gr.Column(scale=2, min_width=120): |
|
|
send_btn = gr.Button( |
|
|
"π SEND", |
|
|
variant="primary", |
|
|
size="lg", |
|
|
elem_classes=["btn-primary"] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
clear_btn = gr.Button("ποΈ Clear Chat", variant="secondary", elem_classes=["btn-secondary"]) |
|
|
with gr.Column(scale=1): |
|
|
examples_btn = gr.Button("π‘ Examples", variant="secondary", elem_classes=["btn-secondary"]) |
|
|
|
|
|
|
|
|
examples_html = f""" |
|
|
<div class="success-box"> |
|
|
<h4>β
CONSTRUCTION QUESTIONS I CAN ANSWER {'(with Live Web Search!)' if chatbot.search_tool else ''}:</h4> |
|
|
<ul> |
|
|
<li><strong>π Current Costs:</strong> "What are today's steel and concrete prices for construction?"</li> |
|
|
<li><strong>π₯ Safety Regulations:</strong> "Latest fire safety codes for high-rise buildings in 2024?"</li> |
|
|
<li><strong>ποΈ Engineering:</strong> "How to calculate foundation requirements for a 10-story building?"</li> |
|
|
<li><strong>β οΈ Safety Equipment:</strong> "OSHA requirements for construction site safety equipment?"</li> |
|
|
<li><strong>π Project Management:</strong> "Best methodologies for managing large construction projects?"</li> |
|
|
<li><strong>π± Sustainable Materials:</strong> "Latest trends in eco-friendly construction materials?"</li> |
|
|
<li><strong>π§ Equipment:</strong> "Heavy machinery recommendations for excavation projects?"</li> |
|
|
<li><strong>π Structural Design:</strong> "Load-bearing calculations for steel frame buildings?"</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div class="warning-box"> |
|
|
<h4>β NON-CONSTRUCTION TOPICS (Will Be Politely Declined):</h4> |
|
|
<ul> |
|
|
<li>Sports, entertainment, cooking, general knowledge</li> |
|
|
<li>Programming, medicine, finance (unless construction-related)</li> |
|
|
<li>Personal advice, relationship help</li> |
|
|
<li>Any topic not related to construction, building, or engineering</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div class="info-box"> |
|
|
<h4>π‘ PRO TIPS FOR BEST RESULTS:</h4> |
|
|
<ul> |
|
|
<li><strong>π Trigger Web Search:</strong> Use words like "current", "latest", "2024", "price", "cost" for real-time data</li> |
|
|
<li><strong>π Be Specific:</strong> Mention location, building type, or specific materials for targeted advice</li> |
|
|
<li><strong>π Multiple Questions:</strong> I remember our last 5 conversations for context</li> |
|
|
<li><strong>π― Focus:</strong> Stay within construction topics for the best expert guidance</li> |
|
|
</ul> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
examples_display = gr.HTML(examples_html, visible=False) |
|
|
|
|
|
|
|
|
def update_memory_display(): |
|
|
return get_status_info() |
|
|
|
|
|
def respond(message, history): |
|
|
if not message.strip(): |
|
|
return history, "", update_memory_display() |
|
|
|
|
|
|
|
|
if history is None: |
|
|
history = [] |
|
|
|
|
|
|
|
|
history.append({"role": "user", "content": message}) |
|
|
|
|
|
|
|
|
try: |
|
|
response = chatbot.generate_response(message) |
|
|
history.append({"role": "assistant", "content": response}) |
|
|
except Exception as e: |
|
|
error_response = f"π¨ I apologize, but I encountered an error while processing your construction query. Please try again or rephrase your question.\n\nError: {str(e)[:100]}..." |
|
|
history.append({"role": "assistant", "content": error_response}) |
|
|
|
|
|
return history, "", update_memory_display() |
|
|
|
|
|
def clear_chat(): |
|
|
chatbot.memory.clear() |
|
|
return [], update_memory_display() |
|
|
|
|
|
def toggle_examples(current_visibility): |
|
|
return not current_visibility |
|
|
|
|
|
|
|
|
msg_input.submit(respond, [msg_input, chatbot_interface], [chatbot_interface, msg_input, memory_status]) |
|
|
send_btn.click(respond, [msg_input, chatbot_interface], [chatbot_interface, msg_input, memory_status]) |
|
|
clear_btn.click(clear_chat, outputs=[chatbot_interface, memory_status]) |
|
|
examples_btn.click(toggle_examples, inputs=examples_display, outputs=examples_display) |
|
|
|
|
|
|
|
|
if chatbot.search_tool: |
|
|
gr.HTML(f""" |
|
|
<div class="success-box"> |
|
|
<h4>π WEB SEARCH ENABLED - REAL-TIME CONSTRUCTION DATA!</h4> |
|
|
<p><strong>β
Connected to Serper API</strong> - I can fetch the latest construction industry information including:</p> |
|
|
<ul> |
|
|
<li>Current material prices and market trends</li> |
|
|
<li>Latest building codes and regulations</li> |
|
|
<li>New construction technologies and methods</li> |
|
|
<li>Industry news and safety updates</li> |
|
|
<li>Equipment specifications and pricing</li> |
|
|
</ul> |
|
|
<p><small>π‘ <strong>Tip:</strong> Ask questions with "current", "latest", "2024", "price" to automatically trigger web search!</small></p> |
|
|
</div> |
|
|
""") |
|
|
else: |
|
|
gr.HTML(f""" |
|
|
<div class="warning-box"> |
|
|
<h4>β οΈ WEB SEARCH CURRENTLY DISABLED</h4> |
|
|
<p><strong>Reason:</strong> Serper API key not properly configured</p> |
|
|
<p><strong>Impact:</strong> I can still answer construction questions using my knowledge base, but won't have access to real-time data.</p> |
|
|
|
|
|
<h5>π§ To Enable Web Search:</h5> |
|
|
<ol> |
|
|
<li>Visit <a href="https://serper.dev" target="_blank">serper.dev</a> and get a free API key</li> |
|
|
<li>Set environment variable: <code>export SERPER_API_KEY='your-key-here'</code></li> |
|
|
<li>Restart the application</li> |
|
|
</ol> |
|
|
<p><small>π‘ Even without web search, I'm still a comprehensive construction expert ready to help!</small></p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(f""" |
|
|
<div class="info-box"> |
|
|
<h4>π§ POWERED BY DEEPSEEK R1 - ADVANCED REASONING AI</h4> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; margin-top: 15px;"> |
|
|
<div> |
|
|
<h5>π¬ Model Capabilities:</h5> |
|
|
<ul> |
|
|
<li>Advanced reasoning and problem-solving</li> |
|
|
<li>Deep construction industry knowledge</li> |
|
|
<li>Complex calculation capabilities</li> |
|
|
<li>Multi-step project planning</li> |
|
|
</ul> |
|
|
</div> |
|
|
<div> |
|
|
<h5>π― Specialized Features:</h5> |
|
|
<ul> |
|
|
<li>Construction-only query filtering</li> |
|
|
<li>Memory of last 5 conversations</li> |
|
|
<li>Web search integration for current data</li> |
|
|
<li>Professional construction advice</li> |
|
|
</ul> |
|
|
</div> |
|
|
<div> |
|
|
<h5>π‘οΈ Safety & Compliance:</h5> |
|
|
<ul> |
|
|
<li>OSHA regulation expertise</li> |
|
|
<li>Building code compliance</li> |
|
|
<li>Fire safety standards</li> |
|
|
<li>Risk assessment guidance</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="text-align: center; margin-top: 30px; padding: 20px; background: linear-gradient(135deg, rgba(255, 107, 26, 0.1), rgba(255, 193, 7, 0.1)); border-radius: 15px; border: 2px solid var(--construction-orange);"> |
|
|
<h5 style="color: var(--construction-orange); margin-bottom: 10px;">ποΈ PROFESSIONAL CONSTRUCTION AI ASSISTANT ποΈ</h5> |
|
|
<p style="color: var(--construction-light-gray); margin: 0;"> |
|
|
<strong>Specialized in:</strong> Building Safety β’ Material Costs β’ Project Management β’ Engineering β’ Regulations β’ Equipment |
|
|
<br> |
|
|
<small>β‘ Powered by DeepSeek R1 via OpenRouter β’ π Enhanced with Web Search β’ π― Construction-Focused AI</small> |
|
|
</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
return interface |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
interface = create_interface() |
|
|
|
|
|
if os.getenv("SPACE_ID"): |
|
|
interface.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860))) |
|
|
else: |
|
|
interface.launch(inbrowser=True, server_name="0.0.0.0", server_port=7860) |