Emmanuel Frimpong Asante
commited on
Commit
·
eaa03eb
1
Parent(s):
4c75ac3
update space
Browse files- .idea/workspace.xml +14 -12
- models/schemas/inquiry_schema.py +41 -0
- routes/chatbot.py +117 -0
- templates/chatbot.html +93 -0
.idea/workspace.xml
CHANGED
|
@@ -5,8 +5,10 @@
|
|
| 5 |
</component>
|
| 6 |
<component name="ChangeListManager">
|
| 7 |
<list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
|
|
|
|
|
|
|
|
|
|
| 8 |
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
| 9 |
-
<change beforePath="$PROJECT_DIR$/templates/main/base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/main/base.html" afterDir="false" />
|
| 10 |
</list>
|
| 11 |
<option name="SHOW_DIALOG" value="false" />
|
| 12 |
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
@@ -19,8 +21,8 @@
|
|
| 19 |
<option value="Dockerfile" />
|
| 20 |
<option value="JavaScript File" />
|
| 21 |
<option value="CSS File" />
|
| 22 |
-
<option value="Python Script" />
|
| 23 |
<option value="HTML File" />
|
|
|
|
| 24 |
</list>
|
| 25 |
</option>
|
| 26 |
</component>
|
|
@@ -125,15 +127,7 @@
|
|
| 125 |
<workItem from="1730378091154" duration="2435000" />
|
| 126 |
<workItem from="1730381153348" duration="8786000" />
|
| 127 |
<workItem from="1730397485849" duration="22781000" />
|
| 128 |
-
<workItem from="1730454506390" duration="
|
| 129 |
-
</task>
|
| 130 |
-
<task id="LOCAL-00053" summary="update space">
|
| 131 |
-
<option name="closed" value="true" />
|
| 132 |
-
<created>1730378908005</created>
|
| 133 |
-
<option name="number" value="00053" />
|
| 134 |
-
<option name="presentableId" value="LOCAL-00053" />
|
| 135 |
-
<option name="project" value="LOCAL" />
|
| 136 |
-
<updated>1730378908005</updated>
|
| 137 |
</task>
|
| 138 |
<task id="LOCAL-00054" summary="update space">
|
| 139 |
<option name="closed" value="true" />
|
|
@@ -519,7 +513,15 @@
|
|
| 519 |
<option name="project" value="LOCAL" />
|
| 520 |
<updated>1730465119418</updated>
|
| 521 |
</task>
|
| 522 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
<servers />
|
| 524 |
</component>
|
| 525 |
<component name="TypeScriptGeneratedFilesManager">
|
|
|
|
| 5 |
</component>
|
| 6 |
<component name="ChangeListManager">
|
| 7 |
<list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
|
| 8 |
+
<change afterPath="$PROJECT_DIR$/models/schemas/inquiry_schema.py" afterDir="false" />
|
| 9 |
+
<change afterPath="$PROJECT_DIR$/routes/chatbot.py" afterDir="false" />
|
| 10 |
+
<change afterPath="$PROJECT_DIR$/templates/chatbot.html" afterDir="false" />
|
| 11 |
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
|
|
|
| 12 |
</list>
|
| 13 |
<option name="SHOW_DIALOG" value="false" />
|
| 14 |
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
|
|
| 21 |
<option value="Dockerfile" />
|
| 22 |
<option value="JavaScript File" />
|
| 23 |
<option value="CSS File" />
|
|
|
|
| 24 |
<option value="HTML File" />
|
| 25 |
+
<option value="Python Script" />
|
| 26 |
</list>
|
| 27 |
</option>
|
| 28 |
</component>
|
|
|
|
| 127 |
<workItem from="1730378091154" duration="2435000" />
|
| 128 |
<workItem from="1730381153348" duration="8786000" />
|
| 129 |
<workItem from="1730397485849" duration="22781000" />
|
| 130 |
+
<workItem from="1730454506390" duration="8819000" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
</task>
|
| 132 |
<task id="LOCAL-00054" summary="update space">
|
| 133 |
<option name="closed" value="true" />
|
|
|
|
| 513 |
<option name="project" value="LOCAL" />
|
| 514 |
<updated>1730465119418</updated>
|
| 515 |
</task>
|
| 516 |
+
<task id="LOCAL-00102" summary="update space">
|
| 517 |
+
<option name="closed" value="true" />
|
| 518 |
+
<created>1730468175296</created>
|
| 519 |
+
<option name="number" value="00102" />
|
| 520 |
+
<option name="presentableId" value="LOCAL-00102" />
|
| 521 |
+
<option name="project" value="LOCAL" />
|
| 522 |
+
<updated>1730468175296</updated>
|
| 523 |
+
</task>
|
| 524 |
+
<option name="localTasksCounter" value="103" />
|
| 525 |
<servers />
|
| 526 |
</component>
|
| 527 |
<component name="TypeScriptGeneratedFilesManager">
|
models/schemas/inquiry_schema.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/schemas/inquiry_schema.py
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from typing import Optional, List
|
| 6 |
+
from bson import ObjectId
|
| 7 |
+
|
| 8 |
+
class PyObjectId(ObjectId):
|
| 9 |
+
@classmethod
|
| 10 |
+
def __get_validators__(cls):
|
| 11 |
+
yield cls.validate
|
| 12 |
+
|
| 13 |
+
@classmethod
|
| 14 |
+
def validate(cls, v):
|
| 15 |
+
if not ObjectId.is_valid(v):
|
| 16 |
+
raise ValueError("Invalid ObjectId")
|
| 17 |
+
return ObjectId(v)
|
| 18 |
+
|
| 19 |
+
class Inquiry(BaseModel):
|
| 20 |
+
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
| 21 |
+
user_id: str
|
| 22 |
+
inquiry_text: str
|
| 23 |
+
response_text: str
|
| 24 |
+
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
| 25 |
+
attached_image: Optional[str] = None # File path or image ID in storage
|
| 26 |
+
detected_disease: Optional[str] = None
|
| 27 |
+
disease_confidence: Optional[float] = None
|
| 28 |
+
|
| 29 |
+
class Config:
|
| 30 |
+
json_encoders = {ObjectId: str}
|
| 31 |
+
schema_extra = {
|
| 32 |
+
"example": {
|
| 33 |
+
"user_id": "user123",
|
| 34 |
+
"inquiry_text": "What is the treatment for coccidiosis?",
|
| 35 |
+
"response_text": "The recommended treatment for coccidiosis is...",
|
| 36 |
+
"timestamp": "2024-11-01T12:00:00Z",
|
| 37 |
+
"attached_image": "image123.jpg",
|
| 38 |
+
"detected_disease": "Coccidiosis",
|
| 39 |
+
"disease_confidence": 85.5
|
| 40 |
+
}
|
| 41 |
+
}
|
routes/chatbot.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# routes/chatbot.py
|
| 2 |
+
|
| 3 |
+
import io
|
| 4 |
+
|
| 5 |
+
import cv2
|
| 6 |
+
import numpy as np
|
| 7 |
+
from fastapi.templating import Jinja2Templates
|
| 8 |
+
from services.utils import db
|
| 9 |
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, UploadFile, File, Depends, Request
|
| 10 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from services.disease_detection_service import bot
|
| 13 |
+
from models.schemas.inquiry_schema import Inquiry
|
| 14 |
+
from services.auth_service import get_current_user
|
| 15 |
+
from typing import List
|
| 16 |
+
|
| 17 |
+
chatbot_router = APIRouter()
|
| 18 |
+
|
| 19 |
+
templates = Jinja2Templates(directory="templates")
|
| 20 |
+
|
| 21 |
+
# MongoDB collection for inquiries
|
| 22 |
+
inquiry_collection = db.get_collection("inquiries")
|
| 23 |
+
|
| 24 |
+
# Store active WebSocket connections
|
| 25 |
+
active_connections: List[WebSocket] = []
|
| 26 |
+
|
| 27 |
+
async def greeting_based_on_time():
|
| 28 |
+
current_hour = datetime.now().hour
|
| 29 |
+
if 5 <= current_hour < 12:
|
| 30 |
+
return "Good morning"
|
| 31 |
+
elif 12 <= current_hour < 17:
|
| 32 |
+
return "Good afternoon"
|
| 33 |
+
else:
|
| 34 |
+
return "Good evening"
|
| 35 |
+
|
| 36 |
+
@chatbot_router.get("/", response_class=HTMLResponse)
|
| 37 |
+
async def chatbot_page(request: Request, current_user: str = Depends(get_current_user)):
|
| 38 |
+
greeting = await greeting_based_on_time()
|
| 39 |
+
context = {"request": request, "greeting": f"{greeting}, {current_user}! How can I help you today?"}
|
| 40 |
+
return templates.TemplateResponse("chatbot.html", context)
|
| 41 |
+
|
| 42 |
+
@chatbot_router.websocket("/ws/chat")
|
| 43 |
+
async def websocket_endpoint(websocket: WebSocket, current_user: str = Depends(get_current_user)):
|
| 44 |
+
"""Handle real-time WebSocket connections for chat and sidebar updates."""
|
| 45 |
+
await websocket.accept()
|
| 46 |
+
active_connections.append(websocket)
|
| 47 |
+
try:
|
| 48 |
+
while True:
|
| 49 |
+
data = await websocket.receive_text()
|
| 50 |
+
response_text, detected_disease, confidence = await handle_inquiry(data, current_user)
|
| 51 |
+
await websocket.send_json({"response": response_text, "disease": detected_disease, "confidence": confidence})
|
| 52 |
+
await broadcast_inquiry_history(current_user) # Update sidebar for all users
|
| 53 |
+
except WebSocketDisconnect:
|
| 54 |
+
active_connections.remove(websocket)
|
| 55 |
+
|
| 56 |
+
async def handle_inquiry(text: str, current_user: str, file: UploadFile = None):
|
| 57 |
+
"""Process inquiries asynchronously and update MongoDB."""
|
| 58 |
+
detected_disease, disease_confidence = None, None
|
| 59 |
+
response_text = ""
|
| 60 |
+
|
| 61 |
+
# Handle image inquiries
|
| 62 |
+
if file:
|
| 63 |
+
image_data = await file.read()
|
| 64 |
+
image_np = np.frombuffer(image_data, np.uint8)
|
| 65 |
+
response_text, detected_disease, status, recommendation, disease_confidence = bot.diagnose_and_respond(
|
| 66 |
+
cv2.imdecode(image_np, cv2.IMREAD_COLOR)
|
| 67 |
+
)
|
| 68 |
+
else:
|
| 69 |
+
response_text = bot.llama_response(text)
|
| 70 |
+
|
| 71 |
+
# Save inquiry to MongoDB
|
| 72 |
+
inquiry = Inquiry(
|
| 73 |
+
user_id=current_user,
|
| 74 |
+
inquiry_text=text,
|
| 75 |
+
response_text=response_text,
|
| 76 |
+
attached_image=file.filename if file else None,
|
| 77 |
+
detected_disease=detected_disease,
|
| 78 |
+
disease_confidence=disease_confidence
|
| 79 |
+
)
|
| 80 |
+
inquiry_collection.insert_one(inquiry.dict(by_alias=True))
|
| 81 |
+
return response_text, detected_disease, disease_confidence
|
| 82 |
+
|
| 83 |
+
async def broadcast_inquiry_history(user_id: str):
|
| 84 |
+
"""Send updated inquiry history to all WebSocket clients."""
|
| 85 |
+
history = list(inquiry_collection.find({"user_id": user_id}).sort("timestamp", -1).limit(10))
|
| 86 |
+
for connection in active_connections:
|
| 87 |
+
await connection.send_json({"history": history})
|
| 88 |
+
|
| 89 |
+
@chatbot_router.post("/inquire")
|
| 90 |
+
async def inquire(text: str, current_user: str = Depends(get_current_user), file: UploadFile = None):
|
| 91 |
+
"""Handle inquiries with text and optional image for disease detection."""
|
| 92 |
+
response_text = ""
|
| 93 |
+
detected_disease, disease_confidence = None, None
|
| 94 |
+
|
| 95 |
+
# Handle image inquiries with disease detection
|
| 96 |
+
if file:
|
| 97 |
+
image_data = await file.read()
|
| 98 |
+
image_np = np.frombuffer(image_data, np.uint8) # Convert to NumPy array
|
| 99 |
+
response, disease_name, status, recommendation, confidence = bot.diagnose_and_respond(cv2.imdecode(image_np, cv2.IMREAD_COLOR))
|
| 100 |
+
response_text = response
|
| 101 |
+
detected_disease, disease_confidence = disease_name, confidence
|
| 102 |
+
else:
|
| 103 |
+
# General inquiries using Llama model
|
| 104 |
+
response_text = bot.llama_response(text)
|
| 105 |
+
|
| 106 |
+
# Log inquiry into MongoDB
|
| 107 |
+
inquiry = Inquiry(
|
| 108 |
+
user_id=current_user,
|
| 109 |
+
inquiry_text=text,
|
| 110 |
+
response_text=response_text,
|
| 111 |
+
attached_image=file.filename if file else None,
|
| 112 |
+
detected_disease=detected_disease,
|
| 113 |
+
disease_confidence=disease_confidence
|
| 114 |
+
)
|
| 115 |
+
inquiry_collection.insert_one(inquiry.dict(by_alias=True))
|
| 116 |
+
|
| 117 |
+
return JSONResponse(content={"response_text": response_text, "detected_disease": detected_disease, "confidence": disease_confidence})
|
templates/chatbot.html
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- templates/chatbot.html -->
|
| 2 |
+
|
| 3 |
+
{% extends "base.html" %}
|
| 4 |
+
{% block title %}Poultry Management Chatbot{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="container mt-5">
|
| 8 |
+
<div class="row">
|
| 9 |
+
<!-- Sidebar for Inquiry History -->
|
| 10 |
+
<div class="col-md-3">
|
| 11 |
+
<div class="card">
|
| 12 |
+
<div class="card-header">
|
| 13 |
+
<h5>Inquiry History</h5>
|
| 14 |
+
</div>
|
| 15 |
+
<div id="inquiry-history" class="list-group" style="height: 60vh; overflow-y: auto;">
|
| 16 |
+
<!-- History items will be dynamically added here -->
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<!-- Main Chat Area -->
|
| 22 |
+
<div class="col-md-9">
|
| 23 |
+
<div id="chat-window" class="card p-4" style="height: 60vh; overflow-y: auto;">
|
| 24 |
+
<div class="text-center mb-3">
|
| 25 |
+
<h5>{{ greeting }}</h5>
|
| 26 |
+
</div>
|
| 27 |
+
<div id="chat-content"></div>
|
| 28 |
+
</div>
|
| 29 |
+
<!-- Chat Input -->
|
| 30 |
+
<form id="chat-form" class="mt-3" enctype="multipart/form-data">
|
| 31 |
+
<div class="input-group">
|
| 32 |
+
<input type="text" class="form-control" id="user-input" placeholder="Type your message..." required>
|
| 33 |
+
<input type="file" id="file-upload" accept="image/*" style="display: none;">
|
| 34 |
+
<button class="btn btn-outline-secondary" type="button" onclick="document.getElementById('file-upload').click()">📎</button>
|
| 35 |
+
<button class="btn btn-primary" type="submit">Send</button>
|
| 36 |
+
</div>
|
| 37 |
+
</form>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<script>
|
| 43 |
+
const chatSocket = new WebSocket(`ws://${window.location.host}/ws/chat`);
|
| 44 |
+
|
| 45 |
+
chatSocket.onmessage = function(event) {
|
| 46 |
+
const data = JSON.parse(event.data);
|
| 47 |
+
|
| 48 |
+
// Display response
|
| 49 |
+
if (data.response) {
|
| 50 |
+
displayMessage(data.response, "assistant");
|
| 51 |
+
if (data.disease) {
|
| 52 |
+
displayMessage(`Detected disease: ${data.disease} with confidence: ${data.confidence}%`, "assistant");
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Update sidebar with inquiry history
|
| 57 |
+
if (data.history) {
|
| 58 |
+
updateInquiryHistory(data.history);
|
| 59 |
+
}
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
document.getElementById("chat-form").addEventListener("submit", async function(e) {
|
| 63 |
+
e.preventDefault();
|
| 64 |
+
const userMessage = document.getElementById("user-input").value;
|
| 65 |
+
displayMessage(userMessage, "user");
|
| 66 |
+
|
| 67 |
+
// Send message to WebSocket
|
| 68 |
+
chatSocket.send(userMessage);
|
| 69 |
+
|
| 70 |
+
document.getElementById("user-input").value = ""; // Clear input
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
function displayMessage(message, sender) {
|
| 74 |
+
const messageBubble = document.createElement("div");
|
| 75 |
+
messageBubble.classList.add("p-3", "rounded", sender === "user" ? "bg-primary text-white text-right" : "bg-light text-dark");
|
| 76 |
+
messageBubble.style.maxWidth = "70%";
|
| 77 |
+
messageBubble.style.margin = sender === "user" ? "10px auto 10px 50px" : "10px 50px 10px auto";
|
| 78 |
+
messageBubble.textContent = message;
|
| 79 |
+
document.getElementById("chat-content").appendChild(messageBubble);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
function updateInquiryHistory(history) {
|
| 83 |
+
const historyDiv = document.getElementById("inquiry-history");
|
| 84 |
+
historyDiv.innerHTML = ""; // Clear existing history
|
| 85 |
+
history.forEach(inquiry => {
|
| 86 |
+
const historyItem = document.createElement("div");
|
| 87 |
+
historyItem.classList.add("list-group-item");
|
| 88 |
+
historyItem.textContent = inquiry.inquiry_text;
|
| 89 |
+
historyDiv.appendChild(historyItem);
|
| 90 |
+
});
|
| 91 |
+
}
|
| 92 |
+
</script>
|
| 93 |
+
{% endblock %}
|