Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .DS_Store +0 -0
- .gitattributes +8 -0
- .github/workflows/update_space.yml +28 -0
- .gradio/certificate.pem +31 -0
- .vector_services/usiu_vector_db/chroma.sqlite3 +3 -0
- README.md +3 -9
- api_gateway/__init__.py +0 -0
- api_gateway/__pycache__/__init__.cpython-311.pyc +0 -0
- api_gateway/__pycache__/gateway.cpython-311.pyc +0 -0
- api_gateway/gateway.py +128 -0
- app.py +41 -0
- configs/__pycache__/config.cpython-311.pyc +0 -0
- configs/config.py +24 -0
- images/2D & 3D Plots of vectors 2.png +3 -0
- images/2D & 3D Plots of vectors.png +3 -0
- images/2D Vectors.png +3 -0
- images/3D Vectors 2.png +3 -0
- images/3D Vectors.png +3 -0
- images/usiu-logo.png +0 -0
- main.py +70 -0
- requirements.txt +564 -0
- requirements2.txt +10 -0
- services/__init__.py +0 -0
- services/__pycache__/__init__.cpython-311.pyc +0 -0
- services/__pycache__/auth_service.cpython-311.pyc +0 -0
- services/__pycache__/chatbot_rag.cpython-311.pyc +0 -0
- services/__pycache__/data_service.cpython-311.pyc +0 -0
- services/__pycache__/file_service.cpython-311.pyc +0 -0
- services/__pycache__/general_chat_service.cpython-311.pyc +0 -0
- services/__pycache__/response_service.cpython-311.pyc +0 -0
- services/__pycache__/study_support_service.cpython-311.pyc +0 -0
- services/__pycache__/system_prompts_service.cpython-311.pyc +0 -0
- services/__pycache__/ui_service.cpython-311.pyc +0 -0
- services/__pycache__/voice_interface.cpython-311.pyc +0 -0
- services/auth_service.py +132 -0
- services/data_service-old1.py +57 -0
- services/data_service.py +14 -0
- services/file_service.py +168 -0
- services/quantized_model_loader.py +67 -0
- services/system_prompts_service.py +15 -0
- services/ui_service-old2.py +944 -0
- services/ui_service-old3.py +1527 -0
- services/ui_service.py +1560 -0
- speech.wav +0 -0
- system_prompts/__init__.py +0 -0
- system_prompts/__pycache__/__init__.cpython-311.pyc +0 -0
- system_prompts/__pycache__/prompt_for_general_chat.cpython-311.pyc +0 -0
- system_prompts/__pycache__/prompt_for_study_support.cpython-311.pyc +0 -0
- system_prompts/__pycache__/prompts_manager.cpython-311.pyc +0 -0
- system_prompts/__pycache__/system_prompts.cpython-311.pyc +0 -0
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
.vector_services/usiu_vector_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
images/2D[[:space:]]&[[:space:]]3D[[:space:]]Plots[[:space:]]of[[:space:]]vectors[[:space:]]2.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
images/2D[[:space:]]&[[:space:]]3D[[:space:]]Plots[[:space:]]of[[:space:]]vectors.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
images/2D[[:space:]]Vectors.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
images/3D[[:space:]]Vectors[[:space:]]2.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
images/3D[[:space:]]Vectors.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
usiu_vector_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
vector_services/usiu_vector_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/update_space.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Run Python script
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
build:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
|
| 12 |
+
steps:
|
| 13 |
+
- name: Checkout
|
| 14 |
+
uses: actions/checkout@v2
|
| 15 |
+
|
| 16 |
+
- name: Set up Python
|
| 17 |
+
uses: actions/setup-python@v2
|
| 18 |
+
with:
|
| 19 |
+
python-version: '3.9'
|
| 20 |
+
|
| 21 |
+
- name: Install Gradio
|
| 22 |
+
run: python -m pip install gradio
|
| 23 |
+
|
| 24 |
+
- name: Log in to Hugging Face
|
| 25 |
+
run: python -c 'import huggingface_hub; huggingface_hub.login(token="${{ secrets.hf_token }}")'
|
| 26 |
+
|
| 27 |
+
- name: Deploy to Spaces
|
| 28 |
+
run: gradio deploy
|
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
| 3 |
+
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
| 4 |
+
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
| 5 |
+
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
| 6 |
+
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
| 7 |
+
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
| 8 |
+
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
| 9 |
+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
| 10 |
+
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
| 11 |
+
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
| 12 |
+
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
| 13 |
+
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
| 14 |
+
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
| 15 |
+
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
| 16 |
+
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
| 17 |
+
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
| 18 |
+
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
| 19 |
+
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
| 20 |
+
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
| 21 |
+
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
| 22 |
+
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
| 23 |
+
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
| 24 |
+
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
| 25 |
+
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
| 26 |
+
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
| 27 |
+
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
| 28 |
+
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
| 29 |
+
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
| 30 |
+
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
| 31 |
+
-----END CERTIFICATE-----
|
.vector_services/usiu_vector_db/chroma.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e7f4d32541e51737af91299dcfae2e6c1919587a129154c993cc0fa7c9cc4096
|
| 3 |
+
size 167936
|
README.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji: 🐠
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: red
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.25.0
|
| 8 |
app_file: app.py
|
| 9 |
-
|
|
|
|
| 10 |
---
|
| 11 |
-
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: chatbot_prototype
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
app_file: app.py
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 5.12.0
|
| 6 |
---
|
|
|
|
|
|
api_gateway/__init__.py
ADDED
|
File without changes
|
api_gateway/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (263 Bytes). View file
|
|
|
api_gateway/__pycache__/gateway.cpython-311.pyc
ADDED
|
Binary file (8.31 kB). View file
|
|
|
api_gateway/gateway.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# api_gateway/gateway.py
|
| 2 |
+
|
| 3 |
+
from typing import Dict, Any, Optional, List, Union, Callable
|
| 4 |
+
import time
|
| 5 |
+
import threading
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
# Configure logging
|
| 9 |
+
logging.basicConfig(level=logging.INFO,
|
| 10 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 11 |
+
logger = logging.getLogger('api_gateway')
|
| 12 |
+
|
| 13 |
+
class APIGateway:
|
| 14 |
+
"""
|
| 15 |
+
A simple API Gateway that routes requests to appropriate services.
|
| 16 |
+
Implements service discovery, request routing, and basic monitoring.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self):
|
| 20 |
+
# Registry of available services
|
| 21 |
+
self._services = {}
|
| 22 |
+
|
| 23 |
+
# Request metrics
|
| 24 |
+
self._request_count = 0
|
| 25 |
+
self._service_metrics = {}
|
| 26 |
+
self._lock = threading.Lock()
|
| 27 |
+
|
| 28 |
+
# Start metrics reporting
|
| 29 |
+
self._start_metrics_reporting()
|
| 30 |
+
|
| 31 |
+
logger.info("API Gateway initialized")
|
| 32 |
+
|
| 33 |
+
def register_service(self, service_name: str, service_instance: Any) -> None:
|
| 34 |
+
"""Register a service with the gateway"""
|
| 35 |
+
self._services[service_name] = service_instance
|
| 36 |
+
self._service_metrics[service_name] = {
|
| 37 |
+
"requests": 0,
|
| 38 |
+
"errors": 0,
|
| 39 |
+
"avg_response_time": 0
|
| 40 |
+
}
|
| 41 |
+
logger.info(f"Registered service: {service_name}")
|
| 42 |
+
|
| 43 |
+
def get_service(self, service_name: str) -> Optional[Any]:
|
| 44 |
+
"""Get a service instance by name"""
|
| 45 |
+
return self._services.get(service_name)
|
| 46 |
+
|
| 47 |
+
def list_services(self) -> List[str]:
|
| 48 |
+
"""List all registered services"""
|
| 49 |
+
return list(self._services.keys())
|
| 50 |
+
|
| 51 |
+
def route_request(self, service_name: str, method_name: str, *args, **kwargs) -> Any:
|
| 52 |
+
"""
|
| 53 |
+
Route a request to the appropriate service method
|
| 54 |
+
Handles metrics collection and error handling
|
| 55 |
+
"""
|
| 56 |
+
service = self.get_service(service_name)
|
| 57 |
+
if not service:
|
| 58 |
+
logger.error(f"Service not found: {service_name}")
|
| 59 |
+
raise ValueError(f"Service '{service_name}' not found")
|
| 60 |
+
|
| 61 |
+
method = getattr(service, method_name, None)
|
| 62 |
+
if not method or not callable(method):
|
| 63 |
+
logger.error(f"Method not found: {service_name}.{method_name}")
|
| 64 |
+
raise ValueError(f"Method '{method_name}' not found in service '{service_name}'")
|
| 65 |
+
|
| 66 |
+
# Track request metrics
|
| 67 |
+
with self._lock:
|
| 68 |
+
self._request_count += 1
|
| 69 |
+
self._service_metrics[service_name]["requests"] += 1
|
| 70 |
+
|
| 71 |
+
# Execute the request with timing
|
| 72 |
+
start_time = time.time()
|
| 73 |
+
try:
|
| 74 |
+
result = method(*args, **kwargs)
|
| 75 |
+
return result
|
| 76 |
+
except Exception as e:
|
| 77 |
+
logger.error(f"Error routing request to {service_name}.{method_name}: {str(e)}")
|
| 78 |
+
with self._lock:
|
| 79 |
+
self._service_metrics[service_name]["errors"] += 1
|
| 80 |
+
raise
|
| 81 |
+
finally:
|
| 82 |
+
# Calculate response time
|
| 83 |
+
response_time = time.time() - start_time
|
| 84 |
+
|
| 85 |
+
# Update average response time
|
| 86 |
+
with self._lock:
|
| 87 |
+
metrics = self._service_metrics[service_name]
|
| 88 |
+
total_requests = metrics["requests"]
|
| 89 |
+
current_avg = metrics["avg_response_time"]
|
| 90 |
+
|
| 91 |
+
# Calculate new rolling average
|
| 92 |
+
if total_requests > 1:
|
| 93 |
+
new_avg = ((current_avg * (total_requests - 1)) + response_time) / total_requests
|
| 94 |
+
else:
|
| 95 |
+
new_avg = response_time
|
| 96 |
+
|
| 97 |
+
metrics["avg_response_time"] = new_avg
|
| 98 |
+
|
| 99 |
+
def _start_metrics_reporting(self) -> None:
|
| 100 |
+
"""Start a background thread to periodically report metrics"""
|
| 101 |
+
def report_metrics():
|
| 102 |
+
while True:
|
| 103 |
+
time.sleep(60) # Report every minute
|
| 104 |
+
with self._lock:
|
| 105 |
+
logger.info(f"Total requests processed: {self._request_count}")
|
| 106 |
+
for service, metrics in self._service_metrics.items():
|
| 107 |
+
logger.info(f"Service: {service}, "
|
| 108 |
+
f"Requests: {metrics['requests']}, "
|
| 109 |
+
f"Errors: {metrics['errors']}, "
|
| 110 |
+
f"Avg Response Time: {metrics['avg_response_time']:.4f}s")
|
| 111 |
+
|
| 112 |
+
thread = threading.Thread(target=report_metrics, daemon=True)
|
| 113 |
+
thread.start()
|
| 114 |
+
|
| 115 |
+
# Create a singleton instance
|
| 116 |
+
gateway = APIGateway()
|
| 117 |
+
|
| 118 |
+
def register_service(service_name: str, service_instance: Any) -> None:
|
| 119 |
+
"""Register a service with the gateway"""
|
| 120 |
+
gateway.register_service(service_name, service_instance)
|
| 121 |
+
|
| 122 |
+
def get_service(service_name: str) -> Optional[Any]:
|
| 123 |
+
"""Get a service instance by name"""
|
| 124 |
+
return gateway.get_service(service_name)
|
| 125 |
+
|
| 126 |
+
def route_request(service_name: str, method_name: str, *args, **kwargs) -> Any:
|
| 127 |
+
"""Route a request to a service method"""
|
| 128 |
+
return gateway.route_request(service_name, method_name, *args, **kwargs)
|
app.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
import os
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from services.data_service import DataService
|
| 5 |
+
from services.ui_service import dashboard_ui
|
| 6 |
+
from configs.config import GPT4O_MODEL, CLAUDE_MODEL
|
| 7 |
+
from vector_services.data_curator import DataCurator
|
| 8 |
+
from api_gateway.gateway import register_service, gateway
|
| 9 |
+
|
| 10 |
+
def main() -> None:
|
| 11 |
+
base_dir = "./usiu-knowledge-base"
|
| 12 |
+
vector_store_dir = "week5/chatbot_prototype/vector_services/usiu_vector_db"
|
| 13 |
+
|
| 14 |
+
# Load the existing vector store and obtain its retriever
|
| 15 |
+
curator = DataCurator(knowledge_base_dir=base_dir, persist_directory=vector_store_dir)
|
| 16 |
+
vector_store = curator.load_vectorstore()
|
| 17 |
+
retriever = curator.get_retriever()
|
| 18 |
+
|
| 19 |
+
# Initialize DataService with the retriever so that general chat uses RAG
|
| 20 |
+
data_service = DataService(retriever=retriever)
|
| 21 |
+
|
| 22 |
+
# Register services with API Gateway
|
| 23 |
+
register_service("data_service", data_service)
|
| 24 |
+
register_service("curator", curator)
|
| 25 |
+
|
| 26 |
+
# Get system prompts for general and study support
|
| 27 |
+
general_chat_prompt, study_prompt = data_service.prompts_service.get_prompt()
|
| 28 |
+
|
| 29 |
+
# Build the dashboard, passing the relevant prompts, model identifiers, and retriever
|
| 30 |
+
dashboard = dashboard_ui(
|
| 31 |
+
general_chat_prompt=general_chat_prompt,
|
| 32 |
+
general_model=GPT4O_MODEL,
|
| 33 |
+
study_prompt=study_prompt,
|
| 34 |
+
study_model=CLAUDE_MODEL,
|
| 35 |
+
retriever=retriever # Pass the retriever directly to ui_service
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
dashboard.launch(share=True, inbrowser=True, server_name="0.0.0.0")
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
main()
|
configs/__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (946 Bytes). View file
|
|
|
configs/config.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# configs/config.py
|
| 2 |
+
import os
|
| 3 |
+
import anthropic
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
# Load environment variables from .env file.
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
claude = anthropic.Anthropic()
|
| 10 |
+
|
| 11 |
+
# LLM Model Settings
|
| 12 |
+
GPT4O_MODEL = "gpt-4o"
|
| 13 |
+
CLAUDE_MODEL = "claude-3-7-sonnet-20250219"
|
| 14 |
+
LLAMA_MODEL = "meta-llama/Meta-Llama-3-8B"
|
| 15 |
+
|
| 16 |
+
# API Keys
|
| 17 |
+
GPT4O_API_KEY = os.getenv('OPENAI_API_KEY')
|
| 18 |
+
CLAUDE_API_KEY = os.getenv('ANTHROPIC_API_KEY')
|
| 19 |
+
LLAMA_API_KEY = os.getenv("HF_TOKEN")
|
| 20 |
+
|
| 21 |
+
# Scraping parameters
|
| 22 |
+
SCRAPER_MAX_DEPTH = 3
|
| 23 |
+
SCRAPER_MAX_PAGES = 200
|
| 24 |
+
MIN_CONTENT_LENGTH = 200
|
images/2D & 3D Plots of vectors 2.png
ADDED
|
Git LFS Details
|
images/2D & 3D Plots of vectors.png
ADDED
|
Git LFS Details
|
images/2D Vectors.png
ADDED
|
Git LFS Details
|
images/3D Vectors 2.png
ADDED
|
Git LFS Details
|
images/3D Vectors.png
ADDED
|
Git LFS Details
|
images/usiu-logo.png
ADDED
|
main.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# #!/usr/bin/env python
|
| 2 |
+
# import os
|
| 3 |
+
# import gradio as gr
|
| 4 |
+
# from services.data_service import DataService
|
| 5 |
+
# from services.ui_service import general_chat_ui, study_support_ui
|
| 6 |
+
# from configs.config import GPT4O_MODEL, CLAUDE_MODEL
|
| 7 |
+
# from vector_services.data_curator import DataCurator
|
| 8 |
+
|
| 9 |
+
# def main() -> None:
|
| 10 |
+
# base_dir = "./usiu-knowledge-base"
|
| 11 |
+
# vector_store_dir = "./usiu_vector_db"
|
| 12 |
+
|
| 13 |
+
# # Load the existing vector store and obtain its retriever.
|
| 14 |
+
# curator = DataCurator(knowledge_base_dir=base_dir, persist_directory=vector_store_dir)
|
| 15 |
+
# vector_store = curator.load_vectorstore()
|
| 16 |
+
# retriever = curator.get_retriever()
|
| 17 |
+
|
| 18 |
+
# # Initialize DataService with the retriever so that general chat uses RAG.
|
| 19 |
+
# data_service = DataService(retriever=retriever)
|
| 20 |
+
# prompts_service = data_service.prompts_service
|
| 21 |
+
|
| 22 |
+
# # Get system prompts for general and study support.
|
| 23 |
+
# general_chat_prompt = prompts_service.get_prompt("general")
|
| 24 |
+
# study_prompt = prompts_service.get_prompt("study")
|
| 25 |
+
|
| 26 |
+
# # Use the ui_service functions that already manage state properly.
|
| 27 |
+
# general_ui = general_chat_ui(general_chat_prompt, GPT4O_MODEL)
|
| 28 |
+
# study_ui = study_support_ui(study_prompt, CLAUDE_MODEL)
|
| 29 |
+
|
| 30 |
+
# # Assemble the interfaces in tabs.
|
| 31 |
+
# interfaces = [general_ui, study_ui]
|
| 32 |
+
# tab_names = ["General Academic Chat", "Study Support Chat"]
|
| 33 |
+
# demo = gr.TabbedInterface(interfaces, tab_names)
|
| 34 |
+
# demo.launch(share=True, inbrowser=True, server_name="localhost", server_port=8001)
|
| 35 |
+
|
| 36 |
+
# if __name__ == "__main__":
|
| 37 |
+
# main()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
#!/usr/bin/env python
|
| 41 |
+
import os
|
| 42 |
+
import gradio as gr
|
| 43 |
+
from services.data_service import DataService
|
| 44 |
+
from services.ui_service import dashboard_ui
|
| 45 |
+
from configs.config import GPT4O_MODEL, CLAUDE_MODEL
|
| 46 |
+
from vector_services.data_curator import DataCurator
|
| 47 |
+
|
| 48 |
+
def main() -> None:
|
| 49 |
+
base_dir = "./usiu-knowledge-base"
|
| 50 |
+
vector_store_dir = "./vector_services/usiu_vector_db"
|
| 51 |
+
|
| 52 |
+
# Load the existing vector store and obtain its retriever.
|
| 53 |
+
curator = DataCurator(knowledge_base_dir=base_dir, persist_directory=vector_store_dir)
|
| 54 |
+
vector_store = curator.load_vectorstore()
|
| 55 |
+
retriever = curator.get_retriever()
|
| 56 |
+
|
| 57 |
+
# Initialize DataService with the retriever so that general chat uses RAG.
|
| 58 |
+
data_service = DataService(retriever=retriever)
|
| 59 |
+
prompts_service = data_service.prompts_service
|
| 60 |
+
|
| 61 |
+
# Get system prompts for general and study support.
|
| 62 |
+
general_chat_prompt, study_prompt = prompts_service.get_prompt()
|
| 63 |
+
|
| 64 |
+
# Build the dashboard, passing the relevant prompts and model identifiers.
|
| 65 |
+
dashboard = dashboard_ui(general_chat_prompt, GPT4O_MODEL, study_prompt, CLAUDE_MODEL)
|
| 66 |
+
dashboard.launch(share=True, inbrowser=True, server_name="0.0.0.0")
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
main()
|
| 70 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
absl-py==2.2.1
|
| 2 |
+
accelerate==0.21.0
|
| 3 |
+
aiofiles @ file:///home/conda/feedstock_root/build_artifacts/aiofiles_1698945915105/work
|
| 4 |
+
aiohappyeyeballs @ file:///home/conda/feedstock_root/build_artifacts/aiohappyeyeballs_1733331917983/work
|
| 5 |
+
aiohttp==3.11.11
|
| 6 |
+
aiohttp-retry==2.8.3
|
| 7 |
+
aiosignal @ file:///home/conda/feedstock_root/build_artifacts/aiosignal_1734342155601/work
|
| 8 |
+
altair==5.5.0
|
| 9 |
+
annotated-types @ file:///home/conda/feedstock_root/build_artifacts/annotated-types_1733247046149/work
|
| 10 |
+
ansi2html @ file:///Users/runner/miniforge3/conda-bld/ansi2html_1726555528398/work
|
| 11 |
+
anthropic==0.45.2
|
| 12 |
+
anyascii==0.3.2
|
| 13 |
+
anyio @ file:///home/conda/feedstock_root/build_artifacts/anyio_1736174388474/work
|
| 14 |
+
appdirs==1.4.4
|
| 15 |
+
appnope @ file:///home/conda/feedstock_root/build_artifacts/appnope_1733332318622/work
|
| 16 |
+
argon2-cffi @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi_1733311059102/work
|
| 17 |
+
argon2-cffi-bindings @ file:///Users/runner/miniforge3/conda-bld/argon2-cffi-bindings_1725356591756/work
|
| 18 |
+
arrow @ file:///home/conda/feedstock_root/build_artifacts/arrow_1733584251875/work
|
| 19 |
+
asgiref @ file:///home/conda/feedstock_root/build_artifacts/asgiref_1733215607532/work
|
| 20 |
+
asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1733250440834/work
|
| 21 |
+
async-lru @ file:///home/conda/feedstock_root/build_artifacts/async-lru_1733584297267/work
|
| 22 |
+
attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1734348785146/work
|
| 23 |
+
audioread==3.0.1
|
| 24 |
+
babel @ file:///home/conda/feedstock_root/build_artifacts/babel_1733236348445/work
|
| 25 |
+
backoff @ file:///home/conda/feedstock_root/build_artifacts/backoff_1733771078379/work
|
| 26 |
+
bangla==0.0.2
|
| 27 |
+
bcrypt @ file:///Users/runner/miniforge3/conda-bld/bcrypt_1732074870204/work
|
| 28 |
+
beautifulsoup4==4.12.2
|
| 29 |
+
bitsandbytes==0.42.0
|
| 30 |
+
bleach @ file:///home/conda/feedstock_root/build_artifacts/bleach_1736148489770/work
|
| 31 |
+
blinker @ file:///home/conda/feedstock_root/build_artifacts/blinker_1731096409132/work
|
| 32 |
+
blis==1.2.0
|
| 33 |
+
bnnumerizer==0.0.2
|
| 34 |
+
bnunicodenormalizer==0.1.7
|
| 35 |
+
Brotli @ file:///Users/runner/miniforge3/conda-bld/brotli-split_1725267563793/work
|
| 36 |
+
build @ file:///home/conda/feedstock_root/build_artifacts/python-build_1733230610871/work
|
| 37 |
+
cached-property @ file:///home/conda/feedstock_root/build_artifacts/cached_property_1615209429212/work
|
| 38 |
+
cachetools @ file:///home/conda/feedstock_root/build_artifacts/cachetools_1737517575301/work
|
| 39 |
+
catalogue==2.0.10
|
| 40 |
+
certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1734380492396/work/certifi
|
| 41 |
+
cffi @ file:///Users/runner/miniforge3/conda-bld/cffi_1725560551097/work
|
| 42 |
+
charset-normalizer==2.1.1
|
| 43 |
+
chroma-hnswlib @ file:///Users/runner/miniforge3/conda-bld/chroma-hnswlib_1725527880077/work
|
| 44 |
+
chromadb @ file:///Users/runner/miniforge3/conda-bld/chromadb_1736965865001/work
|
| 45 |
+
chromedriver-autoinstaller==0.6.4
|
| 46 |
+
click @ file:///home/conda/feedstock_root/build_artifacts/click_1734858813237/work
|
| 47 |
+
clickhouse-connect @ file:///Users/runner/miniforge3/conda-bld/clickhouse-connect_1736287047793/work
|
| 48 |
+
cloudpathlib==0.21.0
|
| 49 |
+
colorama @ file:///home/conda/feedstock_root/build_artifacts/colorama_1733218098505/work
|
| 50 |
+
coloredlogs @ file:///home/conda/feedstock_root/build_artifacts/coloredlogs_1733927977068/work
|
| 51 |
+
comm @ file:///home/conda/feedstock_root/build_artifacts/comm_1733502965406/work
|
| 52 |
+
confection==0.1.5
|
| 53 |
+
contourpy @ file:///Users/runner/miniforge3/conda-bld/contourpy_1731428370657/work
|
| 54 |
+
coqpit==0.0.17
|
| 55 |
+
cryptography @ file:///Users/runner/miniforge3/conda-bld/cryptography-split_1737764929298/work
|
| 56 |
+
cycler @ file:///home/conda/feedstock_root/build_artifacts/cycler_1733332471406/work
|
| 57 |
+
cymem==2.0.11
|
| 58 |
+
Cython==3.0.12
|
| 59 |
+
dash @ file:///home/conda/feedstock_root/build_artifacts/dash_1734247674276/work
|
| 60 |
+
dataclasses-json==0.5.14
|
| 61 |
+
datasets==2.14.5
|
| 62 |
+
dateparser==1.1.8
|
| 63 |
+
debugpy @ file:///Users/runner/miniforge3/conda-bld/debugpy_1734158991118/work
|
| 64 |
+
decorator @ file:///home/conda/feedstock_root/build_artifacts/decorator_1733236420667/work
|
| 65 |
+
deepseek==1.0.0
|
| 66 |
+
defusedxml @ file:///home/conda/feedstock_root/build_artifacts/defusedxml_1615232257335/work
|
| 67 |
+
Deprecated @ file:///home/conda/feedstock_root/build_artifacts/deprecated_1737986966356/work
|
| 68 |
+
dill==0.3.7
|
| 69 |
+
distro==1.9.0
|
| 70 |
+
dnspython @ file:///home/conda/feedstock_root/build_artifacts/dnspython_1733256735222/work
|
| 71 |
+
docker-pycreds==0.4.0
|
| 72 |
+
docopt==0.6.2
|
| 73 |
+
duckdb @ file:///Users/runner/miniforge3/conda-bld/python-duckdb-split_1730798348017/work/tools/pythonpkg
|
| 74 |
+
durationpy @ file:///home/conda/feedstock_root/build_artifacts/durationpy_1734343542351/work
|
| 75 |
+
einops==0.8.1
|
| 76 |
+
email_validator @ file:///home/conda/feedstock_root/build_artifacts/email-validator-meta_1733300719943/work
|
| 77 |
+
encodec==0.1.1
|
| 78 |
+
entrypoints @ file:///home/conda/feedstock_root/build_artifacts/entrypoints_1733327148154/work
|
| 79 |
+
exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1733208806608/work
|
| 80 |
+
executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1733569351617/work
|
| 81 |
+
faiss==1.9.0
|
| 82 |
+
faiss-cpu==1.10.0
|
| 83 |
+
fastapi @ file:///home/conda/feedstock_root/build_artifacts/fastapi_1733362299471/work
|
| 84 |
+
fastapi-cli @ file:///home/conda/feedstock_root/build_artifacts/fastapi-cli_1734302308128/work
|
| 85 |
+
fastjsonschema @ file:///home/conda/feedstock_root/build_artifacts/python-fastjsonschema_1733235979760/work/dist
|
| 86 |
+
feedparser==6.0.11
|
| 87 |
+
ffmpy @ file:///home/conda/feedstock_root/build_artifacts/ffmpy_1659474992694/work
|
| 88 |
+
filelock @ file:///home/conda/feedstock_root/build_artifacts/filelock_1733240801289/work
|
| 89 |
+
Flask==2.2.2
|
| 90 |
+
flatbuffers @ file:///home/conda/feedstock_root/build_artifacts/python-flatbuffers_1733838640534/work
|
| 91 |
+
fonttools @ file:///Users/runner/miniforge3/conda-bld/fonttools_1735335860711/work
|
| 92 |
+
fqdn @ file:///home/conda/feedstock_root/build_artifacts/fqdn_1733327382592/work/dist
|
| 93 |
+
frozenlist @ file:///Users/runner/miniforge3/conda-bld/frozenlist_1737645277522/work
|
| 94 |
+
fsspec==2023.6.0
|
| 95 |
+
g2pkk==0.1.2
|
| 96 |
+
gensim==4.3.3
|
| 97 |
+
gitdb==4.0.12
|
| 98 |
+
GitPython==3.1.44
|
| 99 |
+
gmpy2 @ file:///Users/runner/miniforge3/conda-bld/gmpy2_1733462580180/work
|
| 100 |
+
google-ai-generativelanguage==0.6.15
|
| 101 |
+
google-api-core==2.24.0
|
| 102 |
+
google-api-python-client==2.158.0
|
| 103 |
+
google-auth @ file:///home/conda/feedstock_root/build_artifacts/google-auth_1737618250101/work
|
| 104 |
+
google-auth-httplib2==0.2.0
|
| 105 |
+
google-generativeai==0.8.4
|
| 106 |
+
googleapis-common-protos @ file:///home/conda/feedstock_root/build_artifacts/googleapis-common-protos-feedstock_1731458889232/work
|
| 107 |
+
gradio==5.23.1
|
| 108 |
+
gradio_client==1.8.0
|
| 109 |
+
groovy==0.1.2
|
| 110 |
+
groq==0.18.0
|
| 111 |
+
grpcio==1.69.0
|
| 112 |
+
grpcio-status==1.69.0
|
| 113 |
+
grpclib==0.4.7
|
| 114 |
+
gruut==2.2.3
|
| 115 |
+
gruut-ipa==0.13.0
|
| 116 |
+
gruut_lang_de==2.0.1
|
| 117 |
+
gruut_lang_en==2.0.1
|
| 118 |
+
gruut_lang_es==2.0.1
|
| 119 |
+
gruut_lang_fr==2.0.2
|
| 120 |
+
gTTS==2.5.4
|
| 121 |
+
h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1733327467879/work
|
| 122 |
+
h2 @ file:///home/conda/feedstock_root/build_artifacts/h2_1733298745555/work
|
| 123 |
+
hangul-romanize==0.1.0
|
| 124 |
+
hnswlib @ file:///Users/runner/miniforge3/conda-bld/hnswlib_1732136479184/work
|
| 125 |
+
hpack @ file:///home/conda/feedstock_root/build_artifacts/hpack_1733299205993/work
|
| 126 |
+
httpcore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_httpcore_1731707562/work
|
| 127 |
+
httplib2==0.22.0
|
| 128 |
+
httptools @ file:///Users/runner/miniforge3/conda-bld/httptools_1732707694945/work
|
| 129 |
+
httpx==0.27.2
|
| 130 |
+
httpx-sse==0.4.0
|
| 131 |
+
huggingface-hub==0.29.3
|
| 132 |
+
humanfriendly @ file:///home/conda/feedstock_root/build_artifacts/humanfriendly_1733927922002/work
|
| 133 |
+
hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1733298771451/work
|
| 134 |
+
idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1733211830134/work
|
| 135 |
+
importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/importlib-metadata_1721856510709/work
|
| 136 |
+
importlib_resources @ file:///home/conda/feedstock_root/build_artifacts/importlib_resources_1736252299705/work
|
| 137 |
+
inflect==7.5.0
|
| 138 |
+
ipykernel @ file:///Users/runner/miniforge3/conda-bld/ipykernel_1719845458456/work
|
| 139 |
+
ipython @ file:///home/conda/feedstock_root/build_artifacts/ipython_1734788142186/work
|
| 140 |
+
ipywidgets @ file:///home/conda/feedstock_root/build_artifacts/ipywidgets_1733493556527/work
|
| 141 |
+
isoduration @ file:///home/conda/feedstock_root/build_artifacts/isoduration_1733493628631/work/dist
|
| 142 |
+
itsdangerous @ file:///home/conda/feedstock_root/build_artifacts/itsdangerous_1733308265247/work
|
| 143 |
+
jamo==0.4.1
|
| 144 |
+
jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1733300866624/work
|
| 145 |
+
jieba==0.42.1
|
| 146 |
+
Jinja2 @ file:///home/conda/feedstock_root/build_artifacts/jinja2_1734823942230/work
|
| 147 |
+
jiter==0.8.2
|
| 148 |
+
jiwer==3.1.0
|
| 149 |
+
joblib @ file:///home/conda/feedstock_root/build_artifacts/joblib_1733736026804/work
|
| 150 |
+
json5 @ file:///home/conda/feedstock_root/build_artifacts/json5_1733272076743/work
|
| 151 |
+
jsonlines==1.2.0
|
| 152 |
+
jsonpatch==1.33
|
| 153 |
+
jsonpointer @ file:///Users/runner/miniforge3/conda-bld/jsonpointer_1725302946874/work
|
| 154 |
+
jsonschema @ file:///home/conda/feedstock_root/build_artifacts/jsonschema_1733472696581/work
|
| 155 |
+
jsonschema-specifications @ file:///tmp/tmpk0f344m9/src
|
| 156 |
+
jupyter-dash @ file:///home/conda/feedstock_root/build_artifacts/jupyter-dash_1648919001274/work
|
| 157 |
+
jupyter-events @ file:///home/conda/feedstock_root/build_artifacts/jupyter_events_1734531682843/work
|
| 158 |
+
jupyter-lsp @ file:///home/conda/feedstock_root/build_artifacts/jupyter-lsp-meta_1733492907176/work/jupyter-lsp
|
| 159 |
+
jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1733440914442/work
|
| 160 |
+
jupyter_core @ file:///home/conda/feedstock_root/build_artifacts/jupyter_core_1727163409502/work
|
| 161 |
+
jupyter_server @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_1734702637701/work
|
| 162 |
+
jupyter_server_terminals @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_terminals_1733427956852/work
|
| 163 |
+
jupyterlab @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_1738184697351/work
|
| 164 |
+
jupyterlab_pygments @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_pygments_1733328101776/work
|
| 165 |
+
jupyterlab_server @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_server_1733599573484/work
|
| 166 |
+
jupyterlab_widgets @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_widgets_1733428046021/work
|
| 167 |
+
kiwisolver @ file:///Users/runner/miniforge3/conda-bld/kiwisolver_1725459112978/work
|
| 168 |
+
kubernetes @ file:///home/conda/feedstock_root/build_artifacts/python-kubernetes_1737737545868/work
|
| 169 |
+
langchain==0.3.4
|
| 170 |
+
langchain-chroma==0.2.1
|
| 171 |
+
langchain-community==0.3.3
|
| 172 |
+
langchain-core==0.3.34
|
| 173 |
+
langchain-openai==0.3.4
|
| 174 |
+
langchain-text-splitters==0.3.6
|
| 175 |
+
langchainplus-sdk==0.0.20
|
| 176 |
+
langcodes==3.5.0
|
| 177 |
+
langsmith==0.1.147
|
| 178 |
+
language_data==1.3.0
|
| 179 |
+
lazy_loader==0.4
|
| 180 |
+
librosa==0.10.2.post1
|
| 181 |
+
linkify-it-py==2.0.3
|
| 182 |
+
llvmlite==0.44.0
|
| 183 |
+
lz4 @ file:///Users/runner/miniforge3/conda-bld/lz4_1725089476490/work
|
| 184 |
+
marisa-trie==1.2.1
|
| 185 |
+
Markdown==3.7
|
| 186 |
+
markdown-it-py==2.2.0
|
| 187 |
+
MarkupSafe @ file:///Users/runner/miniforge3/conda-bld/markupsafe_1724959500379/work
|
| 188 |
+
marshmallow==3.25.0
|
| 189 |
+
matplotlib==3.7.2
|
| 190 |
+
matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1733416936468/work
|
| 191 |
+
mdit-py-plugins==0.3.3
|
| 192 |
+
mdurl @ file:///home/conda/feedstock_root/build_artifacts/mdurl_1733255585584/work
|
| 193 |
+
mistune @ file:///home/conda/feedstock_root/build_artifacts/mistune_1735686876364/work
|
| 194 |
+
mmh3 @ file:///Users/runner/miniforge3/conda-bld/mmh3_1737968080672/work
|
| 195 |
+
modal==0.73.126
|
| 196 |
+
monotonic @ file:///home/conda/feedstock_root/build_artifacts/monotonic_1734029696393/work
|
| 197 |
+
more-itertools==10.6.0
|
| 198 |
+
mpmath @ file:///home/conda/feedstock_root/build_artifacts/mpmath_1733302684489/work
|
| 199 |
+
msgpack==1.1.0
|
| 200 |
+
multidict @ file:///Users/runner/miniforge3/conda-bld/multidict_1729065505264/work
|
| 201 |
+
multiprocess==0.70.15
|
| 202 |
+
munkres==1.1.4
|
| 203 |
+
murmurhash==1.0.12
|
| 204 |
+
mypy-extensions==1.0.0
|
| 205 |
+
narwhals==1.25.2
|
| 206 |
+
nbclient @ file:///home/conda/feedstock_root/build_artifacts/nbclient_1734628800805/work
|
| 207 |
+
nbconvert @ file:///home/conda/feedstock_root/build_artifacts/nbconvert-meta_1736258671456/work
|
| 208 |
+
nbformat @ file:///home/conda/feedstock_root/build_artifacts/nbformat_1733402752141/work
|
| 209 |
+
nest_asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1733325553580/work
|
| 210 |
+
networkx==2.8.8
|
| 211 |
+
nltk==3.9.1
|
| 212 |
+
notebook_shim @ file:///home/conda/feedstock_root/build_artifacts/notebook-shim_1733408315203/work
|
| 213 |
+
num2words==0.5.14
|
| 214 |
+
numba==0.61.0
|
| 215 |
+
numexpr==2.10.2
|
| 216 |
+
numpy @ file:///Users/runner/miniforge3/conda-bld/numpy_1707225640867/work/dist/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl#sha256=b62b8b824024855c3eb209bd45bfcf60bc9a11b94cbf9d5b7b25dc503021e672
|
| 217 |
+
oauthlib @ file:///home/conda/feedstock_root/build_artifacts/oauthlib_1733752848439/work
|
| 218 |
+
ollama==0.4.7
|
| 219 |
+
onnxruntime @ file:///Users/runner/miniforge3/conda-bld/onnxruntime_1735443977499/work/build-ci/Release/dist/onnxruntime-1.20.1-cp311-cp311-macosx_11_0_arm64.whl#sha256=f2a0666c1ab7a3326e5ff6b4c81cef4246c1d88692eff576fb61d17628379742
|
| 220 |
+
openai==1.68.2
|
| 221 |
+
openai-whisper==20240930
|
| 222 |
+
openapi-schema-pydantic==1.2.4
|
| 223 |
+
opentelemetry-api==1.29.0
|
| 224 |
+
opentelemetry-exporter-otlp-proto-common==1.29.0
|
| 225 |
+
opentelemetry-exporter-otlp-proto-grpc==1.29.0
|
| 226 |
+
opentelemetry-instrumentation==0.50b0
|
| 227 |
+
opentelemetry-instrumentation-asgi==0.50b0
|
| 228 |
+
opentelemetry-instrumentation-fastapi==0.50b0
|
| 229 |
+
opentelemetry-proto==1.29.0
|
| 230 |
+
opentelemetry-sdk==1.29.0
|
| 231 |
+
opentelemetry-semantic-conventions==0.50b0
|
| 232 |
+
opentelemetry-util-http==0.50b0
|
| 233 |
+
orjson==3.10.14
|
| 234 |
+
outcome==1.3.0.post0
|
| 235 |
+
overrides @ file:///home/conda/feedstock_root/build_artifacts/overrides_1734587627321/work
|
| 236 |
+
packaging==23.2
|
| 237 |
+
pandas==1.5.3
|
| 238 |
+
pandocfilters @ file:///home/conda/feedstock_root/build_artifacts/pandocfilters_1631603243851/work
|
| 239 |
+
parso @ file:///home/conda/feedstock_root/build_artifacts/parso_1733271261340/work
|
| 240 |
+
pathtools==0.1.2
|
| 241 |
+
peft==0.4.0
|
| 242 |
+
pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1733301927746/work
|
| 243 |
+
pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1733327343728/work
|
| 244 |
+
pillow @ file:///Users/runner/miniforge3/conda-bld/pillow_1735929745664/work
|
| 245 |
+
pkgutil_resolve_name @ file:///home/conda/feedstock_root/build_artifacts/pkgutil-resolve-name_1733344503739/work
|
| 246 |
+
platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1733232627818/work
|
| 247 |
+
plotly==5.15.0
|
| 248 |
+
pooch==1.8.2
|
| 249 |
+
posthog @ file:///home/conda/feedstock_root/build_artifacts/posthog_1726036274425/work
|
| 250 |
+
preshed==3.0.9
|
| 251 |
+
prometheus_client @ file:///home/conda/feedstock_root/build_artifacts/prometheus_client_1733327310477/work
|
| 252 |
+
prompt_toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1733302527033/work
|
| 253 |
+
propcache @ file:///Users/runner/miniforge3/conda-bld/propcache_1737635547864/work
|
| 254 |
+
proto-plus==1.25.0
|
| 255 |
+
protobuf==5.29.4
|
| 256 |
+
psutil==5.9.8
|
| 257 |
+
ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1733302279685/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=92c32ff62b5fd8cf325bec5ab90d7be3d2a8ca8c8a3813ff487a8d2002630d1f
|
| 258 |
+
pulsar-client @ file:///Users/runner/miniforge3/conda-bld/pulsar-client_1725542299588/work/dist/pulsar_client-3.5.0-cp311-cp311-macosx_11_0_arm64.whl#sha256=1570a636e1ae2647174be973a3c55b8f54e046f2e549b4e0043a4e18c9090df2
|
| 259 |
+
pure_eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1733569405015/work
|
| 260 |
+
pyarrow==17.0.0
|
| 261 |
+
pyasn1 @ file:///home/conda/feedstock_root/build_artifacts/pyasn1_1733217608156/work
|
| 262 |
+
pyasn1_modules @ file:///home/conda/feedstock_root/build_artifacts/pyasn1-modules_1733324602540/work
|
| 263 |
+
pycparser @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_pycparser_1733195786/work
|
| 264 |
+
pydantic==2.10.6
|
| 265 |
+
pydantic-settings==2.7.1
|
| 266 |
+
pydantic_core @ file:///Users/runner/miniforge3/conda-bld/pydantic-core_1734571525262/work
|
| 267 |
+
pydub @ file:///home/conda/feedstock_root/build_artifacts/pydub_1734638032476/work
|
| 268 |
+
pygame==2.6.1
|
| 269 |
+
Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1736243443484/work
|
| 270 |
+
PyJWT @ file:///home/conda/feedstock_root/build_artifacts/pyjwt_1732782409051/work
|
| 271 |
+
pynndescent==0.5.13
|
| 272 |
+
pyobjc==11.0
|
| 273 |
+
pyobjc-core==11.0
|
| 274 |
+
pyobjc-framework-Accessibility==11.0
|
| 275 |
+
pyobjc-framework-Accounts==11.0
|
| 276 |
+
pyobjc-framework-AddressBook==11.0
|
| 277 |
+
pyobjc-framework-AdServices==11.0
|
| 278 |
+
pyobjc-framework-AdSupport==11.0
|
| 279 |
+
pyobjc-framework-AppleScriptKit==11.0
|
| 280 |
+
pyobjc-framework-AppleScriptObjC==11.0
|
| 281 |
+
pyobjc-framework-ApplicationServices==11.0
|
| 282 |
+
pyobjc-framework-AppTrackingTransparency==11.0
|
| 283 |
+
pyobjc-framework-AudioVideoBridging==11.0
|
| 284 |
+
pyobjc-framework-AuthenticationServices==11.0
|
| 285 |
+
pyobjc-framework-AutomaticAssessmentConfiguration==11.0
|
| 286 |
+
pyobjc-framework-Automator==11.0
|
| 287 |
+
pyobjc-framework-AVFoundation==11.0
|
| 288 |
+
pyobjc-framework-AVKit==11.0
|
| 289 |
+
pyobjc-framework-AVRouting==11.0
|
| 290 |
+
pyobjc-framework-BackgroundAssets==11.0
|
| 291 |
+
pyobjc-framework-BrowserEngineKit==11.0
|
| 292 |
+
pyobjc-framework-BusinessChat==11.0
|
| 293 |
+
pyobjc-framework-CalendarStore==11.0
|
| 294 |
+
pyobjc-framework-CallKit==11.0
|
| 295 |
+
pyobjc-framework-Carbon==11.0
|
| 296 |
+
pyobjc-framework-CFNetwork==11.0
|
| 297 |
+
pyobjc-framework-Cinematic==11.0
|
| 298 |
+
pyobjc-framework-ClassKit==11.0
|
| 299 |
+
pyobjc-framework-CloudKit==11.0
|
| 300 |
+
pyobjc-framework-Cocoa==11.0
|
| 301 |
+
pyobjc-framework-Collaboration==11.0
|
| 302 |
+
pyobjc-framework-ColorSync==11.0
|
| 303 |
+
pyobjc-framework-Contacts==11.0
|
| 304 |
+
pyobjc-framework-ContactsUI==11.0
|
| 305 |
+
pyobjc-framework-CoreAudio==11.0
|
| 306 |
+
pyobjc-framework-CoreAudioKit==11.0
|
| 307 |
+
pyobjc-framework-CoreBluetooth==11.0
|
| 308 |
+
pyobjc-framework-CoreData==11.0
|
| 309 |
+
pyobjc-framework-CoreHaptics==11.0
|
| 310 |
+
pyobjc-framework-CoreLocation==11.0
|
| 311 |
+
pyobjc-framework-CoreMedia==11.0
|
| 312 |
+
pyobjc-framework-CoreMediaIO==11.0
|
| 313 |
+
pyobjc-framework-CoreMIDI==11.0
|
| 314 |
+
pyobjc-framework-CoreML==11.0
|
| 315 |
+
pyobjc-framework-CoreMotion==11.0
|
| 316 |
+
pyobjc-framework-CoreServices==11.0
|
| 317 |
+
pyobjc-framework-CoreSpotlight==11.0
|
| 318 |
+
pyobjc-framework-CoreText==11.0
|
| 319 |
+
pyobjc-framework-CoreWLAN==11.0
|
| 320 |
+
pyobjc-framework-CryptoTokenKit==11.0
|
| 321 |
+
pyobjc-framework-DataDetection==11.0
|
| 322 |
+
pyobjc-framework-DeviceCheck==11.0
|
| 323 |
+
pyobjc-framework-DeviceDiscoveryExtension==11.0
|
| 324 |
+
pyobjc-framework-DictionaryServices==11.0
|
| 325 |
+
pyobjc-framework-DiscRecording==11.0
|
| 326 |
+
pyobjc-framework-DiscRecordingUI==11.0
|
| 327 |
+
pyobjc-framework-DiskArbitration==11.0
|
| 328 |
+
pyobjc-framework-DVDPlayback==11.0
|
| 329 |
+
pyobjc-framework-EventKit==11.0
|
| 330 |
+
pyobjc-framework-ExceptionHandling==11.0
|
| 331 |
+
pyobjc-framework-ExecutionPolicy==11.0
|
| 332 |
+
pyobjc-framework-ExtensionKit==11.0
|
| 333 |
+
pyobjc-framework-ExternalAccessory==11.0
|
| 334 |
+
pyobjc-framework-FileProvider==11.0
|
| 335 |
+
pyobjc-framework-FileProviderUI==11.0
|
| 336 |
+
pyobjc-framework-FinderSync==11.0
|
| 337 |
+
pyobjc-framework-FSEvents==11.0
|
| 338 |
+
pyobjc-framework-GameCenter==11.0
|
| 339 |
+
pyobjc-framework-GameController==11.0
|
| 340 |
+
pyobjc-framework-GameKit==11.0
|
| 341 |
+
pyobjc-framework-GameplayKit==11.0
|
| 342 |
+
pyobjc-framework-HealthKit==11.0
|
| 343 |
+
pyobjc-framework-ImageCaptureCore==11.0
|
| 344 |
+
pyobjc-framework-InputMethodKit==11.0
|
| 345 |
+
pyobjc-framework-InstallerPlugins==11.0
|
| 346 |
+
pyobjc-framework-InstantMessage==11.0
|
| 347 |
+
pyobjc-framework-Intents==11.0
|
| 348 |
+
pyobjc-framework-IntentsUI==11.0
|
| 349 |
+
pyobjc-framework-IOBluetooth==11.0
|
| 350 |
+
pyobjc-framework-IOBluetoothUI==11.0
|
| 351 |
+
pyobjc-framework-IOSurface==11.0
|
| 352 |
+
pyobjc-framework-iTunesLibrary==11.0
|
| 353 |
+
pyobjc-framework-KernelManagement==11.0
|
| 354 |
+
pyobjc-framework-LatentSemanticMapping==11.0
|
| 355 |
+
pyobjc-framework-LaunchServices==11.0
|
| 356 |
+
pyobjc-framework-libdispatch==11.0
|
| 357 |
+
pyobjc-framework-libxpc==11.0
|
| 358 |
+
pyobjc-framework-LinkPresentation==11.0
|
| 359 |
+
pyobjc-framework-LocalAuthentication==11.0
|
| 360 |
+
pyobjc-framework-LocalAuthenticationEmbeddedUI==11.0
|
| 361 |
+
pyobjc-framework-MailKit==11.0
|
| 362 |
+
pyobjc-framework-MapKit==11.0
|
| 363 |
+
pyobjc-framework-MediaAccessibility==11.0
|
| 364 |
+
pyobjc-framework-MediaExtension==11.0
|
| 365 |
+
pyobjc-framework-MediaLibrary==11.0
|
| 366 |
+
pyobjc-framework-MediaPlayer==11.0
|
| 367 |
+
pyobjc-framework-MediaToolbox==11.0
|
| 368 |
+
pyobjc-framework-Metal==11.0
|
| 369 |
+
pyobjc-framework-MetalFX==11.0
|
| 370 |
+
pyobjc-framework-MetalKit==11.0
|
| 371 |
+
pyobjc-framework-MetalPerformanceShaders==11.0
|
| 372 |
+
pyobjc-framework-MetalPerformanceShadersGraph==11.0
|
| 373 |
+
pyobjc-framework-MetricKit==11.0
|
| 374 |
+
pyobjc-framework-MLCompute==11.0
|
| 375 |
+
pyobjc-framework-ModelIO==11.0
|
| 376 |
+
pyobjc-framework-MultipeerConnectivity==11.0
|
| 377 |
+
pyobjc-framework-NaturalLanguage==11.0
|
| 378 |
+
pyobjc-framework-NetFS==11.0
|
| 379 |
+
pyobjc-framework-Network==11.0
|
| 380 |
+
pyobjc-framework-NetworkExtension==11.0
|
| 381 |
+
pyobjc-framework-NotificationCenter==11.0
|
| 382 |
+
pyobjc-framework-OpenDirectory==11.0
|
| 383 |
+
pyobjc-framework-OSAKit==11.0
|
| 384 |
+
pyobjc-framework-OSLog==11.0
|
| 385 |
+
pyobjc-framework-PassKit==11.0
|
| 386 |
+
pyobjc-framework-PencilKit==11.0
|
| 387 |
+
pyobjc-framework-PHASE==11.0
|
| 388 |
+
pyobjc-framework-Photos==11.0
|
| 389 |
+
pyobjc-framework-PhotosUI==11.0
|
| 390 |
+
pyobjc-framework-PreferencePanes==11.0
|
| 391 |
+
pyobjc-framework-PushKit==11.0
|
| 392 |
+
pyobjc-framework-Quartz==11.0
|
| 393 |
+
pyobjc-framework-QuickLookThumbnailing==11.0
|
| 394 |
+
pyobjc-framework-ReplayKit==11.0
|
| 395 |
+
pyobjc-framework-SafariServices==11.0
|
| 396 |
+
pyobjc-framework-SafetyKit==11.0
|
| 397 |
+
pyobjc-framework-SceneKit==11.0
|
| 398 |
+
pyobjc-framework-ScreenCaptureKit==11.0
|
| 399 |
+
pyobjc-framework-ScreenSaver==11.0
|
| 400 |
+
pyobjc-framework-ScreenTime==11.0
|
| 401 |
+
pyobjc-framework-ScriptingBridge==11.0
|
| 402 |
+
pyobjc-framework-SearchKit==11.0
|
| 403 |
+
pyobjc-framework-Security==11.0
|
| 404 |
+
pyobjc-framework-SecurityFoundation==11.0
|
| 405 |
+
pyobjc-framework-SecurityInterface==11.0
|
| 406 |
+
pyobjc-framework-SensitiveContentAnalysis==11.0
|
| 407 |
+
pyobjc-framework-ServiceManagement==11.0
|
| 408 |
+
pyobjc-framework-SharedWithYou==11.0
|
| 409 |
+
pyobjc-framework-SharedWithYouCore==11.0
|
| 410 |
+
pyobjc-framework-ShazamKit==11.0
|
| 411 |
+
pyobjc-framework-Social==11.0
|
| 412 |
+
pyobjc-framework-SoundAnalysis==11.0
|
| 413 |
+
pyobjc-framework-Speech==11.0
|
| 414 |
+
pyobjc-framework-SpriteKit==11.0
|
| 415 |
+
pyobjc-framework-StoreKit==11.0
|
| 416 |
+
pyobjc-framework-Symbols==11.0
|
| 417 |
+
pyobjc-framework-SyncServices==11.0
|
| 418 |
+
pyobjc-framework-SystemConfiguration==11.0
|
| 419 |
+
pyobjc-framework-SystemExtensions==11.0
|
| 420 |
+
pyobjc-framework-ThreadNetwork==11.0
|
| 421 |
+
pyobjc-framework-UniformTypeIdentifiers==11.0
|
| 422 |
+
pyobjc-framework-UserNotifications==11.0
|
| 423 |
+
pyobjc-framework-UserNotificationsUI==11.0
|
| 424 |
+
pyobjc-framework-VideoSubscriberAccount==11.0
|
| 425 |
+
pyobjc-framework-VideoToolbox==11.0
|
| 426 |
+
pyobjc-framework-Virtualization==11.0
|
| 427 |
+
pyobjc-framework-Vision==11.0
|
| 428 |
+
pyobjc-framework-WebKit==11.0
|
| 429 |
+
pyOpenSSL @ file:///home/conda/feedstock_root/build_artifacts/pyopenssl_1737243356468/work
|
| 430 |
+
pyparsing==3.0.9
|
| 431 |
+
PyPDF2==3.0.1
|
| 432 |
+
PyPika @ file:///home/conda/feedstock_root/build_artifacts/pypika_1734974148700/work
|
| 433 |
+
pypinyin==0.53.0
|
| 434 |
+
pyproject_hooks @ file:///home/conda/feedstock_root/build_artifacts/pyproject_hooks_1733710025763/work
|
| 435 |
+
pysbd==0.3.4
|
| 436 |
+
PySocks @ file:///home/conda/feedstock_root/build_artifacts/pysocks_1733217236728/work
|
| 437 |
+
pytesseract==0.3.13
|
| 438 |
+
python-crfsuite==0.9.11
|
| 439 |
+
python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/python-dateutil_1733215673016/work
|
| 440 |
+
python-dotenv @ file:///home/conda/feedstock_root/build_artifacts/python-dotenv_1733243180666/work
|
| 441 |
+
python-json-logger @ file:///home/conda/feedstock_root/build_artifacts/python-json-logger_1677079630776/work
|
| 442 |
+
python-multipart @ file:///home/conda/feedstock_root/build_artifacts/python-multipart_1734420773152/work
|
| 443 |
+
pyttsx3==2.98
|
| 444 |
+
pytz @ file:///home/conda/feedstock_root/build_artifacts/pytz_1706886791323/work
|
| 445 |
+
pyu2f @ file:///home/conda/feedstock_root/build_artifacts/pyu2f_1733738580568/work
|
| 446 |
+
PyYAML @ file:///Users/runner/miniforge3/conda-bld/pyyaml_1725456256847/work
|
| 447 |
+
pyzmq @ file:///Users/runner/miniforge3/conda-bld/pyzmq_1728642247211/work
|
| 448 |
+
RapidFuzz==3.12.2
|
| 449 |
+
referencing @ file:///home/conda/feedstock_root/build_artifacts/referencing_1733366743674/work
|
| 450 |
+
regex==2024.11.6
|
| 451 |
+
requests==2.28.2
|
| 452 |
+
requests-oauthlib @ file:///home/conda/feedstock_root/build_artifacts/requests-oauthlib_1733772243268/work
|
| 453 |
+
requests-toolbelt==1.0.0
|
| 454 |
+
retrying @ file:///home/conda/feedstock_root/build_artifacts/retrying_1721646300555/work
|
| 455 |
+
rfc3339_validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3339-validator_1733599910982/work
|
| 456 |
+
rfc3986-validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3986-validator_1598024191506/work
|
| 457 |
+
rich @ file:///home/conda/feedstock_root/build_artifacts/rich_1733342254348/work/dist
|
| 458 |
+
rich-toolkit @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_rich-toolkit_1733750834/work
|
| 459 |
+
rpds-py @ file:///Users/runner/miniforge3/conda-bld/rpds-py_1733366661843/work
|
| 460 |
+
rsa @ file:///home/conda/feedstock_root/build_artifacts/rsa_1733662684165/work
|
| 461 |
+
ruff==0.9.5
|
| 462 |
+
safehttpx @ file:///home/conda/feedstock_root/build_artifacts/safehttpx_1734360621373/work
|
| 463 |
+
safetensors==0.5.2
|
| 464 |
+
scikit-learn==1.3.0
|
| 465 |
+
scipy==1.13.1
|
| 466 |
+
selenium==4.10.0
|
| 467 |
+
semantic-version @ file:///home/conda/feedstock_root/build_artifacts/semantic_version_1653579368137/work
|
| 468 |
+
Send2Trash @ file:///Users/runner/miniforge3/conda-bld/send2trash_1733322099476/work
|
| 469 |
+
sentence-transformers==2.2.2
|
| 470 |
+
sentencepiece @ file:///Users/runner/miniforge3/conda-bld/sentencepiece-split_1727549050457/work/python
|
| 471 |
+
sentry-sdk==2.20.0
|
| 472 |
+
setproctitle==1.3.4
|
| 473 |
+
sgmllib3k==1.0.0
|
| 474 |
+
shellingham @ file:///home/conda/feedstock_root/build_artifacts/shellingham_1733300899265/work
|
| 475 |
+
sigtools==4.0.1
|
| 476 |
+
simpleaudio==1.0.4
|
| 477 |
+
six @ file:///home/conda/feedstock_root/build_artifacts/six_1733380938961/work
|
| 478 |
+
smart-open==7.1.0
|
| 479 |
+
smmap==5.0.2
|
| 480 |
+
sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1733244044561/work
|
| 481 |
+
sortedcontainers==2.4.0
|
| 482 |
+
sounddevice==0.5.1
|
| 483 |
+
soundfile==0.13.1
|
| 484 |
+
soupsieve @ file:///home/conda/feedstock_root/build_artifacts/soupsieve_1693929250441/work
|
| 485 |
+
soxr==0.5.0.post1
|
| 486 |
+
spaces==0.32.0
|
| 487 |
+
spacy==3.8.4
|
| 488 |
+
spacy-legacy==3.0.12
|
| 489 |
+
spacy-loggers==1.0.5
|
| 490 |
+
SpeechRecognition==3.14.1
|
| 491 |
+
speedtest-cli==2.1.3
|
| 492 |
+
SQLAlchemy==1.4.54
|
| 493 |
+
srsly==2.5.1
|
| 494 |
+
stack_data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1733569443808/work
|
| 495 |
+
starlette @ file:///home/conda/feedstock_root/build_artifacts/starlette_1733344384719/work
|
| 496 |
+
SudachiDict-core==20250129
|
| 497 |
+
SudachiPy==0.6.10
|
| 498 |
+
sympy==1.13.1
|
| 499 |
+
synchronicity==0.9.10
|
| 500 |
+
tenacity==8.5.0
|
| 501 |
+
tensorboard==2.19.0
|
| 502 |
+
tensorboard-data-server==0.7.2
|
| 503 |
+
terminado @ file:///Users/runner/miniforge3/conda-bld/terminado_1710263781917/work
|
| 504 |
+
thinc==8.3.4
|
| 505 |
+
threadpoolctl @ file:///home/conda/feedstock_root/build_artifacts/threadpoolctl_1714400101435/work
|
| 506 |
+
tiktoken==0.8.0
|
| 507 |
+
tinycss2 @ file:///home/conda/feedstock_root/build_artifacts/tinycss2_1729802851396/work
|
| 508 |
+
tokenizers==0.13.3
|
| 509 |
+
toml==0.10.2
|
| 510 |
+
tomli @ file:///home/conda/feedstock_root/build_artifacts/tomli_1733256695513/work
|
| 511 |
+
tomlkit @ file:///home/conda/feedstock_root/build_artifacts/tomlkit_1733230743009/work
|
| 512 |
+
torch==2.6.0
|
| 513 |
+
torchaudio==2.6.0
|
| 514 |
+
torchvision==0.21.0
|
| 515 |
+
tornado @ file:///Users/runner/miniforge3/conda-bld/tornado_1732615936501/work
|
| 516 |
+
tqdm @ file:///home/conda/feedstock_root/build_artifacts/tqdm_1735661334605/work
|
| 517 |
+
trainer==0.0.36
|
| 518 |
+
traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1733367359838/work
|
| 519 |
+
transformers==4.30.0
|
| 520 |
+
trio==0.29.0
|
| 521 |
+
trio-websocket==0.12.2
|
| 522 |
+
trl==0.5.0
|
| 523 |
+
TTS==0.22.0
|
| 524 |
+
twilio==9.4.4
|
| 525 |
+
typeguard==4.4.2
|
| 526 |
+
typer==0.15.1
|
| 527 |
+
typer-slim==0.15.1
|
| 528 |
+
types-certifi==2021.10.8.3
|
| 529 |
+
types-python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/types-python-dateutil_1733612335562/work
|
| 530 |
+
types-toml==0.10.8.20240310
|
| 531 |
+
typing-inspect==0.9.0
|
| 532 |
+
typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1733188668063/work
|
| 533 |
+
typing_utils @ file:///home/conda/feedstock_root/build_artifacts/typing_utils_1733331286120/work
|
| 534 |
+
tzdata @ file:///home/conda/feedstock_root/build_artifacts/python-tzdata_1733235305708/work
|
| 535 |
+
tzlocal==5.3.1
|
| 536 |
+
uc-micro-py==1.0.3
|
| 537 |
+
umap==0.1.1
|
| 538 |
+
umap-learn==0.5.7
|
| 539 |
+
unicodedata2 @ file:///Users/runner/miniforge3/conda-bld/unicodedata2_1729704585212/work
|
| 540 |
+
Unidecode==1.3.8
|
| 541 |
+
uri-template @ file:///home/conda/feedstock_root/build_artifacts/uri-template_1733323593477/work/dist
|
| 542 |
+
uritemplate==4.1.1
|
| 543 |
+
urllib3==1.26.20
|
| 544 |
+
uvicorn @ file:///home/conda/feedstock_root/build_artifacts/uvicorn_1734292939144/work
|
| 545 |
+
uvloop @ file:///Users/runner/miniforge3/conda-bld/uvloop_1730214404471/work
|
| 546 |
+
wandb==0.15.8
|
| 547 |
+
wasabi==1.1.3
|
| 548 |
+
watchfiles @ file:///Users/runner/miniforge3/conda-bld/watchfiles_1736550451638/work
|
| 549 |
+
wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1733231326287/work
|
| 550 |
+
weasel==0.4.1
|
| 551 |
+
webcolors @ file:///home/conda/feedstock_root/build_artifacts/webcolors_1733359735138/work
|
| 552 |
+
webdriver-manager==3.8.6
|
| 553 |
+
webencodings @ file:///home/conda/feedstock_root/build_artifacts/webencodings_1733236011802/work
|
| 554 |
+
websocket-client @ file:///home/conda/feedstock_root/build_artifacts/websocket-client_1733157342724/work
|
| 555 |
+
websockets==11.0.3
|
| 556 |
+
Werkzeug==2.2.3
|
| 557 |
+
whisper==1.1.10
|
| 558 |
+
widgetsnbextension @ file:///home/conda/feedstock_root/build_artifacts/widgetsnbextension_1733128559935/work
|
| 559 |
+
wrapt==1.17.1
|
| 560 |
+
wsproto==1.2.0
|
| 561 |
+
xxhash==3.5.0
|
| 562 |
+
yarl @ file:///Users/runner/miniforge3/conda-bld/yarl_1737575825523/work
|
| 563 |
+
zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1732827521216/work
|
| 564 |
+
zstandard==0.23.0
|
requirements2.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=3.41.2
|
| 2 |
+
openai>=0.28.0
|
| 3 |
+
anthropic>=0.5.0
|
| 4 |
+
python-dotenv>=1.0.0
|
| 5 |
+
langchain>=0.0.267
|
| 6 |
+
faiss-cpu>=1.7.4
|
| 7 |
+
SpeechRecognition>=3.10.0
|
| 8 |
+
gtts>=2.3.2
|
| 9 |
+
playsound>=1.3.0
|
| 10 |
+
PyAudio>=0.2.13
|
services/__init__.py
ADDED
|
File without changes
|
services/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (260 Bytes). View file
|
|
|
services/__pycache__/auth_service.cpython-311.pyc
ADDED
|
Binary file (5.87 kB). View file
|
|
|
services/__pycache__/chatbot_rag.cpython-311.pyc
ADDED
|
Binary file (5.59 kB). View file
|
|
|
services/__pycache__/data_service.cpython-311.pyc
ADDED
|
Binary file (1.19 kB). View file
|
|
|
services/__pycache__/file_service.cpython-311.pyc
ADDED
|
Binary file (9.18 kB). View file
|
|
|
services/__pycache__/general_chat_service.cpython-311.pyc
ADDED
|
Binary file (2.58 kB). View file
|
|
|
services/__pycache__/response_service.cpython-311.pyc
ADDED
|
Binary file (763 Bytes). View file
|
|
|
services/__pycache__/study_support_service.cpython-311.pyc
ADDED
|
Binary file (1.52 kB). View file
|
|
|
services/__pycache__/system_prompts_service.cpython-311.pyc
ADDED
|
Binary file (1.14 kB). View file
|
|
|
services/__pycache__/ui_service.cpython-311.pyc
ADDED
|
Binary file (57.8 kB). View file
|
|
|
services/__pycache__/voice_interface.cpython-311.pyc
ADDED
|
Binary file (8.18 kB). View file
|
|
|
services/auth_service.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/auth_service.py
|
| 2 |
+
|
| 3 |
+
import time
|
| 4 |
+
import threading
|
| 5 |
+
import uuid
|
| 6 |
+
from typing import Dict, Optional, Any, Tuple
|
| 7 |
+
|
| 8 |
+
# A simple dictionary of sample credentials.
|
| 9 |
+
users = {
|
| 10 |
+
"adabor@usiu.ac.ke": "student123",
|
| 11 |
+
"adavid@usiu.ac.ke": "faculty123",
|
| 12 |
+
"jmilton@usiu.ac.ke": "staff123"
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
# Active user sessions storage
|
| 16 |
+
active_sessions: Dict[str, Dict[str, Any]] = {}
|
| 17 |
+
|
| 18 |
+
# Inactivity timeout in seconds (15 minutes)
|
| 19 |
+
INACTIVITY_TIMEOUT = 15 * 60
|
| 20 |
+
|
| 21 |
+
def authenticate(user_id: str, password: str) -> bool:
|
| 22 |
+
"""Return True if the provided credentials match the lookup table."""
|
| 23 |
+
return users.get(user_id) == password
|
| 24 |
+
|
| 25 |
+
def create_session(user_id: str) -> str:
|
| 26 |
+
"""
|
| 27 |
+
Create a new user session with a unique session ID
|
| 28 |
+
Returns the session ID
|
| 29 |
+
"""
|
| 30 |
+
session_id = str(uuid.uuid4())
|
| 31 |
+
active_sessions[session_id] = {
|
| 32 |
+
"user_id": user_id,
|
| 33 |
+
"logged_in": True,
|
| 34 |
+
"last_activity": time.time(),
|
| 35 |
+
"inactivity_timer": None,
|
| 36 |
+
"warned": False,
|
| 37 |
+
"created_at": time.time()
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
# Set the initial inactivity timer
|
| 41 |
+
timer = threading.Timer(INACTIVITY_TIMEOUT, check_inactivity, args=[session_id])
|
| 42 |
+
timer.daemon = True
|
| 43 |
+
timer.start()
|
| 44 |
+
active_sessions[session_id]["inactivity_timer"] = timer
|
| 45 |
+
|
| 46 |
+
return session_id
|
| 47 |
+
|
| 48 |
+
def get_session(session_id: str) -> Optional[Dict[str, Any]]:
|
| 49 |
+
"""Get session data for a given session ID"""
|
| 50 |
+
return active_sessions.get(session_id)
|
| 51 |
+
|
| 52 |
+
def update_session_activity(session_id: str) -> bool:
|
| 53 |
+
"""
|
| 54 |
+
Update the last activity timestamp for a session
|
| 55 |
+
Returns True if session was found and updated, False otherwise
|
| 56 |
+
"""
|
| 57 |
+
if session_id in active_sessions:
|
| 58 |
+
active_sessions[session_id]["last_activity"] = time.time()
|
| 59 |
+
active_sessions[session_id]["warned"] = False
|
| 60 |
+
|
| 61 |
+
# Reset inactivity timer if it exists
|
| 62 |
+
if active_sessions[session_id]["inactivity_timer"]:
|
| 63 |
+
active_sessions[session_id]["inactivity_timer"].cancel()
|
| 64 |
+
|
| 65 |
+
# Set new inactivity timer
|
| 66 |
+
timer = threading.Timer(INACTIVITY_TIMEOUT, check_inactivity, args=[session_id])
|
| 67 |
+
timer.daemon = True
|
| 68 |
+
timer.start()
|
| 69 |
+
active_sessions[session_id]["inactivity_timer"] = timer
|
| 70 |
+
return True
|
| 71 |
+
return False
|
| 72 |
+
|
| 73 |
+
def end_session(session_id: str) -> bool:
|
| 74 |
+
"""
|
| 75 |
+
End a user session
|
| 76 |
+
Returns True if session was found and ended, False otherwise
|
| 77 |
+
"""
|
| 78 |
+
if session_id in active_sessions:
|
| 79 |
+
# Cancel the inactivity timer if it exists
|
| 80 |
+
if active_sessions[session_id]["inactivity_timer"]:
|
| 81 |
+
active_sessions[session_id]["inactivity_timer"].cancel()
|
| 82 |
+
|
| 83 |
+
# Remove the session
|
| 84 |
+
del active_sessions[session_id]
|
| 85 |
+
return True
|
| 86 |
+
return False
|
| 87 |
+
|
| 88 |
+
def check_inactivity(session_id: str) -> None:
|
| 89 |
+
"""Check if the session has been inactive for too long and mark it as logged out if so"""
|
| 90 |
+
if session_id in active_sessions:
|
| 91 |
+
current_time = time.time()
|
| 92 |
+
last_activity = active_sessions[session_id]["last_activity"]
|
| 93 |
+
|
| 94 |
+
if current_time - last_activity >= INACTIVITY_TIMEOUT:
|
| 95 |
+
active_sessions[session_id]["logged_in"] = False
|
| 96 |
+
# The actual logout action will be handled by the UI when it detects this change
|
| 97 |
+
|
| 98 |
+
def get_active_sessions() -> Dict[str, Dict[str, Any]]:
|
| 99 |
+
"""Get all active sessions (for admin or debugging purposes)"""
|
| 100 |
+
return active_sessions
|
| 101 |
+
|
| 102 |
+
def clear_expired_sessions() -> int:
|
| 103 |
+
"""
|
| 104 |
+
Clear all expired sessions
|
| 105 |
+
Returns the number of sessions cleared
|
| 106 |
+
"""
|
| 107 |
+
expired_count = 0
|
| 108 |
+
sessions_to_remove = []
|
| 109 |
+
|
| 110 |
+
for session_id, session_data in active_sessions.items():
|
| 111 |
+
if not session_data["logged_in"]:
|
| 112 |
+
sessions_to_remove.append(session_id)
|
| 113 |
+
|
| 114 |
+
for session_id in sessions_to_remove:
|
| 115 |
+
end_session(session_id)
|
| 116 |
+
expired_count += 1
|
| 117 |
+
|
| 118 |
+
return expired_count
|
| 119 |
+
|
| 120 |
+
# Start a background thread to periodically clean up expired sessions
|
| 121 |
+
def cleanup_sessions_periodically(interval=3600): # Default: every hour
|
| 122 |
+
"""Start a background thread to periodically clean up expired sessions"""
|
| 123 |
+
def cleanup_task():
|
| 124 |
+
while True:
|
| 125 |
+
clear_expired_sessions()
|
| 126 |
+
time.sleep(interval)
|
| 127 |
+
|
| 128 |
+
cleanup_thread = threading.Thread(target=cleanup_task, daemon=True)
|
| 129 |
+
cleanup_thread.start()
|
| 130 |
+
|
| 131 |
+
# Initialize the session cleanup thread
|
| 132 |
+
cleanup_sessions_periodically()
|
services/data_service-old1.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/data_service.py
|
| 2 |
+
from services.system_prompts_service import SystemPromptsService
|
| 3 |
+
from services.response_service import ResponseService
|
| 4 |
+
from configs.config import GPT4O_MODEL, CLAUDE_MODEL, GPT4O_API_KEY, CLAUDE_API_KEY
|
| 5 |
+
from services.general_chat_service import GeneralChatService
|
| 6 |
+
from services.study_support_service import StudySupportService
|
| 7 |
+
from langchain.embeddings import OpenAIEmbeddings
|
| 8 |
+
from langchain.vectorstores import Chroma
|
| 9 |
+
|
| 10 |
+
class DataService:
|
| 11 |
+
"""
|
| 12 |
+
Data service that routes user queries to the appropriate agent:
|
| 13 |
+
- General academic inquiries use GPT-4o via RAG if a retriever is provided,
|
| 14 |
+
using the general_chat function from GeneralChatService.
|
| 15 |
+
- Study support inquiries use Claude 3.5 Sonnet.
|
| 16 |
+
"""
|
| 17 |
+
def __init__(self, retriever=None) -> None:
|
| 18 |
+
self.prompts_service = SystemPromptsService()
|
| 19 |
+
self.response_service = ResponseService()
|
| 20 |
+
|
| 21 |
+
# If a retriever is provided as a string (e.g., a directory path),
|
| 22 |
+
# convert it into a proper retriever object.
|
| 23 |
+
if retriever and isinstance(retriever, str):
|
| 24 |
+
embeddings = OpenAIEmbeddings(openai_api_key=GPT4O_API_KEY)
|
| 25 |
+
vectorstore = Chroma(persist_directory=retriever, embedding_function=embeddings)
|
| 26 |
+
retriever = vectorstore.as_retriever(
|
| 27 |
+
search_type="mmr",
|
| 28 |
+
search_kwargs={'k': 40, 'lambda_mult': 0.6}
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
self.general_chat_service = GeneralChatService(
|
| 32 |
+
model=GPT4O_MODEL,
|
| 33 |
+
api_key=GPT4O_API_KEY,
|
| 34 |
+
system_prompt=self.prompts_service.get_prompt()[0],
|
| 35 |
+
retriever=retriever # RAG will be used if this is a valid retriever object.
|
| 36 |
+
)
|
| 37 |
+
self.study_support_service = StudySupportService(
|
| 38 |
+
model=CLAUDE_MODEL,
|
| 39 |
+
api_key=CLAUDE_API_KEY,
|
| 40 |
+
system_prompt=self.prompts_service.get_prompt()[1]
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
def route_query(self, query: str, chat_history: any, query_type: str = "general") -> str:
|
| 44 |
+
"""
|
| 45 |
+
Routes the user query to the appropriate service based on query type.
|
| 46 |
+
- If query_type is "study", the study support service is used.
|
| 47 |
+
- Otherwise, the general chat service (using general_chat) is used.
|
| 48 |
+
The final response is formatted via the response_service.
|
| 49 |
+
"""
|
| 50 |
+
if query_type == "study":
|
| 51 |
+
response = self.study_support_service.ask(query)
|
| 52 |
+
else:
|
| 53 |
+
# Call the general_chat function which streams responses.
|
| 54 |
+
# Convert the generator into a list, and use the final yielded value.
|
| 55 |
+
responses = list(self.general_chat_service.general_chat(query, chat_history))
|
| 56 |
+
response = responses[-1] if responses else ""
|
| 57 |
+
return self.response_service.format_response(response)
|
services/data_service.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/data_service.py
|
| 2 |
+
from services.system_prompts_service import SystemPromptsService
|
| 3 |
+
|
| 4 |
+
class DataService:
|
| 5 |
+
"""
|
| 6 |
+
Data service that provides access to system prompts and manages data-related operations.
|
| 7 |
+
"""
|
| 8 |
+
def __init__(self, retriever=None) -> None:
|
| 9 |
+
self.prompts_service = SystemPromptsService()
|
| 10 |
+
self.retriever = retriever
|
| 11 |
+
|
| 12 |
+
def get_retriever(self):
|
| 13 |
+
"""Get the configured retriever for RAG"""
|
| 14 |
+
return self.retriever
|
services/file_service.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/file_service.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import io
|
| 5 |
+
import PyPDF2
|
| 6 |
+
import docx
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import pptx
|
| 9 |
+
from PIL import Image
|
| 10 |
+
import openai
|
| 11 |
+
import csv
|
| 12 |
+
from typing import Dict, Any
|
| 13 |
+
|
| 14 |
+
class FileProcessor:
|
| 15 |
+
"""Service for processing different types of files"""
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def read_file(file) -> str:
|
| 19 |
+
"""
|
| 20 |
+
Extract content from various file types
|
| 21 |
+
Returns the content as a string
|
| 22 |
+
"""
|
| 23 |
+
if file is None:
|
| 24 |
+
return "No file provided"
|
| 25 |
+
|
| 26 |
+
file_extension = os.path.splitext(file.name)[1].lower()
|
| 27 |
+
|
| 28 |
+
if file_extension == '.txt':
|
| 29 |
+
content = file.read().decode('utf-8')
|
| 30 |
+
elif file_extension == '.pdf':
|
| 31 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 32 |
+
content = ''
|
| 33 |
+
for page in pdf_reader.pages:
|
| 34 |
+
text = page.extract_text()
|
| 35 |
+
if text:
|
| 36 |
+
content += text
|
| 37 |
+
elif file_extension in ['.docx', '.doc']:
|
| 38 |
+
doc = docx.Document(file)
|
| 39 |
+
content = "\n".join([para.text for para in doc.paragraphs])
|
| 40 |
+
elif file_extension in ['.xlsx', '.xls']:
|
| 41 |
+
df = pd.read_excel(file)
|
| 42 |
+
content = df.to_string()
|
| 43 |
+
elif file_extension == '.csv':
|
| 44 |
+
# Reset file pointer to the beginning
|
| 45 |
+
file.seek(0)
|
| 46 |
+
|
| 47 |
+
# First, try to read with pandas
|
| 48 |
+
try:
|
| 49 |
+
df = pd.read_csv(file)
|
| 50 |
+
content = df.to_string()
|
| 51 |
+
except Exception as e:
|
| 52 |
+
# If pandas fails, try with csv module
|
| 53 |
+
file.seek(0)
|
| 54 |
+
try:
|
| 55 |
+
csv_content = []
|
| 56 |
+
csv_reader = csv.reader(io.StringIO(file.read().decode('utf-8')))
|
| 57 |
+
for row in csv_reader:
|
| 58 |
+
csv_content.append(','.join(row))
|
| 59 |
+
content = '\n'.join(csv_content)
|
| 60 |
+
except Exception as csv_e:
|
| 61 |
+
content = f"Error processing CSV file: {str(csv_e)}"
|
| 62 |
+
elif file_extension in ['.pptx', '.ppt']:
|
| 63 |
+
prs = pptx.Presentation(file)
|
| 64 |
+
content = ''
|
| 65 |
+
for slide in prs.slides:
|
| 66 |
+
for shape in slide.shapes:
|
| 67 |
+
if hasattr(shape, 'text'):
|
| 68 |
+
content += shape.text + '\n'
|
| 69 |
+
elif file_extension in ['.png', '.jpg', '.jpeg', '.gif']:
|
| 70 |
+
try:
|
| 71 |
+
image = Image.open(file)
|
| 72 |
+
buffered = io.BytesIO()
|
| 73 |
+
image.save(buffered, format="PNG")
|
| 74 |
+
# Create an image variation via OpenAI's API
|
| 75 |
+
response = openai.Image.create_variation(
|
| 76 |
+
image=buffered,
|
| 77 |
+
n=1,
|
| 78 |
+
size="256x256"
|
| 79 |
+
)
|
| 80 |
+
image_description = response['data'][0]['url']
|
| 81 |
+
content = f"[Image uploaded. Description: {image_description}]"
|
| 82 |
+
except Exception as e:
|
| 83 |
+
content = f"Error processing image: {str(e)}"
|
| 84 |
+
else:
|
| 85 |
+
content = f"Unsupported file type: {file_extension}"
|
| 86 |
+
|
| 87 |
+
return content
|
| 88 |
+
|
| 89 |
+
@staticmethod
|
| 90 |
+
def get_file_metadata(file) -> Dict[str, Any]:
|
| 91 |
+
"""
|
| 92 |
+
Get metadata about a file
|
| 93 |
+
Returns a dictionary of file metadata
|
| 94 |
+
"""
|
| 95 |
+
if file is None:
|
| 96 |
+
return {"error": "No file provided"}
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
file_extension = os.path.splitext(file.name)[1].lower()
|
| 100 |
+
file_size = os.path.getsize(file.name) if hasattr(file, 'name') else 0
|
| 101 |
+
|
| 102 |
+
metadata = {
|
| 103 |
+
"filename": os.path.basename(file.name),
|
| 104 |
+
"extension": file_extension,
|
| 105 |
+
"size_bytes": file_size,
|
| 106 |
+
"size_formatted": FileProcessor._format_file_size(file_size)
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
# Get additional metadata based on file type
|
| 110 |
+
if file_extension == '.pdf':
|
| 111 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 112 |
+
metadata["pages"] = len(pdf_reader.pages)
|
| 113 |
+
if pdf_reader.metadata:
|
| 114 |
+
metadata["author"] = pdf_reader.metadata.author
|
| 115 |
+
metadata["creator"] = pdf_reader.metadata.creator
|
| 116 |
+
metadata["producer"] = pdf_reader.metadata.producer
|
| 117 |
+
elif file_extension in ['.docx', '.doc']:
|
| 118 |
+
doc = docx.Document(file)
|
| 119 |
+
metadata["paragraphs"] = len(doc.paragraphs)
|
| 120 |
+
elif file_extension in ['.xlsx', '.xls']:
|
| 121 |
+
df = pd.read_excel(file)
|
| 122 |
+
metadata["rows"] = len(df)
|
| 123 |
+
metadata["columns"] = len(df.columns)
|
| 124 |
+
elif file_extension == '.csv':
|
| 125 |
+
# Reset file pointer
|
| 126 |
+
file.seek(0)
|
| 127 |
+
try:
|
| 128 |
+
df = pd.read_csv(file)
|
| 129 |
+
metadata["rows"] = len(df)
|
| 130 |
+
metadata["columns"] = len(df.columns)
|
| 131 |
+
|
| 132 |
+
# Get sample of column names
|
| 133 |
+
col_sample = df.columns.tolist()[:5]
|
| 134 |
+
if len(df.columns) > 5:
|
| 135 |
+
col_sample.append("...")
|
| 136 |
+
metadata["column_sample"] = col_sample
|
| 137 |
+
except Exception:
|
| 138 |
+
# If pandas fails, try with csv module
|
| 139 |
+
file.seek(0)
|
| 140 |
+
try:
|
| 141 |
+
csv_reader = csv.reader(io.StringIO(file.read().decode('utf-8')))
|
| 142 |
+
rows = list(csv_reader)
|
| 143 |
+
metadata["rows"] = len(rows)
|
| 144 |
+
if rows:
|
| 145 |
+
metadata["columns"] = len(rows[0])
|
| 146 |
+
except Exception:
|
| 147 |
+
pass
|
| 148 |
+
elif file_extension in ['.pptx', '.ppt']:
|
| 149 |
+
prs = pptx.Presentation(file)
|
| 150 |
+
metadata["slides"] = len(prs.slides)
|
| 151 |
+
elif file_extension in ['.png', '.jpg', '.jpeg', '.gif']:
|
| 152 |
+
image = Image.open(file)
|
| 153 |
+
metadata["dimensions"] = f"{image.width}x{image.height}"
|
| 154 |
+
metadata["mode"] = image.mode
|
| 155 |
+
metadata["format"] = image.format
|
| 156 |
+
|
| 157 |
+
return metadata
|
| 158 |
+
except Exception as e:
|
| 159 |
+
return {"error": f"Error getting file metadata: {str(e)}"}
|
| 160 |
+
|
| 161 |
+
@staticmethod
|
| 162 |
+
def _format_file_size(size_bytes: int) -> str:
|
| 163 |
+
"""Format file size from bytes to human-readable format"""
|
| 164 |
+
for unit in ['B', 'KB', 'MB', 'GB']:
|
| 165 |
+
if size_bytes < 1024.0:
|
| 166 |
+
return f"{size_bytes:.2f} {unit}"
|
| 167 |
+
size_bytes /= 1024.0
|
| 168 |
+
return f"{size_bytes:.2f} TB"
|
services/quantized_model_loader.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
|
| 3 |
+
from configs.config import GPT4O_MODEL, LLAMA_MODEL
|
| 4 |
+
|
| 5 |
+
class QuantizedModelLoader:
|
| 6 |
+
"""
|
| 7 |
+
This class loads a quantized model (either GPT-4.0 or Llama) at initialization.
|
| 8 |
+
The model is loaded in memory once and can be accessed via the `model` and `tokenizer` attributes.
|
| 9 |
+
|
| 10 |
+
Parameters:
|
| 11 |
+
model_type (str): A string indicating which model to load.
|
| 12 |
+
Expected values: "gpt4o" or "llama".
|
| 13 |
+
quant_4_bit (bool): If True, loads the model in 4-bit mode; otherwise, in 8-bit mode.
|
| 14 |
+
"""
|
| 15 |
+
def __init__(self, model_type: str, quant_4_bit: bool = False):
|
| 16 |
+
self.quant_4_bit = quant_4_bit
|
| 17 |
+
|
| 18 |
+
# Select the model name from configuration.
|
| 19 |
+
if model_type.lower() == "gpt4o":
|
| 20 |
+
self.model_name = GPT4O_MODEL
|
| 21 |
+
elif model_type.lower() == "llama":
|
| 22 |
+
self.model_name = LLAMA_MODEL
|
| 23 |
+
else:
|
| 24 |
+
raise ValueError("Invalid model_type. Expected 'gpt4o' or 'llama'.")
|
| 25 |
+
|
| 26 |
+
self._initialize_model()
|
| 27 |
+
|
| 28 |
+
def _initialize_model(self):
|
| 29 |
+
# Set up quantization configuration.
|
| 30 |
+
if self.quant_4_bit:
|
| 31 |
+
quant_config = BitsAndBytesConfig(
|
| 32 |
+
load_in_4bit=True,
|
| 33 |
+
bnb_4bit_use_double_quant=True,
|
| 34 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
| 35 |
+
bnb_4bit_quant_type="nf4"
|
| 36 |
+
)
|
| 37 |
+
else:
|
| 38 |
+
quant_config = BitsAndBytesConfig(
|
| 39 |
+
load_in_8bit=True,
|
| 40 |
+
bnb_8bit_compute_dtype=torch.bfloat16
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# Load the tokenizer.
|
| 44 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True)
|
| 45 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 46 |
+
self.tokenizer.padding_side = "right"
|
| 47 |
+
|
| 48 |
+
# Load the base model in quantized mode.
|
| 49 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 50 |
+
self.model_name,
|
| 51 |
+
quantization_config=quant_config,
|
| 52 |
+
device_map="auto"
|
| 53 |
+
)
|
| 54 |
+
# Update the tokenizer with the quantized model's pad token.
|
| 55 |
+
self.model.generation_config.pad_token_id = self.tokenizer.pad_token_id
|
| 56 |
+
|
| 57 |
+
print(f"Quantized model '{self.model_name}' loaded. Memory footprint: {self.model.get_memory_footprint() / 1e6:.1f} MB")
|
| 58 |
+
|
| 59 |
+
# Expose the class for import.
|
| 60 |
+
__all__ = ["QuantizedModelLoader"]
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
# Initialization of the models.
|
| 64 |
+
print("Initializing GPT-4.0 quantized model loader:")
|
| 65 |
+
gpt_loader = QuantizedModelLoader("gpt4o", quant_4_bit=False)
|
| 66 |
+
print("Initializing Llama quantized model loader:")
|
| 67 |
+
llama_loader = QuantizedModelLoader("llama", quant_4_bit=False)
|
services/system_prompts_service.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/system_prompts_service.py
|
| 2 |
+
|
| 3 |
+
from system_prompts.prompts_manager import get_system_prompt
|
| 4 |
+
|
| 5 |
+
class SystemPromptsService:
|
| 6 |
+
def __init__(self):
|
| 7 |
+
self.general_prompt = (
|
| 8 |
+
get_system_prompt(model_type="general")
|
| 9 |
+
)
|
| 10 |
+
self.study_prompt = (
|
| 11 |
+
get_system_prompt(model_type="study")
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
def get_prompt(self):
|
| 15 |
+
return [self.general_prompt, self.study_prompt]
|
services/ui_service-old2.py
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import openai
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import PyPDF2
|
| 7 |
+
import docx
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import pptx
|
| 10 |
+
from PIL import Image
|
| 11 |
+
import io
|
| 12 |
+
import base64
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
from configs.config import claude
|
| 16 |
+
from .voice_interface import VoiceInterface
|
| 17 |
+
from .auth_service import authenticate
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Set the OpenAI API key from the environment variable
|
| 21 |
+
openai.api_key = os.getenv('OPENAI_API_KEY')
|
| 22 |
+
if not openai.api_key:
|
| 23 |
+
raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it before running this script.")
|
| 24 |
+
|
| 25 |
+
# Initialize Voice Interface
|
| 26 |
+
voice_interface = VoiceInterface()
|
| 27 |
+
|
| 28 |
+
def custom_css():
|
| 29 |
+
css = """
|
| 30 |
+
/* Professional custom CSS with transitions, hover effects, and responsive design */
|
| 31 |
+
/* Your existing CSS remains here - unchanged */
|
| 32 |
+
|
| 33 |
+
/* Voice chat related CSS */
|
| 34 |
+
.voice-btn {
|
| 35 |
+
border-radius: 50%;
|
| 36 |
+
width: 45px;
|
| 37 |
+
height: 45px;
|
| 38 |
+
padding: 0;
|
| 39 |
+
display: flex;
|
| 40 |
+
align-items: center;
|
| 41 |
+
justify-content: center;
|
| 42 |
+
background-color: #000080;
|
| 43 |
+
color: white;
|
| 44 |
+
margin-right: 5px;
|
| 45 |
+
transition: all 0.3s ease;
|
| 46 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.voice-btn:hover {
|
| 50 |
+
transform: scale(1.1);
|
| 51 |
+
background-color: #000066;
|
| 52 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.voice-btn.recording {
|
| 56 |
+
background-color: #cc0000;
|
| 57 |
+
animation: pulse 1.5s infinite;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
@keyframes pulse {
|
| 61 |
+
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(204, 0, 0, 0.7); }
|
| 62 |
+
70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(204, 0, 0, 0); }
|
| 63 |
+
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(204, 0, 0, 0); }
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.voice-options {
|
| 67 |
+
margin-top: 10px;
|
| 68 |
+
padding: 10px;
|
| 69 |
+
border-radius: 8px;
|
| 70 |
+
background-color: rgba(0, 0, 128, 0.05);
|
| 71 |
+
}
|
| 72 |
+
"""
|
| 73 |
+
return css
|
| 74 |
+
|
| 75 |
+
# ----------------- Login Form UI -----------------
|
| 76 |
+
|
| 77 |
+
def login_form_ui():
|
| 78 |
+
with gr.Blocks() as login_form:
|
| 79 |
+
|
| 80 |
+
# USIU logo
|
| 81 |
+
gr.HTML('''
|
| 82 |
+
<div class="logo-container">
|
| 83 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 84 |
+
</div>
|
| 85 |
+
''')
|
| 86 |
+
|
| 87 |
+
gr.Markdown("## Please Login")
|
| 88 |
+
user_id_input = gr.Textbox(
|
| 89 |
+
placeholder="Enter your User ID",
|
| 90 |
+
label="User ID",
|
| 91 |
+
type="email",
|
| 92 |
+
autofocus=True
|
| 93 |
+
)
|
| 94 |
+
# Error message for invalid user ID
|
| 95 |
+
user_id_error = gr.Markdown("", visible=True)
|
| 96 |
+
|
| 97 |
+
password_input = gr.Textbox(
|
| 98 |
+
placeholder="Enter your Password",
|
| 99 |
+
label="Password",
|
| 100 |
+
type="password"
|
| 101 |
+
)
|
| 102 |
+
# Error message for invalid password
|
| 103 |
+
password_error = gr.Markdown("", visible=True)
|
| 104 |
+
|
| 105 |
+
login_btn = gr.Button("Login")
|
| 106 |
+
# Error message for invalid credentials
|
| 107 |
+
login_msg = gr.Markdown("")
|
| 108 |
+
proceed_btn = gr.Button("Proceed", visible=False)
|
| 109 |
+
back_btn = gr.Button("Back to Dashboard")
|
| 110 |
+
# Return the login form along with the necessary components in order.
|
| 111 |
+
|
| 112 |
+
return login_form, user_id_input, user_id_error, password_input, password_error, login_btn, login_msg, proceed_btn, back_btn
|
| 113 |
+
|
| 114 |
+
# ----------------- Authentication Logic -----------------
|
| 115 |
+
|
| 116 |
+
def show_success(message: str, duration: float = 10, visible: bool = True, title: str = "Success") -> None:
|
| 117 |
+
# Bright, material-style success colors:
|
| 118 |
+
styled_message = (
|
| 119 |
+
f'<span style="color: #2e7d32; background-color: #c8e6c9; padding: 10px; '
|
| 120 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 121 |
+
)
|
| 122 |
+
gr.Success(styled_message, duration=duration, visible=visible, title=title)
|
| 123 |
+
# Add a small delay to help the modal appear
|
| 124 |
+
time.sleep(0.1)
|
| 125 |
+
|
| 126 |
+
def show_error(message: str, duration: float = 13, visible: bool = True, title: str = "Error") -> None:
|
| 127 |
+
# Bright, material-style error colors:
|
| 128 |
+
styled_message = (
|
| 129 |
+
f'<span style="color: #c62828; background-color: #ffcdd2; padding: 10px; '
|
| 130 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 131 |
+
)
|
| 132 |
+
gr.Error(styled_message, duration=duration, visible=visible, title=title)
|
| 133 |
+
# Add a small delay to help the modal appear
|
| 134 |
+
time.sleep(0.1)
|
| 135 |
+
|
| 136 |
+
def handle_login(user_id, password):
|
| 137 |
+
"""
|
| 138 |
+
Validates login fields and the User ID (which must end with '@usiu.ac.ke').
|
| 139 |
+
- If both fields are empty, a generic error modal is shown.
|
| 140 |
+
- If a specific field is empty, an error message for that field is returned.
|
| 141 |
+
- On successful authentication, a success modal pops up.
|
| 142 |
+
|
| 143 |
+
Returns a tuple of five outputs:
|
| 144 |
+
1. Overall status message (displayed below the login button).
|
| 145 |
+
2. User ID field-specific error (displayed below the User ID input).
|
| 146 |
+
3. Password field-specific error (displayed below the Password input).
|
| 147 |
+
4. A gr.update() call to control the visibility of the "Proceed" button.
|
| 148 |
+
5. A gr.update() call to control the visibility of the "Login" button.
|
| 149 |
+
"""
|
| 150 |
+
try:
|
| 151 |
+
user_id = user_id.strip() if user_id else ""
|
| 152 |
+
password = password.strip() if password else ""
|
| 153 |
+
|
| 154 |
+
# Both fields empty:
|
| 155 |
+
if user_id == "" and password == "":
|
| 156 |
+
show_error("Please enter your User ID and Password.")
|
| 157 |
+
return (
|
| 158 |
+
"<span style='color:#c62828;'>Please enter your User ID and Password.</span>",
|
| 159 |
+
"", # No field-specific error for User ID.
|
| 160 |
+
"", # No field-specific error for Password.
|
| 161 |
+
gr.update(visible=False),
|
| 162 |
+
gr.update(visible=True)
|
| 163 |
+
)
|
| 164 |
+
# User ID is empty:
|
| 165 |
+
elif user_id == "":
|
| 166 |
+
show_error("User ID is required.")
|
| 167 |
+
return (
|
| 168 |
+
"",
|
| 169 |
+
"<span style='color:#c62828;'>User ID is required.</span>",
|
| 170 |
+
"",
|
| 171 |
+
gr.update(visible=False),
|
| 172 |
+
gr.update(visible=True)
|
| 173 |
+
)
|
| 174 |
+
# Password is empty:
|
| 175 |
+
elif password == "":
|
| 176 |
+
show_error("Password is required.")
|
| 177 |
+
return (
|
| 178 |
+
"",
|
| 179 |
+
"",
|
| 180 |
+
"<span style='color:#c62828;'>Password is required.</span>",
|
| 181 |
+
gr.update(visible=False),
|
| 182 |
+
gr.update(visible=True)
|
| 183 |
+
)
|
| 184 |
+
else:
|
| 185 |
+
# Validate the User ID: must end with "@usiu.ac.ke"
|
| 186 |
+
email_regex = r'^[\w\.-]+@usiu\.ac\.ke$'
|
| 187 |
+
if not re.match(email_regex, user_id):
|
| 188 |
+
show_error("User ID must be a valid \"@usiu.ac.ke\" email address.")
|
| 189 |
+
return (
|
| 190 |
+
"",
|
| 191 |
+
"<span style='color:#c62828;'>User ID must be a valid @usiu.ac.ke email address.</span>",
|
| 192 |
+
"",
|
| 193 |
+
gr.update(visible=False),
|
| 194 |
+
gr.update(visible=True)
|
| 195 |
+
)
|
| 196 |
+
# Attempt authentication.
|
| 197 |
+
if authenticate(user_id, password):
|
| 198 |
+
show_success("Login successful! Click 'Proceed' to continue!", duration=10)
|
| 199 |
+
return (
|
| 200 |
+
"",
|
| 201 |
+
"",
|
| 202 |
+
"",
|
| 203 |
+
gr.update(visible=True), # Show Proceed button.
|
| 204 |
+
gr.update(visible=False) # Hide Login button.
|
| 205 |
+
)
|
| 206 |
+
else:
|
| 207 |
+
show_error("Invalid credentials. Please try again.")
|
| 208 |
+
return (
|
| 209 |
+
"<span style='color:#c62828;'>Invalid credentials. Please try again.</span>",
|
| 210 |
+
"",
|
| 211 |
+
"",
|
| 212 |
+
gr.update(visible=False),
|
| 213 |
+
gr.update(visible=True)
|
| 214 |
+
)
|
| 215 |
+
except NameError as e:
|
| 216 |
+
show_error(f"System error: {str(e)}. Please ensure required modules are imported.")
|
| 217 |
+
return (
|
| 218 |
+
"<span style='color:#c62828;'>System error encountered.</span>",
|
| 219 |
+
"",
|
| 220 |
+
"",
|
| 221 |
+
gr.update(visible=False),
|
| 222 |
+
gr.update(visible=True)
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
# ----------------- Voice Interaction Logic -----------------
|
| 226 |
+
|
| 227 |
+
def voice_button_click():
|
| 228 |
+
"""Callback for the voice input button"""
|
| 229 |
+
voice_interface.start_recording()
|
| 230 |
+
# Wait for up to 10 seconds for voice input
|
| 231 |
+
text = voice_interface.get_text_from_speech(timeout=10)
|
| 232 |
+
voice_interface.stop_recording()
|
| 233 |
+
return text if text else ""
|
| 234 |
+
|
| 235 |
+
#############################
|
| 236 |
+
# READ FILE UTILITY
|
| 237 |
+
#############################
|
| 238 |
+
def read_file(file):
|
| 239 |
+
file_extension = os.path.splitext(file.name)[1].lower()
|
| 240 |
+
|
| 241 |
+
if file_extension == '.txt':
|
| 242 |
+
content = file.read().decode('utf-8')
|
| 243 |
+
elif file_extension == '.pdf':
|
| 244 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 245 |
+
content = ''
|
| 246 |
+
for page in pdf_reader.pages:
|
| 247 |
+
text = page.extract_text()
|
| 248 |
+
if text:
|
| 249 |
+
content += text
|
| 250 |
+
elif file_extension in ['.docx', '.doc']:
|
| 251 |
+
doc = docx.Document(file)
|
| 252 |
+
content = "\n".join([para.text for para in doc.paragraphs])
|
| 253 |
+
elif file_extension in ['.xlsx', '.xls']:
|
| 254 |
+
df = pd.read_excel(file)
|
| 255 |
+
content = df.to_string()
|
| 256 |
+
elif file_extension in ['.pptx', '.ppt']:
|
| 257 |
+
prs = pptx.Presentation(file)
|
| 258 |
+
content = ''
|
| 259 |
+
for slide in prs.slides:
|
| 260 |
+
for shape in slide.shapes:
|
| 261 |
+
if hasattr(shape, 'text'):
|
| 262 |
+
content += shape.text + '\n'
|
| 263 |
+
elif file_extension in ['.png', '.jpg', '.jpeg', '.gif']:
|
| 264 |
+
image = Image.open(file)
|
| 265 |
+
buffered = io.BytesIO()
|
| 266 |
+
image.save(buffered, format="PNG")
|
| 267 |
+
# Create an image variation via OpenAI's API (example)
|
| 268 |
+
response = openai.Image.create_variation(
|
| 269 |
+
image=buffered,
|
| 270 |
+
n=1,
|
| 271 |
+
size="256x256"
|
| 272 |
+
)
|
| 273 |
+
image_description = response['data'][0]['url']
|
| 274 |
+
content = f"[Image uploaded. Description: {image_description}]"
|
| 275 |
+
else:
|
| 276 |
+
content = f"Unsupported file type: {file_extension}"
|
| 277 |
+
|
| 278 |
+
return content
|
| 279 |
+
|
| 280 |
+
#############################
|
| 281 |
+
# GLOBAL RETRIEVER SETUP (for RAG in general_chat_ui)
|
| 282 |
+
#############################
|
| 283 |
+
from vector_services.data_curator import DataCurator
|
| 284 |
+
curator = DataCurator(knowledge_base_dir="usiu-knowledge-base")
|
| 285 |
+
curator.load_vectorstore()
|
| 286 |
+
# GLOBAL_RETRIEVER enables RAG for general chat.
|
| 287 |
+
GLOBAL_RETRIEVER = curator.get_retriever()
|
| 288 |
+
|
| 289 |
+
#############################
|
| 290 |
+
# GENERAL CHAT UI (with RAG)
|
| 291 |
+
#############################
|
| 292 |
+
def general_chat_ui(system_prompt: str, model: str) -> None:
|
| 293 |
+
"""
|
| 294 |
+
Creates a Gradio ChatInterface for general academic chat with voice capabilities.
|
| 295 |
+
"""
|
| 296 |
+
def general_chat(message: str, chat_history, file, play_response=False) -> str:
|
| 297 |
+
# Ensure chat_history is a list.
|
| 298 |
+
if chat_history is None:
|
| 299 |
+
chat_history = []
|
| 300 |
+
|
| 301 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 302 |
+
if not message.strip() and file is not None:
|
| 303 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 304 |
+
|
| 305 |
+
# If this is a new conversation, insert a conversation header.
|
| 306 |
+
if not chat_history:
|
| 307 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 308 |
+
conv_header = {
|
| 309 |
+
"role": "header",
|
| 310 |
+
"content": (
|
| 311 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 312 |
+
f"Conversation started on {date_str}"
|
| 313 |
+
f"</div>"
|
| 314 |
+
),
|
| 315 |
+
"files": []
|
| 316 |
+
}
|
| 317 |
+
chat_history.insert(0, conv_header) # Insert at the beginning
|
| 318 |
+
|
| 319 |
+
# Start with the initial system prompt.
|
| 320 |
+
messages = [
|
| 321 |
+
{"role": "system", "content": system_prompt, "files": []},
|
| 322 |
+
]
|
| 323 |
+
|
| 324 |
+
# Add chat history to messages (all messages, including header).
|
| 325 |
+
if chat_history:
|
| 326 |
+
# If the first element is a tuple/list with two elements, assume pair format.
|
| 327 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 328 |
+
for human, assistant in chat_history:
|
| 329 |
+
messages.append({"role": "user", "content": human, "files": []})
|
| 330 |
+
messages.append({"role": "assistant", "content": assistant, "files": []})
|
| 331 |
+
# Otherwise, if they are dicts, include all messages.
|
| 332 |
+
elif isinstance(chat_history[0], dict):
|
| 333 |
+
for msg in chat_history:
|
| 334 |
+
if msg is None:
|
| 335 |
+
continue
|
| 336 |
+
# Skip header messages when sending to API.
|
| 337 |
+
if msg.get("role") == "header":
|
| 338 |
+
continue
|
| 339 |
+
# Ensure each message has a files field.
|
| 340 |
+
msg_to_send = dict(msg)
|
| 341 |
+
if "files" not in msg_to_send or msg_to_send["files"] is None:
|
| 342 |
+
msg_to_send["files"] = []
|
| 343 |
+
messages.append(msg_to_send)
|
| 344 |
+
|
| 345 |
+
# If a file is uploaded, add its content to the messages.
|
| 346 |
+
if file is not None:
|
| 347 |
+
try:
|
| 348 |
+
file_content = read_file(file)
|
| 349 |
+
messages.append({
|
| 350 |
+
"role": "user",
|
| 351 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}",
|
| 352 |
+
"files": []
|
| 353 |
+
})
|
| 354 |
+
except Exception as e:
|
| 355 |
+
messages.append({
|
| 356 |
+
"role": "user",
|
| 357 |
+
"content": f"Error reading file: {str(e)}",
|
| 358 |
+
"files": []
|
| 359 |
+
})
|
| 360 |
+
|
| 361 |
+
# RAG: Retrieve additional context using GLOBAL_RETRIEVER if available.
|
| 362 |
+
if GLOBAL_RETRIEVER is not None:
|
| 363 |
+
try:
|
| 364 |
+
docs = GLOBAL_RETRIEVER.get_relevant_documents(message)
|
| 365 |
+
if docs:
|
| 366 |
+
context_text = "\n\n".join([doc.page_content for doc in docs])
|
| 367 |
+
# Insert the retrieved context as an additional system message.
|
| 368 |
+
messages.insert(1, {
|
| 369 |
+
"role": "system",
|
| 370 |
+
"content": f"Additional Context:\n{context_text}",
|
| 371 |
+
"files": []
|
| 372 |
+
})
|
| 373 |
+
except Exception as e:
|
| 374 |
+
print("RAG retrieval failed:", e)
|
| 375 |
+
|
| 376 |
+
# Append the current user message to chat_history as a dictionary for consistency.
|
| 377 |
+
chat_history.append({"role": "user", "content": message, "files": []})
|
| 378 |
+
|
| 379 |
+
# Also add the user message to the messages list (so it is sent to the API).
|
| 380 |
+
messages.append({"role": "user", "content": message, "files": []})
|
| 381 |
+
|
| 382 |
+
print("General Enquiries - Conversation with context:")
|
| 383 |
+
print(messages)
|
| 384 |
+
|
| 385 |
+
# Call OpenAI's ChatCompletion with streaming enabled.
|
| 386 |
+
completion = openai.ChatCompletion.create(
|
| 387 |
+
model=model,
|
| 388 |
+
messages=messages,
|
| 389 |
+
max_tokens=2_000,
|
| 390 |
+
temperature=0.7,
|
| 391 |
+
stream=True,
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
response = ""
|
| 395 |
+
for chunk in completion:
|
| 396 |
+
token = chunk.choices[0].delta.get("content", "")
|
| 397 |
+
response += token
|
| 398 |
+
yield response
|
| 399 |
+
|
| 400 |
+
# After the response is completely generated, append a timestamp.
|
| 401 |
+
timestamp = time.strftime("%I:%M %p", time.localtime())
|
| 402 |
+
final_response = response + f"\n<span style='font-size:11px; color:#999;'>{timestamp}</span>"
|
| 403 |
+
|
| 404 |
+
# If voice playback is enabled, read the response aloud in a separate thread
|
| 405 |
+
if play_response:
|
| 406 |
+
threading.Thread(
|
| 407 |
+
target=voice_interface.text_to_speech,
|
| 408 |
+
args=(response,),
|
| 409 |
+
daemon=True
|
| 410 |
+
).start()
|
| 411 |
+
|
| 412 |
+
yield final_response
|
| 413 |
+
|
| 414 |
+
with gr.Blocks() as interface:
|
| 415 |
+
# Create the main chatbot UI
|
| 416 |
+
chatbot = gr.Chatbot(
|
| 417 |
+
[],
|
| 418 |
+
elem_id="general-chatbot",
|
| 419 |
+
avatar_images=(None, "https://i.imgur.com/KLhAVLW.png"),
|
| 420 |
+
bubble_full_width=False,
|
| 421 |
+
height=500
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
with gr.Row():
|
| 425 |
+
with gr.Column(scale=10):
|
| 426 |
+
msg = gr.Textbox(
|
| 427 |
+
show_label=False,
|
| 428 |
+
placeholder="Type your message here...",
|
| 429 |
+
container=False,
|
| 430 |
+
lines=1
|
| 431 |
+
)
|
| 432 |
+
with gr.Column(scale=1, min_width=50):
|
| 433 |
+
voice_input_btn = gr.Button("🎤", elem_classes="voice-btn")
|
| 434 |
+
with gr.Column(scale=1, min_width=60):
|
| 435 |
+
send_btn = gr.Button("Send")
|
| 436 |
+
|
| 437 |
+
with gr.Accordion("Additional Options", open=False, elem_classes="additional-inputs-accordion"):
|
| 438 |
+
with gr.Row():
|
| 439 |
+
file_upload = gr.File(label="Upload a file (optional)", elem_classes="file-upload", scale=2)
|
| 440 |
+
voice_output = gr.Checkbox(label="Enable Voice Responses", value=False, scale=1, elem_classes="voice-options")
|
| 441 |
+
|
| 442 |
+
# Set up event handlers
|
| 443 |
+
voice_input_btn.click(
|
| 444 |
+
voice_button_click,
|
| 445 |
+
outputs=[msg]
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
msg.submit(
|
| 449 |
+
general_chat,
|
| 450 |
+
[msg, chatbot, file_upload, voice_output],
|
| 451 |
+
[chatbot]
|
| 452 |
+
).then(
|
| 453 |
+
lambda: "", None, [msg]
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
send_btn.click(
|
| 457 |
+
general_chat,
|
| 458 |
+
[msg, chatbot, file_upload, voice_output],
|
| 459 |
+
[chatbot]
|
| 460 |
+
).then(
|
| 461 |
+
lambda: "", None, [msg]
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
# Add JavaScript for voice button behavior
|
| 465 |
+
gr.HTML("""
|
| 466 |
+
<script>
|
| 467 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 468 |
+
// Find voice button after a delay to ensure it's loaded
|
| 469 |
+
setTimeout(function() {
|
| 470 |
+
const voiceBtn = document.querySelector('.voice-btn');
|
| 471 |
+
if (voiceBtn) {
|
| 472 |
+
voiceBtn.addEventListener('click', function() {
|
| 473 |
+
this.classList.add('recording');
|
| 474 |
+
|
| 475 |
+
// Remove recording class after 10 seconds (max recording time)
|
| 476 |
+
setTimeout(() => {
|
| 477 |
+
this.classList.remove('recording');
|
| 478 |
+
}, 10000);
|
| 479 |
+
});
|
| 480 |
+
}
|
| 481 |
+
}, 2000);
|
| 482 |
+
});
|
| 483 |
+
</script>
|
| 484 |
+
""")
|
| 485 |
+
|
| 486 |
+
return interface
|
| 487 |
+
|
| 488 |
+
#############################
|
| 489 |
+
# STUDY SUPPORT UI (without RAG)
|
| 490 |
+
#############################
|
| 491 |
+
def study_support_ui(system_prompt: str, model: str) -> None:
|
| 492 |
+
"""
|
| 493 |
+
Creates a Gradio interface for study support with voice capabilities.
|
| 494 |
+
"""
|
| 495 |
+
def study_support_chat(message: str, chat_history, file, play_response=False) -> str:
|
| 496 |
+
# Ensure chat_history is a list.
|
| 497 |
+
if chat_history is None:
|
| 498 |
+
chat_history = []
|
| 499 |
+
|
| 500 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 501 |
+
if not message.strip() and file is not None:
|
| 502 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 503 |
+
|
| 504 |
+
# If this is a new conversation, insert a conversation header.
|
| 505 |
+
if not chat_history:
|
| 506 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 507 |
+
conv_header = {
|
| 508 |
+
"role": "header",
|
| 509 |
+
"content": (
|
| 510 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 511 |
+
f"Conversation started on {date_str}"
|
| 512 |
+
f"</div>"
|
| 513 |
+
)
|
| 514 |
+
}
|
| 515 |
+
chat_history.insert(0, conv_header)
|
| 516 |
+
|
| 517 |
+
# Build the messages list that will be sent to the API.
|
| 518 |
+
messages = []
|
| 519 |
+
if chat_history:
|
| 520 |
+
# If chat_history items are tuples/lists with two elements, unpack them.
|
| 521 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 522 |
+
for human, assistant in chat_history:
|
| 523 |
+
messages.append({"role": "user", "content": human})
|
| 524 |
+
messages.append({"role": "assistant", "content": assistant})
|
| 525 |
+
# Otherwise, if they are dicts, include only "role" and "content" for non-system messages.
|
| 526 |
+
elif isinstance(chat_history[0], dict):
|
| 527 |
+
for msg in chat_history:
|
| 528 |
+
if msg is None:
|
| 529 |
+
continue
|
| 530 |
+
if msg.get("role") == "header":
|
| 531 |
+
continue # Skip header messages.
|
| 532 |
+
msg_to_send = {"role": msg.get("role"), "content": msg.get("content")}
|
| 533 |
+
messages.append(msg_to_send)
|
| 534 |
+
|
| 535 |
+
# Add the current message.
|
| 536 |
+
messages.append({"role": "user", "content": message})
|
| 537 |
+
|
| 538 |
+
# If a file is uploaded, add its content.
|
| 539 |
+
if file is not None:
|
| 540 |
+
try:
|
| 541 |
+
file_content = read_file(file)
|
| 542 |
+
messages.append({
|
| 543 |
+
"role": "user",
|
| 544 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}"
|
| 545 |
+
})
|
| 546 |
+
except Exception as e:
|
| 547 |
+
messages.append({
|
| 548 |
+
"role": "user",
|
| 549 |
+
"content": f"Error reading file: {str(e)}"
|
| 550 |
+
})
|
| 551 |
+
|
| 552 |
+
# Add current user message to chat history for display
|
| 553 |
+
chat_history.append({"role": "user", "content": message})
|
| 554 |
+
|
| 555 |
+
# Implement a retry mechanism to handle overloaded errors.
|
| 556 |
+
max_retries = 7
|
| 557 |
+
delay = 3 # seconds
|
| 558 |
+
attempt = 0
|
| 559 |
+
|
| 560 |
+
while attempt < max_retries:
|
| 561 |
+
try:
|
| 562 |
+
result = claude.messages.stream(
|
| 563 |
+
model=model,
|
| 564 |
+
max_tokens=64_000,
|
| 565 |
+
system=system_prompt,
|
| 566 |
+
messages=messages,
|
| 567 |
+
extra_query={"extended_thinking": True}
|
| 568 |
+
)
|
| 569 |
+
response = ""
|
| 570 |
+
with result as stream:
|
| 571 |
+
for text in stream.text_stream:
|
| 572 |
+
response += text
|
| 573 |
+
yield response # Yield incremental responses.
|
| 574 |
+
|
| 575 |
+
# After the response is completely generated, append a timestamp.
|
| 576 |
+
timestamp = time.strftime("%I:%M %p", time.localtime())
|
| 577 |
+
final_response = response + f"\n<span style='font-size:11px; color:#999;'>{timestamp}</span>"
|
| 578 |
+
|
| 579 |
+
# If voice playback is enabled, read the response aloud in a separate thread
|
| 580 |
+
if play_response:
|
| 581 |
+
threading.Thread(
|
| 582 |
+
target=voice_interface.text_to_speech,
|
| 583 |
+
args=(response,),
|
| 584 |
+
daemon=True
|
| 585 |
+
).start()
|
| 586 |
+
|
| 587 |
+
yield final_response
|
| 588 |
+
break # Exit the retry loop if successful.
|
| 589 |
+
except Exception as e:
|
| 590 |
+
# Check if error indicates the API is overloaded.
|
| 591 |
+
if "overloaded" in str(e).lower():
|
| 592 |
+
attempt += 1
|
| 593 |
+
yield f"Service unavailable. Retrying in {delay} seconds... (attempt {attempt}/{max_retries})\n"
|
| 594 |
+
time.sleep(delay)
|
| 595 |
+
else:
|
| 596 |
+
print(f"An error occurred: {str(e)}")
|
| 597 |
+
break
|
| 598 |
+
else:
|
| 599 |
+
yield "The service is currently unavailable. Please try again later."
|
| 600 |
+
|
| 601 |
+
with gr.Blocks() as interface:
|
| 602 |
+
# Create the main chatbot UI
|
| 603 |
+
chatbot = gr.Chatbot(
|
| 604 |
+
[],
|
| 605 |
+
elem_id="study-chatbot",
|
| 606 |
+
avatar_images=(None, "https://i.imgur.com/KLhAVLW.png"),
|
| 607 |
+
bubble_full_width=False,
|
| 608 |
+
height=500
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
with gr.Row():
|
| 612 |
+
with gr.Column(scale=10):
|
| 613 |
+
msg = gr.Textbox(
|
| 614 |
+
show_label=False,
|
| 615 |
+
placeholder="Type your message here...",
|
| 616 |
+
container=False,
|
| 617 |
+
lines=1
|
| 618 |
+
)
|
| 619 |
+
with gr.Column(scale=1, min_width=50):
|
| 620 |
+
voice_input_btn = gr.Button("🎤", elem_classes="voice-btn")
|
| 621 |
+
with gr.Column(scale=1, min_width=60):
|
| 622 |
+
send_btn = gr.Button("Send")
|
| 623 |
+
|
| 624 |
+
with gr.Accordion("Additional Options", open=False, elem_classes="additional-inputs-accordion"):
|
| 625 |
+
with gr.Row():
|
| 626 |
+
file_upload = gr.File(label="Upload a file (optional)", elem_classes="file-upload", scale=2)
|
| 627 |
+
voice_output = gr.Checkbox(label="Enable Voice Responses", value=False, scale=1, elem_classes="voice-options")
|
| 628 |
+
|
| 629 |
+
# Set up event handlers
|
| 630 |
+
voice_input_btn.click(
|
| 631 |
+
voice_button_click,
|
| 632 |
+
outputs=[msg]
|
| 633 |
+
)
|
| 634 |
+
|
| 635 |
+
msg.submit(
|
| 636 |
+
study_support_chat,
|
| 637 |
+
[msg, chatbot, file_upload, voice_output],
|
| 638 |
+
[chatbot]
|
| 639 |
+
).then(
|
| 640 |
+
lambda: "", None, [msg]
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
send_btn.click(
|
| 644 |
+
study_support_chat,
|
| 645 |
+
[msg, chatbot, file_upload, voice_output],
|
| 646 |
+
[chatbot]
|
| 647 |
+
).then(
|
| 648 |
+
lambda: "", None, [msg]
|
| 649 |
+
)
|
| 650 |
+
|
| 651 |
+
# Add JavaScript for voice button behavior
|
| 652 |
+
gr.HTML("""
|
| 653 |
+
<script>
|
| 654 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 655 |
+
// Find voice button after a delay to ensure it's loaded
|
| 656 |
+
setTimeout(function() {
|
| 657 |
+
const voiceBtn = document.querySelector('#study-chatbot + div .voice-btn');
|
| 658 |
+
if (voiceBtn) {
|
| 659 |
+
voiceBtn.addEventListener('click', function() {
|
| 660 |
+
this.classList.add('recording');
|
| 661 |
+
|
| 662 |
+
// Remove recording class after 10 seconds (max recording time)
|
| 663 |
+
setTimeout(() => {
|
| 664 |
+
this.classList.remove('recording');
|
| 665 |
+
}, 10000);
|
| 666 |
+
});
|
| 667 |
+
}
|
| 668 |
+
}, 2000);
|
| 669 |
+
});
|
| 670 |
+
</script>
|
| 671 |
+
""")
|
| 672 |
+
|
| 673 |
+
return interface
|
| 674 |
+
|
| 675 |
+
# ---------- Dashboard UI ----------
|
| 676 |
+
def dashboard_ui(general_chat_prompt: str, general_model: str, study_prompt: str, study_model: str):
|
| 677 |
+
"""
|
| 678 |
+
Creates a dashboard with professional styling, two main menus (General Chat and Study Support),
|
| 679 |
+
and smooth transitions between views. For Study Support, a login form (with authentication)
|
| 680 |
+
is shown before revealing the chat interface.
|
| 681 |
+
"""
|
| 682 |
+
# Retrieve the existing chat interfaces.
|
| 683 |
+
general_chat_component = general_chat_ui(general_chat_prompt, general_model)
|
| 684 |
+
study_support_component = study_support_ui(study_prompt, study_model)
|
| 685 |
+
# Retrieve the login form.
|
| 686 |
+
(login_form_component, user_id_input, user_id_error, password_input, password_error,
|
| 687 |
+
login_btn, login_msg, proceed_btn, login_back_btn) = login_form_ui()
|
| 688 |
+
|
| 689 |
+
with gr.Blocks(css=custom_css()) as dashboard:
|
| 690 |
+
# Inject JavaScript for animations and loading effects
|
| 691 |
+
gr.HTML('''
|
| 692 |
+
<script>
|
| 693 |
+
document.addEventListener("DOMContentLoaded", function() {
|
| 694 |
+
// Create and style the spinner element
|
| 695 |
+
let spinner = document.createElement("div");
|
| 696 |
+
spinner.className = "loader";
|
| 697 |
+
spinner.innerHTML = '<div class="cube"></div>';
|
| 698 |
+
document.body.appendChild(spinner);
|
| 699 |
+
|
| 700 |
+
// Show the spinner when needed
|
| 701 |
+
spinner.style.display = "none"; // Initially hidden
|
| 702 |
+
|
| 703 |
+
// Function to show spinner
|
| 704 |
+
function showSpinner() {
|
| 705 |
+
spinner.style.display = "block";
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
// Function to hide spinner
|
| 709 |
+
function hideSpinner() {
|
| 710 |
+
spinner.style.display = "none";
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
// Function to handle animation transitions
|
| 714 |
+
function setupAnimations() {
|
| 715 |
+
// Get all containers that might be animated
|
| 716 |
+
const containers = document.querySelectorAll('.container, .chat-container');
|
| 717 |
+
|
| 718 |
+
// Add transition end listener to each container
|
| 719 |
+
containers.forEach(container => {
|
| 720 |
+
container.addEventListener('animationend', function(e) {
|
| 721 |
+
// When animation completes, remove the animation class
|
| 722 |
+
if (e.animationName.includes('fadeIn')) {
|
| 723 |
+
this.classList.remove('animate-fadeInUp', 'animate-fadeInRight', 'animate-fadeInLeft');
|
| 724 |
+
}
|
| 725 |
+
});
|
| 726 |
+
});
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
// Setup animations after a short delay to ensure DOM is ready
|
| 730 |
+
setTimeout(setupAnimations, 2000);
|
| 731 |
+
|
| 732 |
+
// Add event listeners to all buttons that should trigger the spinner
|
| 733 |
+
setTimeout(function(){
|
| 734 |
+
// Login and Proceed buttons with spinner
|
| 735 |
+
const loginBtn = document.querySelector('button:contains("Login")');
|
| 736 |
+
const proceedBtn = document.querySelector('button:contains("Proceed")');
|
| 737 |
+
|
| 738 |
+
if (loginBtn) {
|
| 739 |
+
loginBtn.addEventListener("click", function() {
|
| 740 |
+
showSpinner(); // Show spinner when login button is clicked
|
| 741 |
+
|
| 742 |
+
if (!this.classList.contains("loading")) {
|
| 743 |
+
const originalText = this.textContent;
|
| 744 |
+
this.setAttribute("data-original-text", originalText);
|
| 745 |
+
this.textContent = "";
|
| 746 |
+
this.classList.add("loading");
|
| 747 |
+
|
| 748 |
+
// Create spinner inside button
|
| 749 |
+
const btnSpinner = document.createElement("div");
|
| 750 |
+
btnSpinner.className = "btn-spinner";
|
| 751 |
+
btnSpinner.style.width = "20px";
|
| 752 |
+
btnSpinner.style.height = "20px";
|
| 753 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 754 |
+
btnSpinner.style.borderRadius = "50%";
|
| 755 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 756 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 757 |
+
btnSpinner.style.display = "inline-block";
|
| 758 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 759 |
+
this.appendChild(btnSpinner);
|
| 760 |
+
|
| 761 |
+
// Reset button after 3 seconds if no response
|
| 762 |
+
setTimeout(() => {
|
| 763 |
+
if (this.classList.contains("loading")) {
|
| 764 |
+
this.classList.remove("loading");
|
| 765 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 766 |
+
}
|
| 767 |
+
hideSpinner(); // Hide spinner after timeout
|
| 768 |
+
}, 3000);
|
| 769 |
+
}
|
| 770 |
+
});
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
if (proceedBtn) {
|
| 774 |
+
proceedBtn.addEventListener("click", function() {
|
| 775 |
+
showSpinner(); // Show spinner when proceed button is clicked
|
| 776 |
+
|
| 777 |
+
if (!this.classList.contains("loading")) {
|
| 778 |
+
const originalText = this.textContent;
|
| 779 |
+
this.setAttribute("data-original-text", originalText);
|
| 780 |
+
this.textContent = "";
|
| 781 |
+
this.classList.add("loading");
|
| 782 |
+
|
| 783 |
+
// Create spinner inside button
|
| 784 |
+
const btnSpinner = document.createElement("div");
|
| 785 |
+
btnSpinner.className = "btn-spinner";
|
| 786 |
+
btnSpinner.style.width = "20px";
|
| 787 |
+
btnSpinner.style.height = "20px";
|
| 788 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 789 |
+
btnSpinner.style.borderRadius = "50%";
|
| 790 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 791 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 792 |
+
btnSpinner.style.display = "inline-block";
|
| 793 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 794 |
+
this.appendChild(btnSpinner);
|
| 795 |
+
|
| 796 |
+
// Reset button after 3 seconds if no response
|
| 797 |
+
setTimeout(() => {
|
| 798 |
+
if (this.classList.contains("loading")) {
|
| 799 |
+
this.classList.remove("loading");
|
| 800 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 801 |
+
}
|
| 802 |
+
hideSpinner(); // Hide spinner after timeout
|
| 803 |
+
}, 3000);
|
| 804 |
+
}
|
| 805 |
+
});
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
// Scale effect for dashboard buttons
|
| 809 |
+
const dashboardButtons = document.querySelectorAll('.menu-button, button:contains("Back to Dashboard")');
|
| 810 |
+
dashboardButtons.forEach(function(btn) {
|
| 811 |
+
btn.addEventListener("click", function() {
|
| 812 |
+
this.style.transform = "scale(1.1)";
|
| 813 |
+
setTimeout(() => {
|
| 814 |
+
this.style.transform = "";
|
| 815 |
+
}, 200);
|
| 816 |
+
});
|
| 817 |
+
});
|
| 818 |
+
}, 2000);
|
| 819 |
+
|
| 820 |
+
// Add keyframes for spinner animation
|
| 821 |
+
const style = document.createElement('style');
|
| 822 |
+
style.textContent = `
|
| 823 |
+
@keyframes spin {
|
| 824 |
+
0% { transform: rotate(0deg); }
|
| 825 |
+
100% { transform: rotate(360deg); }
|
| 826 |
+
}
|
| 827 |
+
`;
|
| 828 |
+
document.head.appendChild(style);
|
| 829 |
+
});
|
| 830 |
+
</script>
|
| 831 |
+
''')
|
| 832 |
+
|
| 833 |
+
# Logo for all pages - positioned in top-left corner
|
| 834 |
+
gr.HTML('''
|
| 835 |
+
<div class="logo-dashboard">
|
| 836 |
+
<!-- USIU Logo Placeholder - Replace the src with actual logo path -->
|
| 837 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 838 |
+
</div>
|
| 839 |
+
''')
|
| 840 |
+
|
| 841 |
+
# Define containers for different views with initial animation state
|
| 842 |
+
dashboard_container = gr.Column(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInUp")
|
| 843 |
+
general_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 844 |
+
login_container = gr.Column(visible=False, elem_classes="container floating-card login-form-container pre-animation")
|
| 845 |
+
study_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 846 |
+
|
| 847 |
+
# ------------- Dashboard View -------------
|
| 848 |
+
with dashboard_container:
|
| 849 |
+
gr.Markdown("<h1 class='with-logo'>AI Assistance Dashboard</h1>")
|
| 850 |
+
with gr.Column(elem_classes="dashboard-menu"):
|
| 851 |
+
gen_button = gr.Button("General Enquiries", elem_classes="menu-button")
|
| 852 |
+
study_button = gr.Button("Study with AI", elem_classes="menu-button")
|
| 853 |
+
|
| 854 |
+
# ------------- General Academic Chat View -------------
|
| 855 |
+
with general_container:
|
| 856 |
+
back_gen = gr.Button("Back to Dashboard")
|
| 857 |
+
general_chat_component.render()
|
| 858 |
+
|
| 859 |
+
# ------------- Login Form (for Study Support) View -------------
|
| 860 |
+
with login_container:
|
| 861 |
+
login_form_component.render()
|
| 862 |
+
|
| 863 |
+
# ------------- Study Support Chat View -------------
|
| 864 |
+
with study_container:
|
| 865 |
+
back_study = gr.Button("Back to Dashboard")
|
| 866 |
+
study_support_component.render()
|
| 867 |
+
|
| 868 |
+
# ------------- Navigation Callbacks -------------
|
| 869 |
+
# When "General Academic Chat" is selected from the dashboard.
|
| 870 |
+
gen_button.click(
|
| 871 |
+
lambda: [
|
| 872 |
+
gr.update(visible=False),
|
| 873 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight"),
|
| 874 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 875 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 876 |
+
],
|
| 877 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 878 |
+
)
|
| 879 |
+
|
| 880 |
+
# When "Study Support Chat" is selected from the dashboard, show the login view with animation.
|
| 881 |
+
study_button.click(
|
| 882 |
+
lambda: [
|
| 883 |
+
gr.update(visible=False),
|
| 884 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 885 |
+
gr.update(visible=True, elem_classes="container floating-card login-form-container animate-fadeInRight"),
|
| 886 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 887 |
+
],
|
| 888 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 889 |
+
)
|
| 890 |
+
|
| 891 |
+
# "Back to Dashboard" buttons with animation.
|
| 892 |
+
back_gen.click(
|
| 893 |
+
lambda: [
|
| 894 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 895 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 896 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 897 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 898 |
+
],
|
| 899 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 900 |
+
)
|
| 901 |
+
|
| 902 |
+
back_study.click(
|
| 903 |
+
lambda: [
|
| 904 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 905 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 906 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 907 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 908 |
+
],
|
| 909 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 910 |
+
)
|
| 911 |
+
|
| 912 |
+
login_back_btn.click(
|
| 913 |
+
lambda: [
|
| 914 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 915 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 916 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 917 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 918 |
+
],
|
| 919 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 920 |
+
)
|
| 921 |
+
|
| 922 |
+
# ------------- Authentication Logic -------------
|
| 923 |
+
# ------------- Authentication Logic -------------
|
| 924 |
+
login_btn.click(
|
| 925 |
+
handle_login,
|
| 926 |
+
inputs=[user_id_input, password_input],
|
| 927 |
+
outputs=[login_msg, user_id_error, password_error, proceed_btn, login_btn]
|
| 928 |
+
)
|
| 929 |
+
|
| 930 |
+
proceed_btn.click(
|
| 931 |
+
lambda: [
|
| 932 |
+
gr.update(visible=False),
|
| 933 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 934 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 935 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight")
|
| 936 |
+
],
|
| 937 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 938 |
+
)
|
| 939 |
+
|
| 940 |
+
# Register the cleanup function to run when the program exits
|
| 941 |
+
import atexit
|
| 942 |
+
atexit.register(voice_interface.cleanup)
|
| 943 |
+
|
| 944 |
+
return dashboard.queue()
|
services/ui_service-old3.py
ADDED
|
@@ -0,0 +1,1527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import openai
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import PyPDF2
|
| 7 |
+
import docx
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import pptx
|
| 10 |
+
from PIL import Image
|
| 11 |
+
import io
|
| 12 |
+
import base64
|
| 13 |
+
|
| 14 |
+
from configs.config import claude
|
| 15 |
+
from .auth_service import authenticate
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# Set the OpenAI API key from the environment variable
|
| 19 |
+
openai.api_key = os.getenv('OPENAI_API_KEY')
|
| 20 |
+
if not openai.api_key:
|
| 21 |
+
raise ValueError("OPENAI_API_KEY environment variable is not set. Please set it before running this script.")
|
| 22 |
+
|
| 23 |
+
def custom_css():
|
| 24 |
+
css = """
|
| 25 |
+
/* Professional custom CSS with transitions, hover effects, and responsive design */
|
| 26 |
+
body {
|
| 27 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 28 |
+
background-color: #f8f9fa;
|
| 29 |
+
margin: 0;
|
| 30 |
+
padding: 0;
|
| 31 |
+
width: 100%;
|
| 32 |
+
overflow-x: hidden; /* Prevent horizontal scrolling */
|
| 33 |
+
}
|
| 34 |
+
h1, h2, h3 {
|
| 35 |
+
text-align: center;
|
| 36 |
+
color: #495057;
|
| 37 |
+
}
|
| 38 |
+
.gr-button {
|
| 39 |
+
transition: background-color 0.3s ease, transform 0.3s ease;
|
| 40 |
+
border-radius: 4px;
|
| 41 |
+
/* USIU-Africa theme color for buttons */
|
| 42 |
+
background-color: #000080;
|
| 43 |
+
color: white;
|
| 44 |
+
}
|
| 45 |
+
.gr-button:hover {
|
| 46 |
+
background-color: #000066;
|
| 47 |
+
color: #fff;
|
| 48 |
+
transform: scale(1.03);
|
| 49 |
+
}
|
| 50 |
+
.container {
|
| 51 |
+
transition: opacity 0.5s ease-in-out;
|
| 52 |
+
padding: 4px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* Glassmorphism effect for floating cards */
|
| 56 |
+
.floating-card {
|
| 57 |
+
background: rgba(255, 255, 255, 0.85);
|
| 58 |
+
border-radius: 12px;
|
| 59 |
+
box-shadow:
|
| 60 |
+
0 10px 25px rgba(0,0,0,0.08),
|
| 61 |
+
0 6px 10px rgba(0,0,0,0.12),
|
| 62 |
+
0 3px 3px rgba(0,0,0,0.15);
|
| 63 |
+
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
| 64 |
+
padding: 25px;
|
| 65 |
+
margin: 15px auto; /* Reduced margin */
|
| 66 |
+
backdrop-filter: blur(10px);
|
| 67 |
+
border: 1px solid rgba(255,255,255,0.25);
|
| 68 |
+
width: 95%; /* Increased width */
|
| 69 |
+
max-width: 98%; /* Increased max-width */
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.floating-card:hover {
|
| 73 |
+
box-shadow:
|
| 74 |
+
0 15px 30px rgba(0,0,0,0.15),
|
| 75 |
+
0 10px 10px rgba(0,0,0,0.18);
|
| 76 |
+
transform: translateY(-5px);
|
| 77 |
+
background: rgba(255, 255, 255, 0.9);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* Login form styling with responsiveness */
|
| 81 |
+
.login-form-container {
|
| 82 |
+
width: 98% !important; /* Increased width for mobile */
|
| 83 |
+
max-width: 500px !important;
|
| 84 |
+
margin: 30px auto !important; /* Reduced top/bottom margin */
|
| 85 |
+
background: rgba(255, 255, 255, 0.88) !important;
|
| 86 |
+
backdrop-filter: blur(12px) !important;
|
| 87 |
+
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
| 88 |
+
border-top: 3px solid #000080 !important;
|
| 89 |
+
padding: 20px 15px !important; /* Added horizontal padding */
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
/* Dashboard container styling with responsiveness */
|
| 93 |
+
.dashboard-card {
|
| 94 |
+
width: 98% !important; /* Take almost full width on mobile */
|
| 95 |
+
max-width: 700px !important;
|
| 96 |
+
margin: 20px auto !important; /* Reduced margin */
|
| 97 |
+
background: rgba(255, 255, 255, 0.88) !important;
|
| 98 |
+
backdrop-filter: blur(12px) !important;
|
| 99 |
+
height: auto !important;
|
| 100 |
+
min-height: 500px !important;
|
| 101 |
+
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
| 102 |
+
border-top: 3px solid #000080 !important;
|
| 103 |
+
opacity: 0;
|
| 104 |
+
animation: dashboardFadeIn 1.5s forwards ease-in-out;
|
| 105 |
+
padding: 20px 10px !important; /* Reduced horizontal padding */
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/* A subtle gradient background to the cards */
|
| 109 |
+
.dashboard-card, .login-form-container {
|
| 110 |
+
background: linear-gradient(
|
| 111 |
+
135deg,
|
| 112 |
+
rgba(255, 255, 255, 0.9) 0%,
|
| 113 |
+
rgba(255, 255, 255, 0.8) 100%
|
| 114 |
+
) !important;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
/* Logo container styling with responsive adjustments */
|
| 118 |
+
.logo-container {
|
| 119 |
+
text-align: center;
|
| 120 |
+
margin-bottom: 25px;
|
| 121 |
+
position: relative;
|
| 122 |
+
width: 100%;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.logo {
|
| 126 |
+
max-height: 80px;
|
| 127 |
+
max-width: 90%; /* Increased max-width */
|
| 128 |
+
margin: 0 auto;
|
| 129 |
+
display: block;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.logo-dashboard {
|
| 133 |
+
position: absolute;
|
| 134 |
+
top: 15px;
|
| 135 |
+
left: 15px;
|
| 136 |
+
max-height: 60px;
|
| 137 |
+
max-width: 35%; /* Increased max-width */
|
| 138 |
+
z-index: 100;
|
| 139 |
+
opacity: 0;
|
| 140 |
+
animation: logoFadeIn 1.8s forwards ease-in-out;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.with-logo {
|
| 144 |
+
margin-top: 10px;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/* Chat UI container with extended width and responsiveness */
|
| 148 |
+
.chat-container {
|
| 149 |
+
max-width: 1000px;
|
| 150 |
+
width: 98%; /* Increased width for mobile */
|
| 151 |
+
margin: 0 auto;
|
| 152 |
+
opacity: 0;
|
| 153 |
+
transform: translateY(20px);
|
| 154 |
+
animation: fadeInUp 0.7s forwards;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
/* Increase chat window height with responsive adjustment */
|
| 158 |
+
.chatbot-container, .gradio-container .chat {
|
| 159 |
+
height: 80vh !important;
|
| 160 |
+
max-height: 900px !important;
|
| 161 |
+
width: 98% !important; /* Take up more width */
|
| 162 |
+
margin: 0 auto !important;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
/* Make Gradio container take full width on mobile */
|
| 166 |
+
.gradio-container {
|
| 167 |
+
width: 100% !important;
|
| 168 |
+
max-width: 100% !important;
|
| 169 |
+
margin: 0 !important;
|
| 170 |
+
padding: 0 !important;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* File input adjustment - restore default styling */
|
| 174 |
+
.file-upload .p-2 {
|
| 175 |
+
padding: inherit !important;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
/* Accordion button adjustment - remove forced padding to restore default behavior */
|
| 179 |
+
.accordion-button {
|
| 180 |
+
padding: inherit !important;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* Fix for accordion collapsed state */
|
| 184 |
+
.accordion-button.collapsed {
|
| 185 |
+
/* Reset any custom styles when collapsed */
|
| 186 |
+
padding: initial !important;
|
| 187 |
+
height: auto !important;
|
| 188 |
+
min-height: initial !important;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/* Allow accordion and file input to use Gradio's default styling */
|
| 192 |
+
.file-upload, .accordion {
|
| 193 |
+
/* Restore default Gradio styling */
|
| 194 |
+
margin: initial !important;
|
| 195 |
+
padding: initial !important;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/* Restore default styling for chat action buttons (clear, retry, undo) */
|
| 199 |
+
.chat-buttons button,
|
| 200 |
+
.chat-buttons .gr-button,
|
| 201 |
+
button[aria-label="Clear"],
|
| 202 |
+
button[aria-label="Retry"],
|
| 203 |
+
button[aria-label="Undo"] {
|
| 204 |
+
padding: initial !important;
|
| 205 |
+
height: auto !important;
|
| 206 |
+
min-height: initial !important;
|
| 207 |
+
width: auto !important;
|
| 208 |
+
min-width: initial !important;
|
| 209 |
+
background-color: initial !important;
|
| 210 |
+
color: initial !important;
|
| 211 |
+
transform: none !important;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
/* Hover effect for chat buttons */
|
| 215 |
+
.chat-buttons button:hover,
|
| 216 |
+
.chat-buttons .gr-button:hover,
|
| 217 |
+
button[aria-label="Clear"]:hover,
|
| 218 |
+
button[aria-label="Retry"]:hover,
|
| 219 |
+
button[aria-label="Undo"]:hover {
|
| 220 |
+
transform: none !important;
|
| 221 |
+
background-color: initial !important;
|
| 222 |
+
background-image: none !important;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/* Dashboard menu buttons styling with responsiveness */
|
| 226 |
+
.dashboard-menu {
|
| 227 |
+
display: flex;
|
| 228 |
+
flex-direction: column;
|
| 229 |
+
align-items: center;
|
| 230 |
+
justify-content: center;
|
| 231 |
+
min-height: 60vh;
|
| 232 |
+
gap: 8px;
|
| 233 |
+
opacity: 0;
|
| 234 |
+
animation: menuFadeIn 3s forwards ease-in-out;
|
| 235 |
+
animation-delay: 0.7s;
|
| 236 |
+
padding: 15px 0; /* Reduced padding */
|
| 237 |
+
width: 100%; /* Full width */
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.menu-button {
|
| 241 |
+
width: 95% !important; /* Increased width for mobile */
|
| 242 |
+
max-width: 300px !important;
|
| 243 |
+
height: auto !important;
|
| 244 |
+
min-height: 60px !important;
|
| 245 |
+
font-size: 16px !important;
|
| 246 |
+
font-weight: 500 !important;
|
| 247 |
+
margin: 6px auto !important; /* Reduced margin */
|
| 248 |
+
border-radius: 8px !important;
|
| 249 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
|
| 250 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
|
| 251 |
+
background-color: #000080 !important;
|
| 252 |
+
color: white !important;
|
| 253 |
+
opacity: 0;
|
| 254 |
+
animation: buttonsFadeIn 0.7s forwards ease-in-out;
|
| 255 |
+
animation-delay: calc(var(--button-index, 0) * 0.3s + 1s);
|
| 256 |
+
padding: 10px 15px !important;
|
| 257 |
+
display: flex !important;
|
| 258 |
+
align-items: center !important;
|
| 259 |
+
justify-content: center !important;
|
| 260 |
+
text-align: center !important;
|
| 261 |
+
word-wrap: break-word !important;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.menu-button:hover {
|
| 265 |
+
transform: scale(1.02);
|
| 266 |
+
background-image: linear-gradient(to bottom right, #000080, #0000cc);
|
| 267 |
+
transition: transform 0.5s ease;
|
| 268 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
/* 3D Loader CSS with responsive sizing */
|
| 272 |
+
.loader {
|
| 273 |
+
width: 40px; /* Reduced size */
|
| 274 |
+
height: 40px; /* Reduced size */
|
| 275 |
+
perspective: 100px;
|
| 276 |
+
position: fixed;
|
| 277 |
+
top: 50%;
|
| 278 |
+
left: 50%;
|
| 279 |
+
transform: translate(-50%, -50%);
|
| 280 |
+
z-index: 9999;
|
| 281 |
+
display: none;
|
| 282 |
+
}
|
| 283 |
+
.cube {
|
| 284 |
+
width: 100%;
|
| 285 |
+
height: 100%;
|
| 286 |
+
background: #000080;
|
| 287 |
+
transform: rotateX(0deg) rotateY(0deg);
|
| 288 |
+
animation: spinCube 1.5s infinite ease-in-out;
|
| 289 |
+
}
|
| 290 |
+
@keyframes spinCube {
|
| 291 |
+
0% { transform: rotateX(0deg) rotateY(0deg); }
|
| 292 |
+
25% { transform: rotateX(90deg) rotateY(0deg); }
|
| 293 |
+
50% { transform: rotateX(90deg) rotateY(90deg); }
|
| 294 |
+
75% { transform: rotateX(0deg) rotateY(90deg); }
|
| 295 |
+
100% { transform: rotateX(0deg) rotateY(0deg); }
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
/* Dashboard specific fade-in animations */
|
| 299 |
+
@keyframes dashboardFadeIn {
|
| 300 |
+
0% {
|
| 301 |
+
opacity: 0;
|
| 302 |
+
transform: translateY(30px);
|
| 303 |
+
}
|
| 304 |
+
100% {
|
| 305 |
+
opacity: 1;
|
| 306 |
+
transform: translateY(0);
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
@keyframes logoFadeIn {
|
| 311 |
+
0% {
|
| 312 |
+
opacity: 0;
|
| 313 |
+
transform: translateY(-20px);
|
| 314 |
+
}
|
| 315 |
+
100% {
|
| 316 |
+
opacity: 1;
|
| 317 |
+
transform: translateY(0);
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
@keyframes menuFadeIn {
|
| 322 |
+
0% {
|
| 323 |
+
opacity: 0;
|
| 324 |
+
transform: scale(0.95);
|
| 325 |
+
}
|
| 326 |
+
100% {
|
| 327 |
+
opacity: 1;
|
| 328 |
+
transform: scale(1);
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
@keyframes buttonsFadeIn {
|
| 333 |
+
0% {
|
| 334 |
+
opacity: 0;
|
| 335 |
+
transform: translateX(-15px);
|
| 336 |
+
}
|
| 337 |
+
100% {
|
| 338 |
+
opacity: 1;
|
| 339 |
+
transform: translateX(0);
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
/* Adding transition effects for interfaces */
|
| 344 |
+
@keyframes fadeInUp {
|
| 345 |
+
from {
|
| 346 |
+
opacity: 0;
|
| 347 |
+
transform: translateY(20px);
|
| 348 |
+
}
|
| 349 |
+
to {
|
| 350 |
+
opacity: 1;
|
| 351 |
+
transform: translateY(0);
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
@keyframes fadeInRight {
|
| 356 |
+
from {
|
| 357 |
+
opacity: 0;
|
| 358 |
+
transform: translateX(20px);
|
| 359 |
+
}
|
| 360 |
+
to {
|
| 361 |
+
opacity: 1;
|
| 362 |
+
transform: translateX(0);
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
@keyframes fadeInLeft {
|
| 367 |
+
from {
|
| 368 |
+
opacity: 0;
|
| 369 |
+
transform: translateX(-20px);
|
| 370 |
+
}
|
| 371 |
+
to {
|
| 372 |
+
opacity: 1;
|
| 373 |
+
transform: translateX(0);
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
/* Container animation classes that can be dynamically applied */
|
| 378 |
+
.animate-fadeInUp {
|
| 379 |
+
animation: fadeInUp 0.7s forwards;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.animate-fadeInRight {
|
| 383 |
+
animation: fadeInRight 0.7s forwards;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.animate-fadeInLeft {
|
| 387 |
+
animation: fadeInLeft 0.7s forwards;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
/* Initial state for containers to be animated */
|
| 391 |
+
.pre-animation {
|
| 392 |
+
opacity: 0;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
/* Specifically target Gradio elements to make them more mobile-friendly */
|
| 396 |
+
.gradio-container {
|
| 397 |
+
margin-left: 0 !important;
|
| 398 |
+
margin-right: 0 !important;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
/* Adjust textbox and button widths for mobile */
|
| 402 |
+
.gr-textbox {
|
| 403 |
+
width: 100% !important;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
.gr-button-primary {
|
| 407 |
+
width: 100% !important;
|
| 408 |
+
margin: 5px 0 !important;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
/* Make all interface elements take more width on mobile */
|
| 412 |
+
.interface-elements {
|
| 413 |
+
width: 98% !important;
|
| 414 |
+
max-width: 100% !important;
|
| 415 |
+
margin: 0 auto !important;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
/* Fix padding for form elements */
|
| 419 |
+
input, select, textarea, button {
|
| 420 |
+
box-sizing: border-box !important;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/* Ensure accordion, file inputs, and chat buttons are not affected by button min-height */
|
| 424 |
+
.accordion-button,
|
| 425 |
+
.file-upload button,
|
| 426 |
+
.chat-buttons button,
|
| 427 |
+
button[aria-label="Clear"],
|
| 428 |
+
button[aria-label="Retry"],
|
| 429 |
+
button[aria-label="Undo"] {
|
| 430 |
+
min-height: auto !important;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
/* Media Queries for better responsiveness */
|
| 434 |
+
@media screen and (max-width: 768px) {
|
| 435 |
+
body {
|
| 436 |
+
padding: 0 !important; /* Remove body padding on mobile */
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
.dashboard-card {
|
| 440 |
+
width: 100% !important; /* Full width */
|
| 441 |
+
padding: 15px 8px !important; /* Reduced padding */
|
| 442 |
+
margin: 10px auto !important; /* Reduced margin */
|
| 443 |
+
border-radius: 0 !important; /* Optional: Remove border radius for full-width look */
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.login-form-container {
|
| 447 |
+
width: 100% !important; /* Full width */
|
| 448 |
+
padding: 15px 10px !important; /* Reduced padding */
|
| 449 |
+
margin: 10px auto !important; /* Reduced margin */
|
| 450 |
+
border-radius: 0 !important; /* Optional: Remove border radius for full-width look */
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
.menu-button {
|
| 454 |
+
width: 98% !important; /* Almost full width */
|
| 455 |
+
font-size: 14px !important;
|
| 456 |
+
min-height: 50px !important;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
.logo-dashboard {
|
| 460 |
+
max-height: 40px;
|
| 461 |
+
top: 10px;
|
| 462 |
+
left: 10px;
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
h1 {
|
| 466 |
+
font-size: 1.5rem !important;
|
| 467 |
+
margin-top: 10px !important;
|
| 468 |
+
margin-bottom: 15px !important;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
h2 {
|
| 472 |
+
font-size: 1.3rem !important;
|
| 473 |
+
margin-top: 8px !important;
|
| 474 |
+
margin-bottom: 12px !important;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
h3 {
|
| 478 |
+
font-size: 1.1rem !important;
|
| 479 |
+
margin-top: 6px !important;
|
| 480 |
+
margin-bottom: 10px !important;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
/* Make chat interface take full width */
|
| 484 |
+
.chat-container, .chatbot-container {
|
| 485 |
+
width: 100% !important;
|
| 486 |
+
margin: 0 !important;
|
| 487 |
+
padding: 0 5px !important;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
/* Adjust Gradio containers */
|
| 491 |
+
.gradio-container {
|
| 492 |
+
padding: 0 !important;
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
@media screen and (max-width: 480px) {
|
| 497 |
+
.dashboard-card {
|
| 498 |
+
padding: 10px 5px !important; /* Further reduced padding */
|
| 499 |
+
margin: 0 auto !important; /* Remove margin completely */
|
| 500 |
+
border-radius: 0 !important; /* Remove border radius */
|
| 501 |
+
width: 100% !important; /* Full width */
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.login-form-container {
|
| 505 |
+
padding: 10px 5px !important; /* Further reduced padding */
|
| 506 |
+
margin: 0 auto !important; /* Remove margin completely */
|
| 507 |
+
border-radius: 0 !important; /* Remove border radius */
|
| 508 |
+
width: 100% !important; /* Full width */
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.menu-button {
|
| 512 |
+
font-size: 13px !important;
|
| 513 |
+
min-height: 45px !important;
|
| 514 |
+
margin: 5px auto !important;
|
| 515 |
+
width: 100% !important; /* Full width */
|
| 516 |
+
border-radius: 6px !important; /* Slightly reduced border radius */
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
.logo-dashboard {
|
| 520 |
+
max-height: 35px;
|
| 521 |
+
top: 5px;
|
| 522 |
+
left: 5px;
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
/* Adjust spacing in forms for mobile */
|
| 526 |
+
input, select, textarea {
|
| 527 |
+
margin-bottom: 8px !important;
|
| 528 |
+
padding: 8px !important;
|
| 529 |
+
width: 100% !important;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
/* Reduce padding and margins for all content */
|
| 533 |
+
.floating-card, .gr-box, .gr-panel {
|
| 534 |
+
padding: 10px 5px !important;
|
| 535 |
+
margin: 5px 0 !important;
|
| 536 |
+
width: 100% !important;
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
/* Full width for all UI components */
|
| 540 |
+
.gr-form, .gr-input, .gr-button, .gr-box {
|
| 541 |
+
width: 100% !important;
|
| 542 |
+
margin-left: 0 !important;
|
| 543 |
+
margin-right: 0 !important;
|
| 544 |
+
}
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/* Additional responsive handling for extreme screen sizes */
|
| 548 |
+
@media screen and (max-width: 320px) {
|
| 549 |
+
/* For very small screens like older iPhones */
|
| 550 |
+
.menu-button {
|
| 551 |
+
font-size: 11px !important;
|
| 552 |
+
min-height: 40px !important;
|
| 553 |
+
padding: 8px 5px !important;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
.dashboard-card {
|
| 557 |
+
padding: 5px 3px !important;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
h1 {
|
| 561 |
+
font-size: 1.3rem !important;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
h2 {
|
| 565 |
+
font-size: 1.1rem !important;
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
h3 {
|
| 569 |
+
font-size: 1rem !important;
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
@media screen and (min-width: 1600px) {
|
| 574 |
+
/* For very large screens */
|
| 575 |
+
.dashboard-card {
|
| 576 |
+
max-width: 1000px !important;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
.menu-button {
|
| 580 |
+
max-width: 400px !important;
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
/* For landscape orientation on mobile */
|
| 585 |
+
@media screen and (max-height: 500px) and (orientation: landscape) {
|
| 586 |
+
.dashboard-menu {
|
| 587 |
+
min-height: auto;
|
| 588 |
+
padding: 5px 0;
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
.dashboard-card {
|
| 592 |
+
min-height: 300px !important;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.menu-button {
|
| 596 |
+
min-height: 40px !important;
|
| 597 |
+
margin: 4px auto !important;
|
| 598 |
+
}
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
/* Fixed positions for mobile views */
|
| 602 |
+
.fixed-top {
|
| 603 |
+
position: fixed;
|
| 604 |
+
top: 0;
|
| 605 |
+
left: 0;
|
| 606 |
+
right: 0;
|
| 607 |
+
z-index: 1000;
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
.fixed-bottom {
|
| 611 |
+
position: fixed;
|
| 612 |
+
bottom: 0;
|
| 613 |
+
left: 0;
|
| 614 |
+
right: 0;
|
| 615 |
+
z-index: 1000;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
/* Ensure minimum touch target size for all clickable elements
|
| 619 |
+
BUT exclude accordion, file input, and chat action buttons */
|
| 620 |
+
button:not(.accordion-button):not(.file-upload button):not([aria-label="Clear"]):not([aria-label="Retry"]):not([aria-label="Undo"]):not(.chat-buttons button),
|
| 621 |
+
.gr-button:not(.file-upload .gr-button):not(.chat-buttons .gr-button),
|
| 622 |
+
a,
|
| 623 |
+
input[type="submit"],
|
| 624 |
+
input[type="button"] {
|
| 625 |
+
min-height: 44px !important; /* Apple's recommended minimum touch target size */
|
| 626 |
+
min-width: 44px !important;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
/* Remove any unnecessary margins from Gradio components */
|
| 630 |
+
.gradio-container .gr-form > *, .gradio-container .gr-group > * {
|
| 631 |
+
margin-bottom: 8px !important;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
/* Ensure Gradio container does not exceed viewport width */
|
| 635 |
+
#component-0, #component-1, #component-2, #component-3, #component-4, #component-5,
|
| 636 |
+
#component-6, #component-7, #component-8, #component-9, #component-10 {
|
| 637 |
+
max-width: 100vw !important;
|
| 638 |
+
overflow-x: hidden !important;
|
| 639 |
+
}
|
| 640 |
+
"""
|
| 641 |
+
return css
|
| 642 |
+
|
| 643 |
+
# ----------------- Login Form UI -----------------
|
| 644 |
+
|
| 645 |
+
def login_form_ui():
|
| 646 |
+
with gr.Blocks() as login_form:
|
| 647 |
+
|
| 648 |
+
# USIU logo
|
| 649 |
+
gr.HTML('''
|
| 650 |
+
<div class="logo-container">
|
| 651 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 652 |
+
</div>
|
| 653 |
+
''')
|
| 654 |
+
|
| 655 |
+
gr.Markdown("## Please Login")
|
| 656 |
+
user_id_input = gr.Textbox(
|
| 657 |
+
placeholder="Enter your User ID",
|
| 658 |
+
label="User ID",
|
| 659 |
+
type="email",
|
| 660 |
+
autofocus=True
|
| 661 |
+
)
|
| 662 |
+
# Error message for invalid user ID
|
| 663 |
+
user_id_error = gr.Markdown("", visible=True)
|
| 664 |
+
|
| 665 |
+
password_input = gr.Textbox(
|
| 666 |
+
placeholder="Enter your Password",
|
| 667 |
+
label="Password",
|
| 668 |
+
type="password"
|
| 669 |
+
)
|
| 670 |
+
# Error message for invalid password
|
| 671 |
+
password_error = gr.Markdown("", visible=True)
|
| 672 |
+
|
| 673 |
+
login_btn = gr.Button("Login")
|
| 674 |
+
# Error message for invalid credentials
|
| 675 |
+
login_msg = gr.Markdown("")
|
| 676 |
+
proceed_btn = gr.Button("Proceed", visible=False)
|
| 677 |
+
back_btn = gr.Button("Back to Dashboard")
|
| 678 |
+
# Return the login form along with the necessary components in order.
|
| 679 |
+
return login_form, user_id_input, user_id_error, password_input, password_error, login_btn, login_msg, proceed_btn, back_btn
|
| 680 |
+
|
| 681 |
+
# ----------------- Authentication Logic -----------------
|
| 682 |
+
|
| 683 |
+
def show_success(message: str, duration: float = 10, visible: bool = True, title: str = "Success") -> None:
|
| 684 |
+
# Bright, material-style success colors:
|
| 685 |
+
styled_message = (
|
| 686 |
+
f'<span style="color: #2e7d32; background-color: #c8e6c9; padding: 10px; '
|
| 687 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 688 |
+
)
|
| 689 |
+
gr.Success(styled_message, duration=duration, visible=visible, title=title)
|
| 690 |
+
# Add a small delay to help the modal appear
|
| 691 |
+
time.sleep(0.1)
|
| 692 |
+
|
| 693 |
+
def show_error(message: str, duration: float = 13, visible: bool = True, title: str = "Error") -> None:
|
| 694 |
+
# Bright, material-style error colors:
|
| 695 |
+
styled_message = (
|
| 696 |
+
f'<span style="color: #c62828; background-color: #ffcdd2; padding: 10px; '
|
| 697 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 698 |
+
)
|
| 699 |
+
gr.Error(styled_message, duration=duration, visible=visible, title=title)
|
| 700 |
+
# Add a small delay to help the modal appear
|
| 701 |
+
time.sleep(0.1)
|
| 702 |
+
|
| 703 |
+
def handle_login(user_id, password):
|
| 704 |
+
"""
|
| 705 |
+
Validates login fields and the User ID (which must end with '@usiu.ac.ke').
|
| 706 |
+
- If both fields are empty, a generic error modal is shown.
|
| 707 |
+
- If a specific field is empty, an error message for that field is returned.
|
| 708 |
+
- On successful authentication, a success modal pops up.
|
| 709 |
+
|
| 710 |
+
Returns a tuple of five outputs:
|
| 711 |
+
1. Overall status message (displayed below the login button).
|
| 712 |
+
2. User ID field-specific error (displayed below the User ID input).
|
| 713 |
+
3. Password field-specific error (displayed below the Password input).
|
| 714 |
+
4. A gr.update() call to control the visibility of the "Proceed" button.
|
| 715 |
+
5. A gr.update() call to control the visibility of the "Login" button.
|
| 716 |
+
"""
|
| 717 |
+
try:
|
| 718 |
+
user_id = user_id.strip() if user_id else ""
|
| 719 |
+
password = password.strip() if password else ""
|
| 720 |
+
|
| 721 |
+
# Both fields empty:
|
| 722 |
+
if user_id == "" and password == "":
|
| 723 |
+
show_error("Please enter your User ID and Password.")
|
| 724 |
+
return (
|
| 725 |
+
"<span style='color:#c62828;'>Please enter your User ID and Password.</span>",
|
| 726 |
+
"", # No field-specific error for User ID.
|
| 727 |
+
"", # No field-specific error for Password.
|
| 728 |
+
gr.update(visible=False),
|
| 729 |
+
gr.update(visible=True)
|
| 730 |
+
)
|
| 731 |
+
# User ID is empty:
|
| 732 |
+
elif user_id == "":
|
| 733 |
+
show_error("User ID is required.")
|
| 734 |
+
return (
|
| 735 |
+
"",
|
| 736 |
+
"<span style='color:#c62828;'>User ID is required.</span>",
|
| 737 |
+
"",
|
| 738 |
+
gr.update(visible=False),
|
| 739 |
+
gr.update(visible=True)
|
| 740 |
+
)
|
| 741 |
+
# Password is empty:
|
| 742 |
+
elif password == "":
|
| 743 |
+
show_error("Password is required.")
|
| 744 |
+
return (
|
| 745 |
+
"",
|
| 746 |
+
"",
|
| 747 |
+
"<span style='color:#c62828;'>Password is required.</span>",
|
| 748 |
+
gr.update(visible=False),
|
| 749 |
+
gr.update(visible=True)
|
| 750 |
+
)
|
| 751 |
+
else:
|
| 752 |
+
# Validate the User ID: must end with "@usiu.ac.ke"
|
| 753 |
+
email_regex = r'^[\w\.-]+@usiu\.ac\.ke$'
|
| 754 |
+
if not re.match(email_regex, user_id):
|
| 755 |
+
show_error("User ID must be a valid \"@usiu.ac.ke\" email address.")
|
| 756 |
+
return (
|
| 757 |
+
"",
|
| 758 |
+
"<span style='color:#c62828;'>User ID must be a valid @usiu.ac.ke email address.</span>",
|
| 759 |
+
"",
|
| 760 |
+
gr.update(visible=False),
|
| 761 |
+
gr.update(visible=True)
|
| 762 |
+
)
|
| 763 |
+
# Attempt authentication.
|
| 764 |
+
if authenticate(user_id, password):
|
| 765 |
+
show_success("Login successful! Click 'Proceed' to continue!", duration=10)
|
| 766 |
+
return (
|
| 767 |
+
"",
|
| 768 |
+
"",
|
| 769 |
+
"",
|
| 770 |
+
gr.update(visible=True), # Show Proceed button.
|
| 771 |
+
gr.update(visible=False) # Hide Login button.
|
| 772 |
+
)
|
| 773 |
+
else:
|
| 774 |
+
show_error("Invalid credentials. Please try again.")
|
| 775 |
+
return (
|
| 776 |
+
"<span style='color:#c62828;'>Invalid credentials. Please try again.</span>",
|
| 777 |
+
"",
|
| 778 |
+
"",
|
| 779 |
+
gr.update(visible=False),
|
| 780 |
+
gr.update(visible=True)
|
| 781 |
+
)
|
| 782 |
+
except NameError as e:
|
| 783 |
+
show_error(f"System error: {str(e)}. Please ensure required modules are imported.")
|
| 784 |
+
return (
|
| 785 |
+
"<span style='color:#c62828;'>System error encountered.</span>",
|
| 786 |
+
"",
|
| 787 |
+
"",
|
| 788 |
+
gr.update(visible=False),
|
| 789 |
+
gr.update(visible=True)
|
| 790 |
+
)
|
| 791 |
+
|
| 792 |
+
# ---------- Dashboard UI ----------
|
| 793 |
+
def dashboard_ui(general_chat_prompt: str, general_model: str, study_prompt: str, study_model: str):
|
| 794 |
+
"""
|
| 795 |
+
Creates a dashboard with professional styling, two main menus (General Chat and Study Support),
|
| 796 |
+
and smooth transitions between views. For Study Support, a login form (with authentication)
|
| 797 |
+
is shown before revealing the chat interface.
|
| 798 |
+
"""
|
| 799 |
+
# Retrieve the existing chat interfaces.
|
| 800 |
+
general_chat_component = general_chat_ui(general_chat_prompt, general_model)
|
| 801 |
+
study_support_component = study_support_ui(study_prompt, study_model)
|
| 802 |
+
# Retrieve the login form.
|
| 803 |
+
(login_form_component, user_id_input, user_id_error, password_input, password_error,
|
| 804 |
+
login_btn, login_msg, proceed_btn, login_back_btn) = login_form_ui()
|
| 805 |
+
|
| 806 |
+
with gr.Blocks(css=custom_css()) as dashboard:
|
| 807 |
+
# Inject JavaScript for animations and loading effects
|
| 808 |
+
gr.HTML('''
|
| 809 |
+
<script>
|
| 810 |
+
document.addEventListener("DOMContentLoaded", function() {
|
| 811 |
+
// Create and style the spinner element
|
| 812 |
+
let spinner = document.createElement("div");
|
| 813 |
+
spinner.className = "loader";
|
| 814 |
+
spinner.innerHTML = '<div class="cube"></div>';
|
| 815 |
+
document.body.appendChild(spinner);
|
| 816 |
+
|
| 817 |
+
// Show the spinner when needed
|
| 818 |
+
spinner.style.display = "none"; // Initially hidden
|
| 819 |
+
|
| 820 |
+
// Function to show spinner
|
| 821 |
+
function showSpinner() {
|
| 822 |
+
spinner.style.display = "block";
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
// Function to hide spinner
|
| 826 |
+
function hideSpinner() {
|
| 827 |
+
spinner.style.display = "none";
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
// Function to handle animation transitions
|
| 831 |
+
function setupAnimations() {
|
| 832 |
+
// Get all containers that might be animated
|
| 833 |
+
const containers = document.querySelectorAll('.container, .chat-container');
|
| 834 |
+
|
| 835 |
+
// Add transition end listener to each container
|
| 836 |
+
containers.forEach(container => {
|
| 837 |
+
container.addEventListener('animationend', function(e) {
|
| 838 |
+
// When animation completes, remove the animation class
|
| 839 |
+
if (e.animationName.includes('fadeIn')) {
|
| 840 |
+
this.classList.remove('animate-fadeInUp', 'animate-fadeInRight', 'animate-fadeInLeft');
|
| 841 |
+
}
|
| 842 |
+
});
|
| 843 |
+
});
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
// Setup animations after a short delay to ensure DOM is ready
|
| 847 |
+
setTimeout(setupAnimations, 2000);
|
| 848 |
+
|
| 849 |
+
// Add event listeners to all buttons that should trigger the spinner
|
| 850 |
+
setTimeout(function(){
|
| 851 |
+
// Login and Proceed buttons with spinner
|
| 852 |
+
const loginBtn = document.querySelector('button:contains("Login")');
|
| 853 |
+
const proceedBtn = document.querySelector('button:contains("Proceed")');
|
| 854 |
+
|
| 855 |
+
if (loginBtn) {
|
| 856 |
+
loginBtn.addEventListener("click", function() {
|
| 857 |
+
showSpinner(); // Show spinner when login button is clicked
|
| 858 |
+
|
| 859 |
+
if (!this.classList.contains("loading")) {
|
| 860 |
+
const originalText = this.textContent;
|
| 861 |
+
this.setAttribute("data-original-text", originalText);
|
| 862 |
+
this.textContent = "";
|
| 863 |
+
this.classList.add("loading");
|
| 864 |
+
|
| 865 |
+
// Create spinner inside button
|
| 866 |
+
const btnSpinner = document.createElement("div");
|
| 867 |
+
btnSpinner.className = "btn-spinner";
|
| 868 |
+
btnSpinner.style.width = "20px";
|
| 869 |
+
btnSpinner.style.height = "20px";
|
| 870 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 871 |
+
btnSpinner.style.borderRadius = "50%";
|
| 872 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 873 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 874 |
+
btnSpinner.style.display = "inline-block";
|
| 875 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 876 |
+
this.appendChild(btnSpinner);
|
| 877 |
+
|
| 878 |
+
// Reset button after 3 seconds if no response
|
| 879 |
+
setTimeout(() => {
|
| 880 |
+
if (this.classList.contains("loading")) {
|
| 881 |
+
this.classList.remove("loading");
|
| 882 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 883 |
+
}
|
| 884 |
+
hideSpinner(); // Hide spinner after timeout
|
| 885 |
+
}, 3000);
|
| 886 |
+
}
|
| 887 |
+
});
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
if (proceedBtn) {
|
| 891 |
+
proceedBtn.addEventListener("click", function() {
|
| 892 |
+
showSpinner(); // Show spinner when proceed button is clicked
|
| 893 |
+
|
| 894 |
+
if (!this.classList.contains("loading")) {
|
| 895 |
+
const originalText = this.textContent;
|
| 896 |
+
this.setAttribute("data-original-text", originalText);
|
| 897 |
+
this.textContent = "";
|
| 898 |
+
this.classList.add("loading");
|
| 899 |
+
|
| 900 |
+
// Create spinner inside button
|
| 901 |
+
const btnSpinner = document.createElement("div");
|
| 902 |
+
btnSpinner.className = "btn-spinner";
|
| 903 |
+
btnSpinner.style.width = "20px";
|
| 904 |
+
btnSpinner.style.height = "20px";
|
| 905 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 906 |
+
btnSpinner.style.borderRadius = "50%";
|
| 907 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 908 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 909 |
+
btnSpinner.style.display = "inline-block";
|
| 910 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 911 |
+
this.appendChild(btnSpinner);
|
| 912 |
+
|
| 913 |
+
// Reset button after 3 seconds if no response
|
| 914 |
+
setTimeout(() => {
|
| 915 |
+
if (this.classList.contains("loading")) {
|
| 916 |
+
this.classList.remove("loading");
|
| 917 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 918 |
+
}
|
| 919 |
+
hideSpinner(); // Hide spinner after timeout
|
| 920 |
+
}, 3000);
|
| 921 |
+
}
|
| 922 |
+
});
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
// Scale effect for dashboard buttons
|
| 926 |
+
const dashboardButtons = document.querySelectorAll('.menu-button, button:contains("Back to Dashboard")');
|
| 927 |
+
dashboardButtons.forEach(function(btn) {
|
| 928 |
+
btn.addEventListener("click", function() {
|
| 929 |
+
this.style.transform = "scale(1.1)";
|
| 930 |
+
setTimeout(() => {
|
| 931 |
+
this.style.transform = "";
|
| 932 |
+
}, 200);
|
| 933 |
+
});
|
| 934 |
+
});
|
| 935 |
+
}, 2000);
|
| 936 |
+
|
| 937 |
+
// Add keyframes for spinner animation
|
| 938 |
+
const style = document.createElement('style');
|
| 939 |
+
style.textContent = `
|
| 940 |
+
@keyframes spin {
|
| 941 |
+
0% { transform: rotate(0deg); }
|
| 942 |
+
100% { transform: rotate(360deg); }
|
| 943 |
+
}
|
| 944 |
+
`;
|
| 945 |
+
document.head.appendChild(style);
|
| 946 |
+
});
|
| 947 |
+
</script>
|
| 948 |
+
''')
|
| 949 |
+
|
| 950 |
+
# Logo for all pages - positioned in top-left corner
|
| 951 |
+
gr.HTML('''
|
| 952 |
+
<div class="logo-dashboard">
|
| 953 |
+
<!-- USIU Logo Placeholder - Replace the src with actual logo path -->
|
| 954 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 955 |
+
</div>
|
| 956 |
+
''')
|
| 957 |
+
|
| 958 |
+
# Define containers for different views with initial animation state
|
| 959 |
+
dashboard_container = gr.Column(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInUp")
|
| 960 |
+
general_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 961 |
+
login_container = gr.Column(visible=False, elem_classes="container floating-card login-form-container pre-animation")
|
| 962 |
+
study_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 963 |
+
|
| 964 |
+
# ------------- Dashboard View -------------
|
| 965 |
+
with dashboard_container:
|
| 966 |
+
gr.Markdown("<h1 class='with-logo'>AI Assistance Dashboard</h1>")
|
| 967 |
+
with gr.Column(elem_classes="dashboard-menu"):
|
| 968 |
+
gen_button = gr.Button("General Enquiries", elem_classes="menu-button")
|
| 969 |
+
study_button = gr.Button("Study with AI", elem_classes="menu-button")
|
| 970 |
+
|
| 971 |
+
# ------------- General Academic Chat View -------------
|
| 972 |
+
with general_container:
|
| 973 |
+
back_gen = gr.Button("Back to Dashboard")
|
| 974 |
+
general_chat_component.render()
|
| 975 |
+
|
| 976 |
+
# ------------- Login Form (for Study Support) View -------------
|
| 977 |
+
with login_container:
|
| 978 |
+
login_form_component.render()
|
| 979 |
+
|
| 980 |
+
# ------------- Study Support Chat View -------------
|
| 981 |
+
with study_container:
|
| 982 |
+
back_study = gr.Button("Back to Dashboard")
|
| 983 |
+
study_support_component.render()
|
| 984 |
+
|
| 985 |
+
# ------------- Navigation Callbacks -------------
|
| 986 |
+
# When "General Academic Chat" is selected from the dashboard.
|
| 987 |
+
gen_button.click(
|
| 988 |
+
lambda: [
|
| 989 |
+
gr.update(visible=False),
|
| 990 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight"),
|
| 991 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 992 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 993 |
+
],
|
| 994 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 995 |
+
)
|
| 996 |
+
|
| 997 |
+
# When "Study Support Chat" is selected from the dashboard, show the login view with animation.
|
| 998 |
+
study_button.click(
|
| 999 |
+
lambda: [
|
| 1000 |
+
gr.update(visible=False),
|
| 1001 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1002 |
+
gr.update(visible=True, elem_classes="container floating-card login-form-container animate-fadeInRight"),
|
| 1003 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 1004 |
+
],
|
| 1005 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1006 |
+
)
|
| 1007 |
+
|
| 1008 |
+
# "Back to Dashboard" buttons with animation.
|
| 1009 |
+
back_gen.click(
|
| 1010 |
+
lambda: [
|
| 1011 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1012 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1013 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1014 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 1015 |
+
],
|
| 1016 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1017 |
+
)
|
| 1018 |
+
|
| 1019 |
+
back_study.click(
|
| 1020 |
+
lambda: [
|
| 1021 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1022 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1023 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1024 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 1025 |
+
],
|
| 1026 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1027 |
+
)
|
| 1028 |
+
|
| 1029 |
+
login_back_btn.click(
|
| 1030 |
+
lambda: [
|
| 1031 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1032 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1033 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1034 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 1035 |
+
],
|
| 1036 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1037 |
+
)
|
| 1038 |
+
|
| 1039 |
+
# ------------- Authentication Logic -------------
|
| 1040 |
+
login_btn.click(
|
| 1041 |
+
handle_login,
|
| 1042 |
+
inputs=[user_id_input, password_input],
|
| 1043 |
+
outputs=[login_msg, user_id_error, password_error, proceed_btn, login_btn]
|
| 1044 |
+
)
|
| 1045 |
+
|
| 1046 |
+
# When "Proceed" is clicked (after successful authentication), show the study support chat with animation.
|
| 1047 |
+
proceed_btn.click(
|
| 1048 |
+
lambda: [
|
| 1049 |
+
gr.update(visible=False),
|
| 1050 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1051 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1052 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight")
|
| 1053 |
+
],
|
| 1054 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1055 |
+
)
|
| 1056 |
+
|
| 1057 |
+
return dashboard.queue()
|
| 1058 |
+
|
| 1059 |
+
import speech_recognition as sr
|
| 1060 |
+
from gtts import gTTS
|
| 1061 |
+
import os
|
| 1062 |
+
import tempfile
|
| 1063 |
+
import time
|
| 1064 |
+
import threading
|
| 1065 |
+
import queue
|
| 1066 |
+
from playsound import playsound
|
| 1067 |
+
# from voice_interface import VoiceInterface
|
| 1068 |
+
|
| 1069 |
+
class VoiceInterface:
|
| 1070 |
+
def __init__(self):
|
| 1071 |
+
# Initialize speech recognizer
|
| 1072 |
+
self.recognizer = sr.Recognizer()
|
| 1073 |
+
self.audio_queue = queue.Queue()
|
| 1074 |
+
self.recording = False
|
| 1075 |
+
self.temp_dir = tempfile.mkdtemp()
|
| 1076 |
+
|
| 1077 |
+
def start_recording(self):
|
| 1078 |
+
"""Start recording audio from the microphone"""
|
| 1079 |
+
self.recording = True
|
| 1080 |
+
threading.Thread(target=self._record_audio, daemon=True).start()
|
| 1081 |
+
print("Recording started... Speak now!")
|
| 1082 |
+
|
| 1083 |
+
def stop_recording(self):
|
| 1084 |
+
"""Stop recording audio"""
|
| 1085 |
+
self.recording = False
|
| 1086 |
+
print("Recording stopped.")
|
| 1087 |
+
|
| 1088 |
+
def _record_audio(self):
|
| 1089 |
+
"""Record audio from microphone"""
|
| 1090 |
+
try:
|
| 1091 |
+
with sr.Microphone() as source:
|
| 1092 |
+
# Adjust for ambient noise
|
| 1093 |
+
self.recognizer.adjust_for_ambient_noise(source, duration=0.5)
|
| 1094 |
+
|
| 1095 |
+
while self.recording:
|
| 1096 |
+
try:
|
| 1097 |
+
print("Listening...")
|
| 1098 |
+
audio = self.recognizer.listen(source, timeout=5, phrase_time_limit=10)
|
| 1099 |
+
|
| 1100 |
+
# Process the audio in a separate thread
|
| 1101 |
+
threading.Thread(
|
| 1102 |
+
target=self._process_audio,
|
| 1103 |
+
args=(audio,),
|
| 1104 |
+
daemon=True
|
| 1105 |
+
).start()
|
| 1106 |
+
except sr.WaitTimeoutError:
|
| 1107 |
+
continue
|
| 1108 |
+
except Exception as e:
|
| 1109 |
+
print(f"Error while listening: {e}")
|
| 1110 |
+
time.sleep(1)
|
| 1111 |
+
|
| 1112 |
+
except Exception as e:
|
| 1113 |
+
print(f"Error in recording thread: {e}")
|
| 1114 |
+
|
| 1115 |
+
def _process_audio(self, audio):
|
| 1116 |
+
"""Process recorded audio and convert to text"""
|
| 1117 |
+
try:
|
| 1118 |
+
# Use multiple APIs for better reliability
|
| 1119 |
+
text = None
|
| 1120 |
+
apis = [
|
| 1121 |
+
lambda: self.recognizer.recognize_google(audio),
|
| 1122 |
+
lambda: self.recognizer.recognize_whisper(audio),
|
| 1123 |
+
lambda: self.recognizer.recognize_sphinx(audio)
|
| 1124 |
+
]
|
| 1125 |
+
|
| 1126 |
+
for api_func in apis:
|
| 1127 |
+
try:
|
| 1128 |
+
text = api_func()
|
| 1129 |
+
break
|
| 1130 |
+
except:
|
| 1131 |
+
continue
|
| 1132 |
+
|
| 1133 |
+
if text and text.strip():
|
| 1134 |
+
print(f"Recognized: {text}")
|
| 1135 |
+
self.audio_queue.put(text)
|
| 1136 |
+
else:
|
| 1137 |
+
print("Speech not recognized")
|
| 1138 |
+
|
| 1139 |
+
except Exception as e:
|
| 1140 |
+
print(f"Error processing speech: {e}")
|
| 1141 |
+
|
| 1142 |
+
def get_text_from_speech(self, timeout=None):
|
| 1143 |
+
"""Get recognized text from the audio queue"""
|
| 1144 |
+
try:
|
| 1145 |
+
return self.audio_queue.get(timeout=timeout)
|
| 1146 |
+
except queue.Empty:
|
| 1147 |
+
return None
|
| 1148 |
+
|
| 1149 |
+
def text_to_speech(self, text):
|
| 1150 |
+
"""Convert text to speech and play it"""
|
| 1151 |
+
try:
|
| 1152 |
+
# Create temporary file for audio
|
| 1153 |
+
temp_file = os.path.join(self.temp_dir, f"speech_{int(time.time())}.mp3")
|
| 1154 |
+
|
| 1155 |
+
# Generate speech
|
| 1156 |
+
tts = gTTS(text=text, lang='en', slow=False)
|
| 1157 |
+
tts.save(temp_file)
|
| 1158 |
+
|
| 1159 |
+
# Play the audio
|
| 1160 |
+
playsound(temp_file)
|
| 1161 |
+
|
| 1162 |
+
# Clean up file after playing
|
| 1163 |
+
if os.path.exists(temp_file):
|
| 1164 |
+
os.remove(temp_file)
|
| 1165 |
+
|
| 1166 |
+
except Exception as e:
|
| 1167 |
+
print(f"Error in text-to-speech: {e}")
|
| 1168 |
+
|
| 1169 |
+
def cleanup(self):
|
| 1170 |
+
"""Clean up temporary files"""
|
| 1171 |
+
try:
|
| 1172 |
+
for file in os.listdir(self.temp_dir):
|
| 1173 |
+
os.remove(os.path.join(self.temp_dir, file))
|
| 1174 |
+
os.rmdir(self.temp_dir)
|
| 1175 |
+
except:
|
| 1176 |
+
pass
|
| 1177 |
+
|
| 1178 |
+
#############################
|
| 1179 |
+
# READ FILE UTILITY
|
| 1180 |
+
#############################
|
| 1181 |
+
def read_file(file):
|
| 1182 |
+
file_extension = os.path.splitext(file.name)[1].lower()
|
| 1183 |
+
|
| 1184 |
+
if file_extension == '.txt':
|
| 1185 |
+
content = file.read().decode('utf-8')
|
| 1186 |
+
elif file_extension == '.pdf':
|
| 1187 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 1188 |
+
content = ''
|
| 1189 |
+
for page in pdf_reader.pages:
|
| 1190 |
+
text = page.extract_text()
|
| 1191 |
+
if text:
|
| 1192 |
+
content += text
|
| 1193 |
+
elif file_extension in ['.docx', '.doc']:
|
| 1194 |
+
doc = docx.Document(file)
|
| 1195 |
+
content = "\n".join([para.text for para in doc.paragraphs])
|
| 1196 |
+
elif file_extension in ['.xlsx', '.xls']:
|
| 1197 |
+
df = pd.read_excel(file)
|
| 1198 |
+
content = df.to_string()
|
| 1199 |
+
elif file_extension in ['.pptx', '.ppt']:
|
| 1200 |
+
prs = pptx.Presentation(file)
|
| 1201 |
+
content = ''
|
| 1202 |
+
for slide in prs.slides:
|
| 1203 |
+
for shape in slide.shapes:
|
| 1204 |
+
if hasattr(shape, 'text'):
|
| 1205 |
+
content += shape.text + '\n'
|
| 1206 |
+
elif file_extension in ['.png', '.jpg', '.jpeg', '.gif']:
|
| 1207 |
+
image = Image.open(file)
|
| 1208 |
+
buffered = io.BytesIO()
|
| 1209 |
+
image.save(buffered, format="PNG")
|
| 1210 |
+
# Create an image variation via OpenAI's API (example)
|
| 1211 |
+
response = openai.Image.create_variation(
|
| 1212 |
+
image=buffered,
|
| 1213 |
+
n=1,
|
| 1214 |
+
size="256x256"
|
| 1215 |
+
)
|
| 1216 |
+
image_description = response['data'][0]['url']
|
| 1217 |
+
content = f"[Image uploaded. Description: {image_description}]"
|
| 1218 |
+
else:
|
| 1219 |
+
content = f"Unsupported file type: {file_extension}"
|
| 1220 |
+
|
| 1221 |
+
return content
|
| 1222 |
+
|
| 1223 |
+
#############################
|
| 1224 |
+
# GLOBAL RETRIEVER SETUP (for RAG in general_chat_ui)
|
| 1225 |
+
#############################
|
| 1226 |
+
from vector_services.data_curator import DataCurator
|
| 1227 |
+
curator = DataCurator(knowledge_base_dir="usiu-knowledge-base")
|
| 1228 |
+
curator.load_vectorstore()
|
| 1229 |
+
# GLOBAL_RETRIEVER enables RAG for general chat.
|
| 1230 |
+
GLOBAL_RETRIEVER = curator.get_retriever()
|
| 1231 |
+
|
| 1232 |
+
#############################
|
| 1233 |
+
# GENERAL CHAT UI (with RAG)
|
| 1234 |
+
#############################
|
| 1235 |
+
def general_chat_ui(system_prompt: str, model: str) -> None:
|
| 1236 |
+
"""
|
| 1237 |
+
Creates a Gradio ChatInterface for general academic chat.
|
| 1238 |
+
Uses additional_inputs for file upload, maintains RAG by retrieving context via GLOBAL_RETRIEVER,
|
| 1239 |
+
and streams the response. The function takes three inputs (text message, state, file) and
|
| 1240 |
+
yields incremental final responses as a string.
|
| 1241 |
+
"""
|
| 1242 |
+
|
| 1243 |
+
# def voice_button_click():
|
| 1244 |
+
# """Callback for the voice input button"""
|
| 1245 |
+
# voice_interface.start_recording()
|
| 1246 |
+
# # Wait for up to 10 seconds for voice input
|
| 1247 |
+
# text = voice_interface.get_text_from_speech(timeout=10)
|
| 1248 |
+
# voice_interface.stop_recording()
|
| 1249 |
+
# return text if text else ""
|
| 1250 |
+
|
| 1251 |
+
def general_chat(message: str, chat_history, file) -> str:
|
| 1252 |
+
# Ensure chat_history is a list.
|
| 1253 |
+
if chat_history is None:
|
| 1254 |
+
chat_history = []
|
| 1255 |
+
|
| 1256 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 1257 |
+
if not message.strip() and file is not None:
|
| 1258 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 1259 |
+
|
| 1260 |
+
# If this is a new conversation, insert a conversation header.
|
| 1261 |
+
if not chat_history:
|
| 1262 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 1263 |
+
conv_header = {
|
| 1264 |
+
"role": "header",
|
| 1265 |
+
"content": (
|
| 1266 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 1267 |
+
f"Conversation started on {date_str}"
|
| 1268 |
+
f"</div>"
|
| 1269 |
+
),
|
| 1270 |
+
"files": []
|
| 1271 |
+
}
|
| 1272 |
+
chat_history.insert(0, conv_header) # Insert at the beginning
|
| 1273 |
+
|
| 1274 |
+
# Start with the initial system prompt.
|
| 1275 |
+
messages = [
|
| 1276 |
+
{"role": "system", "content": system_prompt, "files": []},
|
| 1277 |
+
]
|
| 1278 |
+
|
| 1279 |
+
# Add chat history to messages (all messages, including header).
|
| 1280 |
+
if chat_history:
|
| 1281 |
+
# If the first element is a tuple/list with two elements, assume pair format.
|
| 1282 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 1283 |
+
for human, assistant in chat_history:
|
| 1284 |
+
messages.append({"role": "user", "content": human, "files": []})
|
| 1285 |
+
messages.append({"role": "assistant", "content": assistant, "files": []})
|
| 1286 |
+
# Otherwise, if they are dicts, include all messages.
|
| 1287 |
+
elif isinstance(chat_history[0], dict):
|
| 1288 |
+
for msg in chat_history:
|
| 1289 |
+
if msg is None:
|
| 1290 |
+
continue
|
| 1291 |
+
# Skip header messages when sending to API.
|
| 1292 |
+
if msg.get("role") == "header":
|
| 1293 |
+
continue
|
| 1294 |
+
# Ensure each message has a files field.
|
| 1295 |
+
msg_to_send = dict(msg)
|
| 1296 |
+
if "files" not in msg_to_send or msg_to_send["files"] is None:
|
| 1297 |
+
msg_to_send["files"] = []
|
| 1298 |
+
messages.append(msg_to_send)
|
| 1299 |
+
|
| 1300 |
+
# If a file is uploaded, add its content to the messages.
|
| 1301 |
+
if file is not None:
|
| 1302 |
+
try:
|
| 1303 |
+
file_content = read_file(file)
|
| 1304 |
+
messages.append({
|
| 1305 |
+
"role": "user",
|
| 1306 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}"
|
| 1307 |
+
})
|
| 1308 |
+
except Exception as e:
|
| 1309 |
+
messages.append({
|
| 1310 |
+
"role": "user",
|
| 1311 |
+
"content": f"Error reading file: {str(e)}",
|
| 1312 |
+
"files": []
|
| 1313 |
+
})
|
| 1314 |
+
|
| 1315 |
+
# RAG: Retrieve additional context using GLOBAL_RETRIEVER if available.
|
| 1316 |
+
if GLOBAL_RETRIEVER is not None:
|
| 1317 |
+
try:
|
| 1318 |
+
docs = GLOBAL_RETRIEVER.get_relevant_documents(message)
|
| 1319 |
+
if docs:
|
| 1320 |
+
context_text = "\n\n".join([doc.page_content for doc in docs])
|
| 1321 |
+
# Insert the retrieved context as an additional system message.
|
| 1322 |
+
messages.insert(1, {
|
| 1323 |
+
"role": "system",
|
| 1324 |
+
"content": f"Additional Context:\n{context_text}",
|
| 1325 |
+
"files": []
|
| 1326 |
+
})
|
| 1327 |
+
except Exception as e:
|
| 1328 |
+
print("RAG retrieval failed:", e)
|
| 1329 |
+
|
| 1330 |
+
chat_history.append({"role": "user", "content": message, "files": []})
|
| 1331 |
+
messages.append({"role": "user", "content": message, "files": []})
|
| 1332 |
+
|
| 1333 |
+
print("General Enquiries - Conversation with context:")
|
| 1334 |
+
print(messages)
|
| 1335 |
+
|
| 1336 |
+
# Call OpenAI's ChatCompletion with streaming enabled.
|
| 1337 |
+
completion = openai.ChatCompletion.create(
|
| 1338 |
+
model=model,
|
| 1339 |
+
messages=messages,
|
| 1340 |
+
max_tokens=2_000,
|
| 1341 |
+
temperature=0.7,
|
| 1342 |
+
stream=True,
|
| 1343 |
+
)
|
| 1344 |
+
|
| 1345 |
+
response = ""
|
| 1346 |
+
for chunk in completion:
|
| 1347 |
+
token = chunk.choices[0].delta.get("content", "")
|
| 1348 |
+
response += token
|
| 1349 |
+
yield response
|
| 1350 |
+
|
| 1351 |
+
# # After the response is completely generated, append a timestamp.
|
| 1352 |
+
# timestamp = time.strftime("%I:%M %p", time.localtime())
|
| 1353 |
+
# final_response = response + f"\n<span style='font-size:11px; color:#999;'>{timestamp}</span>"
|
| 1354 |
+
|
| 1355 |
+
# # If voice playback is enabled, read the response aloud in a separate thread
|
| 1356 |
+
# if play_response:
|
| 1357 |
+
# threading.Thread(
|
| 1358 |
+
# target=voice_interface.text_to_speech,
|
| 1359 |
+
# args=(response,),
|
| 1360 |
+
# daemon=True
|
| 1361 |
+
# ).start()
|
| 1362 |
+
|
| 1363 |
+
# yield final_response
|
| 1364 |
+
|
| 1365 |
+
# iface = gr.ChatInterface(
|
| 1366 |
+
# fn=general_chat,
|
| 1367 |
+
# additional_inputs=[gr.File(label="Upload a file (optional)")],
|
| 1368 |
+
# additional_inputs_accordion="File Upload",
|
| 1369 |
+
# editable=True,
|
| 1370 |
+
# save_history=True,
|
| 1371 |
+
# title="General Academic Chat with File Upload",
|
| 1372 |
+
# type="messages",
|
| 1373 |
+
# cache_mode="eager"
|
| 1374 |
+
# )
|
| 1375 |
+
iface = gr.ChatInterface(
|
| 1376 |
+
fn=general_chat,
|
| 1377 |
+
additional_inputs=[gr.File(label="Upload a file (optional)", elem_classes="file-upload")],
|
| 1378 |
+
additional_inputs_accordion=gr.Accordion("File Upload", open=False, elem_classes="additional-inputs-accordion"),
|
| 1379 |
+
editable=True,
|
| 1380 |
+
save_history=True,
|
| 1381 |
+
title="General Enquiries",
|
| 1382 |
+
description="<div style='display:flex; justify-content:center;'>Hi there! I'm your AI assistant, here to help you with queries related to USIU. Feel free to ask any questions or seek assistance.</div>",
|
| 1383 |
+
type="messages",
|
| 1384 |
+
cache_mode="eager"
|
| 1385 |
+
)
|
| 1386 |
+
return iface.queue()
|
| 1387 |
+
|
| 1388 |
+
#############################
|
| 1389 |
+
# STUDY SUPPORT UI (without RAG)
|
| 1390 |
+
#############################
|
| 1391 |
+
def study_support_ui(system_prompt: str, model: str) -> None:
|
| 1392 |
+
"""
|
| 1393 |
+
Creates a Gradio ChatInterface for study support.
|
| 1394 |
+
Uses additional_inputs for file upload and streams the response using Claude's API.
|
| 1395 |
+
No RAG is applied in this interface.
|
| 1396 |
+
The function takes three inputs (text message, state, file) and returns a final response as a string.
|
| 1397 |
+
"""
|
| 1398 |
+
|
| 1399 |
+
# def voice_button_click():
|
| 1400 |
+
# """Callback for the voice input button"""
|
| 1401 |
+
# voice_interface.start_recording()
|
| 1402 |
+
# # Wait for up to 10 seconds for voice input
|
| 1403 |
+
# text = voice_interface.get_text_from_speech(timeout=10)
|
| 1404 |
+
# voice_interface.stop_recording()
|
| 1405 |
+
# return text if text else ""
|
| 1406 |
+
|
| 1407 |
+
def study_support_chat(message: str, chat_history, file) -> str:
|
| 1408 |
+
# Ensure chat_history is a list.
|
| 1409 |
+
if chat_history is None:
|
| 1410 |
+
chat_history = []
|
| 1411 |
+
|
| 1412 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 1413 |
+
if not message.strip() and file is not None:
|
| 1414 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 1415 |
+
|
| 1416 |
+
# If this is a new conversation, insert a conversation header.
|
| 1417 |
+
if not chat_history:
|
| 1418 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 1419 |
+
conv_header = {
|
| 1420 |
+
"role": "header",
|
| 1421 |
+
"content": (
|
| 1422 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 1423 |
+
f"Conversation started on {date_str}"
|
| 1424 |
+
f"</div>"
|
| 1425 |
+
)
|
| 1426 |
+
}
|
| 1427 |
+
chat_history.insert(0, conv_header)
|
| 1428 |
+
|
| 1429 |
+
# Build the messages list that will be sent to the API.
|
| 1430 |
+
messages = []
|
| 1431 |
+
if chat_history:
|
| 1432 |
+
# If chat_history items are tuples/lists with two elements, unpack them.
|
| 1433 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 1434 |
+
for human, assistant in chat_history:
|
| 1435 |
+
messages.append({"role": "user", "content": human})
|
| 1436 |
+
messages.append({"role": "assistant", "content": assistant})
|
| 1437 |
+
# Otherwise, if they are dicts, include only "role" and "content" for non-system messages.
|
| 1438 |
+
elif isinstance(chat_history[0], dict):
|
| 1439 |
+
for msg in chat_history:
|
| 1440 |
+
if msg is None:
|
| 1441 |
+
continue
|
| 1442 |
+
if msg.get("role") == "header":
|
| 1443 |
+
continue # Skip header messages.
|
| 1444 |
+
msg_to_send = {"role": msg.get("role"), "content": msg.get("content")}
|
| 1445 |
+
messages.append(msg_to_send)
|
| 1446 |
+
|
| 1447 |
+
# Add the current message.
|
| 1448 |
+
messages.append({"role": "user", "content": message})
|
| 1449 |
+
|
| 1450 |
+
# If a file is uploaded, add its content.
|
| 1451 |
+
if file is not None:
|
| 1452 |
+
try:
|
| 1453 |
+
file_content = read_file(file)
|
| 1454 |
+
messages.append({
|
| 1455 |
+
"role": "user",
|
| 1456 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}"
|
| 1457 |
+
})
|
| 1458 |
+
except Exception as e:
|
| 1459 |
+
messages.append({
|
| 1460 |
+
"role": "user",
|
| 1461 |
+
"content": f"Error reading file: {str(e)}"
|
| 1462 |
+
})
|
| 1463 |
+
|
| 1464 |
+
# Implement a retry mechanism to handle overloaded errors.
|
| 1465 |
+
max_retries = 7
|
| 1466 |
+
delay = 3 # seconds
|
| 1467 |
+
attempt = 0
|
| 1468 |
+
while attempt < max_retries:
|
| 1469 |
+
try:
|
| 1470 |
+
result = claude.messages.stream(
|
| 1471 |
+
model=model,
|
| 1472 |
+
max_tokens=64_000,
|
| 1473 |
+
system=system_prompt,
|
| 1474 |
+
messages=messages,
|
| 1475 |
+
extra_query={"extended_thinking": True}
|
| 1476 |
+
)
|
| 1477 |
+
response = ""
|
| 1478 |
+
with result as stream:
|
| 1479 |
+
for text in stream.text_stream:
|
| 1480 |
+
response += text
|
| 1481 |
+
yield response # Yield incremental responses.
|
| 1482 |
+
|
| 1483 |
+
# After generating the complete response
|
| 1484 |
+
# if play_response:
|
| 1485 |
+
# threading.Thread(
|
| 1486 |
+
# target=voice_interface.text_to_speech,
|
| 1487 |
+
# args=(response,),
|
| 1488 |
+
# daemon=True
|
| 1489 |
+
# ).start()
|
| 1490 |
+
|
| 1491 |
+
# yield final_response
|
| 1492 |
+
|
| 1493 |
+
break # Exit the retry loop if successful.
|
| 1494 |
+
except Exception as e:
|
| 1495 |
+
# Check if error indicates the API is overloaded.
|
| 1496 |
+
if "overloaded" in str(e).lower():
|
| 1497 |
+
attempt += 1
|
| 1498 |
+
yield f"Service unavailable. Retrying in {delay} seconds... (attempt {attempt}/{max_retries})\n"
|
| 1499 |
+
time.sleep(delay)
|
| 1500 |
+
else:
|
| 1501 |
+
print(f"An error occurred: {str(e)}")
|
| 1502 |
+
break
|
| 1503 |
+
else:
|
| 1504 |
+
yield "The service is currently unavailable. Please try again later."
|
| 1505 |
+
|
| 1506 |
+
# iface = gr.ChatInterface(
|
| 1507 |
+
# fn=study_support_chat,
|
| 1508 |
+
# additional_inputs=[gr.File(label="Upload a file (optional)")],
|
| 1509 |
+
# additional_inputs_accordion="File Upload",
|
| 1510 |
+
# editable=True,
|
| 1511 |
+
# save_history=True,
|
| 1512 |
+
# title="Study Support Chat with File Upload",
|
| 1513 |
+
# type="messages",
|
| 1514 |
+
# cache_mode="eager"
|
| 1515 |
+
# )
|
| 1516 |
+
iface = gr.ChatInterface(
|
| 1517 |
+
fn=study_support_chat,
|
| 1518 |
+
additional_inputs=[gr.File(label="Upload a file (optional)", elem_classes="file-upload")],
|
| 1519 |
+
additional_inputs_accordion=gr.Accordion("File Upload", open=False, elem_classes="additional-inputs-accordion"),
|
| 1520 |
+
editable=True,
|
| 1521 |
+
save_history=True,
|
| 1522 |
+
title="Study Bud",
|
| 1523 |
+
description="<div style='display:flex; justify-content:center;'>Hi there! I'm your AI study bud. Feel free to ask questions, share resources, and get personalized support.</div>",
|
| 1524 |
+
type="messages",
|
| 1525 |
+
cache_mode="eager"
|
| 1526 |
+
)
|
| 1527 |
+
return iface.queue()
|
services/ui_service.py
ADDED
|
@@ -0,0 +1,1560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# services/ui_service.py
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
import os
|
| 5 |
+
import time
|
| 6 |
+
import openai
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import threading
|
| 9 |
+
from typing import List, Dict, Any, Tuple, Optional, Generator
|
| 10 |
+
|
| 11 |
+
from configs.config import claude, GPT4O_MODEL, CLAUDE_MODEL
|
| 12 |
+
from services.auth_service import (
|
| 13 |
+
authenticate, create_session, get_session, update_session_activity,
|
| 14 |
+
end_session, check_inactivity, INACTIVITY_TIMEOUT
|
| 15 |
+
)
|
| 16 |
+
from services.file_service import FileProcessor
|
| 17 |
+
from api_gateway.gateway import route_request
|
| 18 |
+
# from vector_services.data_curator import DataCurator
|
| 19 |
+
|
| 20 |
+
# Will be set when dashboard_ui is called (during runtime)
|
| 21 |
+
GLOBAL_RETRIEVER = None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# ----------------- New Summarization Helper -----------------
|
| 25 |
+
def summarize_context(text: str, summarization_model: str = GPT4O_MODEL) -> str:
|
| 26 |
+
"""
|
| 27 |
+
Summarize the given text concisely while preserving key details.
|
| 28 |
+
Uses the provided summarization model (e.g., GPT-4o or llama-3-8b).
|
| 29 |
+
"""
|
| 30 |
+
summarization_prompt = (
|
| 31 |
+
"Summarize the following text concisely while preserving the essential details:\n\n"
|
| 32 |
+
f"{text}\n\nSummary:"
|
| 33 |
+
)
|
| 34 |
+
try:
|
| 35 |
+
# Non-streaming call to get the summary
|
| 36 |
+
response = openai.ChatCompletion.create(
|
| 37 |
+
model=summarization_model,
|
| 38 |
+
messages=[{"role": "system", "content": summarization_prompt}],
|
| 39 |
+
max_tokens=150,
|
| 40 |
+
temperature=0.5,
|
| 41 |
+
)
|
| 42 |
+
summary = response.choices[0].message.content.strip()
|
| 43 |
+
return summary
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print("Summarization failed:", e)
|
| 46 |
+
# Fallback: return a truncated version if summarization fails
|
| 47 |
+
return text[:1000]
|
| 48 |
+
|
| 49 |
+
def custom_css():
|
| 50 |
+
css = """
|
| 51 |
+
/* Professional custom CSS with transitions, hover effects, and responsive design */
|
| 52 |
+
body {
|
| 53 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 54 |
+
background-color: #f8f9fa;
|
| 55 |
+
margin: 0;
|
| 56 |
+
padding: 0;
|
| 57 |
+
width: 100%;
|
| 58 |
+
overflow-x: hidden; /* Prevent horizontal scrolling */
|
| 59 |
+
}
|
| 60 |
+
h1, h2, h3 {
|
| 61 |
+
text-align: center;
|
| 62 |
+
color: #495057;
|
| 63 |
+
}
|
| 64 |
+
.gr-button:not(.secondary-button):not([aria-label="Clear"]):not([aria-label="Retry"]):not([aria-label="Undo"]):not(.chat-buttons button) {
|
| 65 |
+
transition: background-color 0.3s ease, transform 0.3s ease;
|
| 66 |
+
border-radius: 4px;
|
| 67 |
+
/* USIU-Africa theme color for buttons */
|
| 68 |
+
background-color: #000080;
|
| 69 |
+
color: white;
|
| 70 |
+
}
|
| 71 |
+
.gr-button:not(.secondary-button):not([aria-label="Clear"]):not([aria-label="Retry"]):not([aria-label="Undo"]):not(.chat-buttons button):hover {
|
| 72 |
+
background-color: #000066;
|
| 73 |
+
color: #fff;
|
| 74 |
+
transform: scale(1.03);
|
| 75 |
+
}
|
| 76 |
+
.container {
|
| 77 |
+
transition: opacity 0.5s ease-in-out;
|
| 78 |
+
padding: 4px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/* Style the "New chat" button to match USIU-Africa theme */
|
| 82 |
+
button.secondary-button {
|
| 83 |
+
background-color: #000080 !important; /* USIU-Africa navy blue */
|
| 84 |
+
color: white !important;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
button.secondary-button:hover {
|
| 88 |
+
background-color: #000066 !important; /* Slightly darker on hover */
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* Glassmorphism effect for floating cards */
|
| 92 |
+
.floating-card {
|
| 93 |
+
background: rgba(255, 255, 255, 0.85);
|
| 94 |
+
border-radius: 12px;
|
| 95 |
+
box-shadow:
|
| 96 |
+
0 10px 25px rgba(0,0,0,0.08),
|
| 97 |
+
0 6px 10px rgba(0,0,0,0.12),
|
| 98 |
+
0 3px 3px rgba(0,0,0,0.15);
|
| 99 |
+
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
| 100 |
+
padding: 25px;
|
| 101 |
+
margin: 15px auto; /* Reduced margin */
|
| 102 |
+
backdrop-filter: blur(10px);
|
| 103 |
+
border: 1px solid rgba(255,255,255,0.25);
|
| 104 |
+
width: 95%; /* Increased width */
|
| 105 |
+
max-width: 98%; /* Increased max-width */
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.floating-card:hover {
|
| 109 |
+
box-shadow:
|
| 110 |
+
0 15px 30px rgba(0,0,0,0.15),
|
| 111 |
+
0 10px 10px rgba(0,0,0,0.18);
|
| 112 |
+
transform: translateY(-5px);
|
| 113 |
+
background: rgba(255, 255, 255, 0.9);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
/* Login form styling with responsiveness */
|
| 117 |
+
.login-form-container {
|
| 118 |
+
width: 98% !important; /* Increased width for mobile */
|
| 119 |
+
max-width: 500px !important;
|
| 120 |
+
margin: 30px auto !important; /* Reduced top/bottom margin */
|
| 121 |
+
background: rgba(255, 255, 255, 0.88) !important;
|
| 122 |
+
backdrop-filter: blur(12px) !important;
|
| 123 |
+
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
| 124 |
+
border-top: 3px solid #000080 !important;
|
| 125 |
+
padding: 20px 15px !important; /* Added horizontal padding */
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
/* Dashboard container styling with responsiveness */
|
| 129 |
+
.dashboard-card {
|
| 130 |
+
width: 98% !important; /* Take almost full width on mobile */
|
| 131 |
+
max-width: 700px !important;
|
| 132 |
+
margin: 20px auto !important; /* Reduced margin */
|
| 133 |
+
background: rgba(255, 255, 255, 0.88) !important;
|
| 134 |
+
backdrop-filter: blur(12px) !important;
|
| 135 |
+
height: auto !important;
|
| 136 |
+
min-height: 500px !important;
|
| 137 |
+
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
| 138 |
+
border-top: 3px solid #000080 !important;
|
| 139 |
+
opacity: 0;
|
| 140 |
+
animation: dashboardFadeIn 1.5s forwards ease-in-out;
|
| 141 |
+
padding: 20px 10px !important; /* Reduced horizontal padding */
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* A subtle gradient background to the cards */
|
| 145 |
+
.dashboard-card, .login-form-container {
|
| 146 |
+
background: linear-gradient(
|
| 147 |
+
135deg,
|
| 148 |
+
rgba(255, 255, 255, 0.9) 0%,
|
| 149 |
+
rgba(255, 255, 255, 0.8) 100%
|
| 150 |
+
) !important;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/* Logo container styling with responsive adjustments */
|
| 154 |
+
.logo-container {
|
| 155 |
+
text-align: center;
|
| 156 |
+
margin-bottom: 25px;
|
| 157 |
+
position: relative;
|
| 158 |
+
width: 100%;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.logo {
|
| 162 |
+
max-height: 80px;
|
| 163 |
+
max-width: 90%; /* Increased max-width */
|
| 164 |
+
margin: 0 auto;
|
| 165 |
+
display: block;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.logo-dashboard {
|
| 169 |
+
position: absolute;
|
| 170 |
+
top: 15px;
|
| 171 |
+
left: 15px;
|
| 172 |
+
max-height: 60px;
|
| 173 |
+
max-width: 35%; /* Increased max-width */
|
| 174 |
+
z-index: 100;
|
| 175 |
+
opacity: 1; /* Changed from 0 to 1 to make it fully visible */
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.with-logo {
|
| 179 |
+
margin-top: 10px;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
/* Chat UI container with extended width and responsiveness */
|
| 183 |
+
.chat-container {
|
| 184 |
+
max-width: 1000px;
|
| 185 |
+
width: 98%; /* Increased width for mobile */
|
| 186 |
+
margin: 0 auto;
|
| 187 |
+
opacity: 0;
|
| 188 |
+
transform: translateY(20px);
|
| 189 |
+
animation: fadeInUp 0.7s forwards;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
/* Increase chat window height with responsive adjustment */
|
| 193 |
+
.chatbot-container, .gradio-container .chat {
|
| 194 |
+
height: 80vh !important;
|
| 195 |
+
max-height: 900px !important;
|
| 196 |
+
width: 98% !important; /* Take up more width */
|
| 197 |
+
margin: 0 auto !important;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/* Make Gradio container take full width on mobile */
|
| 201 |
+
.gradio-container {
|
| 202 |
+
width: 100% !important;
|
| 203 |
+
max-width: 100% !important;
|
| 204 |
+
margin: 0 !important;
|
| 205 |
+
padding: 0 !important;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
/* Logout button styling */
|
| 209 |
+
.logout-btn {
|
| 210 |
+
/* When using a Gradio Button with elem_classes, these styles will be applied */
|
| 211 |
+
background-color: #f44336 !important;
|
| 212 |
+
color: white !important;
|
| 213 |
+
border: none !important;
|
| 214 |
+
padding: 8px 16px !important;
|
| 215 |
+
cursor: pointer !important;
|
| 216 |
+
border-radius: 4px !important;
|
| 217 |
+
display: flex !important;
|
| 218 |
+
align-items: center !important;
|
| 219 |
+
gap: 8px !important;
|
| 220 |
+
font-size: 14px !important;
|
| 221 |
+
transition: background-color 0.3s ease !important;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.logout-btn:hover {
|
| 225 |
+
background-color: #d32f2f !important;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.logout-btn svg {
|
| 229 |
+
width: 16px;
|
| 230 |
+
height: 16px;
|
| 231 |
+
fill: white;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.logout-btn-container {
|
| 235 |
+
position: relative;
|
| 236 |
+
width: 100%;
|
| 237 |
+
height: 0px;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.session-info {
|
| 241 |
+
position: absolute;
|
| 242 |
+
top: 50px;
|
| 243 |
+
right: 15px;
|
| 244 |
+
font-size: 12px;
|
| 245 |
+
color: #666;
|
| 246 |
+
background-color: rgba(255, 255, 255, 0.7);
|
| 247 |
+
padding: 4px 8px;
|
| 248 |
+
border-radius: 4px;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* Inactivity warning modal */
|
| 252 |
+
.inactive-warning {
|
| 253 |
+
position: fixed;
|
| 254 |
+
top: 50%;
|
| 255 |
+
left: 50%;
|
| 256 |
+
transform: translate(-50%, -50%);
|
| 257 |
+
background-color: white;
|
| 258 |
+
border: 1px solid #ccc;
|
| 259 |
+
border-radius: 8px;
|
| 260 |
+
padding: 20px;
|
| 261 |
+
z-index: 1001;
|
| 262 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 263 |
+
max-width: 400px;
|
| 264 |
+
width: 90%;
|
| 265 |
+
text-align: center;
|
| 266 |
+
display: none;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.inactive-warning h3 {
|
| 270 |
+
margin-top: 0;
|
| 271 |
+
color: #f44336;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
.inactive-warning button {
|
| 275 |
+
margin-top: 15px;
|
| 276 |
+
background-color: #000080;
|
| 277 |
+
color: white;
|
| 278 |
+
border: none;
|
| 279 |
+
padding: 8px 16px;
|
| 280 |
+
border-radius: 4px;
|
| 281 |
+
cursor: pointer;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.inactive-warning button:hover {
|
| 285 |
+
background-color: #000066;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.overlay {
|
| 289 |
+
position: fixed;
|
| 290 |
+
top: 0;
|
| 291 |
+
left: 0;
|
| 292 |
+
right: 0;
|
| 293 |
+
bottom: 0;
|
| 294 |
+
background-color: rgba(0,0,0,0.5);
|
| 295 |
+
z-index: 1000;
|
| 296 |
+
display: none;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
/* Dashboard menu buttons styling with responsiveness */
|
| 300 |
+
.dashboard-menu {
|
| 301 |
+
display: flex;
|
| 302 |
+
flex-direction: column;
|
| 303 |
+
align-items: center;
|
| 304 |
+
justify-content: center;
|
| 305 |
+
min-height: 60vh;
|
| 306 |
+
gap: 8px;
|
| 307 |
+
opacity: 0;
|
| 308 |
+
animation: menuFadeIn 3s forwards ease-in-out;
|
| 309 |
+
animation-delay: 0.7s;
|
| 310 |
+
padding: 15px 0; /* Reduced padding */
|
| 311 |
+
width: 100%; /* Full width */
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
.menu-button {
|
| 315 |
+
width: 95% !important; /* Increased width for mobile */
|
| 316 |
+
max-width: 300px !important;
|
| 317 |
+
height: auto !important;
|
| 318 |
+
min-height: 60px !important;
|
| 319 |
+
font-size: 16px !important;
|
| 320 |
+
font-weight: 500 !important;
|
| 321 |
+
margin: 6px auto !important; /* Reduced margin */
|
| 322 |
+
border-radius: 8px !important;
|
| 323 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
|
| 324 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
|
| 325 |
+
background-color: #000080 !important;
|
| 326 |
+
color: white !important;
|
| 327 |
+
opacity: 0;
|
| 328 |
+
animation: buttonsFadeIn 0.7s forwards ease-in-out;
|
| 329 |
+
animation-delay: calc(var(--button-index, 0) * 0.3s + 1s);
|
| 330 |
+
padding: 10px 15px !important;
|
| 331 |
+
display: flex !important;
|
| 332 |
+
align-items: center !important;
|
| 333 |
+
justify-content: center !important;
|
| 334 |
+
text-align: center !important;
|
| 335 |
+
word-wrap: break-word !important;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.menu-button:hover {
|
| 339 |
+
transform: scale(1.02);
|
| 340 |
+
background-image: linear-gradient(to bottom right, #000080, #0000cc);
|
| 341 |
+
transition: transform 0.5s ease;
|
| 342 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
/* 3D Loader CSS with responsive sizing */
|
| 346 |
+
.loader {
|
| 347 |
+
width: 40px; /* Reduced size */
|
| 348 |
+
height: 40px; /* Reduced size */
|
| 349 |
+
perspective: 100px;
|
| 350 |
+
position: fixed;
|
| 351 |
+
top: 50%;
|
| 352 |
+
left: 50%;
|
| 353 |
+
transform: translate(-50%, -50%);
|
| 354 |
+
z-index: 9999;
|
| 355 |
+
display: none;
|
| 356 |
+
}
|
| 357 |
+
.cube {
|
| 358 |
+
width: 100%;
|
| 359 |
+
height: 100%;
|
| 360 |
+
background: #000080;
|
| 361 |
+
transform: rotateX(0deg) rotateY(0deg);
|
| 362 |
+
animation: spinCube 1.5s infinite ease-in-out;
|
| 363 |
+
}
|
| 364 |
+
@keyframes spinCube {
|
| 365 |
+
0% { transform: rotateX(0deg) rotateY(0deg); }
|
| 366 |
+
25% { transform: rotateX(90deg) rotateY(0deg); }
|
| 367 |
+
50% { transform: rotateX(90deg) rotateY(90deg); }
|
| 368 |
+
75% { transform: rotateX(0deg) rotateY(90deg); }
|
| 369 |
+
100% { transform: rotateX(0deg) rotateY(0deg); }
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
/* Dashboard specific fade-in animations */
|
| 373 |
+
@keyframes dashboardFadeIn {
|
| 374 |
+
0% {
|
| 375 |
+
opacity: 0;
|
| 376 |
+
transform: translateY(30px);
|
| 377 |
+
}
|
| 378 |
+
100% {
|
| 379 |
+
opacity: 1;
|
| 380 |
+
transform: translateY(0);
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
@keyframes menuFadeIn {
|
| 385 |
+
0% {
|
| 386 |
+
opacity: 0;
|
| 387 |
+
transform: scale(0.95);
|
| 388 |
+
}
|
| 389 |
+
100% {
|
| 390 |
+
opacity: 1;
|
| 391 |
+
transform: scale(1);
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
@keyframes buttonsFadeIn {
|
| 396 |
+
0% {
|
| 397 |
+
opacity: 0;
|
| 398 |
+
transform: translateX(-15px);
|
| 399 |
+
}
|
| 400 |
+
100% {
|
| 401 |
+
opacity: 1;
|
| 402 |
+
transform: translateX(0);
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
/* Adding transition effects for interfaces */
|
| 407 |
+
@keyframes fadeInUp {
|
| 408 |
+
from {
|
| 409 |
+
opacity: 0;
|
| 410 |
+
transform: translateY(20px);
|
| 411 |
+
}
|
| 412 |
+
to {
|
| 413 |
+
opacity: 1;
|
| 414 |
+
transform: translateY(0);
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
@keyframes fadeInRight {
|
| 419 |
+
from {
|
| 420 |
+
opacity: 0;
|
| 421 |
+
transform: translateX(20px);
|
| 422 |
+
}
|
| 423 |
+
to {
|
| 424 |
+
opacity: 1;
|
| 425 |
+
transform: translateX(0);
|
| 426 |
+
}
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
@keyframes fadeInLeft {
|
| 430 |
+
from {
|
| 431 |
+
opacity: 0;
|
| 432 |
+
transform: translateX(-20px);
|
| 433 |
+
}
|
| 434 |
+
to {
|
| 435 |
+
opacity: 1;
|
| 436 |
+
transform: translateX(0);
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
/* Container animation classes that can be dynamically applied */
|
| 441 |
+
.animate-fadeInUp {
|
| 442 |
+
animation: fadeInUp 0.7s forwards;
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
.animate-fadeInRight {
|
| 446 |
+
animation: fadeInRight 0.7s forwards;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.animate-fadeInLeft {
|
| 450 |
+
animation: fadeInLeft 0.7s forwards;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
/* Initial state for containers to be animated */
|
| 454 |
+
.pre-animation {
|
| 455 |
+
opacity: 0;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
/* Specifically target Gradio elements to make them more mobile-friendly */
|
| 459 |
+
.gradio-container {
|
| 460 |
+
margin-left: 0 !important;
|
| 461 |
+
margin-right: 0 !important;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* Adjust textbox and button widths for mobile */
|
| 465 |
+
.gr-textbox {
|
| 466 |
+
width: 100% !important;
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
.gr-button-primary {
|
| 470 |
+
width: 100% !important;
|
| 471 |
+
margin: 5px 0 !important;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
/* Make all interface elements take more width on mobile */
|
| 475 |
+
.interface-elements {
|
| 476 |
+
width: 98% !important;
|
| 477 |
+
max-width: 100% !important;
|
| 478 |
+
margin: 0 auto !important;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
/* Fix padding for form elements */
|
| 482 |
+
input, select, textarea, button {
|
| 483 |
+
box-sizing: border-box !important;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
/* Ensure minimum touch target size for all clickable elements
|
| 487 |
+
BUT exclude accordion, file input, and chat action buttons */
|
| 488 |
+
button:not(.accordion-button):not(.file-upload button):not([aria-label="Clear"]):not([aria-label="Retry"]):not([aria-label="Undo"]):not(.chat-buttons button),
|
| 489 |
+
.gr-button:not(.file-upload .gr-button):not(.chat-buttons .gr-button),
|
| 490 |
+
a,
|
| 491 |
+
input[type="submit"],
|
| 492 |
+
input[type="button"] {
|
| 493 |
+
min-height: 44px !important; /* Apple's recommended minimum touch target size */
|
| 494 |
+
min-width: 44px !important;
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
/* Remove any unnecessary margins from Gradio components */
|
| 498 |
+
.gradio-container .gr-form > *, .gradio-container .gr-group > * {
|
| 499 |
+
margin-bottom: 8px !important;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
/* Ensure Gradio container does not exceed viewport width */
|
| 503 |
+
#component-0, #component-1, #component-2, #component-3, #component-4, #component-5,
|
| 504 |
+
#component-6, #component-7, #component-8, #component-9, #component-10 {
|
| 505 |
+
max-width: 100vw !important;
|
| 506 |
+
overflow-x: hidden !important;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
/* Media Queries for better responsiveness */
|
| 510 |
+
@media screen and (max-width: 768px) {
|
| 511 |
+
body {
|
| 512 |
+
padding: 0 !important; /* Remove body padding on mobile */
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
.dashboard-card {
|
| 516 |
+
width: 100% !important; /* Full width */
|
| 517 |
+
padding: 15px 8px !important; /* Reduced padding */
|
| 518 |
+
margin: 10px auto !important; /* Reduced margin */
|
| 519 |
+
border-radius: 0 !important; /* Optional: Remove border radius for full-width look */
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
.login-form-container {
|
| 523 |
+
width: 100% !important; /* Full width */
|
| 524 |
+
padding: 15px 10px !important; /* Reduced padding */
|
| 525 |
+
margin: 10px auto !important; /* Reduced margin */
|
| 526 |
+
border-radius: 0 !important; /* Optional: Remove border radius for full-width look */
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
.menu-button {
|
| 530 |
+
width: 98% !important; /* Almost full width */
|
| 531 |
+
font-size: 14px !important;
|
| 532 |
+
min-height: 50px !important;
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
.logo-dashboard {
|
| 536 |
+
max-height: 40px;
|
| 537 |
+
top: 10px;
|
| 538 |
+
left: 10px;
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
h1 {
|
| 542 |
+
font-size: 1.5rem !important;
|
| 543 |
+
margin-top: 10px !important;
|
| 544 |
+
margin-bottom: 15px !important;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
h2 {
|
| 548 |
+
font-size: 1.3rem !important;
|
| 549 |
+
margin-top: 8px !important;
|
| 550 |
+
margin-bottom: 12px !important;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
h3 {
|
| 554 |
+
font-size: 1.1rem !important;
|
| 555 |
+
margin-top: 6px !important;
|
| 556 |
+
margin-bottom: 10px !important;
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
/* Make chat interface take full width */
|
| 560 |
+
.chat-container, .chatbot-container {
|
| 561 |
+
width: 100% !important;
|
| 562 |
+
margin: 0 !important;
|
| 563 |
+
padding: 0 5px !important;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
/* Adjust Gradio containers */
|
| 567 |
+
.gradio-container {
|
| 568 |
+
padding: 0 !important;
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
/* Adjust logout button for mobile */
|
| 572 |
+
.logout-btn {
|
| 573 |
+
top: 5px;
|
| 574 |
+
right: 5px;
|
| 575 |
+
padding: 6px 12px !important;
|
| 576 |
+
font-size: 12px !important;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
.session-info {
|
| 580 |
+
top: 40px;
|
| 581 |
+
right: 5px;
|
| 582 |
+
font-size: 10px;
|
| 583 |
+
}
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
@media screen and (max-width: 480px) {
|
| 587 |
+
.dashboard-card {
|
| 588 |
+
padding: 10px 5px !important; /* Further reduced padding */
|
| 589 |
+
margin: 0 auto !important; /* Remove margin completely */
|
| 590 |
+
border-radius: 0 !important; /* Remove border radius */
|
| 591 |
+
width: 100% !important; /* Full width */
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.login-form-container {
|
| 595 |
+
padding: 10px 5px !important; /* Further reduced padding */
|
| 596 |
+
margin: 0 auto !important; /* Remove margin completely */
|
| 597 |
+
border-radius: 0 !important; /* Remove border radius */
|
| 598 |
+
width: 100% !important; /* Full width */
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.menu-button {
|
| 602 |
+
font-size: 13px !important;
|
| 603 |
+
min-height: 45px !important;
|
| 604 |
+
margin: 5px auto !important;
|
| 605 |
+
width: 100% !important; /* Full width */
|
| 606 |
+
border-radius: 6px !important; /* Slightly reduced border radius */
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.logo-dashboard {
|
| 610 |
+
max-height: 35px;
|
| 611 |
+
top: 5px;
|
| 612 |
+
left: 5px;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
/* Adjust spacing in forms for mobile */
|
| 616 |
+
input, select, textarea {
|
| 617 |
+
margin-bottom: 8px !important;
|
| 618 |
+
padding: 8px !important;
|
| 619 |
+
width: 100% !important;
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
/* Reduce padding and margins for all content */
|
| 623 |
+
.floating-card, .gr-box, .gr-panel {
|
| 624 |
+
padding: 10px 5px !important;
|
| 625 |
+
margin: 5px 0 !important;
|
| 626 |
+
width: 100% !important;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
/* Full width for all UI components */
|
| 630 |
+
.gr-form, .gr-input, .gr-button, .gr-box {
|
| 631 |
+
width: 100% !important;
|
| 632 |
+
margin-left: 0 !important;
|
| 633 |
+
margin-right: 0 !important;
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
/* Additional responsive handling for extreme screen sizes */
|
| 638 |
+
@media screen and (max-width: 320px) {
|
| 639 |
+
/* For very small screens like older iPhones */
|
| 640 |
+
.menu-button {
|
| 641 |
+
font-size: 11px !important;
|
| 642 |
+
min-height: 40px !important;
|
| 643 |
+
padding: 8px 5px !important;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
.dashboard-card {
|
| 647 |
+
padding: 5px 3px !important;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
h1 {
|
| 651 |
+
font-size: 1.3rem !important;
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
h2 {
|
| 655 |
+
font-size: 1.1rem !important;
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
h3 {
|
| 659 |
+
font-size: 1rem !important;
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
@media screen and (min-width: 1600px) {
|
| 664 |
+
/* For very large screens */
|
| 665 |
+
.dashboard-card {
|
| 666 |
+
max-width: 1000px !important;
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
.menu-button {
|
| 670 |
+
max-width: 400px !important;
|
| 671 |
+
}
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
/* For landscape orientation on mobile */
|
| 675 |
+
@media screen and (max-height: 500px) and (orientation: landscape) {
|
| 676 |
+
.dashboard-menu {
|
| 677 |
+
min-height: auto;
|
| 678 |
+
padding: 5px 0;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.dashboard-card {
|
| 682 |
+
min-height: 300px !important;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
.menu-button {
|
| 686 |
+
min-height: 40px !important;
|
| 687 |
+
margin: 4px auto !important;
|
| 688 |
+
}
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
/* Fixed positions for mobile views */
|
| 692 |
+
.fixed-top {
|
| 693 |
+
position: fixed;
|
| 694 |
+
top: 0;
|
| 695 |
+
left: 0;
|
| 696 |
+
right: 0;
|
| 697 |
+
z-index: 1000;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
.fixed-bottom {
|
| 701 |
+
position: fixed;
|
| 702 |
+
bottom: 0;
|
| 703 |
+
left: 0;
|
| 704 |
+
right: 0;
|
| 705 |
+
z-index: 1000;
|
| 706 |
+
}
|
| 707 |
+
"""
|
| 708 |
+
return css
|
| 709 |
+
|
| 710 |
+
# ----------------- Notification Helpers -----------------
|
| 711 |
+
|
| 712 |
+
def show_success(message: str, duration: float = 10, visible: bool = True, title: str = "Success") -> None:
|
| 713 |
+
"""Display a success notification"""
|
| 714 |
+
styled_message = (
|
| 715 |
+
f'<span style="color: #2e7d32; background-color: #c8e6c9; padding: 10px; '
|
| 716 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 717 |
+
)
|
| 718 |
+
gr.Success(styled_message, duration=duration, visible=visible, title=title)
|
| 719 |
+
# Add a small delay to help the modal appear
|
| 720 |
+
time.sleep(0.1)
|
| 721 |
+
|
| 722 |
+
def show_error(message: str, duration: float = 13, visible: bool = True, title: str = "Error") -> None:
|
| 723 |
+
"""Display an error notification"""
|
| 724 |
+
styled_message = (
|
| 725 |
+
f'<span style="color: #c62828; background-color: #ffcdd2; padding: 10px; '
|
| 726 |
+
f'border-radius: 4px; display: block;">{message}</span>'
|
| 727 |
+
)
|
| 728 |
+
gr.Error(styled_message, duration=duration, visible=visible, title=title)
|
| 729 |
+
# Add a small delay to help the modal appear
|
| 730 |
+
time.sleep(0.1)
|
| 731 |
+
|
| 732 |
+
# ----------------- Login Form UI -----------------
|
| 733 |
+
|
| 734 |
+
def login_form_ui():
|
| 735 |
+
"""Create and return the login form interface"""
|
| 736 |
+
with gr.Blocks() as login_form:
|
| 737 |
+
# USIU logo
|
| 738 |
+
gr.HTML('''
|
| 739 |
+
<div class="logo-container">
|
| 740 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 741 |
+
</div>
|
| 742 |
+
''')
|
| 743 |
+
|
| 744 |
+
gr.Markdown("## Please Login")
|
| 745 |
+
user_id_input = gr.Textbox(
|
| 746 |
+
placeholder="Enter your User ID",
|
| 747 |
+
label="User ID",
|
| 748 |
+
type="email",
|
| 749 |
+
autofocus=True
|
| 750 |
+
)
|
| 751 |
+
# Error message for invalid user ID
|
| 752 |
+
user_id_error = gr.Markdown("", visible=True)
|
| 753 |
+
|
| 754 |
+
password_input = gr.Textbox(
|
| 755 |
+
placeholder="Enter your Password",
|
| 756 |
+
label="Password",
|
| 757 |
+
type="password"
|
| 758 |
+
)
|
| 759 |
+
# Error message for invalid password
|
| 760 |
+
password_error = gr.Markdown("", visible=True)
|
| 761 |
+
|
| 762 |
+
login_btn = gr.Button("Login")
|
| 763 |
+
# Error message for invalid credentials
|
| 764 |
+
login_msg = gr.Markdown("")
|
| 765 |
+
proceed_btn = gr.Button("Proceed", visible=False)
|
| 766 |
+
back_btn = gr.Button("Back to Dashboard")
|
| 767 |
+
|
| 768 |
+
# Hidden field to store session ID
|
| 769 |
+
session_id = gr.Textbox(visible=False)
|
| 770 |
+
# Return the login form along with the necessary components in order.
|
| 771 |
+
return login_form, user_id_input, user_id_error, password_input, password_error, login_btn, login_msg, proceed_btn, back_btn, session_id
|
| 772 |
+
|
| 773 |
+
# ----------------- Authentication Logic -----------------
|
| 774 |
+
|
| 775 |
+
def handle_login(user_id, password):
|
| 776 |
+
"""
|
| 777 |
+
Validates login fields and the User ID (which must end with '@usiu.ac.ke').
|
| 778 |
+
Returns appropriate error messages and updates UI components.
|
| 779 |
+
"""
|
| 780 |
+
try:
|
| 781 |
+
user_id = user_id.strip() if user_id else ""
|
| 782 |
+
password = password.strip() if password else ""
|
| 783 |
+
|
| 784 |
+
# Both fields empty:
|
| 785 |
+
if user_id == "" and password == "":
|
| 786 |
+
show_error("Please enter your User ID and Password.")
|
| 787 |
+
return (
|
| 788 |
+
"<span style='color:#c62828;'>Please enter your User ID and Password.</span>",
|
| 789 |
+
"", # No field-specific error for User ID.
|
| 790 |
+
"", # No field-specific error for Password.
|
| 791 |
+
gr.update(visible=False),
|
| 792 |
+
gr.update(visible=True),
|
| 793 |
+
"" # No session ID since login failed
|
| 794 |
+
)
|
| 795 |
+
# User ID is empty:
|
| 796 |
+
elif user_id == "":
|
| 797 |
+
show_error("User ID is required.")
|
| 798 |
+
return (
|
| 799 |
+
"",
|
| 800 |
+
"<span style='color:#c62828;'>User ID is required.</span>",
|
| 801 |
+
"",
|
| 802 |
+
gr.update(visible=False),
|
| 803 |
+
gr.update(visible=True),
|
| 804 |
+
"" # No session ID since login failed
|
| 805 |
+
)
|
| 806 |
+
# Password is empty:
|
| 807 |
+
elif password == "":
|
| 808 |
+
show_error("Password is required.")
|
| 809 |
+
return (
|
| 810 |
+
"",
|
| 811 |
+
"",
|
| 812 |
+
"<span style='color:#c62828;'>Password is required.</span>",
|
| 813 |
+
gr.update(visible=False),
|
| 814 |
+
gr.update(visible=True),
|
| 815 |
+
"" # No session ID since login failed
|
| 816 |
+
)
|
| 817 |
+
else:
|
| 818 |
+
# Validate the User ID: must end with "@usiu.ac.ke"
|
| 819 |
+
email_regex = r'^[\w\.-]+@usiu\.ac\.ke$'
|
| 820 |
+
if not re.match(email_regex, user_id):
|
| 821 |
+
show_error("User ID must be a valid \"@usiu.ac.ke\" email address.")
|
| 822 |
+
return (
|
| 823 |
+
"",
|
| 824 |
+
"<span style='color:#c62828;'>User ID must be a valid @usiu.ac.ke email address.</span>",
|
| 825 |
+
"",
|
| 826 |
+
gr.update(visible=False),
|
| 827 |
+
gr.update(visible=True),
|
| 828 |
+
"" # No session ID since login failed
|
| 829 |
+
)
|
| 830 |
+
# Attempt authentication.
|
| 831 |
+
if authenticate(user_id, password):
|
| 832 |
+
# Create a session for the authenticated user
|
| 833 |
+
session_id = create_session(user_id)
|
| 834 |
+
|
| 835 |
+
show_success("Login successful! Click 'Proceed' to continue!", duration=10)
|
| 836 |
+
return (
|
| 837 |
+
"",
|
| 838 |
+
"",
|
| 839 |
+
"",
|
| 840 |
+
gr.update(visible=True), # Show Proceed button.
|
| 841 |
+
gr.update(visible=False), # Hide Login button.
|
| 842 |
+
session_id # Return the session ID for the new session
|
| 843 |
+
)
|
| 844 |
+
else:
|
| 845 |
+
show_error("Invalid credentials. Please try again.")
|
| 846 |
+
return (
|
| 847 |
+
"<span style='color:#c62828;'>Invalid credentials. Please try again.</span>",
|
| 848 |
+
"",
|
| 849 |
+
"",
|
| 850 |
+
gr.update(visible=False),
|
| 851 |
+
gr.update(visible=True),
|
| 852 |
+
"" # No session ID since login failed
|
| 853 |
+
)
|
| 854 |
+
except Exception as e:
|
| 855 |
+
show_error(f"System error: {str(e)}. Please ensure required modules are imported.")
|
| 856 |
+
return (
|
| 857 |
+
"<span style='color:#c62828;'>System error encountered.</span>",
|
| 858 |
+
"",
|
| 859 |
+
"",
|
| 860 |
+
gr.update(visible=False),
|
| 861 |
+
gr.update(visible=True),
|
| 862 |
+
"" # No session ID since login failed
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
def handle_logout(session_id):
|
| 866 |
+
"""End the user session and redirect to dashboard"""
|
| 867 |
+
end_session(session_id)
|
| 868 |
+
return [
|
| 869 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 870 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 871 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 872 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation")
|
| 873 |
+
]
|
| 874 |
+
|
| 875 |
+
# ----------------- Chat Interfaces -----------------
|
| 876 |
+
|
| 877 |
+
def general_chat_ui(system_prompt: str, model: str) -> gr.ChatInterface:
|
| 878 |
+
"""Creates a Gradio ChatInterface for general academic chat with RAG."""
|
| 879 |
+
|
| 880 |
+
def general_chat(message: str, chat_history, file) -> str:
|
| 881 |
+
# Ensure chat_history is a list.
|
| 882 |
+
if chat_history is None:
|
| 883 |
+
chat_history = []
|
| 884 |
+
|
| 885 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 886 |
+
if not message.strip() and file is not None:
|
| 887 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 888 |
+
|
| 889 |
+
# If this is a new conversation, insert a conversation header.
|
| 890 |
+
if not chat_history:
|
| 891 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 892 |
+
conv_header = {
|
| 893 |
+
"role": "header",
|
| 894 |
+
"content": (
|
| 895 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 896 |
+
f"Conversation started on {date_str}"
|
| 897 |
+
f"</div>"
|
| 898 |
+
),
|
| 899 |
+
"files": []
|
| 900 |
+
}
|
| 901 |
+
chat_history.insert(0, conv_header) # Insert at the beginning
|
| 902 |
+
|
| 903 |
+
# Start with the initial system prompt.
|
| 904 |
+
messages = [
|
| 905 |
+
{"role": "system", "content": system_prompt, "files": []},
|
| 906 |
+
]
|
| 907 |
+
|
| 908 |
+
# Add chat history to messages (all messages, including header).
|
| 909 |
+
if chat_history:
|
| 910 |
+
# If the first element is a tuple/list with two elements, assume pair format.
|
| 911 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 912 |
+
for human, assistant in chat_history:
|
| 913 |
+
messages.append({"role": "user", "content": human, "files": []})
|
| 914 |
+
messages.append({"role": "assistant", "content": assistant, "files": []})
|
| 915 |
+
# Otherwise, if they are dicts, include all messages.
|
| 916 |
+
elif isinstance(chat_history[0], dict):
|
| 917 |
+
for msg in chat_history:
|
| 918 |
+
if msg is None:
|
| 919 |
+
continue
|
| 920 |
+
# Skip header messages when sending to API.
|
| 921 |
+
if msg.get("role") == "header":
|
| 922 |
+
continue
|
| 923 |
+
# Ensure each message has a files field.
|
| 924 |
+
msg_to_send = dict(msg)
|
| 925 |
+
if "files" not in msg_to_send or msg_to_send["files"] is None:
|
| 926 |
+
msg_to_send["files"] = []
|
| 927 |
+
messages.append(msg_to_send)
|
| 928 |
+
|
| 929 |
+
# If a file is uploaded, add its content to the messages.
|
| 930 |
+
if file is not None:
|
| 931 |
+
try:
|
| 932 |
+
file_content = FileProcessor.read_file(file)
|
| 933 |
+
messages.append({
|
| 934 |
+
"role": "user",
|
| 935 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}"
|
| 936 |
+
})
|
| 937 |
+
except Exception as e:
|
| 938 |
+
messages.append({
|
| 939 |
+
"role": "user",
|
| 940 |
+
"content": f"Error reading file: {str(e)}",
|
| 941 |
+
"files": []
|
| 942 |
+
})
|
| 943 |
+
|
| 944 |
+
# RAG: Retrieve additional context using GLOBAL_RETRIEVER if available.
|
| 945 |
+
# if GLOBAL_RETRIEVER is not None:
|
| 946 |
+
# try:
|
| 947 |
+
# docs = GLOBAL_RETRIEVER.get_relevant_documents(message)
|
| 948 |
+
# if docs:
|
| 949 |
+
# context_text = "\n\n".join([doc.page_content for doc in docs])
|
| 950 |
+
# # Insert the retrieved context as an additional system message.
|
| 951 |
+
# messages.insert(1, {
|
| 952 |
+
# "role": "system",
|
| 953 |
+
# "content": f"Additional Context:\n{context_text}",
|
| 954 |
+
# "files": []
|
| 955 |
+
# })
|
| 956 |
+
# except Exception as e:
|
| 957 |
+
# print("RAG retrieval failed:", e)
|
| 958 |
+
|
| 959 |
+
# First, ensure no None values are present in the messages list.
|
| 960 |
+
messages = [msg for msg in messages if msg is not None]
|
| 961 |
+
|
| 962 |
+
# RAG Retrieval and Summarization Implementation:
|
| 963 |
+
if GLOBAL_RETRIEVER is not None:
|
| 964 |
+
try:
|
| 965 |
+
# Retrieve relevant documents using the global retriever.
|
| 966 |
+
docs = GLOBAL_RETRIEVER.get_relevant_documents(message)
|
| 967 |
+
if docs:
|
| 968 |
+
summarized_texts = []
|
| 969 |
+
for doc in docs:
|
| 970 |
+
# Summarize each document's page content.
|
| 971 |
+
# Use the summarize_context() function; if it returns None, fallback to an empty string.
|
| 972 |
+
summary = summarize_context(doc.page_content, GPT4O_MODEL) or ""
|
| 973 |
+
# Only add non-empty summaries.
|
| 974 |
+
if summary.strip():
|
| 975 |
+
summarized_texts.append(summary)
|
| 976 |
+
# If we have one or more summaries, combine them and insert as an additional context.
|
| 977 |
+
if summarized_texts:
|
| 978 |
+
context_text = "\n\n".join(summarized_texts)
|
| 979 |
+
messages.insert(1, {
|
| 980 |
+
"role": "system",
|
| 981 |
+
"content": f"Additional Context Summaries:\n{context_text}",
|
| 982 |
+
"files": [] # Always provide an empty list for the "files" key.
|
| 983 |
+
})
|
| 984 |
+
except Exception as e:
|
| 985 |
+
print("RAG retrieval failed:", e)
|
| 986 |
+
|
| 987 |
+
|
| 988 |
+
chat_history.append({"role": "user", "content": message, "files": []})
|
| 989 |
+
messages.append({"role": "user", "content": message, "files": []})
|
| 990 |
+
|
| 991 |
+
# Call OpenAI's ChatCompletion with streaming enabled.
|
| 992 |
+
completion = openai.ChatCompletion.create(
|
| 993 |
+
model=model,
|
| 994 |
+
messages=messages,
|
| 995 |
+
max_tokens=1_000,
|
| 996 |
+
temperature=0.7,
|
| 997 |
+
stream=True,
|
| 998 |
+
)
|
| 999 |
+
|
| 1000 |
+
response = ""
|
| 1001 |
+
for chunk in completion:
|
| 1002 |
+
token = chunk.choices[0].delta.get("content", "")
|
| 1003 |
+
response += token
|
| 1004 |
+
yield response
|
| 1005 |
+
|
| 1006 |
+
iface = gr.ChatInterface(
|
| 1007 |
+
fn=general_chat,
|
| 1008 |
+
additional_inputs=[gr.File(label="Upload a file (optional)", elem_classes="file-upload")],
|
| 1009 |
+
additional_inputs_accordion=gr.Accordion("File Upload", open=False, elem_classes="additional-inputs-accordion"),
|
| 1010 |
+
editable=True,
|
| 1011 |
+
save_history=True,
|
| 1012 |
+
title="General Enquiries",
|
| 1013 |
+
description="<div style='display:flex; justify-content:center;'>Hi there! I'm your AI assistant, here to help you with queries related to USIU. Feel free to ask any questions or seek assistance.</div>",
|
| 1014 |
+
type="messages",
|
| 1015 |
+
cache_mode="eager"
|
| 1016 |
+
)
|
| 1017 |
+
return iface.queue()
|
| 1018 |
+
|
| 1019 |
+
def study_support_ui(system_prompt: str, model: str) -> gr.ChatInterface:
|
| 1020 |
+
"""Creates a Gradio ChatInterface for study support with Claude."""
|
| 1021 |
+
|
| 1022 |
+
def study_support_chat(message: str, chat_history, file, session_id) -> str:
|
| 1023 |
+
# Update session activity timestamp
|
| 1024 |
+
if session_id:
|
| 1025 |
+
update_session_activity(session_id)
|
| 1026 |
+
|
| 1027 |
+
# Check if session is still valid
|
| 1028 |
+
session = get_session(session_id)
|
| 1029 |
+
if not session or not session.get("logged_in", False):
|
| 1030 |
+
return "Your session has expired. Please log in again."
|
| 1031 |
+
|
| 1032 |
+
# Ensure chat_history is a list.
|
| 1033 |
+
if chat_history is None:
|
| 1034 |
+
chat_history = []
|
| 1035 |
+
|
| 1036 |
+
# If a file is uploaded and the message is empty, replace the empty message with the file name.
|
| 1037 |
+
if not message.strip() and file is not None:
|
| 1038 |
+
message = f"Uploaded file: {os.path.basename(file.name)}"
|
| 1039 |
+
|
| 1040 |
+
# If this is a new conversation, insert a conversation header.
|
| 1041 |
+
if not chat_history:
|
| 1042 |
+
date_str = time.strftime("%Y-%m-%d %H:%M", time.localtime())
|
| 1043 |
+
conv_header = {
|
| 1044 |
+
"role": "header",
|
| 1045 |
+
"content": (
|
| 1046 |
+
f"<div style='text-align: center; font-weight: bold; font-size: 16px; color: #000;'>"
|
| 1047 |
+
f"Conversation started on {date_str}"
|
| 1048 |
+
f"</div>"
|
| 1049 |
+
)
|
| 1050 |
+
}
|
| 1051 |
+
chat_history.insert(0, conv_header)
|
| 1052 |
+
|
| 1053 |
+
# Build the messages list that will be sent to the API.
|
| 1054 |
+
messages = []
|
| 1055 |
+
if chat_history:
|
| 1056 |
+
# If chat_history items are tuples/lists with two elements, unpack them.
|
| 1057 |
+
if isinstance(chat_history[0], (tuple, list)) and len(chat_history[0]) == 2:
|
| 1058 |
+
for human, assistant in chat_history:
|
| 1059 |
+
messages.append({"role": "user", "content": human})
|
| 1060 |
+
messages.append({"role": "assistant", "content": assistant})
|
| 1061 |
+
# Otherwise, if they are dicts, include only "role" and "content" for non-system messages.
|
| 1062 |
+
elif isinstance(chat_history[0], dict):
|
| 1063 |
+
for msg in chat_history:
|
| 1064 |
+
if msg is None:
|
| 1065 |
+
continue
|
| 1066 |
+
if msg.get("role") == "header":
|
| 1067 |
+
continue # Skip header messages.
|
| 1068 |
+
msg_to_send = {"role": msg.get("role"), "content": msg.get("content")}
|
| 1069 |
+
messages.append(msg_to_send)
|
| 1070 |
+
|
| 1071 |
+
# Add the current message.
|
| 1072 |
+
messages.append({"role": "user", "content": message})
|
| 1073 |
+
|
| 1074 |
+
# If a file is uploaded, add its content.
|
| 1075 |
+
if file is not None:
|
| 1076 |
+
try:
|
| 1077 |
+
file_content = FileProcessor.read_file(file)
|
| 1078 |
+
messages.append({
|
| 1079 |
+
"role": "user",
|
| 1080 |
+
"content": f"Here's the content of the uploaded file:\n\n{file_content}"
|
| 1081 |
+
})
|
| 1082 |
+
except Exception as e:
|
| 1083 |
+
messages.append({
|
| 1084 |
+
"role": "user",
|
| 1085 |
+
"content": f"Error reading file: {str(e)}"
|
| 1086 |
+
})
|
| 1087 |
+
|
| 1088 |
+
# Implement a retry mechanism to handle overloaded errors.
|
| 1089 |
+
max_retries = 7
|
| 1090 |
+
delay = 3 # seconds
|
| 1091 |
+
attempt = 0
|
| 1092 |
+
while attempt < max_retries:
|
| 1093 |
+
try:
|
| 1094 |
+
# Use Claude's API for the study support interface
|
| 1095 |
+
result = claude.messages.stream(
|
| 1096 |
+
model=model,
|
| 1097 |
+
max_tokens=63_500,
|
| 1098 |
+
system=system_prompt,
|
| 1099 |
+
messages=messages,
|
| 1100 |
+
extra_query={"extended_thinking": True}
|
| 1101 |
+
)
|
| 1102 |
+
response = ""
|
| 1103 |
+
with result as stream:
|
| 1104 |
+
for text in stream.text_stream:
|
| 1105 |
+
response += text
|
| 1106 |
+
yield response # Yield incremental responses.
|
| 1107 |
+
|
| 1108 |
+
break # Exit the retry loop if successful.
|
| 1109 |
+
except Exception as e:
|
| 1110 |
+
# Check if error indicates the API is overloaded.
|
| 1111 |
+
if "overloaded" in str(e).lower():
|
| 1112 |
+
attempt += 1
|
| 1113 |
+
yield f"Service unavailable. Retrying in {delay} seconds... (attempt {attempt}/{max_retries})\n"
|
| 1114 |
+
time.sleep(delay)
|
| 1115 |
+
else:
|
| 1116 |
+
print(f"An error occurred: {str(e)}")
|
| 1117 |
+
break
|
| 1118 |
+
else:
|
| 1119 |
+
yield "The service is currently unavailable. Please try again later."
|
| 1120 |
+
|
| 1121 |
+
iface = gr.ChatInterface(
|
| 1122 |
+
fn=study_support_chat,
|
| 1123 |
+
additional_inputs=[
|
| 1124 |
+
gr.File(label="Upload a file (optional)", elem_classes="file-upload"),
|
| 1125 |
+
gr.Textbox(visible=False) # Hidden input for session_id
|
| 1126 |
+
],
|
| 1127 |
+
additional_inputs_accordion=gr.Accordion("File Upload", open=False, elem_classes="additional-inputs-accordion"),
|
| 1128 |
+
editable=True,
|
| 1129 |
+
save_history=True,
|
| 1130 |
+
title="Study Bud",
|
| 1131 |
+
description="<div style='display:flex; justify-content:center;'>Hi there! I'm your AI study bud. Feel free to ask questions, share resources, and get personalized support.</div>",
|
| 1132 |
+
type="messages",
|
| 1133 |
+
cache_mode="eager"
|
| 1134 |
+
)
|
| 1135 |
+
return iface.queue()
|
| 1136 |
+
|
| 1137 |
+
# ---------- Dashboard UI ----------
|
| 1138 |
+
|
| 1139 |
+
def dashboard_ui(general_chat_prompt: str, general_model: str,
|
| 1140 |
+
study_prompt: str, study_model: str, retriever=None):
|
| 1141 |
+
"""
|
| 1142 |
+
Creates a dashboard with professional styling, two main menus (General Chat and Study Support),
|
| 1143 |
+
and smooth transitions between views. For Study Support, a login form (with authentication)
|
| 1144 |
+
is shown before revealing the chat interface.
|
| 1145 |
+
|
| 1146 |
+
Args:
|
| 1147 |
+
general_chat_prompt: System prompt for general enquiries
|
| 1148 |
+
general_model: Model ID for general chat (e.g., GPT4O_MODEL)
|
| 1149 |
+
study_prompt: System prompt for study support
|
| 1150 |
+
study_model: Model ID for study support (e.g., CLAUDE_MODEL)
|
| 1151 |
+
retriever: The retriever to use for RAG in general chat
|
| 1152 |
+
"""
|
| 1153 |
+
global GLOBAL_RETRIEVER
|
| 1154 |
+
GLOBAL_RETRIEVER = retriever
|
| 1155 |
+
|
| 1156 |
+
# Retrieve the existing chat interfaces.
|
| 1157 |
+
general_chat_component = general_chat_ui(general_chat_prompt, general_model)
|
| 1158 |
+
study_support_component = study_support_ui(study_prompt, study_model)
|
| 1159 |
+
# Retrieve the login form.
|
| 1160 |
+
(login_form_component, user_id_input, user_id_error, password_input, password_error,
|
| 1161 |
+
login_btn, login_msg, proceed_btn, login_back_btn, session_id) = login_form_ui()
|
| 1162 |
+
|
| 1163 |
+
with gr.Blocks(css=custom_css()) as dashboard:
|
| 1164 |
+
# Inject JavaScript for animations, loading effects, and inactivity timer
|
| 1165 |
+
gr.HTML('''
|
| 1166 |
+
<script>
|
| 1167 |
+
document.addEventListener("DOMContentLoaded", function() {
|
| 1168 |
+
// Create and style the spinner element
|
| 1169 |
+
let spinner = document.createElement("div");
|
| 1170 |
+
spinner.className = "loader";
|
| 1171 |
+
spinner.innerHTML = '<div class="cube"></div>';
|
| 1172 |
+
document.body.appendChild(spinner);
|
| 1173 |
+
|
| 1174 |
+
// Create inactivity warning modal
|
| 1175 |
+
let warningModal = document.createElement("div");
|
| 1176 |
+
warningModal.className = "inactive-warning";
|
| 1177 |
+
warningModal.innerHTML = `
|
| 1178 |
+
<h3>Session Timeout Warning</h3>
|
| 1179 |
+
<p>Your session will expire due to inactivity in <span id="countdown">60</span> seconds.</p>
|
| 1180 |
+
<p>Click the button below to continue your session.</p>
|
| 1181 |
+
<button id="stay-active">Stay Active</button>
|
| 1182 |
+
`;
|
| 1183 |
+
document.body.appendChild(warningModal);
|
| 1184 |
+
|
| 1185 |
+
// Create overlay for modal
|
| 1186 |
+
let overlay = document.createElement("div");
|
| 1187 |
+
overlay.className = "overlay";
|
| 1188 |
+
document.body.appendChild(overlay);
|
| 1189 |
+
|
| 1190 |
+
// Variables for inactivity tracking
|
| 1191 |
+
let inactivityTimer;
|
| 1192 |
+
let lastActivity = Date.now();
|
| 1193 |
+
let countdownTimer;
|
| 1194 |
+
let countdownTime = 60; // Countdown from 60 seconds
|
| 1195 |
+
|
| 1196 |
+
// Function to reset inactivity timer
|
| 1197 |
+
function resetInactivityTimer() {
|
| 1198 |
+
lastActivity = Date.now();
|
| 1199 |
+
|
| 1200 |
+
// Clear existing timer
|
| 1201 |
+
if (inactivityTimer) {
|
| 1202 |
+
clearTimeout(inactivityTimer);
|
| 1203 |
+
}
|
| 1204 |
+
|
| 1205 |
+
// Set new timer - show warning after 14 minutes of inactivity
|
| 1206 |
+
inactivityTimer = setTimeout(function() {
|
| 1207 |
+
showInactivityWarning();
|
| 1208 |
+
}, 14 * 60 * 1000); // 14 minutes in milliseconds
|
| 1209 |
+
}
|
| 1210 |
+
|
| 1211 |
+
// Function to show inactivity warning
|
| 1212 |
+
function showInactivityWarning() {
|
| 1213 |
+
overlay.style.display = "block";
|
| 1214 |
+
warningModal.style.display = "block";
|
| 1215 |
+
|
| 1216 |
+
// Start countdown
|
| 1217 |
+
countdownTime = 60;
|
| 1218 |
+
document.getElementById("countdown").textContent = countdownTime;
|
| 1219 |
+
|
| 1220 |
+
countdownTimer = setInterval(function() {
|
| 1221 |
+
countdownTime--;
|
| 1222 |
+
document.getElementById("countdown").textContent = countdownTime;
|
| 1223 |
+
|
| 1224 |
+
if (countdownTime <= 0) {
|
| 1225 |
+
clearInterval(countdownTimer);
|
| 1226 |
+
// Find and click logout button to end session
|
| 1227 |
+
const logoutButton = document.querySelector('.logout-btn');
|
| 1228 |
+
if (logoutButton) {
|
| 1229 |
+
logoutButton.click();
|
| 1230 |
+
}
|
| 1231 |
+
hideInactivityWarning();
|
| 1232 |
+
}
|
| 1233 |
+
}, 1000);
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
// Function to hide inactivity warning
|
| 1237 |
+
function hideInactivityWarning() {
|
| 1238 |
+
overlay.style.display = "none";
|
| 1239 |
+
warningModal.style.display = "none";
|
| 1240 |
+
if (countdownTimer) {
|
| 1241 |
+
clearInterval(countdownTimer);
|
| 1242 |
+
}
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
// Event listener for "Stay Active" button
|
| 1246 |
+
document.getElementById("stay-active").addEventListener("click", function() {
|
| 1247 |
+
hideInactivityWarning();
|
| 1248 |
+
resetInactivityTimer();
|
| 1249 |
+
});
|
| 1250 |
+
|
| 1251 |
+
// Track user activity
|
| 1252 |
+
["mousemove", "keydown", "click", "scroll", "touchstart"].forEach(function(event) {
|
| 1253 |
+
document.addEventListener(event, function() {
|
| 1254 |
+
// Only reset timer if we're not showing the warning
|
| 1255 |
+
if (warningModal.style.display !== "block") {
|
| 1256 |
+
resetInactivityTimer();
|
| 1257 |
+
}
|
| 1258 |
+
});
|
| 1259 |
+
});
|
| 1260 |
+
|
| 1261 |
+
// Initial setup of inactivity timer
|
| 1262 |
+
resetInactivityTimer();
|
| 1263 |
+
|
| 1264 |
+
// Show the spinner when needed
|
| 1265 |
+
spinner.style.display = "none"; // Initially hidden
|
| 1266 |
+
|
| 1267 |
+
// Function to show spinner
|
| 1268 |
+
function showSpinner() {
|
| 1269 |
+
spinner.style.display = "block";
|
| 1270 |
+
}
|
| 1271 |
+
|
| 1272 |
+
// Function to hide spinner
|
| 1273 |
+
function hideSpinner() {
|
| 1274 |
+
spinner.style.display = "none";
|
| 1275 |
+
}
|
| 1276 |
+
|
| 1277 |
+
// Function to handle animation transitions
|
| 1278 |
+
function setupAnimations() {
|
| 1279 |
+
// Get all containers that might be animated
|
| 1280 |
+
const containers = document.querySelectorAll('.container, .chat-container');
|
| 1281 |
+
|
| 1282 |
+
// Add transition end listener to each container
|
| 1283 |
+
containers.forEach(container => {
|
| 1284 |
+
container.addEventListener('animationend', function(e) {
|
| 1285 |
+
// When animation completes, remove the animation class
|
| 1286 |
+
if (e.animationName.includes('fadeIn')) {
|
| 1287 |
+
this.classList.remove('animate-fadeInUp', 'animate-fadeInRight', 'animate-fadeInLeft');
|
| 1288 |
+
}
|
| 1289 |
+
});
|
| 1290 |
+
});
|
| 1291 |
+
}
|
| 1292 |
+
|
| 1293 |
+
// Setup animations after a short delay to ensure DOM is ready
|
| 1294 |
+
setTimeout(setupAnimations, 2000);
|
| 1295 |
+
|
| 1296 |
+
// Add event listeners to all buttons that should trigger the spinner
|
| 1297 |
+
setTimeout(function(){
|
| 1298 |
+
// Login and Proceed buttons with spinner
|
| 1299 |
+
const loginBtn = document.querySelector('button:contains("Login")');
|
| 1300 |
+
const proceedBtn = document.querySelector('button:contains("Proceed")');
|
| 1301 |
+
|
| 1302 |
+
if (loginBtn) {
|
| 1303 |
+
loginBtn.addEventListener("click", function() {
|
| 1304 |
+
showSpinner(); // Show spinner when login button is clicked
|
| 1305 |
+
|
| 1306 |
+
if (!this.classList.contains("loading")) {
|
| 1307 |
+
const originalText = this.textContent;
|
| 1308 |
+
this.setAttribute("data-original-text", originalText);
|
| 1309 |
+
this.textContent = "";
|
| 1310 |
+
this.classList.add("loading");
|
| 1311 |
+
|
| 1312 |
+
// Create spinner inside button
|
| 1313 |
+
const btnSpinner = document.createElement("div");
|
| 1314 |
+
btnSpinner.className = "btn-spinner";
|
| 1315 |
+
btnSpinner.style.width = "20px";
|
| 1316 |
+
btnSpinner.style.height = "20px";
|
| 1317 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 1318 |
+
btnSpinner.style.borderRadius = "50%";
|
| 1319 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 1320 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 1321 |
+
btnSpinner.style.display = "inline-block";
|
| 1322 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 1323 |
+
this.appendChild(btnSpinner);
|
| 1324 |
+
|
| 1325 |
+
// Reset button after 3 seconds if no response
|
| 1326 |
+
setTimeout(() => {
|
| 1327 |
+
if (this.classList.contains("loading")) {
|
| 1328 |
+
this.classList.remove("loading");
|
| 1329 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 1330 |
+
}
|
| 1331 |
+
hideSpinner(); // Hide spinner after timeout
|
| 1332 |
+
}, 3000);
|
| 1333 |
+
}
|
| 1334 |
+
});
|
| 1335 |
+
}
|
| 1336 |
+
|
| 1337 |
+
if (proceedBtn) {
|
| 1338 |
+
proceedBtn.addEventListener("click", function() {
|
| 1339 |
+
showSpinner(); // Show spinner when proceed button is clicked
|
| 1340 |
+
|
| 1341 |
+
if (!this.classList.contains("loading")) {
|
| 1342 |
+
const originalText = this.textContent;
|
| 1343 |
+
this.setAttribute("data-original-text", originalText);
|
| 1344 |
+
this.textContent = "";
|
| 1345 |
+
this.classList.add("loading");
|
| 1346 |
+
|
| 1347 |
+
// Create spinner inside button
|
| 1348 |
+
const btnSpinner = document.createElement("div");
|
| 1349 |
+
btnSpinner.className = "btn-spinner";
|
| 1350 |
+
btnSpinner.style.width = "20px";
|
| 1351 |
+
btnSpinner.style.height = "20px";
|
| 1352 |
+
btnSpinner.style.border = "3px solid rgba(255,255,255,0.3)";
|
| 1353 |
+
btnSpinner.style.borderRadius = "50%";
|
| 1354 |
+
btnSpinner.style.borderTopColor = "#fff";
|
| 1355 |
+
btnSpinner.style.animation = "spin 1s linear infinite";
|
| 1356 |
+
btnSpinner.style.display = "inline-block";
|
| 1357 |
+
btnSpinner.style.verticalAlign = "middle";
|
| 1358 |
+
this.appendChild(btnSpinner);
|
| 1359 |
+
|
| 1360 |
+
// Reset button after 3 seconds if no response
|
| 1361 |
+
setTimeout(() => {
|
| 1362 |
+
if (this.classList.contains("loading")) {
|
| 1363 |
+
this.classList.remove("loading");
|
| 1364 |
+
this.textContent = this.getAttribute("data-original-text");
|
| 1365 |
+
}
|
| 1366 |
+
hideSpinner(); // Hide spinner after timeout
|
| 1367 |
+
}, 3000);
|
| 1368 |
+
}
|
| 1369 |
+
});
|
| 1370 |
+
}
|
| 1371 |
+
|
| 1372 |
+
// Scale effect for dashboard buttons
|
| 1373 |
+
const dashboardButtons = document.querySelectorAll('.menu-button, button:contains("Back to Dashboard")');
|
| 1374 |
+
dashboardButtons.forEach(function(btn) {
|
| 1375 |
+
btn.addEventListener("click", function() {
|
| 1376 |
+
this.style.transform = "scale(1.1)";
|
| 1377 |
+
setTimeout(() => {
|
| 1378 |
+
this.style.transform = "";
|
| 1379 |
+
}, 200);
|
| 1380 |
+
});
|
| 1381 |
+
});
|
| 1382 |
+
}, 2000);
|
| 1383 |
+
|
| 1384 |
+
// Add keyframes for spinner animation
|
| 1385 |
+
const style = document.createElement('style');
|
| 1386 |
+
style.textContent = `
|
| 1387 |
+
@keyframes spin {
|
| 1388 |
+
0% { transform: rotate(0deg); }
|
| 1389 |
+
100% { transform: rotate(360deg); }
|
| 1390 |
+
}
|
| 1391 |
+
`;
|
| 1392 |
+
document.head.appendChild(style);
|
| 1393 |
+
});
|
| 1394 |
+
</script>
|
| 1395 |
+
''')
|
| 1396 |
+
|
| 1397 |
+
# Logo for all pages - positioned in top-left corner
|
| 1398 |
+
gr.HTML('''
|
| 1399 |
+
<div class="logo-dashboard">
|
| 1400 |
+
<!-- USIU Logo Placeholder - Replace with actual logo path -->
|
| 1401 |
+
<img src="week5/chatbot_prototype/images/usiu-logo.png" alt="USIU Logo" class="logo" />
|
| 1402 |
+
</div>
|
| 1403 |
+
''')
|
| 1404 |
+
|
| 1405 |
+
# Define containers for different views with initial animation state
|
| 1406 |
+
dashboard_container = gr.Column(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInUp")
|
| 1407 |
+
general_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 1408 |
+
login_container = gr.Column(visible=False, elem_classes="container floating-card login-form-container pre-animation")
|
| 1409 |
+
study_container = gr.Column(visible=False, elem_classes="chat-container pre-animation")
|
| 1410 |
+
|
| 1411 |
+
# Logout Button Component for Study Support (initially hidden)
|
| 1412 |
+
with gr.Column(visible=False) as logout_component:
|
| 1413 |
+
# Session information display (hidden textbox for now)
|
| 1414 |
+
current_user = gr.Textbox(visible=False)
|
| 1415 |
+
# Create a dedicated logout button with custom styling
|
| 1416 |
+
logout_btn = gr.Button("Logout", elem_classes="logout-btn")
|
| 1417 |
+
# An additional HTML component for JS updates (used in the Proceed callback)
|
| 1418 |
+
js_update = gr.HTML("", visible=False)
|
| 1419 |
+
|
| 1420 |
+
# ------------- Dashboard View -------------
|
| 1421 |
+
with dashboard_container:
|
| 1422 |
+
gr.Markdown("<h1 class='with-logo'>AI Assistance Dashboard</h1>")
|
| 1423 |
+
with gr.Column(elem_classes="dashboard-menu"):
|
| 1424 |
+
gen_button = gr.Button("General Enquiries", elem_classes="menu-button")
|
| 1425 |
+
study_button = gr.Button("Study with AI", elem_classes="menu-button")
|
| 1426 |
+
|
| 1427 |
+
# ------------- General Academic Chat View -------------
|
| 1428 |
+
with general_container:
|
| 1429 |
+
back_gen = gr.Button("Back to Dashboard")
|
| 1430 |
+
general_chat_component.render()
|
| 1431 |
+
|
| 1432 |
+
# ------------- Login Form (for Study Support) View -------------
|
| 1433 |
+
with login_container:
|
| 1434 |
+
login_form_component.render()
|
| 1435 |
+
|
| 1436 |
+
# ------------- Study Support Chat View -------------
|
| 1437 |
+
with study_container:
|
| 1438 |
+
back_study = gr.Button("Back to Dashboard")
|
| 1439 |
+
study_support_component.render()
|
| 1440 |
+
|
| 1441 |
+
# Set session ID for the study chat interface
|
| 1442 |
+
# This ensures the session is refreshed on user interaction
|
| 1443 |
+
session_id_for_study = gr.Textbox(visible=False)
|
| 1444 |
+
|
| 1445 |
+
# ------------- Navigation Callbacks -------------
|
| 1446 |
+
# When "General Academic Chat" is selected from the dashboard.
|
| 1447 |
+
gen_button.click(
|
| 1448 |
+
lambda: [
|
| 1449 |
+
gr.update(visible=False),
|
| 1450 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight"),
|
| 1451 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1452 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1453 |
+
gr.update(visible=False) # Hide logout component
|
| 1454 |
+
],
|
| 1455 |
+
outputs=[dashboard_container, general_container, login_container, study_container, logout_component]
|
| 1456 |
+
)
|
| 1457 |
+
|
| 1458 |
+
# When "Study Support Chat" is selected from the dashboard, show the login view with animation.
|
| 1459 |
+
study_button.click(
|
| 1460 |
+
lambda: [
|
| 1461 |
+
gr.update(visible=False),
|
| 1462 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1463 |
+
gr.update(visible=True, elem_classes="container floating-card login-form-container animate-fadeInRight"),
|
| 1464 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1465 |
+
gr.update(visible=False) # Hide logout component
|
| 1466 |
+
],
|
| 1467 |
+
outputs=[dashboard_container, general_container, login_container, study_container, logout_component]
|
| 1468 |
+
)
|
| 1469 |
+
|
| 1470 |
+
# "Back to Dashboard" buttons with animation.
|
| 1471 |
+
back_gen.click(
|
| 1472 |
+
lambda: [
|
| 1473 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1474 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1475 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1476 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1477 |
+
gr.update(visible=False) # Hide logout component
|
| 1478 |
+
],
|
| 1479 |
+
outputs=[dashboard_container, general_container, login_container, study_container, logout_component]
|
| 1480 |
+
)
|
| 1481 |
+
|
| 1482 |
+
back_study.click(
|
| 1483 |
+
lambda: [
|
| 1484 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1485 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1486 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1487 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1488 |
+
gr.update(visible=False) # Hide logout component
|
| 1489 |
+
],
|
| 1490 |
+
outputs=[dashboard_container, general_container, login_container, study_container, logout_component]
|
| 1491 |
+
)
|
| 1492 |
+
|
| 1493 |
+
login_back_btn.click(
|
| 1494 |
+
lambda: [
|
| 1495 |
+
gr.update(visible=True, elem_classes="container floating-card dashboard-card animate-fadeInLeft"),
|
| 1496 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1497 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1498 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1499 |
+
gr.update(visible=False) # Hide logout component
|
| 1500 |
+
],
|
| 1501 |
+
outputs=[dashboard_container, general_container, login_container, study_container, logout_component]
|
| 1502 |
+
)
|
| 1503 |
+
|
| 1504 |
+
# ------------- Authentication Logic -------------
|
| 1505 |
+
login_btn.click(
|
| 1506 |
+
handle_login,
|
| 1507 |
+
inputs=[user_id_input, password_input],
|
| 1508 |
+
outputs=[login_msg, user_id_error, password_error, proceed_btn, login_btn, session_id]
|
| 1509 |
+
)
|
| 1510 |
+
|
| 1511 |
+
# When "Proceed" is clicked (after successful authentication), show the study support chat with animation.
|
| 1512 |
+
def proceed_to_study(session_id_value):
|
| 1513 |
+
# Get user info from the session
|
| 1514 |
+
session = get_session(session_id_value)
|
| 1515 |
+
user_email = session["user_id"] if session else "Guest"
|
| 1516 |
+
|
| 1517 |
+
# Updated JS code wrapped in <script> tags to update the user email display.
|
| 1518 |
+
js_code = f"<script>document.getElementById('user-email').textContent = '{user_email}';</script>"
|
| 1519 |
+
|
| 1520 |
+
# Start tracking activity for this session
|
| 1521 |
+
update_session_activity(session_id_value)
|
| 1522 |
+
|
| 1523 |
+
return [
|
| 1524 |
+
gr.update(visible=False),
|
| 1525 |
+
gr.update(visible=False, elem_classes="chat-container pre-animation"),
|
| 1526 |
+
gr.update(visible=False, elem_classes="container floating-card login-form-container pre-animation"),
|
| 1527 |
+
gr.update(visible=True, elem_classes="chat-container animate-fadeInRight"),
|
| 1528 |
+
gr.update(visible=True), # Show logout component
|
| 1529 |
+
user_email, # Update current user display
|
| 1530 |
+
session_id_value, # Pass session ID to the study chat interface
|
| 1531 |
+
js_code # Update the new HTML component (js_update)
|
| 1532 |
+
]
|
| 1533 |
+
|
| 1534 |
+
proceed_btn.click(
|
| 1535 |
+
proceed_to_study,
|
| 1536 |
+
inputs=[session_id],
|
| 1537 |
+
outputs=[
|
| 1538 |
+
dashboard_container,
|
| 1539 |
+
general_container,
|
| 1540 |
+
login_container,
|
| 1541 |
+
study_container,
|
| 1542 |
+
logout_component,
|
| 1543 |
+
current_user,
|
| 1544 |
+
session_id_for_study,
|
| 1545 |
+
js_update # Replaces gr.JavaScript.update()
|
| 1546 |
+
]
|
| 1547 |
+
)
|
| 1548 |
+
|
| 1549 |
+
# Logout button functionality – using the dedicated logout button (logout_btn)
|
| 1550 |
+
logout_btn.click(
|
| 1551 |
+
handle_logout,
|
| 1552 |
+
inputs=[session_id],
|
| 1553 |
+
outputs=[dashboard_container, general_container, login_container, study_container]
|
| 1554 |
+
)
|
| 1555 |
+
|
| 1556 |
+
# NOTE: Removed the .change() callback on the ChatInterface (study_support_component)
|
| 1557 |
+
# because ChatInterface objects do not support the .change() method.
|
| 1558 |
+
# Session activity is now updated within the chat callback of study_support_chat.
|
| 1559 |
+
|
| 1560 |
+
return dashboard.queue()
|
speech.wav
ADDED
|
File without changes
|
system_prompts/__init__.py
ADDED
|
File without changes
|
system_prompts/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (266 Bytes). View file
|
|
|
system_prompts/__pycache__/prompt_for_general_chat.cpython-311.pyc
ADDED
|
Binary file (7.22 kB). View file
|
|
|
system_prompts/__pycache__/prompt_for_study_support.cpython-311.pyc
ADDED
|
Binary file (7.55 kB). View file
|
|
|
system_prompts/__pycache__/prompts_manager.cpython-311.pyc
ADDED
|
Binary file (27.2 kB). View file
|
|
|
system_prompts/__pycache__/system_prompts.cpython-311.pyc
ADDED
|
Binary file (14.9 kB). View file
|
|
|