Spaces:
Sleeping
Sleeping
added the docker server now
Browse files- .gitignore +1 -0
- Dockerfile +16 -0
- README.md +17 -8
- app.py +115 -0
- requirements.txt +3 -0
- script.js +43 -43
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
venv_front/
|
Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy requirements and install dependencies
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 8 |
+
|
| 9 |
+
# Copy application files
|
| 10 |
+
COPY . .
|
| 11 |
+
|
| 12 |
+
# Expose port
|
| 13 |
+
EXPOSE 7860
|
| 14 |
+
|
| 15 |
+
# Run the application
|
| 16 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
|
@@ -3,7 +3,8 @@ title: ImmoReserv - Apartment Viewing Assistant
|
|
| 3 |
emoji: 🏠
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
-
sdk:
|
|
|
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
---
|
|
@@ -18,14 +19,22 @@ An elegant conversational UI for apartment viewing reservations. This AI assista
|
|
| 18 |
- 💬 Natural conversation flow with realistic timing
|
| 19 |
- 🏠 Specialized for apartment viewing reservations
|
| 20 |
- 📱 Fully responsive mobile and desktop design
|
| 21 |
-
- ⚡
|
|
|
|
| 22 |
|
| 23 |
-
##
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
| 3 |
emoji: 🏠
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
---
|
|
|
|
| 19 |
- 💬 Natural conversation flow with realistic timing
|
| 20 |
- 🏠 Specialized for apartment viewing reservations
|
| 21 |
- 📱 Fully responsive mobile and desktop design
|
| 22 |
+
- ⚡ FastAPI backend with conversation logic
|
| 23 |
+
- 🔄 Real-time API communication
|
| 24 |
|
| 25 |
+
## Architecture
|
| 26 |
|
| 27 |
+
- **Frontend**: Pure HTML5, CSS3, and JavaScript
|
| 28 |
+
- **Backend**: FastAPI with async conversation engine
|
| 29 |
+
- **Communication**: RESTful API endpoints
|
| 30 |
+
- **Deployment**: Dockerized for Hugging Face Spaces
|
| 31 |
+
|
| 32 |
+
## API Endpoints
|
| 33 |
|
| 34 |
+
- `GET /` - Serves the main chat interface
|
| 35 |
+
- `POST /chat` - Processes conversation messages
|
| 36 |
+
- `GET /health` - Health check endpoint
|
| 37 |
|
| 38 |
+
## Usage
|
| 39 |
+
|
| 40 |
+
The assistant initiates conversations by asking about apartment availability and helps coordinate viewing schedules. Perfect for real estate agents and property managers.
|
app.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.responses import FileResponse
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
import os
|
| 6 |
+
from typing import Optional, List, Dict
|
| 7 |
+
import asyncio
|
| 8 |
+
import random
|
| 9 |
+
|
| 10 |
+
app = FastAPI(title="ImmoReserv API", version="1.0.0")
|
| 11 |
+
|
| 12 |
+
# Serve static files
|
| 13 |
+
app.mount("/static", StaticFiles(directory="."), name="static")
|
| 14 |
+
|
| 15 |
+
class ChatMessage(BaseModel):
|
| 16 |
+
message: str
|
| 17 |
+
conversation_history: Optional[List[Dict[str, str]]] = []
|
| 18 |
+
|
| 19 |
+
class ChatResponse(BaseModel):
|
| 20 |
+
message: str
|
| 21 |
+
quick_replies: Optional[List[str]] = None
|
| 22 |
+
|
| 23 |
+
# In-memory conversation logic (this will be replaced with real AI later)
|
| 24 |
+
class ConversationEngine:
|
| 25 |
+
def __init__(self):
|
| 26 |
+
self.responses = {
|
| 27 |
+
"apartment a": {
|
| 28 |
+
"message": "Perfect! You're interested in Apartment A - the lovely 2-bedroom, 1-bathroom unit. What days work best for you this week? I can check availability for morning or afternoon appointments.",
|
| 29 |
+
"quick_replies": ["Monday morning", "Tuesday afternoon", "Wednesday anytime", "Weekend preferred"]
|
| 30 |
+
},
|
| 31 |
+
"apartment b": {
|
| 32 |
+
"message": "Excellent choice! Apartment B is our spacious 3-bedroom, 2-bathroom unit with a great view. When would be most convenient for you to schedule the viewing?",
|
| 33 |
+
"quick_replies": ["This week", "Next week", "Morning slots", "Evening slots"]
|
| 34 |
+
},
|
| 35 |
+
"apartment c": {
|
| 36 |
+
"message": "Great! Apartment C is our cozy 1-bedroom, 1-bathroom unit - perfect for singles or couples. What time slots work best for your schedule?",
|
| 37 |
+
"quick_replies": ["Weekday morning", "Weekday evening", "Weekend", "I'm flexible"]
|
| 38 |
+
},
|
| 39 |
+
"multiple units": {
|
| 40 |
+
"message": "Wonderful! Since you have multiple units available, would you prefer to schedule viewings for all of them, or should we start with the one you think would be the best fit for our client?",
|
| 41 |
+
"quick_replies": ["Schedule all units", "Start with best fit", "Tell me about the client", "Show availability calendar"]
|
| 42 |
+
},
|
| 43 |
+
"monday morning": {
|
| 44 |
+
"message": "Monday morning sounds perfect! Would 10:00 AM work for you? I'll need to confirm a few details - what's the address of the property, and should our client bring any specific documents?",
|
| 45 |
+
"quick_replies": ["10 AM works", "Prefer 9 AM", "11 AM better", "Need different day"]
|
| 46 |
+
},
|
| 47 |
+
"tuesday afternoon": {
|
| 48 |
+
"message": "Tuesday afternoon is great! How about 2:00 PM? I'll make sure our client arrives on time. Could you please provide the exact address and any parking instructions?",
|
| 49 |
+
"quick_replies": ["2 PM perfect", "3 PM better", "1 PM works", "Send me details"]
|
| 50 |
+
},
|
| 51 |
+
"this week": {
|
| 52 |
+
"message": "Perfect! This week works well. What days are you available? I can offer morning slots (9 AM - 12 PM) or afternoon slots (2 PM - 5 PM).",
|
| 53 |
+
"quick_replies": ["Monday this week", "Tuesday this week", "Wednesday this week", "Thursday this week"]
|
| 54 |
+
},
|
| 55 |
+
"next week": {
|
| 56 |
+
"message": "Next week sounds great! That gives us time to prepare properly. Which days work best for you next week?",
|
| 57 |
+
"quick_replies": ["Monday next week", "Tuesday next week", "Weekend next week", "Any day works"]
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
async def generate_response(self, message: str, history: List[Dict[str, str]]) -> ChatResponse:
|
| 62 |
+
# Simulate processing time
|
| 63 |
+
await asyncio.sleep(random.uniform(1.5, 2.5))
|
| 64 |
+
|
| 65 |
+
key = message.lower()
|
| 66 |
+
|
| 67 |
+
# Check for exact matches first
|
| 68 |
+
if key in self.responses:
|
| 69 |
+
response_data = self.responses[key]
|
| 70 |
+
return ChatResponse(
|
| 71 |
+
message=response_data["message"],
|
| 72 |
+
quick_replies=response_data.get("quick_replies")
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Check for partial matches
|
| 76 |
+
for response_key in self.responses:
|
| 77 |
+
if key in response_key or response_key in key:
|
| 78 |
+
response_data = self.responses[response_key]
|
| 79 |
+
return ChatResponse(
|
| 80 |
+
message=response_data["message"],
|
| 81 |
+
quick_replies=response_data.get("quick_replies")
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# Default response for unmatched messages
|
| 85 |
+
return ChatResponse(
|
| 86 |
+
message="I understand. Let me make sure I have all the details correct. Could you please provide more information about your availability and the apartment details?",
|
| 87 |
+
quick_replies=["Share apartment details", "Check calendar", "Call instead", "Send email"]
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
# Initialize conversation engine
|
| 91 |
+
conversation_engine = ConversationEngine()
|
| 92 |
+
|
| 93 |
+
@app.get("/")
|
| 94 |
+
async def read_root():
|
| 95 |
+
return FileResponse("index.html")
|
| 96 |
+
|
| 97 |
+
@app.post("/chat", response_model=ChatResponse)
|
| 98 |
+
async def chat_endpoint(chat_request: ChatMessage):
|
| 99 |
+
try:
|
| 100 |
+
response = await conversation_engine.generate_response(
|
| 101 |
+
chat_request.message,
|
| 102 |
+
chat_request.conversation_history
|
| 103 |
+
)
|
| 104 |
+
return response
|
| 105 |
+
except Exception as e:
|
| 106 |
+
raise HTTPException(status_code=500, detail=f"Error processing message: {str(e)}")
|
| 107 |
+
|
| 108 |
+
@app.get("/health")
|
| 109 |
+
async def health_check():
|
| 110 |
+
return {"status": "healthy", "service": "ImmoReserv API"}
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
import uvicorn
|
| 114 |
+
port = int(os.environ.get("PORT", 7860))
|
| 115 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.104.0
|
| 2 |
+
uvicorn[standard]>=0.24.0
|
| 3 |
+
pydantic>=2.0.0
|
script.js
CHANGED
|
@@ -104,55 +104,55 @@ class ChatInterface {
|
|
| 104 |
}
|
| 105 |
|
| 106 |
async generateResponse(userMessage) {
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
quickReplies: ["Weekday morning", "Weekday evening", "Weekend", "I'm flexible"]
|
| 122 |
-
},
|
| 123 |
-
"multiple units": {
|
| 124 |
-
message: "Wonderful! Since you have multiple units available, would you prefer to schedule viewings for all of them, or should we start with the one you think would be the best fit for our client?",
|
| 125 |
-
quickReplies: ["Schedule all units", "Start with best fit", "Tell me about the client", "Show availability calendar"]
|
| 126 |
-
},
|
| 127 |
-
"monday morning": {
|
| 128 |
-
message: "Monday morning sounds perfect! Would 10:00 AM work for you? I'll need to confirm a few details - what's the address of the property, and should our client bring any specific documents?",
|
| 129 |
-
quickReplies: ["10 AM works", "Prefer 9 AM", "11 AM better", "Need different day"]
|
| 130 |
-
},
|
| 131 |
-
"tuesday afternoon": {
|
| 132 |
-
message: "Tuesday afternoon is great! How about 2:00 PM? I'll make sure our client arrives on time. Could you please provide the exact address and any parking instructions?",
|
| 133 |
-
quickReplies: ["2 PM perfect", "3 PM better", "1 PM works", "Send me details"]
|
| 134 |
}
|
| 135 |
-
};
|
| 136 |
|
| 137 |
-
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
| 149 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
-
|
| 152 |
-
return {
|
| 153 |
-
message: "I understand. Let me make sure I have all the details correct. Could you please provide more information about your availability and the apartment details?",
|
| 154 |
-
quickReplies: ["Share apartment details", "Check calendar", "Call instead", "Send email"]
|
| 155 |
-
};
|
| 156 |
}
|
| 157 |
|
| 158 |
addUserMessage(message) {
|
|
|
|
| 104 |
}
|
| 105 |
|
| 106 |
async generateResponse(userMessage) {
|
| 107 |
+
try {
|
| 108 |
+
const response = await fetch('/chat', {
|
| 109 |
+
method: 'POST',
|
| 110 |
+
headers: {
|
| 111 |
+
'Content-Type': 'application/json',
|
| 112 |
+
},
|
| 113 |
+
body: JSON.stringify({
|
| 114 |
+
message: userMessage,
|
| 115 |
+
conversation_history: this.getConversationHistory()
|
| 116 |
+
})
|
| 117 |
+
});
|
| 118 |
+
|
| 119 |
+
if (!response.ok) {
|
| 120 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
}
|
|
|
|
| 122 |
|
| 123 |
+
const data = await response.json();
|
| 124 |
|
| 125 |
+
return {
|
| 126 |
+
message: data.message,
|
| 127 |
+
quickReplies: data.quick_replies || []
|
| 128 |
+
};
|
| 129 |
|
| 130 |
+
} catch (error) {
|
| 131 |
+
console.error('Error calling chat API:', error);
|
| 132 |
+
|
| 133 |
+
// Fallback response if API fails
|
| 134 |
+
return {
|
| 135 |
+
message: "I apologize, but I'm having trouble connecting right now. Could you please try again in a moment?",
|
| 136 |
+
quickReplies: ["Try again", "Contact support"]
|
| 137 |
+
};
|
| 138 |
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
getConversationHistory() {
|
| 142 |
+
const messages = this.messagesContainer.querySelectorAll('.message');
|
| 143 |
+
const history = [];
|
| 144 |
+
|
| 145 |
+
messages.forEach(messageEl => {
|
| 146 |
+
const isBot = messageEl.classList.contains('message-bot');
|
| 147 |
+
const content = messageEl.querySelector('.message-content').textContent;
|
| 148 |
+
|
| 149 |
+
history.push({
|
| 150 |
+
role: isBot ? 'assistant' : 'user',
|
| 151 |
+
content: content
|
| 152 |
+
});
|
| 153 |
+
});
|
| 154 |
|
| 155 |
+
return history;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
}
|
| 157 |
|
| 158 |
addUserMessage(message) {
|