parthmax24 commited on
Commit
52de2ff
·
1 Parent(s): d5074ec

Initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /venv
2
+ .env
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.12-slim
3
+
4
+ ENV PYTHONDONTWRITEBYTECODE=1 \
5
+ PYTHONUNBUFFERED=1
6
+
7
+ WORKDIR /app
8
+
9
+ RUN apt-get update && apt-get install -y \
10
+ build-essential \
11
+ git \
12
+ curl \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ COPY requirements.txt .
16
+ RUN pip install --no-cache-dir --upgrade pip && \
17
+ pip install --no-cache-dir -r requirements.txt
18
+
19
+ COPY . .
20
+
21
+ EXPOSE 7860
22
+
23
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
__pycache__/app.cpython-313.pyc ADDED
Binary file (3.04 kB). View file
 
__pycache__/chatbot.cpython-313.pyc ADDED
Binary file (2.84 kB). View file
 
app.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from fastapi import FastAPI, Request, Form
4
+ from fastapi.responses import JSONResponse, HTMLResponse
5
+ from fastapi.staticfiles import StaticFiles
6
+ from fastapi.templating import Jinja2Templates
7
+ from dotenv import load_dotenv
8
+ from pathlib import Path
9
+
10
+ from chatbot import WatershedChatbot
11
+
12
+ load_dotenv()
13
+
14
+ app = FastAPI()
15
+
16
+ app.mount("/static", StaticFiles(directory="static"), name="static")
17
+
18
+ templates = Jinja2Templates(directory="templates")
19
+
20
+ chatbot = WatershedChatbot()
21
+
22
+ csv_path = Path(__file__).resolve().parent / "summary.csv"
23
+
24
+ try:
25
+ summary_df = pd.read_csv(csv_path)
26
+ except FileNotFoundError:
27
+ dummy_data = pd.DataFrame([
28
+ {
29
+ "district": "Example District",
30
+ "cropping_intensity": 100,
31
+ "soil_loss": 10,
32
+ "check_dams": 5,
33
+ "reforested_area": 200
34
+ }
35
+ ])
36
+ dummy_data.to_csv(csv_path, index=False)
37
+ summary_df = dummy_data
38
+ print(f"[INFO] Created dummy summary.csv at {csv_path}")
39
+
40
+ summary_df.columns = summary_df.columns.str.lower().str.strip()
41
+
42
+ @app.get("/", response_class=HTMLResponse)
43
+ async def get_home(request: Request):
44
+ districts = ["All"] + sorted(summary_df["district"].unique().tolist())
45
+
46
+ summary_data = summary_df.to_dict(orient="records")
47
+
48
+ return templates.TemplateResponse("index.html", {
49
+ "request": request,
50
+ "districts": districts,
51
+ "summary_data": summary_data
52
+ })
53
+
54
+
55
+ @app.post("/api/chat")
56
+ async def chat_endpoint(query: str = Form(...), district: str = Form(None)):
57
+ try:
58
+ response = chatbot.answer_query(query, district)
59
+ return JSONResponse(content=response)
60
+ except Exception as e:
61
+ return JSONResponse(status_code=500, content={"error": str(e)})
62
+
chatbot.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # chatbot.py
2
+ import os
3
+ from langchain_community.vectorstores import FAISS
4
+ from langchain_huggingface import HuggingFaceEmbeddings
5
+ from langchain_google_genai import ChatGoogleGenerativeAI
6
+ from langchain.chains import RetrievalQA
7
+
8
+
9
+ class WatershedChatbot:
10
+ def __init__(self):
11
+ # Load embeddings model (same as used during indexing)
12
+ self.embedding = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
13
+
14
+ # Load FAISS vectorstore from local folder
15
+ self.vectorstore = FAISS.load_local(
16
+ "dpr_vector_store_hf",
17
+ embeddings=self.embedding,
18
+ allow_dangerous_deserialization=True
19
+ )
20
+
21
+ # Load all districts from metadata for filtering
22
+ self.all_districts = sorted({doc.metadata.get("district", "") for doc in self.vectorstore.docstore._dict.values()})
23
+
24
+ # Set up Gemini LLM, expects GOOGLE_API_KEY in env variables
25
+ self.llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0.3)
26
+
27
+ def get_districts(self):
28
+ return self.all_districts
29
+
30
+ def answer_query(self, query: str, district: str = None):
31
+ if district and district != "All":
32
+ retriever = self.vectorstore.as_retriever(search_kwargs={"filter": {"district": district}})
33
+ else:
34
+ retriever = self.vectorstore.as_retriever()
35
+
36
+ qa_chain = RetrievalQA.from_chain_type(llm=self.llm, retriever=retriever, return_source_documents=True)
37
+
38
+ result = qa_chain(query)
39
+ return {
40
+ "answer": result["result"],
41
+ "sources": [
42
+ {
43
+ "source_file": doc.metadata.get("source_file", "Unknown"),
44
+ "chunk_index": doc.metadata.get("chunk_index", "N/A")
45
+ }
46
+ for doc in result["source_documents"]
47
+ ]
48
+ }
notebooks/RAG_Watershed.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: uvicorn app:app --host=0.0.0.0 --port=${PORT:-7860}
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pandas
4
+ jinja2
5
+ python-dotenv
6
+ langchain
7
+ langchain-community
8
+ langchain-google-genai
9
+ langchain-huggingface
10
+ sentence-transformers
11
+ faiss-cpu
static/script.js ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ======= Tab Switching Logic =======
2
+ const tabButtons = document.querySelectorAll('.tab-btn');
3
+ const tabContents = document.querySelectorAll('.tab-content');
4
+
5
+ tabButtons.forEach(button => {
6
+ button.addEventListener('click', () => {
7
+ // Remove active class from all tabs and contents
8
+ tabButtons.forEach(btn => btn.classList.remove('active'));
9
+ tabContents.forEach(content => content.classList.remove('active'));
10
+
11
+ // Add active class to clicked tab and corresponding content
12
+ button.classList.add('active');
13
+ const targetId = button.id === 'tab-chat' ? 'chat-section' : 'dashboard-section';
14
+ document.getElementById(targetId).classList.add('active');
15
+
16
+ // If dashboard tab is activated, re-render chart & metrics
17
+ if (button.id === 'tab-dashboard') {
18
+ setTimeout(() => {
19
+ const selectedDistrict = dashboardDistrictSelect.value;
20
+ if (selectedDistrict === "All") {
21
+ renderChart(summaryData);
22
+ renderMetrics("All");
23
+ } else {
24
+ const filteredData = summaryData.filter(item => item.district === selectedDistrict);
25
+ renderChart(filteredData);
26
+ renderMetrics(selectedDistrict);
27
+ }
28
+ }, 100);
29
+ }
30
+ });
31
+ });
32
+
33
+ // ======= Chatbot Form Submission =======
34
+ const chatForm = document.getElementById('chat-form');
35
+ const queryInput = document.getElementById('query-input');
36
+ const districtSelect = document.getElementById('district-select');
37
+ const answerContainer = document.getElementById('answer-container');
38
+ const sourcesContainer = document.getElementById('sources-container');
39
+
40
+ chatForm.addEventListener('submit', async (event) => {
41
+ event.preventDefault();
42
+
43
+ const query = queryInput.value.trim();
44
+ const district = districtSelect.value;
45
+
46
+ if (!query) return;
47
+
48
+ // Show loading state
49
+ answerContainer.textContent = "Fetching answer...";
50
+ sourcesContainer.textContent = "";
51
+
52
+ // Prepare form data
53
+ const formData = new FormData();
54
+ formData.append('query', query);
55
+ formData.append('district', district);
56
+
57
+ try {
58
+ const response = await fetch('/api/chat', {
59
+ method: 'POST',
60
+ body: formData,
61
+ });
62
+
63
+ const data = await response.json();
64
+
65
+ if (response.ok) {
66
+ answerContainer.textContent = data.answer;
67
+
68
+ if (data.sources && data.sources.length > 0) {
69
+ sourcesContainer.innerHTML = `<strong>Sources:</strong><br>` +
70
+ data.sources.map(source =>
71
+ `📄 ${source.source_file} — Chunk ${source.chunk_index}`
72
+ ).join('<br>');
73
+ } else {
74
+ sourcesContainer.textContent = "No sources found.";
75
+ }
76
+ } else {
77
+ answerContainer.textContent = `Error: ${data.error || "Unknown error"}`;
78
+ }
79
+
80
+ } catch (error) {
81
+ answerContainer.textContent = `Request failed: ${error.message}`;
82
+ }
83
+ });
84
+
85
+ // ======= Dashboard Chart & Metrics =======
86
+ // Ensure summaryData is injected into window before this script runs
87
+ const summaryData = window.summaryData;
88
+
89
+ console.log("summaryData loaded:", summaryData);
90
+ if (!summaryData || !Array.isArray(summaryData)) {
91
+ console.error("summaryData is missing or invalid. Dashboard won't load.");
92
+ }
93
+
94
+ const dashboardDistrictSelect = document.getElementById('dashboard-district-select');
95
+ const metricsContainer = document.getElementById('metrics-container');
96
+ const chartCanvas = document.getElementById('summary-chart').getContext('2d');
97
+
98
+ let summaryChart = null;
99
+
100
+ // Function to render bar chart
101
+ function renderChart(data) {
102
+ if (!data || data.length === 0) {
103
+ console.warn("No data provided to renderChart");
104
+ if (summaryChart) {
105
+ summaryChart.destroy();
106
+ summaryChart = null;
107
+ }
108
+ return;
109
+ }
110
+
111
+ const labels = data.map(item => item.district);
112
+ const croppingIntensity = data.map(item => Number(item.cropping_intensity || 0));
113
+ const soilLoss = data.map(item => Number(item.soil_loss || 0));
114
+
115
+ if (summaryChart) {
116
+ summaryChart.destroy();
117
+ }
118
+
119
+ summaryChart = new Chart(chartCanvas, {
120
+ type: 'bar',
121
+ data: {
122
+ labels,
123
+ datasets: [
124
+ {
125
+ label: 'Cropping Intensity (%)',
126
+ data: croppingIntensity,
127
+ backgroundColor: 'rgba(11, 61, 145, 0.7)'
128
+ },
129
+ {
130
+ label: 'Soil Loss (tons/ha/year)',
131
+ data: soilLoss,
132
+ backgroundColor: 'rgba(204, 0, 0, 0.7)'
133
+ }
134
+ ]
135
+ },
136
+ options: {
137
+ responsive: true,
138
+ scales: {
139
+ y: { beginAtZero: true }
140
+ }
141
+ }
142
+ });
143
+ }
144
+
145
+ // Function to render detailed metrics for a district
146
+ function renderMetrics(district) {
147
+ metricsContainer.innerHTML = "";
148
+
149
+ if (district === "All") {
150
+ metricsContainer.innerHTML = "<p>Select a specific district to see detailed metrics.</p>";
151
+ return;
152
+ }
153
+
154
+ const districtData = summaryData.find(item => item.district === district);
155
+ if (!districtData) {
156
+ metricsContainer.innerHTML = "<p>No data available for the selected district.</p>";
157
+ return;
158
+ }
159
+
160
+ Object.entries(districtData).forEach(([key, value]) => {
161
+ if (key === "district") return;
162
+
163
+ const metricDiv = document.createElement('div');
164
+ metricDiv.classList.add('metric');
165
+ // Capitalize and replace underscores for better display
166
+ const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
167
+ metricDiv.textContent = `${label}: ${value}`;
168
+ metricsContainer.appendChild(metricDiv);
169
+ });
170
+ }
171
+
172
+ // Event listener for dashboard district selection
173
+ dashboardDistrictSelect.addEventListener('change', () => {
174
+ const selectedDistrict = dashboardDistrictSelect.value;
175
+
176
+ if (selectedDistrict === "All") {
177
+ renderChart(summaryData);
178
+ renderMetrics("All");
179
+ } else {
180
+ const filteredData = summaryData.filter(item => item.district === selectedDistrict);
181
+ renderChart(filteredData);
182
+ renderMetrics(selectedDistrict);
183
+ }
184
+ });
185
+
186
+ // Initial render on page load - render chart & metrics for "All"
187
+ if (summaryData && Array.isArray(summaryData)) {
188
+ renderChart(summaryData);
189
+ renderMetrics("All");
190
+ }
static/styles.css ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: "Roboto", sans-serif;
3
+ background-color: #f0f9f4;
4
+ scroll-behavior: smooth;
5
+ }
6
+ .tab-btn {
7
+ @apply px-5 py-3 rounded-lg text-green-900 font-semibold transition-colors duration-300;
8
+ }
9
+ .tab-btn.active {
10
+ @apply bg-green-600 text-white shadow-lg;
11
+ }
12
+ .tab-btn:hover:not(.active) {
13
+ @apply bg-green-200;
14
+ }
15
+ .output {
16
+ @apply mt-4 p-5 bg-white rounded-lg shadow-md text-gray-800;
17
+ min-height: 4rem;
18
+ }
19
+ .metrics > div {
20
+ @apply bg-white rounded-lg shadow p-6 text-center text-green-900 font-semibold;
21
+ transition: transform 0.3s ease;
22
+ }
23
+ .metrics > div:hover {
24
+ transform: translateY(-6px);
25
+ box-shadow: 0 10px 15px rgba(34, 197, 94, 0.4);
26
+ }
27
+ /* Animate fade-in */
28
+ .fade-in {
29
+ animation: fadeInUp 0.8s ease forwards;
30
+ opacity: 0;
31
+ transform: translateY(20px);
32
+ }
33
+ @keyframes fadeInUp {
34
+ to {
35
+ opacity: 1;
36
+ transform: translateY(0);
37
+ }
38
+ }
39
+ /* Scrollbar for chat answer */
40
+ #answer-container::-webkit-scrollbar {
41
+ width: 8px;
42
+ }
43
+ #answer-container::-webkit-scrollbar-thumb {
44
+ background-color: #34d399;
45
+ border-radius: 10px;
46
+ }
summary.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ district,cropping_intensity,rainfall_mm,whs_total,shgs,income_increase
2
+ Amethi,134,1100,88,512,25-50%
3
+ Varanasi,120,900,76,420,30-45%
4
+ Lucknow,154,1066,106,528,40-60%
templates/index.html ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html lang="en">
2
+ <head>
3
+ <meta charset="utf-8"/>
4
+ <meta content="width=device-width, initial-scale=1" name="viewport"/>
5
+ <title>
6
+ Watershed DPR Chatbot
7
+ </title>
8
+ <script src="https://cdn.tailwindcss.com">
9
+ </script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&amp;display=swap" rel="stylesheet"/>
11
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"/>
12
+ <link rel="stylesheet" href="/static/styles.css" />
13
+
14
+ </head>
15
+ <body class="min-h-screen flex flex-col">
16
+ <!-- Hero Section -->
17
+ <header class="relative bg-green-800 text-white shadow-lg overflow-hidden">
18
+ <img alt="A lush green agriculture field with sunrise and a flowing water stream, symbolizing watershed and farming" class="absolute inset-0 w-full h-full object-cover opacity-30" height="400" loading="lazy" src="https://storage.googleapis.com/a1aa/image/3bd61a66-6bda-429f-11fb-39ce245411f2.jpg" width="1920"/>
19
+ <div class="relative container mx-auto px-6 py-20 flex flex-col md:flex-row items-center justify-between">
20
+ <div class="max-w-xl fade-in">
21
+ <h1 class="text-4xl md:text-5xl font-extrabold leading-tight drop-shadow-lg">
22
+ Watershed DPR Chatbot
23
+ </h1>
24
+ <p class="mt-4 text-lg md:text-xl max-w-md drop-shadow-md">
25
+ Your intelligent assistant for district watershed progress reports and insights.
26
+ </p>
27
+ <a class="inline-block mt-8 bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition transform hover:-translate-y-1" href="#chat-section">
28
+ Get Started
29
+ <i class="fas fa-arrow-right ml-2">
30
+ </i>
31
+ </a>
32
+ </div>
33
+ <img alt="Illustration of a friendly chatbot with agriculture elements like plants and water droplets" class="w-80 mt-12 md:mt-0 fade-in" height="300" loading="lazy" src="https://storage.googleapis.com/a1aa/image/d0c82752-217a-41c1-f4b6-532dabc58b91.jpg" width="400"/>
34
+ </div>
35
+ </header>
36
+ <!-- Navigation Tabs -->
37
+ <nav class="bg-white shadow sticky top-0 z-30">
38
+ <div class="container mx-auto px-6 py-3 flex justify-center space-x-6">
39
+ <button aria-controls="chat-section" aria-selected="true" class="tab-btn active flex items-center space-x-2 text-green-700 hover:text-green-900" id="tab-chat" role="tab">
40
+ <i class="fas fa-comments">
41
+ </i>
42
+ <span>
43
+ Chatbot
44
+ </span>
45
+ </button>
46
+ <button aria-controls="dashboard-section" aria-selected="false" class="tab-btn flex items-center space-x-2 text-green-700 hover:text-green-900" id="tab-dashboard" role="tab">
47
+ <i class="fas fa-chart-bar">
48
+ </i>
49
+ <span>
50
+ Dashboard
51
+ </span>
52
+ </button>
53
+ <button aria-controls="about-section" aria-selected="false" class="tab-btn flex items-center space-x-2 text-green-700 hover:text-green-900" id="tab-about" role="tab">
54
+ <i class="fas fa-info-circle">
55
+ </i>
56
+ <span>
57
+ About
58
+ </span>
59
+ </button>
60
+ </div>
61
+ </nav>
62
+ <main class="flex-grow container mx-auto px-6 py-10">
63
+ <!-- Chat Section -->
64
+ <section aria-labelledby="tab-chat" class="tab-content active max-w-4xl mx-auto bg-white rounded-xl shadow-lg p-8 fade-in" id="chat-section" role="tabpanel" tabindex="0">
65
+ <h2 class="text-3xl font-extrabold text-green-800 mb-6 border-b border-green-300 pb-2">
66
+ Ask the Watershed DPR Chatbot
67
+ </h2>
68
+ <form aria-label="Chatbot question form" class="flex flex-col md:flex-row md:items-center md:space-x-4 space-y-4 md:space-y-0" id="chat-form">
69
+ <input aria-describedby="query-desc" aria-required="true" class="flex-grow border border-green-300 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500" id="query-input" name="query" placeholder="Ask a question about any DPR..." required="" type="text"/>
70
+ <span class="sr-only" id="query-desc">
71
+ Enter your question here
72
+ </span>
73
+ <select aria-label="Select district" class="border border-green-300 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500" id="district-select" name="district">
74
+ {% for d in districts %}
75
+ <option value="{{ d }}">
76
+ {{ d }}
77
+ </option>
78
+ {% endfor %}
79
+ </select>
80
+ <button aria-label="Submit question" class="bg-green-600 hover:bg-green-700 text-white font-semibold rounded-md px-6 py-3 transition-colors duration-300 flex items-center justify-center" type="submit">
81
+ <i class="fas fa-paper-plane mr-2">
82
+ </i>
83
+ Ask
84
+ </button>
85
+ </form>
86
+ <div aria-atomic="true" aria-live="polite" class="output mt-8 rounded-lg border border-green-200 shadow-sm min-h-[6rem] max-h-64 overflow-y-auto" id="answer-container">
87
+ </div>
88
+ <div class="output mt-4 rounded-lg border border-green-200 shadow-sm min-h-[4rem]" id="sources-container">
89
+ </div>
90
+ </section>
91
+ <!-- Dashboard Section -->
92
+ <section aria-labelledby="tab-dashboard" class="tab-content max-w-6xl mx-auto bg-white rounded-xl shadow-lg p-8 hidden fade-in" id="dashboard-section" role="tabpanel" tabindex="0">
93
+ <h2 class="text-3xl font-extrabold text-green-800 mb-6 border-b border-green-300 pb-2">
94
+ District-Level Watershed Summary
95
+ </h2>
96
+ <div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:space-x-4">
97
+ <label class="sr-only" for="dashboard-district-select">
98
+ Select District
99
+ </label>
100
+ <select aria-label="Select district for dashboard" class="border border-green-300 rounded-md px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500 w-full sm:w-64" id="dashboard-district-select">
101
+ <option value="All">
102
+ All
103
+ </option>
104
+ {% for row in summary_data %}
105
+ <option value="{{ row['district'] }}">
106
+ {{ row['district'] }}
107
+ </option>
108
+ {% endfor %}
109
+ </select>
110
+ </div>
111
+ <div class="flex flex-col md:flex-row md:space-x-10">
112
+ <canvas aria-label="Summary chart" class="w-full md:w-2/3 max-w-4xl rounded-lg shadow-lg" id="summary-chart" role="img">
113
+ </canvas>
114
+ <div aria-atomic="true" aria-live="polite" class="metrics grid grid-cols-1 sm:grid-cols-2 gap-8 mt-8 md:mt-0 md:w-1/3" id="metrics-container">
115
+ <div class="p-6 bg-white rounded-lg shadow-lg hover:shadow-2xl transition-transform transform hover:-translate-y-2">
116
+ <p class="text-5xl font-extrabold text-green-700">
117
+ 1200
118
+ </p>
119
+ <p class="text-green-800 mt-2 text-lg">
120
+ Total Watersheds
121
+ </p>
122
+ </div>
123
+ <div class="p-6 bg-white rounded-lg shadow-lg hover:shadow-2xl transition-transform transform hover:-translate-y-2">
124
+ <p class="text-5xl font-extrabold text-green-700">
125
+ 850
126
+ </p>
127
+ <p class="text-green-800 mt-2 text-lg">
128
+ Active DPRs
129
+ </p>
130
+ </div>
131
+ <div class="p-6 bg-white rounded-lg shadow-lg hover:shadow-2xl transition-transform transform hover:-translate-y-2">
132
+ <p class="text-5xl font-extrabold text-green-700">
133
+ 75%
134
+ </p>
135
+ <p class="text-green-800 mt-2 text-lg">
136
+ Completion Rate
137
+ </p>
138
+ </div>
139
+ <div class="p-6 bg-white rounded-lg shadow-lg hover:shadow-2xl transition-transform transform hover:-translate-y-2">
140
+ <p class="text-5xl font-extrabold text-green-700">
141
+ 45
142
+ </p>
143
+ <p class="text-green-800 mt-2 text-lg">
144
+ Districts Covered
145
+ </p>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </section>
150
+ <!-- About Section -->
151
+ <section aria-labelledby="tab-about" class="tab-content max-w-5xl mx-auto bg-white rounded-xl shadow-lg p-10 hidden fade-in" id="about-section" role="tabpanel" tabindex="0">
152
+ <h2 class="text-3xl font-extrabold text-green-800 mb-6 border-b border-green-300 pb-2">
153
+ About Watershed DPR Chatbot
154
+ </h2>
155
+ <div class="flex flex-col md:flex-row md:space-x-10 items-center">
156
+ <img alt="Illustration showing watershed, agriculture fields, and water management" class="rounded-lg shadow-lg mb-6 md:mb-0 w-full md:w-1/2" height="300" loading="lazy" src="https://storage.googleapis.com/a1aa/image/6418e15d-9c5a-4145-e710-625e31e5b76d.jpg" width="400"/>
157
+ <div class="md:w-1/2 text-green-900 text-lg leading-relaxed">
158
+ <p class="mb-4">
159
+ The Watershed DPR Chatbot is designed to assist farmers, researchers, and policymakers by providing quick and accurate answers related to District Progress Reports (DPRs) on watershed management.
160
+ </p>
161
+ <p class="mb-4">
162
+ Using advanced AI technology, it helps you understand the progress, challenges, and achievements in watershed projects across various districts.
163
+ </p>
164
+ <p>
165
+ Our dashboard offers insightful visualizations and metrics to track watershed development and support data-driven decision making.
166
+ </p>
167
+ </div>
168
+ </div>
169
+ </section>
170
+ </main>
171
+ <footer class="bg-green-800 text-green-100 text-center py-6 mt-16">
172
+ <p>
173
+ © 2025 Developed by IIIT Lucknow
174
+ </p>
175
+ </footer>
176
+ <script src="https://cdn.jsdelivr.net/npm/chart.js">
177
+ </script>
178
+ <script src="/static/script.js">
179
+ </script>
180
+ <script>
181
+ // Tab switching logic with ARIA updates
182
+ const tabs = {
183
+ chat: document.getElementById("tab-chat"),
184
+ dashboard: document.getElementById("tab-dashboard"),
185
+ about: document.getElementById("tab-about"),
186
+ };
187
+ const sections = {
188
+ chat: document.getElementById("chat-section"),
189
+ dashboard: document.getElementById("dashboard-section"),
190
+ about: document.getElementById("about-section"),
191
+ };
192
+
193
+ function activateTab(tabKey) {
194
+ Object.keys(tabs).forEach((key) => {
195
+ if (key === tabKey) {
196
+ tabs[key].classList.add("active");
197
+ tabs[key].setAttribute("aria-selected", "true");
198
+ sections[key].classList.remove("hidden");
199
+ sections[key].classList.add("active");
200
+ sections[key].focus();
201
+ } else {
202
+ tabs[key].classList.remove("active");
203
+ tabs[key].setAttribute("aria-selected", "false");
204
+ sections[key].classList.add("hidden");
205
+ sections[key].classList.remove("active");
206
+ }
207
+ });
208
+ }
209
+
210
+ tabs.chat.addEventListener("click", () => activateTab("chat"));
211
+ tabs.dashboard.addEventListener("click", () => activateTab("dashboard"));
212
+ tabs.about.addEventListener("click", () => activateTab("about"));
213
+
214
+ // Optional: Animate metrics on scroll (fade-in)
215
+ const metrics = document.querySelectorAll("#metrics-container > div");
216
+ const observer = new IntersectionObserver(
217
+ (entries) => {
218
+ entries.forEach((entry) => {
219
+ if (entry.isIntersecting) {
220
+ entry.target.classList.add("fade-in");
221
+ observer.unobserve(entry.target);
222
+ }
223
+ });
224
+ },
225
+ { threshold: 0.3 }
226
+ );
227
+ metrics.forEach((metric) => observer.observe(metric));
228
+ </script>
229
+ </body>
230
+ </html>