Legacy Preview Update

This commit is contained in:
2026-04-20 16:16:54 -04:00
parent 8312b5bea6
commit 8be7e1e5f9
10 changed files with 358 additions and 81 deletions
+2 -1
View File
@@ -9,7 +9,7 @@ COPY website/ .
RUN mkdir -p ../backend && bun run build
# Backend and final image
FROM python:3.9-slim
FROM python:3.10-slim
RUN apt-get update && \
apt-get install -y ffmpeg libgl1 libglib2.0-0 && \
@@ -22,6 +22,7 @@ COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
COPY scripts/ /scripts/
# Copy static build from builder
COPY --from=builder /project/backend/static /app/static
+4 -3
View File
@@ -13,7 +13,7 @@
<br/>
HushMap bridges the gap between hardware sensors and top-tier artificial intelligence pipelines (e.g. **Terp AI**, **ElevenLabs**, **YOLOv8**), delivering a seamless real-time learning assistant combined with live noise and occupancy metrics.
HushMap bridges the gap between hardware sensors and top-tier artificial intelligence pipelines (e.g. **Terp AI**, **ElevenLabs**, **YOLOv8**, and **Gemini**), delivering a seamless real-time learning assistant combined with live noise and occupancy metrics.
---
@@ -34,8 +34,9 @@ A responsive, high-fidelity PWA frontend written in Svelte 5 and styled seamless
A blazing fast asynchronous HTTP server facilitating audio chunking and sensor metrics logic over full-duplex sockets.
* **Core Capabilities**:
* **Voice Socket Pipelining**: WebSockets (`/ws/voice`) that hook incoming 16-bit PCM arrays into `faster-whisper`.
* **LLM Context Augmentation**: Seamlessly aggregates live MongoDB noise statistics (Decibel levels per location) to feed contextual history to the TerpAI engine!
* **LLM Context Augmentation**: Seamlessly aggregates live noise statistics (Decibel levels per location) to feed contextual history to the AI engine. Uses **TerpAI** with a seamless fallback to **Gemini 2.5 Flash** if TerpAI is unavailable!
* **Computer Vision Endpoint**: Exposes a `YOLOv8` tensor API (`/api/vision/room-status`) to parse webcam imagery, pinpoint seating capacities, and locate available chairs algorithmically.
* **Dynamic Data Source**: Data can either be fetched in real-time from a **MongoDB** database, or simulated on-the-fly via an in-memory generator depending on the `USE_DB` environment flag.
* **Setup**:
```bash
cd backend
@@ -62,4 +63,4 @@ docker-compose up --build
---
For IoT clients, update `/m5go/main.py` explicitly to broadcast to your running router IP namespace matching your specific VLAN.
For IoT clients, update `/m5go/main.py` explicitly to broadcast to your running router IP namespace matching your specific VLAN.
+10 -6
View File
@@ -14,7 +14,7 @@
## Setup Instructions
### Prerequisites
1. **Python 3.9+** is strictly recommended to support asynchronous typing paradigms.
1. **Python 3.10+** is strictly recommended to support asynchronous typing paradigms.
2. **FFmpeg** must be successfully registered onto your OS PATH environments. This engine handles the core conversions decoding MP3 output arrays into 16-bit, 16kHz Mono arrays natively required for browser contexts:
- **Ubuntu/Debian**: `sudo apt install ffmpeg`
- **macOS**: `brew install ffmpeg`
@@ -33,16 +33,20 @@ pip install -r requirements.txt
### Configuration Tokens
Provide runtime keys securely targeting TerpAI context queues and ElevenLabs synthesized avatars within a `.env` dotfile:
Provide runtime keys securely targeting TerpAI context queues, Gemini Fallback, and ElevenLabs synthesized avatars within a `.env` dotfile:
```ini
ELEVENLABS_API_KEY=sk_...
ELEVENLABS_VOICE_ID=JBFqnCBsd6RMkjVDRZzb
TERP_AI_BEARER_TOKEN=eyJhbGciOiJSUz...
TERP_AI_CONVERSATION_ID=37fa27cc-...
GEMINI_API_KEY=AIza...
MONGODB_URI=mongodb+srv://...
USE_DB=false
```
*Note: `USE_DB` controls whether the application connects to MongoDB (`true`) or uses on-the-fly generated in-memory data for demonstrations (`false`).*
To invoke the engine, simply execute Uvicorn across your `0.0.0.0` loopback:
```bash
@@ -58,7 +62,7 @@ uvicorn server:app --host 0.0.0.0 --port 8000
This WebSocket proxy establishes a fully integrated multi-turn communication bridge seamlessly interacting between Edge node Hardware APIs (ESP32/M5GO/Browsers) and NLP architectures.
1. **Int16 Byte Array Exchange**: Devices connect to `ws://<server_ip>:8000/ws/voice` and push raw binary frames asynchronously over the socket.
2. **Contextual Augmentation**: The server waits for the `"stop_listening"` payload event to signify a completed audio snippet. That float array is cast through `faster-whisper` and combined seamlessly with real-time `MongoDB` decibel tracking telemetry parameters natively attached into the `TerpAI` user conversation chunk.
2. **Contextual Augmentation**: The server waits for the `"stop_listening"` payload event to signify a completed audio snippet. That float array is cast through `faster-whisper` and combined seamlessly with real-time decibel tracking telemetry parameters natively attached into the AI user conversation chunk. We utilize **Terp AI** with an automatic, seamless fallback to **Gemini 2.5 Flash** if the primary Terp service is unavailable.
3. **TTS Pipeline Rendering**: Output predictions are caught instantly, forwarded natively into the `ElevenLabs` TTS interface rendering `pcm_16000` wav codecs, and alerted back down to clients using a `tts_ready` dispatcher.
### Tensor Vision Endpoints (`/api/vision/room-status`)
@@ -90,8 +94,8 @@ Leveraging OpenCV bindings layered beneath a YOLOv8-driven bounding box topology
## Database Registries
* `GET /api/study-rooms/history`: Pulls the active global repository of logged architectural noise measurements captured universally within the preceding 24 hours. Data payloads correspond geographically mapping `GeoJSON` nodes to front-end Mapbox topologies.
* `GET /api/study-rooms`: Pulls generic unstructured noise lists directly unfiltered from Cosmos bounds.
* `GET /api/study-rooms/history`: Pulls the active global repository of logged architectural noise measurements captured universally within the preceding 24 hours. (Uses MongoDB or in-memory generated data based on the `USE_DB` flag).
* `GET /api/study-rooms`: Pulls generic unstructured noise lists.
> [!IMPORTANT]
> The browser frontend strictly configures standard Web Audio API's `ScriptProcessorNode` interfaces routing data synchronously to this backend! Wait to close down pipelines until *after* all WS queues have successfully been delivered.
> The browser frontend strictly configures standard Web Audio API's `ScriptProcessorNode` interfaces routing data synchronously to this backend! Wait to close down pipelines until *after* all WS queues have successfully been delivered.
+3 -1
View File
@@ -9,4 +9,6 @@ ultralytics
opencv-python-headless
scipy
pymongo
pydantic
certifi
google-genai
pydantic
+98 -33
View File
@@ -8,9 +8,16 @@ import requests
import json
import base64
import urllib.request
import time
import sys
# import ssl
# ssl._create_default_https_context = ssl._create_unverified_context
# Add scripts directory to path to import fake data generator
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'scripts'))
try:
from generate_fake_data import get_fake_data
except ImportError:
print("Warning: Could not import get_fake_data from scripts/generate_fake_data.py")
def get_fake_data(locations): return []
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, File, UploadFile
from fastapi.staticfiles import StaticFiles
@@ -21,6 +28,7 @@ from datetime import datetime, timedelta
from pymongo import MongoClient
from dotenv import load_dotenv
from vision import analyze_room_image
from google import genai
from fastapi.middleware.cors import CORSMiddleware
@@ -37,11 +45,30 @@ app.add_middleware(
)
USE_DB = os.getenv("USE_DB", "false").lower() == "true"
import certifi
MONGO_URI = os.getenv("MONGODB_URI", "mongodb://localhost:27017/")
mongo_client = MongoClient(MONGO_URI, tlsCAFile=certifi.where())
db = mongo_client.study_buddy_db
study_rooms_collection = db.study_rooms
if USE_DB:
MONGO_URI = os.getenv("MONGODB_URI", "mongodb://localhost:27017/")
mongo_client = MongoClient(MONGO_URI, tlsCAFile=certifi.where())
db = mongo_client.study_buddy_db
study_rooms_collection = db.study_rooms
else:
print("Running in in-memory mode. MongoDB is disabled. Set USE_DB=true to enable.")
# Fake data cache
_fake_data_cache = None
_fake_data_cache_time = 0
def _get_cached_fake_data():
global _fake_data_cache, _fake_data_cache_time
now = time.time()
# Cache for 5 minutes (300 seconds)
if _fake_data_cache is None or now - _fake_data_cache_time > 300:
# Assuming UMD_LOCATIONS is defined further down, but we can just use the global
_fake_data_cache = get_fake_data(UMD_LOCATIONS)
_fake_data_cache_time = now
return _fake_data_cache
class GeoJSONPoint(BaseModel):
@@ -159,7 +186,7 @@ def _transcribe_pcm(pcm_data: bytes, sample_rate: int = SAMPLE_RATE) -> str:
os.remove(tmp_path)
def get_terp_ai_response(message: str) -> str:
"""Send text to Terp AI and return the full response."""
"""Send text to Terp AI and return the full response. Fallback to Gemini if needed."""
url = f"https://terpai.umd.edu/api/internal/userConversations/{CONVERSATION_ID}/segments"
payload = {
"question": message,
@@ -175,7 +202,7 @@ def get_terp_ai_response(message: str) -> str:
full_response = ""
event = None
try:
resp = requests.post(url, json=payload, headers=HEADERS, stream=True, timeout=30, verify=False)
resp = requests.post(url, json=payload, headers=HEADERS, stream=True, timeout=10, verify=False)
resp.raise_for_status()
for line in resp.iter_lines(decode_unicode=True):
if not line:
@@ -188,13 +215,28 @@ def get_terp_ai_response(message: str) -> str:
if event == "response-updated":
full_response += decoded
resp.close()
if full_response:
return full_response
else:
raise Exception("Empty response from Terp AI")
except Exception as e:
print(f"Terp AI error: {e}")
return "I am sorry, there was an error connecting to Terp AI."
print(f"Terp AI error, falling back to Gemini: {e}")
try:
api_key = os.getenv("GEMINI_API_KEY")
if not api_key or api_key == "your_gemini_api_key":
return "Terp AI is unavailable and Gemini fallback is not configured."
client = genai.Client(api_key=api_key)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=message
)
return response.text
except Exception as gemini_e:
print(f"Gemini fallback error: {gemini_e}")
return "I am sorry, both Terp AI and the Gemini fallback encountered an error."
return full_response
def _convert_to_pcm(audio_data: bytes, input_format: str = "mp3") -> bytes | None:
def _convert_to_pcm(audio_data: bytes, input_format: str = "mp3") -> Optional[bytes]:
"""Convert audio data to 16-bit 16 kHz mono PCM using ffmpeg."""
try:
result = subprocess.run(
@@ -225,7 +267,7 @@ def _convert_to_pcm(audio_data: bytes, input_format: str = "mp3") -> bytes | Non
print("ffmpeg conversion timed out")
return None
def _generate_tts(text: str) -> bytes | None:
def _generate_tts(text: str) -> Optional[bytes]:
"""Generate speech audio from text using ElevenLabs TTS API."""
api_key = os.getenv("ELEVENLABS_API_KEY")
voice_id = os.getenv("ELEVENLABS_VOICE_ID", "JBFqnCBsd6RMkjVDRZzb")
@@ -373,23 +415,36 @@ UMD_LOCATIONS = [
def get_latest_locations_context() -> str:
"""Fetch the latest stats for each known location to feed as AI context."""
twenty_four_hours_ago = datetime.utcnow() - timedelta(hours=24)
pipeline = [
{"$match": {"date": {"$gte": twenty_four_hours_ago}}},
{"$sort": {"date": -1}},
{"$group": {
"_id": "$room_id",
"latest_db": {"$first": "$db"},
"time": {"$first": "$date"}
}}
]
latest_stats = list(study_rooms_collection.aggregate(pipeline))
room_dict = {loc["id"]: loc["name"] for loc in UMD_LOCATIONS}
if USE_DB:
twenty_four_hours_ago = datetime.utcnow() - timedelta(hours=24)
pipeline = [
{"$match": {"date": {"$gte": twenty_four_hours_ago}}},
{"$sort": {"date": -1}},
{"$group": {
"_id": "$room_id",
"latest_db": {"$first": "$db"},
"time": {"$first": "$date"}
}}
]
latest_stats = list(study_rooms_collection.aggregate(pipeline))
else:
fake_data = _get_cached_fake_data()
latest_stats_map = {}
# Data is naturally sorted chronologically in our generator, so reverse it
for d in reversed(fake_data):
if d["room_id"] not in latest_stats_map:
latest_stats_map[d["room_id"]] = {
"_id": d["room_id"],
"latest_db": d["db"],
"time": d["date"]
}
latest_stats = list(latest_stats_map.values())
if not latest_stats:
return "No recent location noise stats available today."
room_dict = {loc["id"]: loc["name"] for loc in UMD_LOCATIONS}
lines = ["Latest Study Room Stats:"]
for stat in latest_stats:
room_id = stat.get("_id")
@@ -426,8 +481,12 @@ async def create_study_room_data(data: StudyRoomData):
if not data.date:
data.date = datetime.utcnow()
doc = data.dict()
result = study_rooms_collection.insert_one(doc)
return {"id": str(result.inserted_id), "room_id": data.room_id, "status": "success"}
if USE_DB:
result = study_rooms_collection.insert_one(doc)
return {"id": str(result.inserted_id), "room_id": data.room_id, "status": "success"}
else:
return {"id": "dummy_id", "room_id": data.room_id, "status": "success (in-memory, not saved)"}
@app.get("/api/study-rooms")
async def get_study_room_data():
@@ -437,11 +496,17 @@ async def get_study_room_data():
@app.get("/api/study-rooms/history")
async def get_study_room_history():
"""Get all study room data from the last 24 hours."""
twenty_four_hours_ago = datetime.utcnow() - timedelta(hours=24)
rooms = list(study_rooms_collection.find(
{"date": {"$gte": twenty_four_hours_ago}},
{"_id": 0}
).sort("date", -1))
if USE_DB:
twenty_four_hours_ago = datetime.utcnow() - timedelta(hours=24)
rooms = list(study_rooms_collection.find(
{"date": {"$gte": twenty_four_hours_ago}},
{"_id": 0}
).sort("date", -1))
else:
rooms = _get_cached_fake_data()
# Ensure we don't leak ObjectIds or non-serializable stuff
# Dates are naturally sorted but let's reverse them to match MongoDB behavior (newest first)
rooms = list(reversed(rooms))
return {"data": rooms}
@app.get("/{full_path:path}")
+5
View File
@@ -1 +1,6 @@
PORT=3000
USE_DB=false
GEMINI_API_KEY=your_gemini_api_key
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here
ELEVENLABS_VOICE_ID=JBFqnCBsd6RMkjVDRZzb
TERP_AI_BEARER_TOKEN=your_terp_ai_token
+32 -35
View File
@@ -1,24 +1,6 @@
import random
from datetime import datetime, timedelta
from pymongo import MongoClient
MONGO_URI = "mongodb+srv://SarayuJ:[EMAIL_ADDRESS]/testing"
client = MongoClient(MONGO_URI)
db = client.study_buddy_db
collection = db.study_rooms
UMD_LOCATIONS = [
{ "id": 'esj', "name": 'Edward St. John (ESJ)', "lng": -76.94209511596014, "lat": 38.987133359608755 },
{ "id": 'mckeldin', "name": 'McKeldin Library', "lng": -76.94494907523277, "lat": 38.986021017749366 },
{ "id": 'hornbake', "name": 'Hornbake Library', "lng": -76.94161787005467, "lat": 38.988233373664826 },
{ "id": 'stem', "name": 'STEM Library', "lng": -76.93942003731279, "lat": 38.988991437126195 },
{ "id": 'clarice', "name": 'Clarice Library', "lng": -76.9500912552473, "lat": 38.990547823732285 },
{ "id": 'yahentamitsi', "name": 'Yahentamitsi', "lng": -76.9448027183373, "lat": 38.99108961575231 },
{ "id": 'iribe', "name": 'Iribe', "lng": -76.93643838603555, "lat": 38.98933701397555 },
{ "id": 'reckord', "name": 'Reckord Armory', "lng": -76.93897470250619, "lat": 38.98609556181066 },
{ "id": 'stamp', "name": 'Stamp Student Union', "lng": -76.94473083972326, "lat": 38.988130238874874 }
]
import os
def get_db_for_time_and_location(hour, loc_id):
"""
@@ -28,7 +10,6 @@ def get_db_for_time_and_location(hour, loc_id):
base_db = 40.0
if loc_id in ['mckeldin', 'esj']:
if 10 <= hour <= 16:
base_db = 75.0
elif 17 <= hour <= 22:
@@ -37,7 +18,6 @@ def get_db_for_time_and_location(hour, loc_id):
base_db = 45.0
elif loc_id in ['stem', 'iribe']:
if 14 <= hour <= 20:
base_db = 70.0
elif 9 <= hour <= 13:
@@ -46,7 +26,6 @@ def get_db_for_time_and_location(hour, loc_id):
base_db = 42.0
elif loc_id == 'stamp':
if 12 <= hour <= 14 or 17 <= hour <= 19:
base_db = 85.0
elif 10 <= hour <= 21:
@@ -55,33 +34,24 @@ def get_db_for_time_and_location(hour, loc_id):
base_db = 50.0
else:
if 9 <= hour <= 18:
base_db = 60.0
else:
base_db = 45.0
noise = random.uniform(-5.0, 5.0)
return max(30.0, min(100.0, base_db + noise))
def generate_fake_data():
print("Clearing existing study room data...")
collection.delete_many({})
def get_fake_data(locations):
now = datetime.utcnow()
start_time = now - timedelta(hours=24)
docs_to_insert = []
print("Generating 24 hours of fake data with patterns...")
docs = []
current_time = start_time
while current_time <= now:
hour = current_time.hour
for loc in UMD_LOCATIONS:
for loc in locations:
db_level = get_db_for_time_and_location(hour, loc["id"])
# Estimate people based on noise level.
@@ -99,9 +69,36 @@ def generate_fake_data():
"people": people_count,
"date": current_time
}
docs_to_insert.append(doc)
docs.append(doc)
current_time += timedelta(minutes=15)
return docs
def generate_fake_data():
from pymongo import MongoClient
MONGO_URI = os.getenv("MONGODB_URI", "mongodb+srv://SarayuJ:[EMAIL_ADDRESS]/testing")
client = MongoClient(MONGO_URI)
db = client.study_buddy_db
collection = db.study_rooms
UMD_LOCATIONS = [
{ "id": 'esj', "name": 'Edward St. John (ESJ)', "lng": -76.94209511596014, "lat": 38.987133359608755 },
{ "id": 'mckeldin', "name": 'McKeldin Library', "lng": -76.94494907523277, "lat": 38.986021017749366 },
{ "id": 'hornbake', "name": 'Hornbake Library', "lng": -76.94161787005467, "lat": 38.988233373664826 },
{ "id": 'stem', "name": 'STEM Library', "lng": -76.93942003731279, "lat": 38.988991437126195 },
{ "id": 'clarice', "name": 'Clarice Library', "lng": -76.9500912552473, "lat": 38.990547823732285 },
{ "id": 'yahentamitsi', "name": 'Yahentamitsi', "lng": -76.9448027183373, "lat": 38.99108961575231 },
{ "id": 'iribe', "name": 'Iribe', "lng": -76.93643838603555, "lat": 38.98933701397555 },
{ "id": 'reckord', "name": 'Reckord Armory', "lng": -76.93897470250619, "lat": 38.98609556181066 },
{ "id": 'stamp', "name": 'Stamp Student Union', "lng": -76.94473083972326, "lat": 38.988130238874874 }
]
print("Clearing existing study room data...")
collection.delete_many({})
print("Generating 24 hours of fake data with patterns...")
docs_to_insert = get_fake_data(UMD_LOCATIONS)
print(f"Inserting {len(docs_to_insert)} records into MongoDB...")
collection.insert_many(docs_to_insert)
+22
View File
@@ -0,0 +1,22 @@
<script lang="ts">
</script>
<footer class="fixed bottom-4 right-4 z-50 glass-panel rounded-2xl border border-white/10 px-6 py-3 flex items-center gap-6 shadow-[0_4px_20px_rgba(0,0,0,0.4)] backdrop-blur-md">
<span class="text-sm font-medium text-slate-300">
Bitcamp 2026 Project
</span>
<div class="w-[1px] h-4 bg-white/20"></div>
<a href="https://github.com/SarayuJ/bitcamp26" target="_blank" rel="noreferrer" class="text-slate-400 hover:text-white transition-colors duration-200">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
</a>
<div class="w-[1px] h-4 bg-white/20"></div>
<a href="/info" class="text-sm font-medium text-neon-blue hover:text-white hover:drop-shadow-[0_0_8px_rgba(0,243,255,0.8)] transition-all duration-200">
Info
</a>
</footer>
+11 -2
View File
@@ -5,6 +5,7 @@
import { page } from '$app/stores';
import VoiceButton from '$lib/components/VoiceButton.svelte';
import LogoBadge from '$lib/components/LogoBadge.svelte';
import Footer from '$lib/components/Footer.svelte';
let { children } = $props();
@@ -15,7 +16,7 @@
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<div class="h-screen w-full overflow-hidden bg-base text-slate-200 relative">
<div class="h-screen w-full bg-base text-slate-200 relative flex flex-col overflow-y-auto overflow-x-hidden">
<!-- Floating Sidebar (Desktop) / Bottom Bar (Mobile) -->
<nav class="absolute bottom-4 md:bottom-auto md:top-1/2 left-1/2 md:left-6 -translate-x-1/2 md:translate-x-0 md:-translate-y-1/2 z-50 rounded-3xl glass-panel md:w-16 w-11/12 md:h-auto py-3 md:py-6 px-4 md:px-0 flex md:flex-col items-center justify-around md:justify-center gap-6 overflow-hidden" style="box-shadow: var(--shadow-glow-primary); border-left: 2px solid var(--color-neon-primary);">
@@ -45,6 +46,12 @@
<Icon icon="mdi:cog-outline" class="text-2xl" />
<span class="absolute left-full ml-4 px-2 py-1 bg-black/80 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none md:block hidden border border-white/10">Settings</span>
</a>
<!-- Nav Item: Info -->
<a href="/info" class="relative flex items-center justify-center p-3 rounded-2xl transition-all duration-300 hover:bg-white/10 group {$page.url.pathname === '/info' ? 'text-neon-blue drop-shadow-[0_0_10px_rgba(0,243,255,0.6)]' : 'text-slate-400 hover:text-white'}">
<Icon icon="mdi:information-outline" class="text-2xl" />
<span class="absolute left-full ml-4 px-2 py-1 bg-black/80 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none md:block hidden border border-white/10">Info</span>
</a>
</nav>
{#if showVoiceButton}
@@ -56,7 +63,9 @@
{/if}
<!-- Main Content Area -->
<main class="flex-1 relative w-full h-full overflow-hidden">
<main class="flex-1 relative w-full h-full overflow-y-auto overflow-x-hidden">
{@render children()}
</main>
<Footer />
</div>
+171
View File
@@ -0,0 +1,171 @@
<script lang="ts">
import Icon from '@iconify/svelte';
</script>
<div class="min-h-screen pt-24 pb-32 px-6 flex flex-col items-center">
<div class="max-w-4xl w-full">
<!-- Header Section -->
<div class="text-center mb-16 relative">
<h1 class="text-5xl md:text-6xl font-bold mb-6 text-white drop-shadow-[0_0_15px_rgba(0,243,255,0.8)]">
HushMap
</h1>
<div class="inline-flex items-center justify-center gap-2 mb-6 px-4 py-2 bg-gradient-to-r from-yellow-500/20 to-amber-500/20 border border-yellow-500/50 rounded-full shadow-[0_0_15px_rgba(234,179,8,0.3)]">
<Icon icon="mdi:trophy" class="text-xl text-yellow-400" />
<span class="text-lg font-semibold text-yellow-400">Best UI/UX Bitcamp 2026</span>
</div>
<p class="text-xl text-slate-300 max-w-2xl mx-auto leading-relaxed">
A real-time study buddy platform integrating vision AI, speech recognition, and map data to optimize campus space utilization.
</p>
</div>
<!-- Problem Section -->
<div class="glass-panel p-10 rounded-3xl border border-white/10 mb-16 relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-br from-red-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-3xl font-semibold mb-8 text-white flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.8)]"></span>
The Problem: Campus Noise and Crowds
</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 class="text-xl font-medium text-white mb-2">Sensory Overload</h3>
<p class="text-slate-400">Unexpectedly loud environments can trigger severe sensory overload for neurodivergent students or other students with high sensitivity.</p>
</div>
<div>
<h3 class="text-xl font-medium text-white mb-2">Awkward Confrontation</h3>
<p class="text-slate-400">Neither librarians nor other students want to initiate uncomfortable confrontations when noise levels spike.</p>
</div>
<div>
<h3 class="text-xl font-medium text-white mb-2">Wasted Time</h3>
<p class="text-slate-400">Students burn time walking to study spots only to find them packed and loud, wishing they knew before leaving their dorm.</p>
</div>
</div>
</div>
<!-- Solution Section -->
<div class="glass-panel p-10 rounded-3xl border border-white/10 mb-16 relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-br from-neon-blue/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-3xl font-semibold mb-8 text-white flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-neon-blue shadow-[0_0_8px_rgba(0,243,255,0.8)]"></span>
Our Solution: HushMap
</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-10">
<div>
<h3 class="text-xl font-medium text-neon-blue mb-2">Real-Time Mapping</h3>
<p class="text-slate-400">We track noise levels across campus as they happen.</p>
</div>
<div>
<h3 class="text-xl font-medium text-neon-blue mb-2">Historical Trends</h3>
<p class="text-slate-400">We analyze past data to predict the best times to study.</p>
</div>
<div>
<h3 class="text-xl font-medium text-neon-blue mb-2">Active Control</h3>
<p class="text-slate-400">We use smart devices to keep noise levels within acceptable limits.</p>
</div>
</div>
<!-- Features -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
<!-- 1 -->
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
<h4 class="text-lg font-semibold text-white mb-3">1. The Interactive Map</h4>
<ul class="space-y-2 text-sm text-slate-400">
<li><strong class="text-slate-300">24hr Data Storage:</strong> Database with the past 24 hrs' worth of sound volume data saved for analysis.</li>
<li><strong class="text-slate-300">Live Updates:</strong> Real-time updates directly from campus-wide noise sensors.</li>
<li><strong class="text-slate-300">Noise Legend:</strong> Visual legend and graph describing noise levels and thresholds.</li>
</ul>
</div>
<!-- 2 -->
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
<h4 class="text-lg font-semibold text-white mb-3">2. TerpAI</h4>
<ul class="space-y-2 text-sm text-slate-400">
<li><strong class="text-slate-300">Multichannel Access:</strong> Talk to Terp AI assistant in the website or in person with the sensors.</li>
<li><strong class="text-slate-300">Study Recommendations:</strong> TerpAI will let you know the best spots to study based on current noise data.</li>
</ul>
</div>
<!-- 3 -->
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
<h4 class="text-lg font-semibold text-white mb-3">3. Accessibility Settings</h4>
<p class="text-sm text-slate-400 mb-2">HushMap ensures usability for all students through integrated accessibility tools.</p>
<ul class="space-y-2 text-sm text-slate-400">
<li><strong class="text-slate-300">Color Blind Mode:</strong> Optimized palette for color vision deficiencies.</li>
<li><strong class="text-slate-300">Language Translation:</strong> Multi-language support for international users.</li>
<li><strong class="text-slate-300">High Contrast Mode:</strong> Enhanced legibility for low-vision accessibility.</li>
</ul>
</div>
<!-- 4 -->
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
<h4 class="text-lg font-semibold text-white mb-3">4. The On-site Librarians</h4>
<ul class="space-y-2 text-sm text-slate-400">
<li><strong class="text-slate-300">Automated Noise Management:</strong> Nodes monitor noise levels and react to noise spikes by telling students to quiet down.</li>
<li><strong class="text-slate-300">Interactive Assistance:</strong> Students can interact directly by asking the librarians questions in real-time.</li>
<li><strong class="text-slate-300">Privacy & Analytics:</strong> No audio recorded—only noise data. Camera footage determines occupancy by comparing seats vs. people.</li>
</ul>
</div>
</div>
</div>
<!-- Tech Stack Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-16">
<!-- Frontend -->
<div class="glass-panel p-8 rounded-3xl border border-white/10 relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-br from-neon-blue/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-2xl font-semibold mb-4 text-white flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-neon-blue shadow-[0_0_8px_rgba(0,243,255,0.8)]"></span>
Frontend
</h2>
<ul class="space-y-3 text-slate-400">
<li class="flex items-center gap-2"><span class="text-neon-blue"></span> SvelteKit 5</li>
<li class="flex items-center gap-2"><span class="text-neon-blue"></span> Tailwind CSS 4</li>
<li class="flex items-center gap-2"><span class="text-neon-blue"></span> MapLibre GL JS</li>
<li class="flex items-center gap-2"><span class="text-neon-blue"></span> Chart.js</li>
</ul>
</div>
<!-- Backend -->
<div class="glass-panel p-8 rounded-3xl border border-white/10 relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-br from-neon-purple/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-2xl font-semibold mb-4 text-white flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-neon-purple shadow-[0_0_8px_rgba(188,19,254,0.8)]"></span>
Backend
</h2>
<ul class="space-y-3 text-slate-400">
<li class="flex items-center gap-2"><span class="text-neon-purple"></span> Python & FastAPI</li>
<li class="flex items-center gap-2"><span class="text-neon-purple"></span> WebSockets</li>
<li class="flex items-center gap-2"><span class="text-neon-purple"></span> MongoDB / In-Memory Mock Data</li>
</ul>
</div>
<!-- AI Features -->
<div class="glass-panel p-8 rounded-3xl border border-white/10 relative overflow-hidden group md:col-span-2">
<div class="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-2xl font-semibold mb-4 text-white flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-white shadow-[0_0_8px_rgba(255,255,255,0.8)]"></span>
AI Integration
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<ul class="space-y-3 text-slate-400">
<li class="flex items-center gap-2"><span class="text-white"></span> Terp AI (Primary Conversational Agent)</li>
<li class="flex items-center gap-2"><span class="text-white"></span> Gemini 2.5 Flash (Seamless Fallback)</li>
<li class="flex items-center gap-2"><span class="text-white"></span> Faster-Whisper (On-device Speech-to-Text)</li>
</ul>
<ul class="space-y-3 text-slate-400">
<li class="flex items-center gap-2"><span class="text-white"></span> ElevenLabs (Text-to-Speech Voice)</li>
<li class="flex items-center gap-2"><span class="text-white"></span> Yolo v8 Vision (Room Image Analysis)</li>
</ul>
</div>
</div>
</div>
<!-- Project Team Section -->
<div class="glass-panel p-10 rounded-3xl border border-white/10 text-center relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-t from-neon-purple/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-3xl font-semibold mb-6 text-white">Project Team</h2>
<div class="flex flex-wrap justify-center gap-6 text-lg text-slate-300">
<span class="px-4 py-2 bg-white/5 rounded-full border border-white/10 shadow-[0_0_10px_rgba(188,19,254,0.2)]">Gagan (Adith) Manjunatha</span>
<span class="px-4 py-2 bg-white/5 rounded-full border border-white/10 shadow-[0_0_10px_rgba(188,19,254,0.2)]">Sameera Nageshwar</span>
<span class="px-4 py-2 bg-white/5 rounded-full border border-white/10 shadow-[0_0_10px_rgba(188,19,254,0.2)]">Jolie Wu</span>
<span class="px-4 py-2 bg-white/5 rounded-full border border-white/10 shadow-[0_0_10px_rgba(188,19,254,0.2)]">Sarayu Jilludumudi</span>
</div>
</div>
</div>
</div>