Emmanuel Frimpong Asante commited on
Commit
eaa03eb
·
1 Parent(s): 4c75ac3

update space

Browse files
.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="6619000" />
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
- <option name="localTasksCounter" value="102" />
 
 
 
 
 
 
 
 
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 %}