Commit ·
131da12
0
Parent(s):
Deploy educational sentiment detection chatbot (excluding PDF)
Browse files- .dockerignore +26 -0
- .gitignore +40 -0
- BadBlog.md +1340 -0
- Dockerfile +45 -0
- backend/__pycache__/main.cpython-313.pyc +0 -0
- backend/benchmark.py +151 -0
- backend/benchmark_b.py +118 -0
- backend/main.py +921 -0
- backend/requirements.txt +12 -0
- backend/test_sentiment.py +40 -0
- frontend/.gitignore +24 -0
- frontend/.oxlintrc.json +8 -0
- frontend/README.md +16 -0
- frontend/index.html +21 -0
- frontend/package-lock.json +2074 -0
- frontend/package.json +24 -0
- frontend/public/favicon.svg +1 -0
- frontend/public/icons.svg +24 -0
- frontend/src/App.css +680 -0
- frontend/src/App.jsx +351 -0
- frontend/src/assets/hero.png +0 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/assets/vite.svg +1 -0
- frontend/src/components/ChatWindow.jsx +640 -0
- frontend/src/components/EmotionChart.jsx +97 -0
- frontend/src/components/Settings.jsx +106 -0
- frontend/src/index.css +6 -0
- frontend/src/main.jsx +10 -0
- frontend/vite.config.js +7 -0
.dockerignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python ignores
|
| 2 |
+
**/__pycache__/
|
| 3 |
+
**/*.pyc
|
| 4 |
+
**/*.pyo
|
| 5 |
+
**/*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
backend/venv/
|
| 8 |
+
backend/.env
|
| 9 |
+
backend/benchmark_results.csv
|
| 10 |
+
backend/benchmark_results_b.csv
|
| 11 |
+
backend/sentiment_log.md
|
| 12 |
+
backend/sentiment_log_b.md
|
| 13 |
+
|
| 14 |
+
# Node ignores
|
| 15 |
+
**/node_modules/
|
| 16 |
+
frontend/dist/
|
| 17 |
+
frontend/.env
|
| 18 |
+
.npm/
|
| 19 |
+
|
| 20 |
+
# Git ignores
|
| 21 |
+
.git/
|
| 22 |
+
.gitignore
|
| 23 |
+
|
| 24 |
+
# OS ignores
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual environments
|
| 2 |
+
venv/
|
| 3 |
+
backend/venv/
|
| 4 |
+
.venv/
|
| 5 |
+
env/
|
| 6 |
+
|
| 7 |
+
# Dependency directories
|
| 8 |
+
node_modules/
|
| 9 |
+
frontend/node_modules/
|
| 10 |
+
|
| 11 |
+
# Local configuration / secrets
|
| 12 |
+
.env
|
| 13 |
+
backend/.env
|
| 14 |
+
frontend/.env
|
| 15 |
+
*.local
|
| 16 |
+
|
| 17 |
+
# Build outputs
|
| 18 |
+
dist/
|
| 19 |
+
frontend/dist/
|
| 20 |
+
build/
|
| 21 |
+
|
| 22 |
+
# Logs and SQLite databases
|
| 23 |
+
*.log
|
| 24 |
+
sentiment_log.md
|
| 25 |
+
sentiment_log_b.md
|
| 26 |
+
backend/sentiment_log.md
|
| 27 |
+
backend/sentiment_log_b.md
|
| 28 |
+
sentiment_log.md
|
| 29 |
+
backend/logpdf.pdf
|
| 30 |
+
logpdf.pdf
|
| 31 |
+
|
| 32 |
+
# Benchmark files
|
| 33 |
+
backend/benchmark_results.csv
|
| 34 |
+
backend/benchmark_results_b.csv
|
| 35 |
+
|
| 36 |
+
# IDEs and OS files
|
| 37 |
+
.vscode/
|
| 38 |
+
.idea/
|
| 39 |
+
.DS_Store
|
| 40 |
+
Thumbs.db
|
BadBlog.md
ADDED
|
@@ -0,0 +1,1340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## [2026-06-28 18:16:39] Query: "This math worksheet is just 50 of the same exact addition problems. This is so boring."
|
| 2 |
+
|
| 3 |
+
<table>
|
| 4 |
+
<thead>
|
| 5 |
+
<tr>
|
| 6 |
+
<th align="left">Metric</th>
|
| 7 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 8 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 9 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 10 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 11 |
+
</tr>
|
| 12 |
+
</thead>
|
| 13 |
+
<tbody>
|
| 14 |
+
<tr>
|
| 15 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 16 |
+
<td><code>boredom</code></td>
|
| 17 |
+
<td><code>boredom</code></td>
|
| 18 |
+
<td><code>Distribution Context</code></td>
|
| 19 |
+
<td><code>frustration</code></td>
|
| 20 |
+
</tr>
|
| 21 |
+
<tr>
|
| 22 |
+
<td><strong>Latency</strong></td>
|
| 23 |
+
<td>1.822s</td>
|
| 24 |
+
<td>1.173s</td>
|
| 25 |
+
<td>1.49s</td>
|
| 26 |
+
<td>1.177s</td>
|
| 27 |
+
</tr>
|
| 28 |
+
<tr>
|
| 29 |
+
<td><strong>Estimated Cost</strong></td>
|
| 30 |
+
<td><code>$0.0000562</code></td>
|
| 31 |
+
<td><code>$0.0000285</code></td>
|
| 32 |
+
<td><code>$0.0000511</code></td>
|
| 33 |
+
<td><code>$0.0000410</code></td>
|
| 34 |
+
</tr>
|
| 35 |
+
<tr>
|
| 36 |
+
<td><strong>Tokens Used</strong></td>
|
| 37 |
+
<td>402 (286 in / 116 out)</td>
|
| 38 |
+
<td>206 (148 in / 58 out)</td>
|
| 39 |
+
<td>339 (225 in / 114 out)</td>
|
| 40 |
+
<td>298 (215 in / 83 out)</td>
|
| 41 |
+
</tr>
|
| 42 |
+
</tbody>
|
| 43 |
+
</table>
|
| 44 |
+
|
| 45 |
+
### Option A Response
|
| 46 |
+
I hear your frustration; repetitive drills can feel like a chore. Instead of the worksheet, imagine you are a shopkeeper who needs to calculate the total cost of items for a customer. If a customer buys a toy for $15 and a book for $20, how would you mentally group those numbers to find the total quickly?
|
| 47 |
+
|
| 48 |
+
### Option B Response
|
| 49 |
+
Since you already know how to solve these, could you find a way to group them or use a pattern to calculate the total sum of all 50 problems at once? What happens if you look at the numbers in pairs?
|
| 50 |
+
|
| 51 |
+
### Option C Response
|
| 52 |
+
I hear your frustration; repeating the same task can certainly feel tedious. Since you already have the process down, what do you think would happen if you tried to solve these using a different method, like mental math or grouping them by tens? Or, if you're up for a challenge, could you find a way to represent these problems using a visual pattern instead of just writing numbers? Which approach sounds like it might make the work feel less like a chore?
|
| 53 |
+
|
| 54 |
+
### Option D Response
|
| 55 |
+
I hear your frustration; repetitive tasks can feel incredibly draining when you already understand the concept. Instead of grinding through them one by one, could you look for a pattern or a shortcut that might group these problems together? If you were to solve just the first five, what strategy could you apply to speed up the rest?
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## [2026-06-28 18:16:46] Query: "Reading about the ancient Egyptians is okay, but copying these definitions is putting me to sleep."
|
| 60 |
+
|
| 61 |
+
<table>
|
| 62 |
+
<thead>
|
| 63 |
+
<tr>
|
| 64 |
+
<th align="left">Metric</th>
|
| 65 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 66 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 67 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 68 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 69 |
+
</tr>
|
| 70 |
+
</thead>
|
| 71 |
+
<tbody>
|
| 72 |
+
<tr>
|
| 73 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 74 |
+
<td><code>boredom</code></td>
|
| 75 |
+
<td><code>boredom</code></td>
|
| 76 |
+
<td><code>Distribution Context</code></td>
|
| 77 |
+
<td><code>frustration</code></td>
|
| 78 |
+
</tr>
|
| 79 |
+
<tr>
|
| 80 |
+
<td><strong>Latency</strong></td>
|
| 81 |
+
<td>1.919s</td>
|
| 82 |
+
<td>1.222s</td>
|
| 83 |
+
<td>1.339s</td>
|
| 84 |
+
<td>1.377s</td>
|
| 85 |
+
</tr>
|
| 86 |
+
<tr>
|
| 87 |
+
<td><strong>Estimated Cost</strong></td>
|
| 88 |
+
<td><code>$0.0000603</code></td>
|
| 89 |
+
<td><code>$0.0000296</code></td>
|
| 90 |
+
<td><code>$0.0000393</code></td>
|
| 91 |
+
<td><code>$0.0000470</code></td>
|
| 92 |
+
</tr>
|
| 93 |
+
<tr>
|
| 94 |
+
<td><strong>Tokens Used</strong></td>
|
| 95 |
+
<td>420 (292 in / 128 out)</td>
|
| 96 |
+
<td>212 (151 in / 61 out)</td>
|
| 97 |
+
<td>302 (228 in / 74 out)</td>
|
| 98 |
+
<td>320 (218 in / 102 out)</td>
|
| 99 |
+
</tr>
|
| 100 |
+
</tbody>
|
| 101 |
+
</table>
|
| 102 |
+
|
| 103 |
+
### Option A Response
|
| 104 |
+
If reading feels like a chore, let’s try being an architect instead. Imagine you are the royal engineer tasked with designing a tomb that will last for eternity; what is the single most important feature you would include to protect the Pharaoh's legacy? How would you use the geography of the Nile to ensure your construction materials arrive on time?
|
| 105 |
+
|
| 106 |
+
### Option B Response
|
| 107 |
+
If you were tasked with building a monument that would last for thousands of years, what specific challenges do you think you would face first? How might those challenges change the way you organize your society?
|
| 108 |
+
|
| 109 |
+
### Option C Response
|
| 110 |
+
I hear your frustration; rote memorization can certainly feel like a chore. Instead of just reading definitions, what if we looked at the *purpose* behind these Egyptian practices? If you were an architect in ancient Egypt, what specific problem would you be trying to solve by building a pyramid?
|
| 111 |
+
|
| 112 |
+
### Option D Response
|
| 113 |
+
I hear your frustration; rote copying is rarely the best way to engage with history. Instead of transcribing, what if you tried to visualize how these concepts actually functioned in their daily lives? If you were an ancient Egyptian, which of these definitions would have the biggest impact on your survival or status? What is one specific term that stands out to you as particularly strange or interesting?
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## [2026-06-28 18:17:32] Query: "Wait, why does a negative times a negative make a positive? I don't get it."
|
| 118 |
+
|
| 119 |
+
<table>
|
| 120 |
+
<thead>
|
| 121 |
+
<tr>
|
| 122 |
+
<th align="left">Metric</th>
|
| 123 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 124 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 125 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 126 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 127 |
+
</tr>
|
| 128 |
+
</thead>
|
| 129 |
+
<tbody>
|
| 130 |
+
<tr>
|
| 131 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 132 |
+
<td><code>confusion</code></td>
|
| 133 |
+
<td><code>confusion</code></td>
|
| 134 |
+
<td><code>Distribution Context</code></td>
|
| 135 |
+
<td><code>confusion</code></td>
|
| 136 |
+
</tr>
|
| 137 |
+
<tr>
|
| 138 |
+
<td><strong>Latency</strong></td>
|
| 139 |
+
<td>1.741s</td>
|
| 140 |
+
<td>18.514s</td>
|
| 141 |
+
<td>6.023s</td>
|
| 142 |
+
<td>18.12s</td>
|
| 143 |
+
</tr>
|
| 144 |
+
<tr>
|
| 145 |
+
<td><strong>Estimated Cost</strong></td>
|
| 146 |
+
<td><code>$0.0000500</code></td>
|
| 147 |
+
<td><code>$0.0000307</code></td>
|
| 148 |
+
<td><code>$0.0000365</code></td>
|
| 149 |
+
<td><code>$0.0000332</code></td>
|
| 150 |
+
</tr>
|
| 151 |
+
<tr>
|
| 152 |
+
<td><strong>Tokens Used</strong></td>
|
| 153 |
+
<td>373 (275 in / 98 out)</td>
|
| 154 |
+
<td>211 (145 in / 66 out)</td>
|
| 155 |
+
<td>289 (223 in / 66 out)</td>
|
| 156 |
+
<td>269 (211 in / 58 out)</td>
|
| 157 |
+
</tr>
|
| 158 |
+
</tbody>
|
| 159 |
+
</table>
|
| 160 |
+
|
| 161 |
+
### Option A Response
|
| 162 |
+
Think about what happens when you remove a debt. If you have a negative balance and someone takes away that debt, does your financial situation improve or worsen? How would you represent "taking away" a negative number mathematically?
|
| 163 |
+
|
| 164 |
+
### Option B Response
|
| 165 |
+
Think about what happens to your position if you walk backward at a constant speed for a negative amount of time. If walking forward is positive and walking backward is negative, what does 'undoing' a backward movement represent?
|
| 166 |
+
|
| 167 |
+
### Option C Response
|
| 168 |
+
It is quite a surprising rule at first glance! To visualize this, think of a negative sign as an instruction to "reverse" or "face the opposite direction." If you are already facing the negative direction and you perform a second reversal, where do you end up facing?
|
| 169 |
+
|
| 170 |
+
### Option D Response
|
| 171 |
+
Think about what happens when you remove a debt. If you have a negative balance and someone takes away that debt, does your financial situation improve or worsen? How would you represent "taking away" a negative number mathematically?
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
## [2026-06-28 18:17:39] Query: "I'm looking at this cell diagram and I can't tell the difference between the cell wall and the cell membrane."
|
| 176 |
+
|
| 177 |
+
<table>
|
| 178 |
+
<thead>
|
| 179 |
+
<tr>
|
| 180 |
+
<th align="left">Metric</th>
|
| 181 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 182 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 183 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 184 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 185 |
+
</tr>
|
| 186 |
+
</thead>
|
| 187 |
+
<tbody>
|
| 188 |
+
<tr>
|
| 189 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 190 |
+
<td><code>confusion</code></td>
|
| 191 |
+
<td><code>confusion</code></td>
|
| 192 |
+
<td><code>Distribution Context</code></td>
|
| 193 |
+
<td><code>boredom</code></td>
|
| 194 |
+
</tr>
|
| 195 |
+
<tr>
|
| 196 |
+
<td><strong>Latency</strong></td>
|
| 197 |
+
<td>1.979s</td>
|
| 198 |
+
<td>1.247s</td>
|
| 199 |
+
<td>1.284s</td>
|
| 200 |
+
<td>1.279s</td>
|
| 201 |
+
</tr>
|
| 202 |
+
<tr>
|
| 203 |
+
<td><strong>Estimated Cost</strong></td>
|
| 204 |
+
<td><code>$0.0000609</code></td>
|
| 205 |
+
<td><code>$0.0000331</code></td>
|
| 206 |
+
<td><code>$0.0000383</code></td>
|
| 207 |
+
<td><code>$0.0000439</code></td>
|
| 208 |
+
</tr>
|
| 209 |
+
<tr>
|
| 210 |
+
<td><strong>Tokens Used</strong></td>
|
| 211 |
+
<td>422 (292 in / 130 out)</td>
|
| 212 |
+
<td>225 (153 in / 72 out)</td>
|
| 213 |
+
<td>301 (231 in / 70 out)</td>
|
| 214 |
+
<td>316 (226 in / 90 out)</td>
|
| 215 |
+
</tr>
|
| 216 |
+
</tbody>
|
| 217 |
+
</table>
|
| 218 |
+
|
| 219 |
+
### Option A Response
|
| 220 |
+
Think about the primary function of each structure: one provides rigid structural support, while the other acts as a selective gatekeeper for the cell. If you were to look at a plant cell, which layer is on the very outside, and which one sits just inside it? Which of these two do you think would be present in an animal cell, which lacks a rigid outer shell?
|
| 221 |
+
|
| 222 |
+
### Option B Response
|
| 223 |
+
If you look at the outer boundary of the cell, does it appear to be a single layer or are there two distinct borders visible? Think about which of these structures is found in all cells versus which one is specific to plants or bacteria for extra support.
|
| 224 |
+
|
| 225 |
+
### Option C Response
|
| 226 |
+
It sounds like you're looking at the outer boundaries of the cell. If you imagine a house, what part would represent the sturdy, protective outer structure, and what part would act like the doors that decide who comes in or out? Which of those two layers do you think is more rigid?
|
| 227 |
+
|
| 228 |
+
### Option D Response
|
| 229 |
+
Think of a cell like a medieval castle. If the cell membrane is the inner gatekeeper deciding who enters the courtyard, what purpose does the thick, rigid stone wall surrounding the entire castle serve?
|
| 230 |
+
|
| 231 |
+
If you were to build a model of this using a balloon and a cardboard box, which one would provide the structure, and which one would hold the "guts" inside?
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## [2026-06-28 18:17:46] Query: "Our teacher said the Earth is tilted, but how does that make summer and winter? It doesn't make sense."
|
| 236 |
+
|
| 237 |
+
<table>
|
| 238 |
+
<thead>
|
| 239 |
+
<tr>
|
| 240 |
+
<th align="left">Metric</th>
|
| 241 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 242 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 243 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 244 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 245 |
+
</tr>
|
| 246 |
+
</thead>
|
| 247 |
+
<tbody>
|
| 248 |
+
<tr>
|
| 249 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 250 |
+
<td><code>confusion</code></td>
|
| 251 |
+
<td><code>confusion</code></td>
|
| 252 |
+
<td><code>Distribution Context</code></td>
|
| 253 |
+
<td><code>frustration</code></td>
|
| 254 |
+
</tr>
|
| 255 |
+
<tr>
|
| 256 |
+
<td><strong>Latency</strong></td>
|
| 257 |
+
<td>1.774s</td>
|
| 258 |
+
<td>1.227s</td>
|
| 259 |
+
<td>1.369s</td>
|
| 260 |
+
<td>1.218s</td>
|
| 261 |
+
</tr>
|
| 262 |
+
<tr>
|
| 263 |
+
<td><strong>Estimated Cost</strong></td>
|
| 264 |
+
<td><code>$0.0000558</code></td>
|
| 265 |
+
<td><code>$0.0000288</code></td>
|
| 266 |
+
<td><code>$0.0000448</code></td>
|
| 267 |
+
<td><code>$0.0000404</code></td>
|
| 268 |
+
</tr>
|
| 269 |
+
<tr>
|
| 270 |
+
<td><strong>Tokens Used</strong></td>
|
| 271 |
+
<td>402 (288 in / 114 out)</td>
|
| 272 |
+
<td>210 (152 in / 58 out)</td>
|
| 273 |
+
<td>321 (229 in / 92 out)</td>
|
| 274 |
+
<td>299 (219 in / 80 out)</td>
|
| 275 |
+
</tr>
|
| 276 |
+
</tbody>
|
| 277 |
+
</table>
|
| 278 |
+
|
| 279 |
+
### Option A Response
|
| 280 |
+
Think about how a flashlight beam hits a surface when it is pointed straight down versus when it is angled. If the Earth is tilted, does the sunlight hit the Northern Hemisphere at the same angle throughout the entire year? How might that angle change the intensity of the heat reaching the ground?
|
| 281 |
+
|
| 282 |
+
### Option B Response
|
| 283 |
+
If you hold a globe and tilt it toward a lamp, notice which half of the globe receives more direct light. How do you think that angle of light affects the intensity of the heat hitting the surface?
|
| 284 |
+
|
| 285 |
+
### Option C Response
|
| 286 |
+
I hear your frustration; it can certainly feel counterintuitive that a tilt changes the temperature. Imagine holding a flashlight directly over a table versus at a sharp angle—how does the shape of the light beam change in those two positions? If the Earth is tilted, how might that affect the intensity of the sunlight hitting a specific hemisphere throughout the year?
|
| 287 |
+
|
| 288 |
+
### Option D Response
|
| 289 |
+
I understand it’s incredibly frustrating when a concept feels counterintuitive. Think about how a flashlight beam looks when you shine it directly at a wall versus when you tilt it at an angle. If the Earth is tilted, how does that change the way sunlight hits the Northern Hemisphere during different parts of the year?
|
| 290 |
+
|
| 291 |
+
---
|
| 292 |
+
|
| 293 |
+
## [2026-06-28 18:17:53] Query: "Is a virus alive or is it not? My textbook says both and I'm really mixed up."
|
| 294 |
+
|
| 295 |
+
<table>
|
| 296 |
+
<thead>
|
| 297 |
+
<tr>
|
| 298 |
+
<th align="left">Metric</th>
|
| 299 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 300 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 301 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 302 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 303 |
+
</tr>
|
| 304 |
+
</thead>
|
| 305 |
+
<tbody>
|
| 306 |
+
<tr>
|
| 307 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 308 |
+
<td><code>confusion</code></td>
|
| 309 |
+
<td><code>confusion</code></td>
|
| 310 |
+
<td><code>Distribution Context</code></td>
|
| 311 |
+
<td><code>boredom</code></td>
|
| 312 |
+
</tr>
|
| 313 |
+
<tr>
|
| 314 |
+
<td><strong>Latency</strong></td>
|
| 315 |
+
<td>1.823s</td>
|
| 316 |
+
<td>1.269s</td>
|
| 317 |
+
<td>1.254s</td>
|
| 318 |
+
<td>1.228s</td>
|
| 319 |
+
</tr>
|
| 320 |
+
<tr>
|
| 321 |
+
<td><strong>Estimated Cost</strong></td>
|
| 322 |
+
<td><code>$0.0000573</code></td>
|
| 323 |
+
<td><code>$0.0000370</code></td>
|
| 324 |
+
<td><code>$0.0000356</code></td>
|
| 325 |
+
<td><code>$0.0000431</code></td>
|
| 326 |
+
</tr>
|
| 327 |
+
<tr>
|
| 328 |
+
<td><strong>Tokens Used</strong></td>
|
| 329 |
+
<td>398 (276 in / 122 out)</td>
|
| 330 |
+
<td>232 (145 in / 87 out)</td>
|
| 331 |
+
<td>286 (223 in / 63 out)</td>
|
| 332 |
+
<td>307 (218 in / 89 out)</td>
|
| 333 |
+
</tr>
|
| 334 |
+
</tbody>
|
| 335 |
+
</table>
|
| 336 |
+
|
| 337 |
+
### Option A Response
|
| 338 |
+
It is helpful to think about what defines "life" in biology. Do viruses possess their own metabolism, or do they require a host cell to replicate? If an entity cannot reproduce on its own, does that align with your textbook's definition of a living organism? What specific criteria for life do you think a virus might be missing?
|
| 339 |
+
|
| 340 |
+
### Option B Response
|
| 341 |
+
To decide if something is alive, scientists often look for a list of specific traits, such as the ability to reproduce independently or maintain internal balance. If you compare a virus to a single-celled bacterium, which of those life-defining traits does the virus lack when it is floating outside of a host cell?
|
| 342 |
+
|
| 343 |
+
### Option C Response
|
| 344 |
+
It is understandable why that feels contradictory! To help clarify, what are the specific criteria scientists typically use to define something as "living"? Think about how a virus behaves when it is outside of a host cell versus when it is inside one.
|
| 345 |
+
|
| 346 |
+
### Option D Response
|
| 347 |
+
It sounds like your textbook is highlighting the "gray area" of biology! Instead of reading, imagine you are a customs officer at a border: what specific criteria would you use to decide if a traveler is "alive" enough to enter your country? If a virus arrived at your border, which of those criteria would it fail to meet, and which might it actually pass?
|
| 348 |
+
|
| 349 |
+
---
|
| 350 |
+
|
| 351 |
+
## [2026-06-28 18:18:35] Query: "What is the difference between a variable and a constant in algebra? I'm lost."
|
| 352 |
+
|
| 353 |
+
<table>
|
| 354 |
+
<thead>
|
| 355 |
+
<tr>
|
| 356 |
+
<th align="left">Metric</th>
|
| 357 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 358 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 359 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 360 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 361 |
+
</tr>
|
| 362 |
+
</thead>
|
| 363 |
+
<tbody>
|
| 364 |
+
<tr>
|
| 365 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 366 |
+
<td><code>confusion</code></td>
|
| 367 |
+
<td><code>confusion</code></td>
|
| 368 |
+
<td><code>Distribution Context</code></td>
|
| 369 |
+
<td><code>confusion</code></td>
|
| 370 |
+
</tr>
|
| 371 |
+
<tr>
|
| 372 |
+
<td><strong>Latency</strong></td>
|
| 373 |
+
<td>36.542s</td>
|
| 374 |
+
<td>1.206s</td>
|
| 375 |
+
<td>1.366s</td>
|
| 376 |
+
<td>1.183s</td>
|
| 377 |
+
</tr>
|
| 378 |
+
<tr>
|
| 379 |
+
<td><strong>Estimated Cost</strong></td>
|
| 380 |
+
<td><code>$0.0000564</code></td>
|
| 381 |
+
<td><code>$0.0000361</code></td>
|
| 382 |
+
<td><code>$0.0000482</code></td>
|
| 383 |
+
<td><code>$0.0000396</code></td>
|
| 384 |
+
</tr>
|
| 385 |
+
<tr>
|
| 386 |
+
<td><strong>Tokens Used</strong></td>
|
| 387 |
+
<td>395 (276 in / 119 out)</td>
|
| 388 |
+
<td>230 (146 in / 84 out)</td>
|
| 389 |
+
<td>328 (223 in / 105 out)</td>
|
| 390 |
+
<td>291 (212 in / 79 out)</td>
|
| 391 |
+
</tr>
|
| 392 |
+
</tbody>
|
| 393 |
+
</table>
|
| 394 |
+
|
| 395 |
+
### Option A Response
|
| 396 |
+
Think of a variable as a placeholder that can change its value depending on the situation, like a box that can hold different numbers. In contrast, what do you think happens to a constant if you use it in different equations? If you had to label the number "5" in the expression $x + 5$, would it ever change its value?
|
| 397 |
+
|
| 398 |
+
### Option B Response
|
| 399 |
+
Think about a situation where you have a fixed starting amount of money versus an amount that changes depending on how many items you buy. If you were to write an equation for your total spending, which part of that scenario would always stay the same, and which part would depend on the number of items?
|
| 400 |
+
|
| 401 |
+
### Option C Response
|
| 402 |
+
It sounds like you're feeling a bit overwhelmed, but that is a perfectly normal place to start. Think about a situation where you have a fixed amount of money in a piggy bank versus a situation where you are earning money for every chore you complete. If the amount in the piggy bank never changes, but the amount you earn depends on how many chores you do, which one feels "fixed" and which one feels like it could change?
|
| 403 |
+
|
| 404 |
+
### Option D Response
|
| 405 |
+
Think of a variable as a placeholder that can change its value depending on the situation, like a box that can hold different numbers. In contrast, what do you think happens to a constant if you use it in different equations? If you had to label the number "5" in the expression $x + 5$, would it ever change its value?
|
| 406 |
+
|
| 407 |
+
---
|
| 408 |
+
|
| 409 |
+
## [2026-06-28 18:18:42] Query: "I've reread this history chapter about the constitution three times and I still don't understand it! This is so annoying!"
|
| 410 |
+
|
| 411 |
+
<table>
|
| 412 |
+
<thead>
|
| 413 |
+
<tr>
|
| 414 |
+
<th align="left">Metric</th>
|
| 415 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 416 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 417 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 418 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 419 |
+
</tr>
|
| 420 |
+
</thead>
|
| 421 |
+
<tbody>
|
| 422 |
+
<tr>
|
| 423 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 424 |
+
<td><code>frustration</code></td>
|
| 425 |
+
<td><code>frustration</code></td>
|
| 426 |
+
<td><code>Distribution Context</code></td>
|
| 427 |
+
<td><code>frustration</code></td>
|
| 428 |
+
</tr>
|
| 429 |
+
<tr>
|
| 430 |
+
<td><strong>Latency</strong></td>
|
| 431 |
+
<td>1.762s</td>
|
| 432 |
+
<td>1.202s</td>
|
| 433 |
+
<td>1.192s</td>
|
| 434 |
+
<td>1.263s</td>
|
| 435 |
+
</tr>
|
| 436 |
+
<tr>
|
| 437 |
+
<td><strong>Estimated Cost</strong></td>
|
| 438 |
+
<td><code>$0.0000581</code></td>
|
| 439 |
+
<td><code>$0.0000279</code></td>
|
| 440 |
+
<td><code>$0.0000419</code></td>
|
| 441 |
+
<td><code>$0.0000405</code></td>
|
| 442 |
+
</tr>
|
| 443 |
+
<tr>
|
| 444 |
+
<td><strong>Tokens Used</strong></td>
|
| 445 |
+
<td>418 (299 in / 119 out)</td>
|
| 446 |
+
<td>210 (156 in / 54 out)</td>
|
| 447 |
+
<td>315 (234 in / 81 out)</td>
|
| 448 |
+
<td>303 (224 in / 79 out)</td>
|
| 449 |
+
</tr>
|
| 450 |
+
</tbody>
|
| 451 |
+
</table>
|
| 452 |
+
|
| 453 |
+
### Option A Response
|
| 454 |
+
I hear your frustration, and it is completely normal to feel overwhelmed by such dense historical text. Instead of trying to absorb the whole chapter at once, try focusing only on the preamble's opening phrase. What do you think the authors meant by "We the People," and who exactly were they including in that group?
|
| 455 |
+
|
| 456 |
+
### Option B Response
|
| 457 |
+
It sounds like the dense language is getting in the way of the core ideas. If you had to explain the main problem the founders were trying to solve in one sentence, what would it be?
|
| 458 |
+
|
| 459 |
+
### Option C Response
|
| 460 |
+
I hear your frustration; it is completely understandable to feel annoyed when a complex text doesn't click after multiple attempts. Let's break it down into smaller pieces to make it less overwhelming. If you had to summarize the main goal of the Constitution in just one sentence, what would you say it was trying to solve?
|
| 461 |
+
|
| 462 |
+
### Option D Response
|
| 463 |
+
I hear your frustration, and it is completely normal to feel overwhelmed by such dense historical text. Instead of trying to absorb the whole chapter at once, try focusing only on the preamble's opening phrase. What do you think the authors meant by "We the People," and who exactly were they including in that group?
|
| 464 |
+
|
| 465 |
+
---
|
| 466 |
+
|
| 467 |
+
## [2026-06-28 18:18:49] Query: "My science experiment failed again! The volcano didn't bubble at all and I did everything exactly right!"
|
| 468 |
+
|
| 469 |
+
<table>
|
| 470 |
+
<thead>
|
| 471 |
+
<tr>
|
| 472 |
+
<th align="left">Metric</th>
|
| 473 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 474 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 475 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 476 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 477 |
+
</tr>
|
| 478 |
+
</thead>
|
| 479 |
+
<tbody>
|
| 480 |
+
<tr>
|
| 481 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 482 |
+
<td><code>frustration</code></td>
|
| 483 |
+
<td><code>frustration</code></td>
|
| 484 |
+
<td><code>Distribution Context</code></td>
|
| 485 |
+
<td><code>frustration</code></td>
|
| 486 |
+
</tr>
|
| 487 |
+
<tr>
|
| 488 |
+
<td><strong>Latency</strong></td>
|
| 489 |
+
<td>1.802s</td>
|
| 490 |
+
<td>1.092s</td>
|
| 491 |
+
<td>1.351s</td>
|
| 492 |
+
<td>1.301s</td>
|
| 493 |
+
</tr>
|
| 494 |
+
<tr>
|
| 495 |
+
<td><strong>Estimated Cost</strong></td>
|
| 496 |
+
<td><code>$0.0000617</code></td>
|
| 497 |
+
<td><code>$0.0000306</code></td>
|
| 498 |
+
<td><code>$0.0000409</code></td>
|
| 499 |
+
<td><code>$0.0000443</code></td>
|
| 500 |
+
</tr>
|
| 501 |
+
<tr>
|
| 502 |
+
<td><strong>Tokens Used</strong></td>
|
| 503 |
+
<td>423 (290 in / 133 out)</td>
|
| 504 |
+
<td>216 (152 in / 64 out)</td>
|
| 505 |
+
<td>309 (230 in / 79 out)</td>
|
| 506 |
+
<td>312 (219 in / 93 out)</td>
|
| 507 |
+
</tr>
|
| 508 |
+
</tbody>
|
| 509 |
+
</table>
|
| 510 |
+
|
| 511 |
+
### Option A Response
|
| 512 |
+
I can hear how frustrating it is to put in all that effort only to have the experiment fall flat. Sometimes, even when we follow the steps, a tiny variable can change the outcome. If you look at your ingredients, which one is responsible for creating the gas bubbles? What happens if you test that specific ingredient with a little water first to see if it's still active?
|
| 513 |
+
|
| 514 |
+
### Option B Response
|
| 515 |
+
It sounds like you followed the steps carefully, but sometimes the ingredients themselves hold the key. If you look at the two main components you mixed, which one do you think is responsible for creating the gas bubbles?
|
| 516 |
+
|
| 517 |
+
### Option C Response
|
| 518 |
+
I am sorry to hear that your experiment didn't go as planned; it can be incredibly frustrating when things don't work after you've put in the effort. Let's take a step back and look at the ingredients. What specific substances did you combine to create the "lava," and what do you know about how they react when mixed?
|
| 519 |
+
|
| 520 |
+
### Option D Response
|
| 521 |
+
I can hear how frustrating it is to put in all that effort only to have the experiment fall flat. Sometimes, even when we follow the steps, a tiny variable can change the outcome. If you look at your ingredients, which one is responsible for creating the gas bubbles? What happens if you test that specific ingredient with a little water first to see if it's still active?
|
| 522 |
+
|
| 523 |
+
---
|
| 524 |
+
|
| 525 |
+
## [2026-06-28 18:19:38] Query: "This long division with decimals is taking forever and I keep getting the wrong remainder! I hate this!"
|
| 526 |
+
|
| 527 |
+
<table>
|
| 528 |
+
<thead>
|
| 529 |
+
<tr>
|
| 530 |
+
<th align="left">Metric</th>
|
| 531 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 532 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 533 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 534 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 535 |
+
</tr>
|
| 536 |
+
</thead>
|
| 537 |
+
<tbody>
|
| 538 |
+
<tr>
|
| 539 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 540 |
+
<td><code>neutral</code></td>
|
| 541 |
+
<td><code>frustration</code></td>
|
| 542 |
+
<td><code>Distribution Context</code></td>
|
| 543 |
+
<td><code>frustration</code></td>
|
| 544 |
+
</tr>
|
| 545 |
+
<tr>
|
| 546 |
+
<td><strong>Latency</strong></td>
|
| 547 |
+
<td>35.594s</td>
|
| 548 |
+
<td>9.119s</td>
|
| 549 |
+
<td>1.281s</td>
|
| 550 |
+
<td>1.254s</td>
|
| 551 |
+
</tr>
|
| 552 |
+
<tr>
|
| 553 |
+
<td><strong>Estimated Cost</strong></td>
|
| 554 |
+
<td><code>$0.0000000</code></td>
|
| 555 |
+
<td><code>$0.0000306</code></td>
|
| 556 |
+
<td><code>$0.0000419</code></td>
|
| 557 |
+
<td><code>$0.0000425</code></td>
|
| 558 |
+
</tr>
|
| 559 |
+
<tr>
|
| 560 |
+
<td><strong>Tokens Used</strong></td>
|
| 561 |
+
<td>0 (0 in / 0 out)</td>
|
| 562 |
+
<td>216 (152 in / 64 out)</td>
|
| 563 |
+
<td>312 (230 in / 82 out)</td>
|
| 564 |
+
<td>306 (219 in / 87 out)</td>
|
| 565 |
+
</tr>
|
| 566 |
+
</tbody>
|
| 567 |
+
</table>
|
| 568 |
+
|
| 569 |
+
### Option A Response
|
| 570 |
+
An error occurred during Flow A generation.
|
| 571 |
+
|
| 572 |
+
### Option B Response
|
| 573 |
+
It sounds like the decimal placement might be causing the trouble. If you multiply both the divisor and the dividend by 10 or 100 to make the divisor a whole number, does the division process feel a bit more manageable?
|
| 574 |
+
|
| 575 |
+
### Option C Response
|
| 576 |
+
I hear your frustration; long division with decimals can feel incredibly tedious when the numbers don't seem to cooperate. Let's take a deep breath and look at the problem together. When you are dividing, what happens to the decimal point in the dividend once you move it in the divisor? Could you walk me through your first step?
|
| 577 |
+
|
| 578 |
+
### Option D Response
|
| 579 |
+
I hear your frustration; long division can feel incredibly tedious when the numbers don't seem to cooperate. Instead of focusing on the remainder right away, try multiplying both the divisor and the dividend by 10 or 100 to turn that decimal into a whole number first. How does the problem look once you've shifted those decimal points to the right?
|
| 580 |
+
|
| 581 |
+
---
|
| 582 |
+
|
| 583 |
+
## [2026-06-28 18:19:45] Query: "I'm trying to write this paragraph about Shakespeare but I have absolutely no idea what to write. I give up!"
|
| 584 |
+
|
| 585 |
+
<table>
|
| 586 |
+
<thead>
|
| 587 |
+
<tr>
|
| 588 |
+
<th align="left">Metric</th>
|
| 589 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 590 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 591 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 592 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 593 |
+
</tr>
|
| 594 |
+
</thead>
|
| 595 |
+
<tbody>
|
| 596 |
+
<tr>
|
| 597 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 598 |
+
<td><code>frustration</code></td>
|
| 599 |
+
<td><code>frustration</code></td>
|
| 600 |
+
<td><code>Distribution Context</code></td>
|
| 601 |
+
<td><code>frustration</code></td>
|
| 602 |
+
</tr>
|
| 603 |
+
<tr>
|
| 604 |
+
<td><strong>Latency</strong></td>
|
| 605 |
+
<td>2.002s</td>
|
| 606 |
+
<td>1.106s</td>
|
| 607 |
+
<td>1.334s</td>
|
| 608 |
+
<td>1.231s</td>
|
| 609 |
+
</tr>
|
| 610 |
+
<tr>
|
| 611 |
+
<td><strong>Estimated Cost</strong></td>
|
| 612 |
+
<td><code>$0.0000585</code></td>
|
| 613 |
+
<td><code>$0.0000256</code></td>
|
| 614 |
+
<td><code>$0.0000422</code></td>
|
| 615 |
+
<td><code>$0.0000411</code></td>
|
| 616 |
+
</tr>
|
| 617 |
+
<tr>
|
| 618 |
+
<td><strong>Tokens Used</strong></td>
|
| 619 |
+
<td>414 (292 in / 122 out)</td>
|
| 620 |
+
<td>200 (153 in / 47 out)</td>
|
| 621 |
+
<td>314 (231 in / 83 out)</td>
|
| 622 |
+
<td>302 (220 in / 82 out)</td>
|
| 623 |
+
</tr>
|
| 624 |
+
</tbody>
|
| 625 |
+
</table>
|
| 626 |
+
|
| 627 |
+
### Option A Response
|
| 628 |
+
I hear your frustration, and it’s completely normal to feel stuck when facing a blank page. Instead of trying to summarize everything at once, what is one specific aspect of Shakespeare’s work or life that you find even slightly interesting? If you had to explain his impact to a friend in just one sentence, what would you say?
|
| 629 |
+
|
| 630 |
+
### Option B Response
|
| 631 |
+
It sounds like you're feeling stuck, but let's break it down: what is one specific aspect of Shakespeare's work or life that you find most interesting?
|
| 632 |
+
|
| 633 |
+
### Option C Response
|
| 634 |
+
I hear how frustrating it is to stare at a blank page, and it’s completely normal to feel stuck when starting a big topic like Shakespeare. Let’s take a breath; what is one specific play or character of his that you actually found interesting or memorable? If you could describe that character in just three words, what would they be?
|
| 635 |
+
|
| 636 |
+
### Option D Response
|
| 637 |
+
I hear your frustration, and it’s completely normal to feel stuck when facing a blank page. Instead of trying to summarize everything at once, what is one specific aspect of Shakespeare’s work or life that you find even slightly interesting? If you had to explain his impact to a friend in just one sentence, what would you say?
|
| 638 |
+
|
| 639 |
+
---
|
| 640 |
+
|
| 641 |
+
## [2026-06-28 18:19:53] Query: "Why is English spelling so weird? These rules make no sense and I keep failing my vocabulary checks!"
|
| 642 |
+
|
| 643 |
+
<table>
|
| 644 |
+
<thead>
|
| 645 |
+
<tr>
|
| 646 |
+
<th align="left">Metric</th>
|
| 647 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 648 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 649 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 650 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 651 |
+
</tr>
|
| 652 |
+
</thead>
|
| 653 |
+
<tbody>
|
| 654 |
+
<tr>
|
| 655 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 656 |
+
<td><code>frustration</code></td>
|
| 657 |
+
<td><code>confusion</code></td>
|
| 658 |
+
<td><code>Distribution Context</code></td>
|
| 659 |
+
<td><code>frustration</code></td>
|
| 660 |
+
</tr>
|
| 661 |
+
<tr>
|
| 662 |
+
<td><strong>Latency</strong></td>
|
| 663 |
+
<td>2.022s</td>
|
| 664 |
+
<td>1.178s</td>
|
| 665 |
+
<td>1.453s</td>
|
| 666 |
+
<td>1.353s</td>
|
| 667 |
+
</tr>
|
| 668 |
+
<tr>
|
| 669 |
+
<td><strong>Estimated Cost</strong></td>
|
| 670 |
+
<td><code>$0.0000651</code></td>
|
| 671 |
+
<td><code>$0.0000329</code></td>
|
| 672 |
+
<td><code>$0.0000544</code></td>
|
| 673 |
+
<td><code>$0.0000478</code></td>
|
| 674 |
+
</tr>
|
| 675 |
+
<tr>
|
| 676 |
+
<td><strong>Tokens Used</strong></td>
|
| 677 |
+
<td>433 (288 in / 145 out)</td>
|
| 678 |
+
<td>223 (151 in / 72 out)</td>
|
| 679 |
+
<td>353 (229 in / 124 out)</td>
|
| 680 |
+
<td>323 (218 in / 105 out)</td>
|
| 681 |
+
</tr>
|
| 682 |
+
</tbody>
|
| 683 |
+
</table>
|
| 684 |
+
|
| 685 |
+
### Option A Response
|
| 686 |
+
I hear your frustration; it is incredibly taxing when rules seem to contradict themselves constantly. Think about how English has evolved over centuries by borrowing words from Latin, French, German, and Greek. If a word keeps its original spelling from another language, does it still have to follow the rules of the language it was borrowed into? What might happen to a word's spelling if it retains its "foreign" roots?
|
| 687 |
+
|
| 688 |
+
### Option B Response
|
| 689 |
+
Have you ever noticed how many words in English look like they were borrowed from other languages, such as French, Latin, or German? If you look at the history of a word that seems 'weirdly' spelled, what do you think you might discover about its origins?
|
| 690 |
+
|
| 691 |
+
### Option C Response
|
| 692 |
+
I hear your frustration; it truly feels like English spelling is a chaotic puzzle rather than a set of logical rules. Have you ever wondered why we use the same alphabet for words that originated from completely different languages, like Latin, French, and Germanic roots? If you look at the history of a word like "knight" or "island," what do you notice about the letters that seem "extra" or silent? Could those silent letters be remnants of how the words were pronounced hundreds of years ago?
|
| 693 |
+
|
| 694 |
+
### Option D Response
|
| 695 |
+
I hear your frustration; it is incredibly taxing when rules seem to contradict themselves constantly. Think about how English has evolved over centuries by borrowing words from Latin, French, German, and Greek. If a word keeps its original spelling from another language, does it still have to follow the rules of the language it was borrowed into? What might happen to a word's spelling if it retains its "foreign" roots?
|
| 696 |
+
|
| 697 |
+
---
|
| 698 |
+
|
| 699 |
+
## [2026-06-28 18:20:00] Query: "Ugh, why do we have to learn about sedimentary rocks? They just sit there. Who cares?"
|
| 700 |
+
|
| 701 |
+
<table>
|
| 702 |
+
<thead>
|
| 703 |
+
<tr>
|
| 704 |
+
<th align="left">Metric</th>
|
| 705 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 706 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 707 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 708 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 709 |
+
</tr>
|
| 710 |
+
</thead>
|
| 711 |
+
<tbody>
|
| 712 |
+
<tr>
|
| 713 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 714 |
+
<td><code>boredom</code></td>
|
| 715 |
+
<td><code>boredom</code></td>
|
| 716 |
+
<td><code>Distribution Context</code></td>
|
| 717 |
+
<td><code>frustration</code></td>
|
| 718 |
+
</tr>
|
| 719 |
+
<tr>
|
| 720 |
+
<td><strong>Latency</strong></td>
|
| 721 |
+
<td>2.001s</td>
|
| 722 |
+
<td>1.212s</td>
|
| 723 |
+
<td>1.362s</td>
|
| 724 |
+
<td>1.317s</td>
|
| 725 |
+
</tr>
|
| 726 |
+
<tr>
|
| 727 |
+
<td><strong>Estimated Cost</strong></td>
|
| 728 |
+
<td><code>$0.0000589</code></td>
|
| 729 |
+
<td><code>$0.0000266</code></td>
|
| 730 |
+
<td><code>$0.0000415</code></td>
|
| 731 |
+
<td><code>$0.0000401</code></td>
|
| 732 |
+
</tr>
|
| 733 |
+
<tr>
|
| 734 |
+
<td><strong>Tokens Used</strong></td>
|
| 735 |
+
<td>411 (286 in / 125 out)</td>
|
| 736 |
+
<td>199 (147 in / 52 out)</td>
|
| 737 |
+
<td>307 (225 in / 82 out)</td>
|
| 738 |
+
<td>295 (215 in / 80 out)</td>
|
| 739 |
+
</tr>
|
| 740 |
+
</tbody>
|
| 741 |
+
</table>
|
| 742 |
+
|
| 743 |
+
### Option A Response
|
| 744 |
+
Think of sedimentary rocks as the Earth's personal diary, where every layer is a page recording a specific moment in history. If you were to bury a modern object in your backyard today, what kind of "layer" would it create for someone digging it up a thousand years from now? How might that object tell a story about our current civilization?
|
| 745 |
+
|
| 746 |
+
### Option B Response
|
| 747 |
+
If you found a layer of rock containing fossils of sea creatures on top of a mountain, what might that tell you about how the Earth's surface has changed over millions of years?
|
| 748 |
+
|
| 749 |
+
### Option C Response
|
| 750 |
+
I hear your frustration; it can certainly feel like they’re just static piles of dirt. If you look closely at a cliff face with layers, what do you think those layers might be trying to tell us about the history of that specific spot? What could cause different materials to stack up on top of each other over millions of years?
|
| 751 |
+
|
| 752 |
+
### Option D Response
|
| 753 |
+
I hear your frustration; it can feel like they’re just static piles of dirt. However, think of them as the Earth's diary—what might you learn about the past if you could read the layers of a canyon wall? If you found a fossil of a fish in a rock on top of a mountain, what would that tell you about that location's history?
|
| 754 |
+
|
| 755 |
+
---
|
| 756 |
+
|
| 757 |
+
## [2026-06-28 18:20:42] Query: "This math worksheet is just 50 of the same exact addition problems. This is so boring."
|
| 758 |
+
|
| 759 |
+
<table>
|
| 760 |
+
<thead>
|
| 761 |
+
<tr>
|
| 762 |
+
<th align="left">Metric</th>
|
| 763 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 764 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 765 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 766 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 767 |
+
</tr>
|
| 768 |
+
</thead>
|
| 769 |
+
<tbody>
|
| 770 |
+
<tr>
|
| 771 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 772 |
+
<td><code>boredom</code></td>
|
| 773 |
+
<td><code>boredom</code></td>
|
| 774 |
+
<td><code>Distribution Context</code></td>
|
| 775 |
+
<td><code>frustration</code></td>
|
| 776 |
+
</tr>
|
| 777 |
+
<tr>
|
| 778 |
+
<td><strong>Latency</strong></td>
|
| 779 |
+
<td>36.617s</td>
|
| 780 |
+
<td>1.234s</td>
|
| 781 |
+
<td>1.467s</td>
|
| 782 |
+
<td>1.221s</td>
|
| 783 |
+
</tr>
|
| 784 |
+
<tr>
|
| 785 |
+
<td><strong>Estimated Cost</strong></td>
|
| 786 |
+
<td><code>$0.0000562</code></td>
|
| 787 |
+
<td><code>$0.0000285</code></td>
|
| 788 |
+
<td><code>$0.0000511</code></td>
|
| 789 |
+
<td><code>$0.0000410</code></td>
|
| 790 |
+
</tr>
|
| 791 |
+
<tr>
|
| 792 |
+
<td><strong>Tokens Used</strong></td>
|
| 793 |
+
<td>402 (286 in / 116 out)</td>
|
| 794 |
+
<td>206 (148 in / 58 out)</td>
|
| 795 |
+
<td>339 (225 in / 114 out)</td>
|
| 796 |
+
<td>298 (215 in / 83 out)</td>
|
| 797 |
+
</tr>
|
| 798 |
+
</tbody>
|
| 799 |
+
</table>
|
| 800 |
+
|
| 801 |
+
### Option A Response
|
| 802 |
+
I hear your frustration; repetitive drills can feel like a chore. Instead of the worksheet, imagine you are a shopkeeper who needs to calculate the total cost of items for a customer. If a customer buys a toy for $15 and a book for $20, how would you mentally group those numbers to find the total quickly?
|
| 803 |
+
|
| 804 |
+
### Option B Response
|
| 805 |
+
Since you already know how to solve these, could you find a way to group them or use a pattern to calculate the total sum of all 50 problems at once? What happens if you look at the numbers in pairs?
|
| 806 |
+
|
| 807 |
+
### Option C Response
|
| 808 |
+
I hear your frustration; repeating the same task can certainly feel tedious. Since you already have the process down, what do you think would happen if you tried to solve these using a different method, like mental math or grouping them by tens? Or, if you're up for a challenge, could you find a way to represent these problems using a visual pattern instead of just writing numbers? Which approach sounds like it might make the work feel less like a chore?
|
| 809 |
+
|
| 810 |
+
### Option D Response
|
| 811 |
+
I hear your frustration; repetitive tasks can feel incredibly draining when you already understand the concept. Instead of grinding through them one by one, could you look for a pattern or a shortcut that might group these problems together? If you were to solve just the first five, what strategy could you apply to speed up the rest?
|
| 812 |
+
|
| 813 |
+
---
|
| 814 |
+
|
| 815 |
+
## [2026-06-28 18:20:49] Query: "Reading about the ancient Egyptians is okay, but copying these definitions is putting me to sleep."
|
| 816 |
+
|
| 817 |
+
<table>
|
| 818 |
+
<thead>
|
| 819 |
+
<tr>
|
| 820 |
+
<th align="left">Metric</th>
|
| 821 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 822 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 823 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 824 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 825 |
+
</tr>
|
| 826 |
+
</thead>
|
| 827 |
+
<tbody>
|
| 828 |
+
<tr>
|
| 829 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 830 |
+
<td><code>boredom</code></td>
|
| 831 |
+
<td><code>boredom</code></td>
|
| 832 |
+
<td><code>Distribution Context</code></td>
|
| 833 |
+
<td><code>frustration</code></td>
|
| 834 |
+
</tr>
|
| 835 |
+
<tr>
|
| 836 |
+
<td><strong>Latency</strong></td>
|
| 837 |
+
<td>1.877s</td>
|
| 838 |
+
<td>1.159s</td>
|
| 839 |
+
<td>1.355s</td>
|
| 840 |
+
<td>1.388s</td>
|
| 841 |
+
</tr>
|
| 842 |
+
<tr>
|
| 843 |
+
<td><strong>Estimated Cost</strong></td>
|
| 844 |
+
<td><code>$0.0000603</code></td>
|
| 845 |
+
<td><code>$0.0000296</code></td>
|
| 846 |
+
<td><code>$0.0000393</code></td>
|
| 847 |
+
<td><code>$0.0000470</code></td>
|
| 848 |
+
</tr>
|
| 849 |
+
<tr>
|
| 850 |
+
<td><strong>Tokens Used</strong></td>
|
| 851 |
+
<td>420 (292 in / 128 out)</td>
|
| 852 |
+
<td>212 (151 in / 61 out)</td>
|
| 853 |
+
<td>302 (228 in / 74 out)</td>
|
| 854 |
+
<td>320 (218 in / 102 out)</td>
|
| 855 |
+
</tr>
|
| 856 |
+
</tbody>
|
| 857 |
+
</table>
|
| 858 |
+
|
| 859 |
+
### Option A Response
|
| 860 |
+
If reading feels like a chore, let’s try being an architect instead. Imagine you are the royal engineer tasked with designing a tomb that will last for eternity; what is the single most important feature you would include to protect the Pharaoh's legacy? How would you use the geography of the Nile to ensure your construction materials arrive on time?
|
| 861 |
+
|
| 862 |
+
### Option B Response
|
| 863 |
+
If you were tasked with building a monument that would last for thousands of years, what specific challenges do you think you would face first? How might those challenges change the way you organize your society?
|
| 864 |
+
|
| 865 |
+
### Option C Response
|
| 866 |
+
I hear your frustration; rote memorization can certainly feel like a chore. Instead of just reading definitions, what if we looked at the *purpose* behind these Egyptian practices? If you were an architect in ancient Egypt, what specific problem would you be trying to solve by building a pyramid?
|
| 867 |
+
|
| 868 |
+
### Option D Response
|
| 869 |
+
I hear your frustration; rote copying is rarely the best way to engage with history. Instead of transcribing, what if you tried to visualize how these concepts actually functioned in their daily lives? If you were an ancient Egyptian, which of these definitions would have the biggest impact on your survival or status? What is one specific term that stands out to you as particularly strange or interesting?
|
| 870 |
+
|
| 871 |
+
---
|
| 872 |
+
|
| 873 |
+
## [2026-06-28 18:20:57] Query: "We are just sitting here listening to a long lecture about grammar. When do we get to do something fun?"
|
| 874 |
+
|
| 875 |
+
<table>
|
| 876 |
+
<thead>
|
| 877 |
+
<tr>
|
| 878 |
+
<th align="left">Metric</th>
|
| 879 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 880 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 881 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 882 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 883 |
+
</tr>
|
| 884 |
+
</thead>
|
| 885 |
+
<tbody>
|
| 886 |
+
<tr>
|
| 887 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 888 |
+
<td><code>boredom</code></td>
|
| 889 |
+
<td><code>boredom</code></td>
|
| 890 |
+
<td><code>Distribution Context</code></td>
|
| 891 |
+
<td><code>boredom</code></td>
|
| 892 |
+
</tr>
|
| 893 |
+
<tr>
|
| 894 |
+
<td><strong>Latency</strong></td>
|
| 895 |
+
<td>1.98s</td>
|
| 896 |
+
<td>1.207s</td>
|
| 897 |
+
<td>1.334s</td>
|
| 898 |
+
<td>1.37s</td>
|
| 899 |
+
</tr>
|
| 900 |
+
<tr>
|
| 901 |
+
<td><strong>Estimated Cost</strong></td>
|
| 902 |
+
<td><code>$0.0000575</code></td>
|
| 903 |
+
<td><code>$0.0000264</code></td>
|
| 904 |
+
<td><code>$0.0000386</code></td>
|
| 905 |
+
<td><code>$0.0000402</code></td>
|
| 906 |
+
</tr>
|
| 907 |
+
<tr>
|
| 908 |
+
<td><strong>Tokens Used</strong></td>
|
| 909 |
+
<td>413 (295 in / 118 out)</td>
|
| 910 |
+
<td>202 (152 in / 50 out)</td>
|
| 911 |
+
<td>301 (230 in / 71 out)</td>
|
| 912 |
+
<td>302 (224 in / 78 out)</td>
|
| 913 |
+
</tr>
|
| 914 |
+
</tbody>
|
| 915 |
+
</table>
|
| 916 |
+
|
| 917 |
+
### Option A Response
|
| 918 |
+
Grammar can feel like a dry list of rules, but it’s actually the "source code" for how we influence others. Instead of listening, let’s play a game: try to write a single sentence that changes its entire meaning just by moving one comma. What happens to the sentence "Let's eat, Grandma" if you remove that comma?
|
| 919 |
+
|
| 920 |
+
### Option B Response
|
| 921 |
+
Grammar is often the hidden engine behind the stories we love; if you could rewrite one rule of language to make a sentence sound more exciting, what would you change?
|
| 922 |
+
|
| 923 |
+
### Option C Response
|
| 924 |
+
It sounds like you're ready to move beyond just listening and start applying these rules. If you could design a game or a challenge to test these grammar concepts, what would it look like? What specific part of the lecture do you think would be most interesting to turn into an activity?
|
| 925 |
+
|
| 926 |
+
### Option D Response
|
| 927 |
+
Grammar can feel like a dry list of rules, but it’s actually the "source code" for how we influence others. Instead of listening, let’s play a game: try to write a single sentence that changes its entire meaning just by moving one comma. What happens to the sentence "Let's eat, Grandma" if you remove that comma?
|
| 928 |
+
|
| 929 |
+
---
|
| 930 |
+
|
| 931 |
+
## [2026-06-28 18:21:38] Query: "I finished all my science reading early. There's nothing else to do except stare at the wall."
|
| 932 |
+
|
| 933 |
+
<table>
|
| 934 |
+
<thead>
|
| 935 |
+
<tr>
|
| 936 |
+
<th align="left">Metric</th>
|
| 937 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 938 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 939 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 940 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 941 |
+
</tr>
|
| 942 |
+
</thead>
|
| 943 |
+
<tbody>
|
| 944 |
+
<tr>
|
| 945 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 946 |
+
<td><code>boredom</code></td>
|
| 947 |
+
<td><code>boredom</code></td>
|
| 948 |
+
<td><code>Distribution Context</code></td>
|
| 949 |
+
<td><code>boredom</code></td>
|
| 950 |
+
</tr>
|
| 951 |
+
<tr>
|
| 952 |
+
<td><strong>Latency</strong></td>
|
| 953 |
+
<td>20.305s</td>
|
| 954 |
+
<td>16.986s</td>
|
| 955 |
+
<td>1.245s</td>
|
| 956 |
+
<td>1.226s</td>
|
| 957 |
+
</tr>
|
| 958 |
+
<tr>
|
| 959 |
+
<td><strong>Estimated Cost</strong></td>
|
| 960 |
+
<td><code>$0.0000562</code></td>
|
| 961 |
+
<td><code>$0.0000313</code></td>
|
| 962 |
+
<td><code>$0.0000383</code></td>
|
| 963 |
+
<td><code>$0.0000391</code></td>
|
| 964 |
+
</tr>
|
| 965 |
+
<tr>
|
| 966 |
+
<td><strong>Tokens Used</strong></td>
|
| 967 |
+
<td>405 (290 in / 115 out)</td>
|
| 968 |
+
<td>216 (149 in / 67 out)</td>
|
| 969 |
+
<td>298 (227 in / 71 out)</td>
|
| 970 |
+
<td>297 (222 in / 75 out)</td>
|
| 971 |
+
</tr>
|
| 972 |
+
</tbody>
|
| 973 |
+
</table>
|
| 974 |
+
|
| 975 |
+
### Option A Response
|
| 976 |
+
Since you've mastered the reading, let's test those concepts in the real world. If you were to build a model of the system you just studied using only items found in your room, what would you choose to represent the core components? How would those items interact to mimic the process you read about?
|
| 977 |
+
|
| 978 |
+
### Option B Response
|
| 979 |
+
Since you've mastered the reading, what is one specific concept from the text that still feels a bit fuzzy or counterintuitive to you? Could you try to explain that concept to an imaginary student using only simple, everyday analogies?
|
| 980 |
+
|
| 981 |
+
### Option C Response
|
| 982 |
+
It sounds like you have a surplus of curiosity and time. Since you've mastered the reading, what is one concept from the text that still feels a bit "fuzzy" or counterintuitive to you? Could you try to design a simple experiment or thought exercise to test that specific idea right now?
|
| 983 |
+
|
| 984 |
+
### Option D Response
|
| 985 |
+
Since you've mastered the reading, let's test those concepts in the real world. If you were to build a model of the system you just studied using only items found in your room, what would you choose to represent the core components? How would those items interact to mimic the process you read about?
|
| 986 |
+
|
| 987 |
+
---
|
| 988 |
+
|
| 989 |
+
## [2026-06-28 18:21:45] Query: "I totally mastered multiplying fractions! Give me a hard practice problem to try!"
|
| 990 |
+
|
| 991 |
+
<table>
|
| 992 |
+
<thead>
|
| 993 |
+
<tr>
|
| 994 |
+
<th align="left">Metric</th>
|
| 995 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 996 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 997 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 998 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 999 |
+
</tr>
|
| 1000 |
+
</thead>
|
| 1001 |
+
<tbody>
|
| 1002 |
+
<tr>
|
| 1003 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1004 |
+
<td><code>confidence</code></td>
|
| 1005 |
+
<td><code>confidence</code></td>
|
| 1006 |
+
<td><code>Distribution Context</code></td>
|
| 1007 |
+
<td><code>confidence</code></td>
|
| 1008 |
+
</tr>
|
| 1009 |
+
<tr>
|
| 1010 |
+
<td><strong>Latency</strong></td>
|
| 1011 |
+
<td>1.836s</td>
|
| 1012 |
+
<td>1.167s</td>
|
| 1013 |
+
<td>1.267s</td>
|
| 1014 |
+
<td>1.314s</td>
|
| 1015 |
+
</tr>
|
| 1016 |
+
<tr>
|
| 1017 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1018 |
+
<td><code>$0.0000493</code></td>
|
| 1019 |
+
<td><code>$0.0000247</code></td>
|
| 1020 |
+
<td><code>$0.0000420</code></td>
|
| 1021 |
+
<td><code>$0.0000324</code></td>
|
| 1022 |
+
</tr>
|
| 1023 |
+
<tr>
|
| 1024 |
+
<td><strong>Tokens Used</strong></td>
|
| 1025 |
+
<td>363 (265 in / 98 out)</td>
|
| 1026 |
+
<td>192 (146 in / 46 out)</td>
|
| 1027 |
+
<td>308 (224 in / 84 out)</td>
|
| 1028 |
+
<td>258 (200 in / 58 out)</td>
|
| 1029 |
+
</tr>
|
| 1030 |
+
</tbody>
|
| 1031 |
+
</table>
|
| 1032 |
+
|
| 1033 |
+
### Option A Response
|
| 1034 |
+
That is fantastic news; your confidence is well-earned! To test your mastery, try multiplying these three fractions: 2/3 × 9/4 × 1/6. Before you calculate, can you identify any opportunities to simplify the terms before multiplying?
|
| 1035 |
+
|
| 1036 |
+
### Option B Response
|
| 1037 |
+
That is fantastic! If you have two fractions, how would you approach multiplying them if one of them is a mixed number instead of a simple fraction?
|
| 1038 |
+
|
| 1039 |
+
### Option C Response
|
| 1040 |
+
That is fantastic progress! Since you have mastered the basics, let's see how you handle a multi-step challenge.
|
| 1041 |
+
|
| 1042 |
+
What happens if you multiply three fractions together, where one is a mixed number and another is a whole number? Try calculating: $2 \frac{1}{2} \times \frac{4}{5} \times 3$. How would you begin to simplify that expression?
|
| 1043 |
+
|
| 1044 |
+
### Option D Response
|
| 1045 |
+
That is fantastic news; your confidence is well-earned! To test your mastery, try multiplying these three fractions: 2/3 × 9/4 × 1/6. Before you calculate, can you identify any opportunities to simplify the terms before multiplying?
|
| 1046 |
+
|
| 1047 |
+
---
|
| 1048 |
+
|
| 1049 |
+
## [2026-06-28 18:21:52] Query: "I just finished writing my first short story in English and it has a really cool plot twist!"
|
| 1050 |
+
|
| 1051 |
+
<table>
|
| 1052 |
+
<thead>
|
| 1053 |
+
<tr>
|
| 1054 |
+
<th align="left">Metric</th>
|
| 1055 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 1056 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 1057 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 1058 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 1059 |
+
</tr>
|
| 1060 |
+
</thead>
|
| 1061 |
+
<tbody>
|
| 1062 |
+
<tr>
|
| 1063 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1064 |
+
<td><code>confidence</code></td>
|
| 1065 |
+
<td><code>confidence</code></td>
|
| 1066 |
+
<td><code>Distribution Context</code></td>
|
| 1067 |
+
<td><code>confidence</code></td>
|
| 1068 |
+
</tr>
|
| 1069 |
+
<tr>
|
| 1070 |
+
<td><strong>Latency</strong></td>
|
| 1071 |
+
<td>1.793s</td>
|
| 1072 |
+
<td>1.173s</td>
|
| 1073 |
+
<td>1.296s</td>
|
| 1074 |
+
<td>1.276s</td>
|
| 1075 |
+
</tr>
|
| 1076 |
+
<tr>
|
| 1077 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1078 |
+
<td><code>$0.0000521</code></td>
|
| 1079 |
+
<td><code>$0.0000217</code></td>
|
| 1080 |
+
<td><code>$0.0000332</code></td>
|
| 1081 |
+
<td><code>$0.0000350</code></td>
|
| 1082 |
+
</tr>
|
| 1083 |
+
<tr>
|
| 1084 |
+
<td><strong>Tokens Used</strong></td>
|
| 1085 |
+
<td>377 (271 in / 106 out)</td>
|
| 1086 |
+
<td>184 (149 in / 35 out)</td>
|
| 1087 |
+
<td>281 (227 in / 54 out)</td>
|
| 1088 |
+
<td>269 (203 in / 66 out)</td>
|
| 1089 |
+
</tr>
|
| 1090 |
+
</tbody>
|
| 1091 |
+
</table>
|
| 1092 |
+
|
| 1093 |
+
### Option A Response
|
| 1094 |
+
That is a fantastic milestone—congratulations on completing your first story! Since you're feeling confident, let's test the impact of that twist. How does the information revealed in the twist change the reader's interpretation of the protagonist's earlier actions?
|
| 1095 |
+
|
| 1096 |
+
### Option B Response
|
| 1097 |
+
That sounds exciting! What is the core conflict that leads your protagonist toward that unexpected twist?
|
| 1098 |
+
|
| 1099 |
+
### Option C Response
|
| 1100 |
+
That sounds like a fantastic achievement! Since you're feeling so confident, let's put that plot twist to the test. How does the reader discover the clues leading up to that moment without guessing the ending too early?
|
| 1101 |
+
|
| 1102 |
+
### Option D Response
|
| 1103 |
+
That is a fantastic milestone—congratulations on completing your first story! Since you're feeling confident, let's test the impact of that twist. How does the information revealed in the twist change the reader's interpretation of the protagonist's earlier actions?
|
| 1104 |
+
|
| 1105 |
+
---
|
| 1106 |
+
|
| 1107 |
+
## [2026-06-28 18:22:34] Query: "I know exactly why the American Revolution started. It was because of tax without representation!"
|
| 1108 |
+
|
| 1109 |
+
<table>
|
| 1110 |
+
<thead>
|
| 1111 |
+
<tr>
|
| 1112 |
+
<th align="left">Metric</th>
|
| 1113 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 1114 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 1115 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 1116 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 1117 |
+
</tr>
|
| 1118 |
+
</thead>
|
| 1119 |
+
<tbody>
|
| 1120 |
+
<tr>
|
| 1121 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1122 |
+
<td><code>confidence</code></td>
|
| 1123 |
+
<td><code>confidence</code></td>
|
| 1124 |
+
<td><code>Distribution Context</code></td>
|
| 1125 |
+
<td><code>frustration</code></td>
|
| 1126 |
+
</tr>
|
| 1127 |
+
<tr>
|
| 1128 |
+
<td><strong>Latency</strong></td>
|
| 1129 |
+
<td>2.003s</td>
|
| 1130 |
+
<td>1.15s</td>
|
| 1131 |
+
<td>35.508s</td>
|
| 1132 |
+
<td>1.382s</td>
|
| 1133 |
+
</tr>
|
| 1134 |
+
<tr>
|
| 1135 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1136 |
+
<td><code>$0.0000538</code></td>
|
| 1137 |
+
<td><code>$0.0000278</code></td>
|
| 1138 |
+
<td><code>$0.0000447</code></td>
|
| 1139 |
+
<td><code>$0.0000472</code></td>
|
| 1140 |
+
</tr>
|
| 1141 |
+
<tr>
|
| 1142 |
+
<td><strong>Tokens Used</strong></td>
|
| 1143 |
+
<td>384 (273 in / 111 out)</td>
|
| 1144 |
+
<td>205 (150 in / 55 out)</td>
|
| 1145 |
+
<td>320 (228 in / 92 out)</td>
|
| 1146 |
+
<td>321 (218 in / 103 out)</td>
|
| 1147 |
+
</tr>
|
| 1148 |
+
</tbody>
|
| 1149 |
+
</table>
|
| 1150 |
+
|
| 1151 |
+
### Option A Response
|
| 1152 |
+
That is a strong starting point! Since you're confident, let's dig deeper: if taxation was the primary grievance, why didn't the colonists simply demand seats in Parliament instead of seeking total independence? What does that suggest about their underlying view of British authority?
|
| 1153 |
+
|
| 1154 |
+
### Option B Response
|
| 1155 |
+
That is a significant factor, but were there other underlying tensions regarding land expansion or trade restrictions that might have also fueled the colonists' desire for independence?
|
| 1156 |
+
|
| 1157 |
+
### Option C Response
|
| 1158 |
+
It sounds like you have a strong grasp of the most famous grievance. While "taxation without representation" was a major catalyst, what do you think were the underlying tensions regarding how the colonies were governed before those taxes were even introduced? Were there other factors, perhaps involving land or trade, that might have contributed to the growing divide?
|
| 1159 |
+
|
| 1160 |
+
### Option D Response
|
| 1161 |
+
I hear your frustration, and it’s completely understandable to feel like there’s one clear answer when history is so complex. While "taxation without representation" was certainly a major spark, why do you think the colonists were so sensitive to that specific issue at that particular time? Was it just about the money, or was there a deeper shift in how they viewed their relationship with the British government?
|
| 1162 |
+
|
| 1163 |
+
---
|
| 1164 |
+
|
| 1165 |
+
## [2026-06-28 18:22:41] Query: "I got a 100 on my spelling quiz today! I finally studied the right way!"
|
| 1166 |
+
|
| 1167 |
+
<table>
|
| 1168 |
+
<thead>
|
| 1169 |
+
<tr>
|
| 1170 |
+
<th align="left">Metric</th>
|
| 1171 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 1172 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 1173 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 1174 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 1175 |
+
</tr>
|
| 1176 |
+
</thead>
|
| 1177 |
+
<tbody>
|
| 1178 |
+
<tr>
|
| 1179 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1180 |
+
<td><code>confidence</code></td>
|
| 1181 |
+
<td><code>confidence</code></td>
|
| 1182 |
+
<td><code>Distribution Context</code></td>
|
| 1183 |
+
<td><code>confidence</code></td>
|
| 1184 |
+
</tr>
|
| 1185 |
+
<tr>
|
| 1186 |
+
<td><strong>Latency</strong></td>
|
| 1187 |
+
<td>1.726s</td>
|
| 1188 |
+
<td>1.074s</td>
|
| 1189 |
+
<td>1.269s</td>
|
| 1190 |
+
<td>1.18s</td>
|
| 1191 |
+
</tr>
|
| 1192 |
+
<tr>
|
| 1193 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1194 |
+
<td><code>$0.0000528</code></td>
|
| 1195 |
+
<td><code>$0.0000222</code></td>
|
| 1196 |
+
<td><code>$0.0000382</code></td>
|
| 1197 |
+
<td><code>$0.0000361</code></td>
|
| 1198 |
+
</tr>
|
| 1199 |
+
<tr>
|
| 1200 |
+
<td><strong>Tokens Used</strong></td>
|
| 1201 |
+
<td>371 (260 in / 111 out)</td>
|
| 1202 |
+
<td>182 (144 in / 38 out)</td>
|
| 1203 |
+
<td>294 (222 in / 72 out)</td>
|
| 1204 |
+
<td>268 (197 in / 71 out)</td>
|
| 1205 |
+
</tr>
|
| 1206 |
+
</tbody>
|
| 1207 |
+
</table>
|
| 1208 |
+
|
| 1209 |
+
### Option A Response
|
| 1210 |
+
That is a fantastic achievement; your hard work clearly paid off! Since you've mastered your current method, what specific change in your study routine do you think made the biggest difference? How might you apply that same strategy to a more complex subject, like history or science?
|
| 1211 |
+
|
| 1212 |
+
### Option B Response
|
| 1213 |
+
That is a fantastic result! What specific study method did you use this time that made the biggest difference for you?
|
| 1214 |
+
|
| 1215 |
+
### Option C Response
|
| 1216 |
+
That is a fantastic achievement; your hard work clearly paid off! Since you’ve mastered this method, what do you think was the specific element of your new study routine that made the biggest difference? Could you apply that same strategy to a more complex subject, like history or science?
|
| 1217 |
+
|
| 1218 |
+
### Option D Response
|
| 1219 |
+
That is a fantastic achievement; your hard work clearly paid off! Since you've mastered your current method, what specific change in your study routine do you think made the biggest difference? How might you apply that same strategy to a more complex subject, like history or science?
|
| 1220 |
+
|
| 1221 |
+
---
|
| 1222 |
+
|
| 1223 |
+
## [2026-06-28 18:22:48] Query: "I can explain the entire water cycle in my sleep! Evaporation, condensation, precipitation, easy!"
|
| 1224 |
+
|
| 1225 |
+
<table>
|
| 1226 |
+
<thead>
|
| 1227 |
+
<tr>
|
| 1228 |
+
<th align="left">Metric</th>
|
| 1229 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 1230 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 1231 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 1232 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 1233 |
+
</tr>
|
| 1234 |
+
</thead>
|
| 1235 |
+
<tbody>
|
| 1236 |
+
<tr>
|
| 1237 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1238 |
+
<td><code>confidence</code></td>
|
| 1239 |
+
<td><code>confidence</code></td>
|
| 1240 |
+
<td><code>Distribution Context</code></td>
|
| 1241 |
+
<td><code>boredom</code></td>
|
| 1242 |
+
</tr>
|
| 1243 |
+
<tr>
|
| 1244 |
+
<td><strong>Latency</strong></td>
|
| 1245 |
+
<td>1.729s</td>
|
| 1246 |
+
<td>1.185s</td>
|
| 1247 |
+
<td>1.256s</td>
|
| 1248 |
+
<td>1.188s</td>
|
| 1249 |
+
</tr>
|
| 1250 |
+
<tr>
|
| 1251 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1252 |
+
<td><code>$0.0000514</code></td>
|
| 1253 |
+
<td><code>$0.0000299</code></td>
|
| 1254 |
+
<td><code>$0.0000396</code></td>
|
| 1255 |
+
<td><code>$0.0000416</code></td>
|
| 1256 |
+
</tr>
|
| 1257 |
+
<tr>
|
| 1258 |
+
<td><strong>Tokens Used</strong></td>
|
| 1259 |
+
<td>376 (273 in / 103 out)</td>
|
| 1260 |
+
<td>212 (150 in / 62 out)</td>
|
| 1261 |
+
<td>303 (228 in / 75 out)</td>
|
| 1262 |
+
<td>306 (223 in / 83 out)</td>
|
| 1263 |
+
</tr>
|
| 1264 |
+
</tbody>
|
| 1265 |
+
</table>
|
| 1266 |
+
|
| 1267 |
+
### Option A Response
|
| 1268 |
+
That is fantastic confidence! Since you have the basics down, let's test your depth: what specific role does the sun play in driving the transition from evaporation to condensation? How would the cycle change if the sun's energy output suddenly decreased?
|
| 1269 |
+
|
| 1270 |
+
### Option B Response
|
| 1271 |
+
That is a great foundation! Since you have the basics down, have you ever considered what role the sun plays in driving these processes, or how gravity influences the movement of water once it reaches the ground?
|
| 1272 |
+
|
| 1273 |
+
### Option C Response
|
| 1274 |
+
That is a solid foundation! Since you have the basics down, let's look at the "engine" behind it: what specific force or energy source is actually driving the water to move through these stages in the first place? If you were to remove that energy source, what do you think would happen to the cycle?
|
| 1275 |
+
|
| 1276 |
+
### Option D Response
|
| 1277 |
+
Since you've mastered the cycle, let's see how it behaves in a closed system. If you were to build a "mini-Earth" inside a sealed glass jar, what specific variables would you need to control to keep the cycle running indefinitely? What would happen to the water levels if you placed that jar in a dark closet versus a sunny windowsill?
|
| 1278 |
+
|
| 1279 |
+
---
|
| 1280 |
+
|
| 1281 |
+
## [2026-06-28 18:23:48] Query: "I'm looking at this cell diagram and I can't tell the difference between the cell wall and the cell membrane."
|
| 1282 |
+
|
| 1283 |
+
<table>
|
| 1284 |
+
<thead>
|
| 1285 |
+
<tr>
|
| 1286 |
+
<th align="left">Metric</th>
|
| 1287 |
+
<th align="left">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>
|
| 1288 |
+
<th align="left">Option B (Gemini Single-Pass)</th>
|
| 1289 |
+
<th align="left">Option C (DistilRoBERTa Distribution + Gemini)</th>
|
| 1290 |
+
<th align="left">Option D (DistilRoBERTa Classifier + Gemini)</th>
|
| 1291 |
+
</tr>
|
| 1292 |
+
</thead>
|
| 1293 |
+
<tbody>
|
| 1294 |
+
<tr>
|
| 1295 |
+
<td><strong>Detected Sentiment</strong></td>
|
| 1296 |
+
<td><code>confusion</code></td>
|
| 1297 |
+
<td><code>confusion</code></td>
|
| 1298 |
+
<td><code>Distribution Context</code></td>
|
| 1299 |
+
<td><code>boredom</code></td>
|
| 1300 |
+
</tr>
|
| 1301 |
+
<tr>
|
| 1302 |
+
<td><strong>Latency</strong></td>
|
| 1303 |
+
<td>3.471s</td>
|
| 1304 |
+
<td>34.624s</td>
|
| 1305 |
+
<td>1.347s</td>
|
| 1306 |
+
<td>1.228s</td>
|
| 1307 |
+
</tr>
|
| 1308 |
+
<tr>
|
| 1309 |
+
<td><strong>Estimated Cost</strong></td>
|
| 1310 |
+
<td><code>$0.0000609</code></td>
|
| 1311 |
+
<td><code>$0.0000331</code></td>
|
| 1312 |
+
<td><code>$0.0000383</code></td>
|
| 1313 |
+
<td><code>$0.0000439</code></td>
|
| 1314 |
+
</tr>
|
| 1315 |
+
<tr>
|
| 1316 |
+
<td><strong>Tokens Used</strong></td>
|
| 1317 |
+
<td>422 (292 in / 130 out)</td>
|
| 1318 |
+
<td>225 (153 in / 72 out)</td>
|
| 1319 |
+
<td>301 (231 in / 70 out)</td>
|
| 1320 |
+
<td>316 (226 in / 90 out)</td>
|
| 1321 |
+
</tr>
|
| 1322 |
+
</tbody>
|
| 1323 |
+
</table>
|
| 1324 |
+
|
| 1325 |
+
### Option A Response
|
| 1326 |
+
Think about the primary function of each structure: one provides rigid structural support, while the other acts as a selective gatekeeper for the cell. If you were to look at a plant cell, which layer is on the very outside, and which one sits just inside it? Which of these two do you think would be present in an animal cell, which lacks a rigid outer shell?
|
| 1327 |
+
|
| 1328 |
+
### Option B Response
|
| 1329 |
+
If you look at the outer boundary of the cell, does it appear to be a single layer or are there two distinct borders visible? Think about which of these structures is found in all cells versus which one is specific to plants or bacteria for extra support.
|
| 1330 |
+
|
| 1331 |
+
### Option C Response
|
| 1332 |
+
It sounds like you're looking at the outer boundaries of the cell. If you imagine a house, what part would represent the sturdy, protective outer structure, and what part would act like the doors that decide who comes in or out? Which of those two layers do you think is more rigid?
|
| 1333 |
+
|
| 1334 |
+
### Option D Response
|
| 1335 |
+
Think of a cell like a medieval castle. If the cell membrane is the inner gatekeeper deciding who enters the courtyard, what purpose does the thick, rigid stone wall surrounding the entire castle serve?
|
| 1336 |
+
|
| 1337 |
+
If you were to build a model of this using a balloon and a cardboard box, which one would provide the structure, and which one would hold the "guts" inside?
|
| 1338 |
+
|
| 1339 |
+
---
|
| 1340 |
+
|
Dockerfile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- Stage 1: Build the frontend ---
|
| 2 |
+
FROM node:20-alpine AS frontend-builder
|
| 3 |
+
WORKDIR /app/frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm install
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
RUN npm run build
|
| 8 |
+
|
| 9 |
+
# --- Stage 2: Build the backend & package the app ---
|
| 10 |
+
FROM python:3.11-slim
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Set environment variables
|
| 14 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 15 |
+
ENV PYTHONUNBUFFERED=1
|
| 16 |
+
ENV HF_HOME=/tmp/hf_cache
|
| 17 |
+
ENV PORT=7860
|
| 18 |
+
|
| 19 |
+
# Create writable cache directory for Hugging Face models
|
| 20 |
+
RUN mkdir -p /tmp/hf_cache && chmod 777 /tmp/hf_cache
|
| 21 |
+
|
| 22 |
+
# Copy backend requirements and install dependencies
|
| 23 |
+
COPY backend/requirements.txt ./backend/
|
| 24 |
+
RUN pip install --no-cache-dir -r backend/requirements.txt
|
| 25 |
+
|
| 26 |
+
# Pre-download the Hugging Face models during Docker build time
|
| 27 |
+
# so the Space starts up instantly without timeout.
|
| 28 |
+
RUN python -c "from transformers import pipeline; \
|
| 29 |
+
pipeline('text-classification', model='j-hartmann/emotion-english-distilroberta-base', top_k=None); \
|
| 30 |
+
pipeline('ner', model='dslim/distilbert-NER', aggregation_strategy='simple')"
|
| 31 |
+
|
| 32 |
+
# Copy built frontend assets from stage 1
|
| 33 |
+
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
| 34 |
+
|
| 35 |
+
# Copy backend source code
|
| 36 |
+
COPY backend/ ./backend/
|
| 37 |
+
|
| 38 |
+
# Make the backend directory writable (needed for writing sentiment logs)
|
| 39 |
+
RUN chmod -R 777 /app/backend
|
| 40 |
+
|
| 41 |
+
# Expose port 7860 (Hugging Face Spaces default port)
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
|
| 44 |
+
# Run uvicorn server, binding to the port specified by Hugging Face Spaces
|
| 45 |
+
CMD ["sh", "-c", "uvicorn backend.main:app --host 0.0.0.0 --port ${PORT}"]
|
backend/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (39.6 kB). View file
|
|
|
backend/benchmark.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import urllib.request
|
| 3 |
+
import csv
|
| 4 |
+
import time
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
samples = [
|
| 8 |
+
# Confusion
|
| 9 |
+
("confusion", "Wait, why does a negative times a negative make a positive? I don't get it."),
|
| 10 |
+
("confusion", "I'm looking at this cell diagram and I can't tell the difference between the cell wall and the cell membrane."),
|
| 11 |
+
("confusion", "Our teacher said the Earth is tilted, but how does that make summer and winter? It doesn't make sense."),
|
| 12 |
+
("confusion", "Is a virus alive or is it not? My textbook says both and I'm really mixed up."),
|
| 13 |
+
("confusion", "What is the difference between a variable and a constant in algebra? I'm lost."),
|
| 14 |
+
("confusion", "Why does dividing by a fraction mean multiplying by its reciprocal? It seems arbitrary."),
|
| 15 |
+
("confusion", "What is the difference between speed and velocity? They sound like the same thing."),
|
| 16 |
+
("confusion", "Why is the mitochondria called the powerhouse of the cell? What does it actually do?"),
|
| 17 |
+
# Frustration
|
| 18 |
+
("frustration", "I've tried to solve this quadratic equation three times using the formula, but I keep getting a negative under the square root!"),
|
| 19 |
+
("frustration", "My science experiment failed again! The volcano didn't bubble at all and I did everything exactly right!"),
|
| 20 |
+
("frustration", "This long division with decimals is taking forever and I keep getting the wrong remainder! I hate this!"),
|
| 21 |
+
("frustration", "This word problem about two trains leaving different cities is making my head spin. I hate word problems!"),
|
| 22 |
+
("frustration", "I don't understand how to convert grams to moles. I keep getting the wrong conversion factor and it's so frustrating!"),
|
| 23 |
+
("frustration", "I've tried balancing this chemical equation five times and the numbers never match up!"),
|
| 24 |
+
("frustration", "I'm trying to draw this ray diagram for a concave lens and the lines are crossing in the wrong place. I give up!"),
|
| 25 |
+
("frustration", "This physics problem about friction has too many variables and I don't even know where to start!"),
|
| 26 |
+
# Boredom
|
| 27 |
+
("boredom", "Ugh, why do we have to learn about sedimentary rocks? They just sit there. Who cares?"),
|
| 28 |
+
("boredom", "This math worksheet is just 50 of the same exact addition problems. This is so boring."),
|
| 29 |
+
("boredom", "We are just copying definitions of different math properties from the board. This is so boring."),
|
| 30 |
+
("boredom", "Another lecture on the phases of mitosis... we've covered this three years in a row now."),
|
| 31 |
+
("boredom", "I finished all my science reading early. There's nothing else to do except stare at the wall."),
|
| 32 |
+
("boredom", "We have to measure the temperature of this water every two minutes for an hour. This is so tedious."),
|
| 33 |
+
("boredom", "Calculating the area of twenty slightly different rectangles is putting me to sleep."),
|
| 34 |
+
("boredom", "This lecture on cell organelles is just slides of definitions. I'm falling asleep."),
|
| 35 |
+
# Confidence
|
| 36 |
+
("confidence", "I totally mastered multiplying fractions! Give me a hard practice problem to try!"),
|
| 37 |
+
("confidence", "I just derived the formula for the volume of a sphere all by myself!"),
|
| 38 |
+
("confidence", "I know exactly how to balance any redox reaction now. Try me!"),
|
| 39 |
+
("confidence", "I got a perfect score on the calculus midterm today! I really understand derivatives now!"),
|
| 40 |
+
("confidence", "I can explain the entire water cycle in my sleep! Evaporation, condensation, precipitation, easy!"),
|
| 41 |
+
("confidence", "I just solved the hardest logic puzzle in the workbook on my very first try!"),
|
| 42 |
+
("confidence", "I can calculate the trajectory of a projectile in my head now, it's so easy!"),
|
| 43 |
+
("confidence", "I fully understand how DNA replication works and could draw every step from memory!"),
|
| 44 |
+
# Neutral
|
| 45 |
+
("neutral", "How do I calculate the hypotenuse of a right triangle when the sides are 3 and 4?"),
|
| 46 |
+
("neutral", "What are the three main types of rocks found in the Earth's crust?"),
|
| 47 |
+
("neutral", "Can you explain how photosynthesis converts sunlight into chemical energy?"),
|
| 48 |
+
("neutral", "What is the chemical formula for photosynthesis and cellular respiration?"),
|
| 49 |
+
("neutral", "How do you find the slope of a line from two points on a graph?"),
|
| 50 |
+
("neutral", "What is the difference between an isotope and an ion in chemistry?"),
|
| 51 |
+
("neutral", "Could you list the steps of the scientific method in order?"),
|
| 52 |
+
("neutral", "What is the value of the constant pi, and how is it calculated?")
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
results = []
|
| 56 |
+
|
| 57 |
+
url = "http://127.0.0.1:8000/api/chat"
|
| 58 |
+
|
| 59 |
+
print(f"Starting benchmark for {len(samples)} sample queries...")
|
| 60 |
+
for idx, (cat, q) in enumerate(samples, 1):
|
| 61 |
+
print(f"[{idx}/{len(samples)}] Query ({cat}): \"{q}\"")
|
| 62 |
+
req_data = json.dumps({"message": q}).encode('utf-8')
|
| 63 |
+
req = urllib.request.Request(
|
| 64 |
+
url,
|
| 65 |
+
data=req_data,
|
| 66 |
+
headers={'Content-Type': 'application/json'}
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
with urllib.request.urlopen(req) as response:
|
| 71 |
+
res_data = json.loads(response.read().decode('utf-8'))
|
| 72 |
+
|
| 73 |
+
# Record result
|
| 74 |
+
results.append({
|
| 75 |
+
"category": cat,
|
| 76 |
+
"query": q,
|
| 77 |
+
"latency_a": res_data["latency_a"],
|
| 78 |
+
"tokens_a": res_data["tokens_a"],
|
| 79 |
+
"latency_b": res_data["latency_b"],
|
| 80 |
+
"tokens_b": res_data["tokens_b"],
|
| 81 |
+
"latency_c": res_data["latency_c"],
|
| 82 |
+
"tokens_c": res_data["tokens_c"],
|
| 83 |
+
"latency_d": res_data["latency_d"],
|
| 84 |
+
"tokens_d": res_data["tokens_d"]
|
| 85 |
+
})
|
| 86 |
+
print(f" Done: A ({res_data['latency_a']}s, {res_data['tokens_a']}t) | B ({res_data['latency_b']}s, {res_data['tokens_b']}t) | C ({res_data['latency_c']}s, {res_data['tokens_c']}t) | D ({res_data['latency_d']}s, {res_data['tokens_d']}t)")
|
| 87 |
+
# Add a small delay between requests
|
| 88 |
+
time.sleep(1.5)
|
| 89 |
+
except Exception as e:
|
| 90 |
+
print(f" Error processing query: {e}")
|
| 91 |
+
|
| 92 |
+
# Save to CSV
|
| 93 |
+
csv_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "benchmark_results.csv")
|
| 94 |
+
with open(csv_file, mode="w", newline="", encoding="utf-8") as f:
|
| 95 |
+
writer = csv.writer(f)
|
| 96 |
+
writer.writerow([
|
| 97 |
+
"Category", "Query",
|
| 98 |
+
"Latency A (s)", "Tokens A",
|
| 99 |
+
"Latency B (s)", "Tokens B",
|
| 100 |
+
"Latency C (s)", "Tokens C",
|
| 101 |
+
"Latency D (s)", "Tokens D"
|
| 102 |
+
])
|
| 103 |
+
|
| 104 |
+
for r in results:
|
| 105 |
+
writer.writerow([
|
| 106 |
+
r["category"], r["query"],
|
| 107 |
+
r["latency_a"], r["tokens_a"],
|
| 108 |
+
r["latency_b"], r["tokens_b"],
|
| 109 |
+
r["latency_c"], r["tokens_c"],
|
| 110 |
+
r["latency_d"], r["tokens_d"]
|
| 111 |
+
])
|
| 112 |
+
|
| 113 |
+
print(f"\nResults successfully saved to: {csv_file}")
|
| 114 |
+
|
| 115 |
+
# Calculate averages
|
| 116 |
+
num_queries = len(results)
|
| 117 |
+
if num_queries > 0:
|
| 118 |
+
valid_latencies_a = [r["latency_a"] for r in results if r["latency_a"] is not None]
|
| 119 |
+
valid_tokens_a = [r["tokens_a"] for r in results if r["tokens_a"] is not None]
|
| 120 |
+
valid_latencies_b = [r["latency_b"] for r in results if r["latency_b"] is not None]
|
| 121 |
+
valid_tokens_b = [r["tokens_b"] for r in results if r["tokens_b"] is not None]
|
| 122 |
+
valid_latencies_c = [r["latency_c"] for r in results if r["latency_c"] is not None]
|
| 123 |
+
valid_tokens_c = [r["tokens_c"] for r in results if r["tokens_c"] is not None]
|
| 124 |
+
valid_latencies_d = [r["latency_d"] for r in results if r["latency_d"] is not None]
|
| 125 |
+
valid_tokens_d = [r["tokens_d"] for r in results if r["tokens_d"] is not None]
|
| 126 |
+
|
| 127 |
+
avg_latency_a = sum(valid_latencies_a) / len(valid_latencies_a) if valid_latencies_a else 0
|
| 128 |
+
avg_tokens_a = sum(valid_tokens_a) / len(valid_tokens_a) if valid_tokens_a else 0
|
| 129 |
+
avg_latency_b = sum(valid_latencies_b) / len(valid_latencies_b) if valid_latencies_b else 0
|
| 130 |
+
avg_tokens_b = sum(valid_tokens_b) / len(valid_tokens_b) if valid_tokens_b else 0
|
| 131 |
+
avg_latency_c = sum(valid_latencies_c) / len(valid_latencies_c) if valid_latencies_c else 0
|
| 132 |
+
avg_tokens_c = sum(valid_tokens_c) / len(valid_tokens_c) if valid_tokens_c else 0
|
| 133 |
+
avg_latency_d = sum(valid_latencies_d) / len(valid_latencies_d) if valid_latencies_d else 0
|
| 134 |
+
avg_tokens_d = sum(valid_tokens_d) / len(valid_tokens_d) if valid_tokens_d else 0
|
| 135 |
+
|
| 136 |
+
print("\n" + "="*50)
|
| 137 |
+
print("BENCHMARK SUMMARY AVERAGES:")
|
| 138 |
+
print("="*50)
|
| 139 |
+
print(f"Option A (Gemini LLM-Classifier):")
|
| 140 |
+
print(f" - Avg Latency: {avg_latency_a:.3f}s")
|
| 141 |
+
print(f" - Avg Tokens: {avg_tokens_a:.1f}")
|
| 142 |
+
print(f"Option B (Gemini Single-Pass):")
|
| 143 |
+
print(f" - Avg Latency: {avg_latency_b:.3f}s")
|
| 144 |
+
print(f" - Avg Tokens: {avg_tokens_b:.1f}")
|
| 145 |
+
print(f"Option C (Raw Distribution + Gemini):")
|
| 146 |
+
print(f" - Avg Latency: {avg_latency_c:.3f}s")
|
| 147 |
+
print(f" - Avg Tokens: {avg_tokens_c:.1f}")
|
| 148 |
+
print(f"Option D (Local-Classifier + Gemini):")
|
| 149 |
+
print(f" - Avg Latency: {avg_latency_d:.3f}s")
|
| 150 |
+
print(f" - Avg Tokens: {avg_tokens_d:.1f}")
|
| 151 |
+
print("="*50)
|
backend/benchmark_b.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import urllib.request
|
| 3 |
+
import csv
|
| 4 |
+
import time
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
samples = [
|
| 8 |
+
# Confusion
|
| 9 |
+
("confusion", "Wait, why does a negative times a negative make a positive? I don't get it."),
|
| 10 |
+
("confusion", "I'm looking at this cell diagram and I can't tell the difference between the cell wall and the cell membrane."),
|
| 11 |
+
("confusion", "Our teacher said the Earth is tilted, but how does that make summer and winter? It doesn't make sense."),
|
| 12 |
+
("confusion", "Is a virus alive or is it not? My textbook says both and I'm really mixed up."),
|
| 13 |
+
("confusion", "What is the difference between a variable and a constant in algebra? I'm lost."),
|
| 14 |
+
("confusion", "Why does dividing by a fraction mean multiplying by its reciprocal? It seems arbitrary."),
|
| 15 |
+
("confusion", "What is the difference between speed and velocity? They sound like the same thing."),
|
| 16 |
+
("confusion", "Why is the mitochondria called the powerhouse of the cell? What does it actually do?"),
|
| 17 |
+
# Frustration
|
| 18 |
+
("frustration", "I've tried to solve this quadratic equation three times using the formula, but I keep getting a negative under the square root!"),
|
| 19 |
+
("frustration", "My science experiment failed again! The volcano didn't bubble at all and I did everything exactly right!"),
|
| 20 |
+
("frustration", "This long division with decimals is taking forever and I keep getting the wrong remainder! I hate this!"),
|
| 21 |
+
("frustration", "This word problem about two trains leaving different cities is making my head spin. I hate word problems!"),
|
| 22 |
+
("frustration", "I don't understand how to convert grams to moles. I keep getting the wrong conversion factor and it's so frustrating!"),
|
| 23 |
+
("frustration", "I've tried balancing this chemical equation five times and the numbers never match up!"),
|
| 24 |
+
("frustration", "Im trying to draw this ray diagram for a concave lens and the lines are crossing in the wrong place. I give up!"),
|
| 25 |
+
("frustration", "This physics problem about friction has too many variables and I don't even know where to start!"),
|
| 26 |
+
# Boredom
|
| 27 |
+
("boredom", "Ugh, why do we have to learn about sedimentary rocks? They just sit there. Who cares?"),
|
| 28 |
+
("boredom", "This math worksheet is just 50 of the same exact addition problems. This is so boring."),
|
| 29 |
+
("boredom", "We are just copying definitions of different math properties from the board. This is so boring."),
|
| 30 |
+
("boredom", "Another lecture on the phases of mitosis... we've covered this three years in a row now."),
|
| 31 |
+
("boredom", "I finished all my science reading early. There's nothing else to do except stare at the wall."),
|
| 32 |
+
("boredom", "We have to measure the temperature of this water every two minutes for an hour. This is so tedious."),
|
| 33 |
+
("boredom", "Calculating the area of twenty slightly different rectangles is putting me to sleep."),
|
| 34 |
+
("boredom", "This lecture on cell organelles is just slides of definitions. I'm falling asleep."),
|
| 35 |
+
# Confidence
|
| 36 |
+
("confidence", "I totally mastered multiplying fractions! Give me a hard practice problem to try!"),
|
| 37 |
+
("confidence", "I just derived the formula for the volume of a sphere all by myself!"),
|
| 38 |
+
("confidence", "I know exactly how to balance any redox reaction now. Try me!"),
|
| 39 |
+
("confidence", "I got a perfect score on the calculus midterm today! I really understand derivatives now!"),
|
| 40 |
+
("confidence", "I can explain the entire water cycle in my sleep! Evaporation, condensation, precipitation, easy!"),
|
| 41 |
+
("confidence", "I just solved the hardest logic puzzle in the workbook on my very first try!"),
|
| 42 |
+
("confidence", "I can calculate the trajectory of a projectile in my head now, it's so easy!"),
|
| 43 |
+
("confidence", "I fully understand how DNA replication works and could draw every step from memory!"),
|
| 44 |
+
# Neutral
|
| 45 |
+
("neutral", "How do I calculate the hypotenuse of a right triangle when the sides are 3 and 4?"),
|
| 46 |
+
("neutral", "What are the three main types of rocks found in the Earth's crust?"),
|
| 47 |
+
("neutral", "Can you explain how photosynthesis converts sunlight into chemical energy?"),
|
| 48 |
+
("neutral", "What is the chemical formula for photosynthesis and cellular respiration?"),
|
| 49 |
+
("neutral", "How do you find the slope of a line from two points on a graph?"),
|
| 50 |
+
("neutral", "What is the difference between an isotope and an ion in chemistry?"),
|
| 51 |
+
("neutral", "Could you list the steps of the scientific method in order?"),
|
| 52 |
+
("neutral", "What is the value of the constant pi, and how is it calculated?")
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
results = []
|
| 56 |
+
|
| 57 |
+
url = "http://127.0.0.1:8000/api/chat"
|
| 58 |
+
|
| 59 |
+
print(f"Starting benchmark targeting Option B only for {len(samples)} sample queries...")
|
| 60 |
+
for idx, (cat, q) in enumerate(samples, 1):
|
| 61 |
+
print(f"[{idx}/{len(samples)}] Query ({cat}): \"{q}\"")
|
| 62 |
+
req_data = json.dumps({"message": q, "selected_option": "B"}).encode('utf-8')
|
| 63 |
+
req = urllib.request.Request(
|
| 64 |
+
url,
|
| 65 |
+
data=req_data,
|
| 66 |
+
headers={'Content-Type': 'application/json'}
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
with urllib.request.urlopen(req) as response:
|
| 71 |
+
res_data = json.loads(response.read().decode('utf-8'))
|
| 72 |
+
|
| 73 |
+
# Record result
|
| 74 |
+
results.append({
|
| 75 |
+
"category": cat,
|
| 76 |
+
"query": q,
|
| 77 |
+
"latency_b": res_data["latency_b"],
|
| 78 |
+
"tokens_b": res_data["tokens_b"]
|
| 79 |
+
})
|
| 80 |
+
print(f" Done: B ({res_data['latency_b']}s, {res_data['tokens_b']}t)")
|
| 81 |
+
# Add a small delay between requests
|
| 82 |
+
time.sleep(1.5)
|
| 83 |
+
except Exception as e:
|
| 84 |
+
print(f" Error processing query: {e}")
|
| 85 |
+
|
| 86 |
+
# Save to CSV
|
| 87 |
+
csv_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "benchmark_results_b.csv")
|
| 88 |
+
with open(csv_file, mode="w", newline="", encoding="utf-8") as f:
|
| 89 |
+
writer = csv.writer(f)
|
| 90 |
+
writer.writerow([
|
| 91 |
+
"Category", "Query",
|
| 92 |
+
"Latency B (s)", "Tokens B"
|
| 93 |
+
])
|
| 94 |
+
|
| 95 |
+
for r in results:
|
| 96 |
+
writer.writerow([
|
| 97 |
+
r["category"], r["query"],
|
| 98 |
+
r["latency_b"], r["tokens_b"]
|
| 99 |
+
])
|
| 100 |
+
|
| 101 |
+
print(f"\nResults successfully saved to: {csv_file}")
|
| 102 |
+
|
| 103 |
+
# Calculate averages
|
| 104 |
+
num_queries = len(results)
|
| 105 |
+
if num_queries > 0:
|
| 106 |
+
valid_latencies_b = [r["latency_b"] for r in results if r["latency_b"] is not None]
|
| 107 |
+
valid_tokens_b = [r["tokens_b"] for r in results if r["tokens_b"] is not None]
|
| 108 |
+
|
| 109 |
+
avg_latency_b = sum(valid_latencies_b) / len(valid_latencies_b) if valid_latencies_b else 0
|
| 110 |
+
avg_tokens_b = sum(valid_tokens_b) / len(valid_tokens_b) if valid_tokens_b else 0
|
| 111 |
+
|
| 112 |
+
print("\n" + "="*50)
|
| 113 |
+
print("BENCHMARK SUMMARY AVERAGES (OPTION B ONLY):")
|
| 114 |
+
print("="*50)
|
| 115 |
+
print(f"Option B (Gemini Single-Pass):")
|
| 116 |
+
print(f" - Avg Latency: {avg_latency_b:.3f}s")
|
| 117 |
+
print(f" - Avg Tokens: {avg_tokens_b:.1f}")
|
| 118 |
+
print("="*50)
|
backend/main.py
ADDED
|
@@ -0,0 +1,921 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
import time
|
| 5 |
+
import os
|
| 6 |
+
import threading
|
| 7 |
+
import csv
|
| 8 |
+
import re
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from typing import List, Optional, Dict, Any, TypedDict
|
| 11 |
+
from pydantic import BaseModel, Field
|
| 12 |
+
from dotenv import load_dotenv
|
| 13 |
+
|
| 14 |
+
# LangChain / LangGraph imports
|
| 15 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 16 |
+
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
|
| 17 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 18 |
+
from langgraph.graph import StateGraph, END
|
| 19 |
+
|
| 20 |
+
load_dotenv()
|
| 21 |
+
|
| 22 |
+
app = FastAPI(title="Educational Sentiment Chatbot API")
|
| 23 |
+
|
| 24 |
+
# Enable CORS for frontend integration
|
| 25 |
+
app.add_middleware(
|
| 26 |
+
CORSMiddleware,
|
| 27 |
+
allow_origins=["*"],
|
| 28 |
+
allow_credentials=True,
|
| 29 |
+
allow_methods=["*"],
|
| 30 |
+
allow_headers=["*"],
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# HuggingFace model state variables (DistilRoBERTa)
|
| 34 |
+
classifier = None
|
| 35 |
+
model_status = "loading"
|
| 36 |
+
model_error = None
|
| 37 |
+
|
| 38 |
+
# HuggingFace NER state variables
|
| 39 |
+
ner_classifier = None
|
| 40 |
+
ner_status = "loading"
|
| 41 |
+
ner_error = None
|
| 42 |
+
|
| 43 |
+
def load_distilroberta():
|
| 44 |
+
global classifier, model_status, model_error
|
| 45 |
+
try:
|
| 46 |
+
print("Loading j-hartmann/emotion-english-distilroberta-base model...")
|
| 47 |
+
# Import transformers inside the loader function to make startup instantaneous
|
| 48 |
+
from transformers import pipeline
|
| 49 |
+
classifier = pipeline(
|
| 50 |
+
"text-classification",
|
| 51 |
+
model="j-hartmann/emotion-english-distilroberta-base",
|
| 52 |
+
top_k=None
|
| 53 |
+
)
|
| 54 |
+
model_status = "ready"
|
| 55 |
+
print("DistilRoBERTa model loaded successfully!")
|
| 56 |
+
except Exception as e:
|
| 57 |
+
model_error = str(e)
|
| 58 |
+
model_status = "failed"
|
| 59 |
+
print(f"Error loading DistilRoBERTa model: {model_error}")
|
| 60 |
+
|
| 61 |
+
def load_ner_model():
|
| 62 |
+
global ner_classifier, ner_status, ner_error
|
| 63 |
+
try:
|
| 64 |
+
print("Loading NER model (dslim/distilbert-NER)...")
|
| 65 |
+
from transformers import pipeline
|
| 66 |
+
ner_classifier = pipeline(
|
| 67 |
+
"ner",
|
| 68 |
+
model="dslim/distilbert-NER",
|
| 69 |
+
aggregation_strategy="simple"
|
| 70 |
+
)
|
| 71 |
+
ner_status = "ready"
|
| 72 |
+
print("NER model loaded successfully!")
|
| 73 |
+
except Exception as e:
|
| 74 |
+
ner_error = str(e)
|
| 75 |
+
ner_status = "failed"
|
| 76 |
+
print(f"Error loading NER model: {ner_error}")
|
| 77 |
+
|
| 78 |
+
def scrub_pii(text: str) -> str:
|
| 79 |
+
if not text:
|
| 80 |
+
return text
|
| 81 |
+
|
| 82 |
+
# 1. Regex PII scrubbing
|
| 83 |
+
# Email addresses
|
| 84 |
+
text = re.sub(r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+', '[EMAIL]', text)
|
| 85 |
+
# Phone numbers (safe regex for standard forms like 555-555-5555, +1-555-555-5555, (555) 555-5555)
|
| 86 |
+
text = re.sub(r'\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b', '[PHONE]', text)
|
| 87 |
+
# IP Addresses
|
| 88 |
+
text = re.sub(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', '[IP_ADDRESS]', text)
|
| 89 |
+
# SSNs
|
| 90 |
+
text = re.sub(r'\b\d{3}-\d{2}-\d{4}\b', '[SSN]', text)
|
| 91 |
+
|
| 92 |
+
# 2. NER PII scrubbing (Person, Location, Organization)
|
| 93 |
+
global ner_classifier
|
| 94 |
+
if ner_classifier is not None:
|
| 95 |
+
try:
|
| 96 |
+
entities = ner_classifier(text)
|
| 97 |
+
# Sort from right to left (reverse index order) to avoid shift offset issues
|
| 98 |
+
entities = sorted(entities, key=lambda x: x["start"], reverse=True)
|
| 99 |
+
for ent in entities:
|
| 100 |
+
ent_type = ent["entity_group"]
|
| 101 |
+
if ent_type in ["PER", "LOC", "ORG"]:
|
| 102 |
+
start = ent["start"]
|
| 103 |
+
end = ent["end"]
|
| 104 |
+
text = text[:start] + f"[{ent_type}]" + text[end:]
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"NER PII scrub error: {e}")
|
| 107 |
+
|
| 108 |
+
return text
|
| 109 |
+
|
| 110 |
+
@app.on_event("startup")
|
| 111 |
+
def startup_event():
|
| 112 |
+
threading.Thread(target=load_distilroberta, daemon=True).start()
|
| 113 |
+
threading.Thread(target=load_ner_model, daemon=True).start()
|
| 114 |
+
|
| 115 |
+
# Pydantic Schemas
|
| 116 |
+
class ChatMessage(BaseModel):
|
| 117 |
+
role: str # "user" or "assistant"
|
| 118 |
+
content: str
|
| 119 |
+
|
| 120 |
+
class ChatRequest(BaseModel):
|
| 121 |
+
message: str
|
| 122 |
+
gemini_api_key: Optional[str] = None
|
| 123 |
+
system_prompt: Optional[str] = None
|
| 124 |
+
history_a: Optional[List[ChatMessage]] = None
|
| 125 |
+
history_b: Optional[List[ChatMessage]] = None
|
| 126 |
+
history_c: Optional[List[ChatMessage]] = None
|
| 127 |
+
history_d: Optional[List[ChatMessage]] = None
|
| 128 |
+
selected_option: Optional[str] = "all" # "all", "A", "B", "C", "D"
|
| 129 |
+
|
| 130 |
+
class EmotionScore(BaseModel):
|
| 131 |
+
label: str
|
| 132 |
+
score: float
|
| 133 |
+
|
| 134 |
+
class SentimentDetailsA(BaseModel):
|
| 135 |
+
detected_sentiment: str
|
| 136 |
+
explanation: str
|
| 137 |
+
|
| 138 |
+
class SentimentDetailsB(BaseModel):
|
| 139 |
+
mapped_sentiment: str
|
| 140 |
+
raw_emotions: List[EmotionScore]
|
| 141 |
+
|
| 142 |
+
class ChatResponse(BaseModel):
|
| 143 |
+
sentiment_a: Optional[SentimentDetailsA] = None
|
| 144 |
+
response_a: Optional[str] = None
|
| 145 |
+
latency_a: Optional[float] = None
|
| 146 |
+
prompt_context_a: Optional[str] = None
|
| 147 |
+
|
| 148 |
+
sentiment_b: Optional[SentimentDetailsB] = None
|
| 149 |
+
response_b: Optional[str] = None
|
| 150 |
+
latency_b: Optional[float] = None
|
| 151 |
+
prompt_context_b: Optional[str] = None
|
| 152 |
+
|
| 153 |
+
response_c: Optional[str] = None
|
| 154 |
+
latency_c: Optional[float] = None
|
| 155 |
+
prompt_context_c: Optional[str] = None
|
| 156 |
+
|
| 157 |
+
sentiment_d: Optional[SentimentDetailsB] = None
|
| 158 |
+
response_d: Optional[str] = None
|
| 159 |
+
latency_d: Optional[float] = None
|
| 160 |
+
prompt_context_d: Optional[str] = None
|
| 161 |
+
|
| 162 |
+
tokens_a: Optional[int] = None
|
| 163 |
+
tokens_b: Optional[int] = None
|
| 164 |
+
tokens_c: Optional[int] = None
|
| 165 |
+
tokens_d: Optional[int] = None
|
| 166 |
+
|
| 167 |
+
# State definition for LangGraph
|
| 168 |
+
class AgentState(TypedDict):
|
| 169 |
+
message: str
|
| 170 |
+
system_prompt: str
|
| 171 |
+
sentiment: str
|
| 172 |
+
explanation: str
|
| 173 |
+
response: str
|
| 174 |
+
input_tokens: int
|
| 175 |
+
output_tokens: int
|
| 176 |
+
history: List[ChatMessage]
|
| 177 |
+
|
| 178 |
+
# Pydantic model for LangChain Structured Output
|
| 179 |
+
class SentimentAnalysis(BaseModel):
|
| 180 |
+
detected_sentiment: str = Field(description="Must be strictly one of: 'confusion', 'frustration', 'boredom', 'confidence', 'sadness', or 'neutral'.")
|
| 181 |
+
explanation: str = Field(description="An extremely concise, single-sentence explanation of why this sentiment was chosen to minimize tokens.")
|
| 182 |
+
|
| 183 |
+
class SentimentAndResponseB(BaseModel):
|
| 184 |
+
detected_sentiment: str = Field(description="Must be strictly one of: 'confusion', 'frustration', 'boredom', 'confidence', 'sadness', or 'neutral'.")
|
| 185 |
+
response: str = Field(description="Your Socratic tutor response. Adjust tone based on the detected sentiment. Keep it under 2 brief paragraphs.")
|
| 186 |
+
|
| 187 |
+
# Token estimation helper (using standard ~4 characters per token multiplier for English)
|
| 188 |
+
def estimate_tokens(text: str) -> int:
|
| 189 |
+
return max(1, int(len(text) / 4.0))
|
| 190 |
+
|
| 191 |
+
# Cost calculation helper
|
| 192 |
+
def calculate_cost(input_tokens: int, output_tokens: int) -> float:
|
| 193 |
+
# Gemini 3.1 Flash Lite pricing ($0.075/1M input tokens, $0.30/1M output tokens)
|
| 194 |
+
input_cost = (input_tokens / 1_000_000.0) * 0.075
|
| 195 |
+
output_cost = (output_tokens / 1_000_000.0) * 0.30
|
| 196 |
+
return input_cost + output_cost
|
| 197 |
+
|
| 198 |
+
# Markdown Logging helper
|
| 199 |
+
MD_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sentiment_log.md")
|
| 200 |
+
MD_FILE_B = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sentiment_log_b.md")
|
| 201 |
+
|
| 202 |
+
def log_to_md(question, sentiment_a, sentiment_b, sentiment_d, latency_a, latency_b, latency_c, latency_d, cost_a, cost_b, cost_c, cost_d, tokens_in_a, tokens_out_a, tokens_in_b, tokens_out_b, tokens_in_c, tokens_out_c, tokens_in_d, tokens_out_d, answer_a, answer_b, answer_c, answer_d, selected_option="all"):
|
| 203 |
+
target_file = MD_FILE_B if selected_option == "b" else MD_FILE
|
| 204 |
+
file_exists = os.path.exists(target_file)
|
| 205 |
+
try:
|
| 206 |
+
with open(target_file, mode="a", encoding="utf-8") as f:
|
| 207 |
+
if not file_exists:
|
| 208 |
+
if selected_option == "b":
|
| 209 |
+
f.write("# Sentiment Analysis Option B Log\n\n")
|
| 210 |
+
f.write("This file tracks Option B (Gemini Single-Pass) user queries, detected sentiments, latencies, estimated costs, and responses.\n\n")
|
| 211 |
+
else:
|
| 212 |
+
f.write("# Sentiment Analysis & Response Comparison Log\n\n")
|
| 213 |
+
f.write("This file tracks and compares user queries, detected sentiments, latencies, estimated costs, and responses across all options.\n\n")
|
| 214 |
+
|
| 215 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 216 |
+
|
| 217 |
+
f.write(f"## [{timestamp}] Query: \"{question}\"\n\n")
|
| 218 |
+
if selected_option == "b":
|
| 219 |
+
total_tokens_b = tokens_in_b + tokens_out_b
|
| 220 |
+
f.write("<table>\n")
|
| 221 |
+
f.write(" <thead>\n")
|
| 222 |
+
f.write(" <tr>\n")
|
| 223 |
+
f.write(" <th align=\"left\">Metric</th>\n")
|
| 224 |
+
f.write(" <th align=\"left\">Option B (Gemini Single-Pass)</th>\n")
|
| 225 |
+
f.write(" </tr>\n")
|
| 226 |
+
f.write(" </thead>\n")
|
| 227 |
+
f.write(" <tbody>\n")
|
| 228 |
+
f.write(" <tr>\n")
|
| 229 |
+
f.write(f" <td><strong>Detected Sentiment</strong></td>\n")
|
| 230 |
+
f.write(f" <td><code>{sentiment_b}</code></td>\n")
|
| 231 |
+
f.write(" </tr>\n")
|
| 232 |
+
f.write(" <tr>\n")
|
| 233 |
+
f.write(f" <td><strong>Latency</strong></td>\n")
|
| 234 |
+
f.write(f" <td>{round(latency_b, 3)}s</td>\n")
|
| 235 |
+
f.write(" </tr>\n")
|
| 236 |
+
f.write(" <tr>\n")
|
| 237 |
+
f.write(f" <td><strong>Estimated Cost</strong></td>\n")
|
| 238 |
+
f.write(f" <td><code>${cost_b:.7f}</code></td>\n")
|
| 239 |
+
f.write(" </tr>\n")
|
| 240 |
+
f.write(" <tr>\n")
|
| 241 |
+
f.write(f" <td><strong>Tokens Used</strong></td>\n")
|
| 242 |
+
f.write(f" <td>{total_tokens_b} ({tokens_in_b} in / {tokens_out_b} out)</td>\n")
|
| 243 |
+
f.write(" </tr>\n")
|
| 244 |
+
f.write(" </tbody>\n")
|
| 245 |
+
f.write("</table>\n\n")
|
| 246 |
+
f.write("### Option B Response\n")
|
| 247 |
+
f.write(f"{answer_b}\n\n")
|
| 248 |
+
f.write("---\n\n")
|
| 249 |
+
else:
|
| 250 |
+
total_tokens_a = tokens_in_a + tokens_out_a
|
| 251 |
+
total_tokens_b = tokens_in_b + tokens_out_b
|
| 252 |
+
total_tokens_c = tokens_in_c + tokens_out_c
|
| 253 |
+
total_tokens_d = tokens_in_d + tokens_out_d
|
| 254 |
+
f.write("<table>\n")
|
| 255 |
+
f.write(" <thead>\n")
|
| 256 |
+
f.write(" <tr>\n")
|
| 257 |
+
f.write(" <th align=\"left\">Metric</th>\n")
|
| 258 |
+
f.write(" <th align=\"left\">Option A (Gemini 3.1 Flash Lite Double-Pass)</th>\n")
|
| 259 |
+
f.write(" <th align=\"left\">Option B (Gemini Single-Pass)</th>\n")
|
| 260 |
+
f.write(" <th align=\"left\">Option C (DistilRoBERTa Distribution + Gemini)</th>\n")
|
| 261 |
+
f.write(" <th align=\"left\">Option D (DistilRoBERTa Classifier + Gemini)</th>\n")
|
| 262 |
+
f.write(" </tr>\n")
|
| 263 |
+
f.write(" </thead>\n")
|
| 264 |
+
f.write(" <tbody>\n")
|
| 265 |
+
f.write(" <tr>\n")
|
| 266 |
+
f.write(f" <td><strong>Detected Sentiment</strong></td>\n")
|
| 267 |
+
f.write(f" <td><code>{sentiment_a}</code></td>\n")
|
| 268 |
+
f.write(f" <td><code>{sentiment_b}</code></td>\n")
|
| 269 |
+
f.write(f" <td><code>Distribution Context</code></td>\n")
|
| 270 |
+
f.write(f" <td><code>{sentiment_d}</code></td>\n")
|
| 271 |
+
f.write(" </tr>\n")
|
| 272 |
+
f.write(" <tr>\n")
|
| 273 |
+
f.write(f" <td><strong>Latency</strong></td>\n")
|
| 274 |
+
f.write(f" <td>{round(latency_a, 3)}s</td>\n")
|
| 275 |
+
f.write(f" <td>{round(latency_b, 3)}s</td>\n")
|
| 276 |
+
f.write(f" <td>{round(latency_c, 3)}s</td>\n")
|
| 277 |
+
f.write(f" <td>{round(latency_d, 3)}s</td>\n")
|
| 278 |
+
f.write(" </tr>\n")
|
| 279 |
+
f.write(" <tr>\n")
|
| 280 |
+
f.write(f" <td><strong>Estimated Cost</strong></td>\n")
|
| 281 |
+
f.write(f" <td><code>${cost_a:.7f}</code></td>\n")
|
| 282 |
+
f.write(f" <td><code>${cost_b:.7f}</code></td>\n")
|
| 283 |
+
f.write(f" <td><code>${cost_c:.7f}</code></td>\n")
|
| 284 |
+
f.write(f" <td><code>${cost_d:.7f}</code></td>\n")
|
| 285 |
+
f.write(" </tr>\n")
|
| 286 |
+
f.write(" <tr>\n")
|
| 287 |
+
f.write(f" <td><strong>Tokens Used</strong></td>\n")
|
| 288 |
+
f.write(f" <td>{total_tokens_a} ({tokens_in_a} in / {tokens_out_a} out)</td>\n")
|
| 289 |
+
f.write(f" <td>{total_tokens_b} ({tokens_in_b} in / {tokens_out_b} out)</td>\n")
|
| 290 |
+
f.write(f" <td>{total_tokens_c} ({tokens_in_c} in / {tokens_out_c} out)</td>\n")
|
| 291 |
+
f.write(f" <td>{total_tokens_d} ({tokens_in_d} in / {tokens_out_d} out)</td>\n")
|
| 292 |
+
f.write(" </tr>\n")
|
| 293 |
+
f.write(" </tbody>\n")
|
| 294 |
+
f.write("</table>\n\n")
|
| 295 |
+
f.write("### Option A Response\n")
|
| 296 |
+
f.write(f"{answer_a}\n\n")
|
| 297 |
+
f.write("### Option B Response\n")
|
| 298 |
+
f.write(f"{answer_b}\n\n")
|
| 299 |
+
f.write("### Option C Response\n")
|
| 300 |
+
f.write(f"{answer_c}\n\n")
|
| 301 |
+
f.write("### Option D Response\n")
|
| 302 |
+
f.write(f"{answer_d}\n\n")
|
| 303 |
+
f.write("---\n\n")
|
| 304 |
+
except Exception as e:
|
| 305 |
+
print(f"Error writing to MD log: {e}")
|
| 306 |
+
|
| 307 |
+
# Helper to extract text from LangChain message content (which may be a list of dicts for Gemini models)
|
| 308 |
+
def get_text_content(content: Any) -> str:
|
| 309 |
+
if isinstance(content, str):
|
| 310 |
+
return content
|
| 311 |
+
elif isinstance(content, list):
|
| 312 |
+
text_parts = []
|
| 313 |
+
for part in content:
|
| 314 |
+
if isinstance(part, dict) and part.get("type") == "text":
|
| 315 |
+
text_parts.append(part.get("text", ""))
|
| 316 |
+
elif isinstance(part, str):
|
| 317 |
+
text_parts.append(part)
|
| 318 |
+
return "".join(text_parts)
|
| 319 |
+
return str(content)
|
| 320 |
+
|
| 321 |
+
# Emotion Mapping for DistilRoBERTa
|
| 322 |
+
def map_distilroberta_emotions(raw_emotions: List[Any]) -> str:
|
| 323 |
+
emo_dict = {}
|
| 324 |
+
for item in raw_emotions:
|
| 325 |
+
if isinstance(item, dict):
|
| 326 |
+
label = item.get("label", "").lower()
|
| 327 |
+
score = float(item.get("score", 0.0))
|
| 328 |
+
else:
|
| 329 |
+
label = getattr(item, "label", "").lower()
|
| 330 |
+
score = float(getattr(item, "score", 0.0))
|
| 331 |
+
emo_dict[label] = score
|
| 332 |
+
|
| 333 |
+
# Define target sentiments based on combinations of raw emotions
|
| 334 |
+
# Confusion: high surprise and fear
|
| 335 |
+
confusion_score = emo_dict.get("surprise", 0.0) * 1.2 + emo_dict.get("fear", 0.0) * 0.8
|
| 336 |
+
|
| 337 |
+
# Frustration: high anger and disgust
|
| 338 |
+
frustration_score = emo_dict.get("anger", 0.0) * 1.2 + emo_dict.get("disgust", 0.0) * 0.8
|
| 339 |
+
|
| 340 |
+
# Boredom: high neutral, and if sadness is minor combined with high neutral
|
| 341 |
+
boredom_score = emo_dict.get("neutral", 0.0) * 1.3 + emo_dict.get("sadness", 0.0) * 0.2
|
| 342 |
+
|
| 343 |
+
# Confidence: driven by joy
|
| 344 |
+
confidence_score = emo_dict.get("joy", 0.0) * 1.2
|
| 345 |
+
|
| 346 |
+
# Sadness: driven by sadness
|
| 347 |
+
sadness_score = emo_dict.get("sadness", 0.0) * 1.2
|
| 348 |
+
|
| 349 |
+
scores = {
|
| 350 |
+
"confusion": confusion_score,
|
| 351 |
+
"frustration": frustration_score,
|
| 352 |
+
"boredom": boredom_score,
|
| 353 |
+
"confidence": confidence_score,
|
| 354 |
+
"sadness": sadness_score
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
return max(scores, key=scores.get)
|
| 358 |
+
|
| 359 |
+
# LangGraph Flow A Executor
|
| 360 |
+
def run_flow_a_langgraph(message: str, system_prompt: Optional[str], api_key: str, history: Optional[List[ChatMessage]] = None):
|
| 361 |
+
llm = ChatGoogleGenerativeAI(
|
| 362 |
+
model="gemini-3.1-flash-lite",
|
| 363 |
+
google_api_key=api_key,
|
| 364 |
+
temperature=0.0,
|
| 365 |
+
max_tokens=300
|
| 366 |
+
)
|
| 367 |
+
structured_llm = llm.with_structured_output(SentimentAnalysis)
|
| 368 |
+
|
| 369 |
+
def detect_sentiment_node(state: AgentState) -> dict:
|
| 370 |
+
prompt = ChatPromptTemplate.from_messages([
|
| 371 |
+
("system", "Analyze the user's educational query. Determine their emotional state. Classify it strictly as one of: 'confusion', 'frustration', 'boredom', 'confidence', 'sadness', or 'neutral'. Keep the explanation extremely short and concise (under 10 words)."),
|
| 372 |
+
("human", "{message}")
|
| 373 |
+
])
|
| 374 |
+
chain = prompt | structured_llm
|
| 375 |
+
res = chain.invoke({"message": state["message"]})
|
| 376 |
+
|
| 377 |
+
# Estimate input & output tokens
|
| 378 |
+
# Estimate input & output tokens
|
| 379 |
+
input_prompt = f"Analyze the user's educational query. Determine their emotional state. Classify it strictly as one of: 'confusion', 'frustration', 'boredom', 'confidence', 'sadness', or 'neutral'. {state['message']}"
|
| 380 |
+
est_input = estimate_tokens(input_prompt)
|
| 381 |
+
est_output = 40 # Sentiment response is very short
|
| 382 |
+
|
| 383 |
+
return {
|
| 384 |
+
"sentiment": res.detected_sentiment.lower(),
|
| 385 |
+
"explanation": res.explanation,
|
| 386 |
+
"input_tokens": est_input,
|
| 387 |
+
"output_tokens": est_output
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
def generate_response_node(state: AgentState) -> dict:
|
| 391 |
+
custom_system = state.get("system_prompt") or (
|
| 392 |
+
"You are a concise, Socratic educational tutor. Your focus is strictly to teach. "
|
| 393 |
+
"NEVER give the user the direct answer or solution. Instead, guide them, nudge them, and ask leading questions to help them figure it out. "
|
| 394 |
+
"Adjust your behavior and tone based on the user's sentiment. Keep responses brief (max 5 sentences)."
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
sentiment = state["sentiment"]
|
| 398 |
+
tone_instruction = (
|
| 399 |
+
"IMPORTANT: You are a Socratic tutor. NEVER directly state the answer, definition, or solution. "
|
| 400 |
+
"Instead, nudge the user and guide them to find the answer themselves through questions. "
|
| 401 |
+
"Be extremely concise and direct (strictly limit your response to max 5 sentences).\n"
|
| 402 |
+
)
|
| 403 |
+
if sentiment == "confusion":
|
| 404 |
+
tone_instruction += "The user is confused. Give them a stronger, clearer hint to guide them, and ask a direct question to help them take the next step towards the answer without telling it to them."
|
| 405 |
+
elif sentiment == "sadness":
|
| 406 |
+
tone_instruction += "The user is sad. Give them brief, warm, empathetic encouragement and practical tips to overcome it (like taking a micro-break or focusing on progress), and ask a gentle guiding question to continue."
|
| 407 |
+
elif sentiment == "frustration":
|
| 408 |
+
tone_instruction += "The user is frustrated. Empathetically acknowledge their frustration, give them a helpful hint or alternative perspective, and ask a guiding question to help them work through it."
|
| 409 |
+
elif sentiment == "boredom":
|
| 410 |
+
tone_instruction += "The user is bored. Suggest a completely different way to learn this concept (e.g., through a hands-on project, analogy, or challenge) to spark interest, and ask a guiding question to get them started."
|
| 411 |
+
elif sentiment == "confidence":
|
| 412 |
+
tone_instruction += "The user is confident. Celebrate their success briefly, and offer a quick challenge or question to test their understanding."
|
| 413 |
+
else:
|
| 414 |
+
tone_instruction += "Ask a guiding question to nudge them towards the answer."
|
| 415 |
+
|
| 416 |
+
prompt_context = f"{tone_instruction}\n\nUser Query: {state['message']}"
|
| 417 |
+
|
| 418 |
+
messages = [SystemMessage(content=custom_system)]
|
| 419 |
+
|
| 420 |
+
# Prepend history if exists
|
| 421 |
+
if state.get("history"):
|
| 422 |
+
for msg in state["history"]:
|
| 423 |
+
if msg.role == "user":
|
| 424 |
+
messages.append(HumanMessage(content=msg.content))
|
| 425 |
+
else:
|
| 426 |
+
messages.append(AIMessage(content=msg.content))
|
| 427 |
+
|
| 428 |
+
messages.append(HumanMessage(content=prompt_context))
|
| 429 |
+
|
| 430 |
+
res = llm.invoke(messages)
|
| 431 |
+
response_text = get_text_content(res.content)
|
| 432 |
+
|
| 433 |
+
est_input = estimate_tokens(custom_system) + estimate_tokens(prompt_context)
|
| 434 |
+
est_output = estimate_tokens(response_text)
|
| 435 |
+
|
| 436 |
+
return {
|
| 437 |
+
"response": response_text,
|
| 438 |
+
"input_tokens": state.get("input_tokens", 0) + est_input,
|
| 439 |
+
"output_tokens": state.get("output_tokens", 0) + est_output
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
builder = StateGraph(AgentState)
|
| 443 |
+
builder.add_node("detect_sentiment", detect_sentiment_node)
|
| 444 |
+
builder.add_node("generate_response", generate_response_node)
|
| 445 |
+
builder.set_entry_point("detect_sentiment")
|
| 446 |
+
builder.add_edge("detect_sentiment", "generate_response")
|
| 447 |
+
builder.add_edge("generate_response", END)
|
| 448 |
+
|
| 449 |
+
graph = builder.compile()
|
| 450 |
+
|
| 451 |
+
initial_state = {
|
| 452 |
+
"message": message,
|
| 453 |
+
"system_prompt": system_prompt or "",
|
| 454 |
+
"sentiment": "",
|
| 455 |
+
"explanation": "",
|
| 456 |
+
"response": "",
|
| 457 |
+
"input_tokens": 0,
|
| 458 |
+
"output_tokens": 0,
|
| 459 |
+
"history": history or []
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
return graph.invoke(initial_state)
|
| 463 |
+
|
| 464 |
+
# Option B response helper doing both sentiment detection and response generation in one pass
|
| 465 |
+
def run_flow_b(message: str, system_prompt: Optional[str], api_key: str, history: Optional[List[ChatMessage]] = None):
|
| 466 |
+
import json
|
| 467 |
+
from datetime import datetime
|
| 468 |
+
|
| 469 |
+
# OPTIMIZATION 1: Enforce structural JSON natively. Drops formatting fluff from prompt.
|
| 470 |
+
llm = ChatGoogleGenerativeAI(
|
| 471 |
+
model="gemini-3.1-flash-lite",
|
| 472 |
+
google_api_key=api_key,
|
| 473 |
+
temperature=0.0,
|
| 474 |
+
max_tokens=350,
|
| 475 |
+
generation_config={"response_mime_type": "application/json"}
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
# Cleaned up core system prompt
|
| 479 |
+
custom_system = system_prompt or "Socratic tutor. Never give direct answers. Guide using leading questions."
|
| 480 |
+
|
| 481 |
+
# OPTIMIZATION 2: Condensed to minimize prompt tokens while retaining response style constraints.
|
| 482 |
+
tone_instruction = (
|
| 483 |
+
"JSON: {\"state\": \"confusion|frustration|boredom|confidence\", \"reply\": \"string\"}\n"
|
| 484 |
+
"Rules: Socratic reply (max 5 sentences, no direct solutions). Acknowledge sentiment (confusion/frustration/boredom/confidence) with natural, varied phrasing. NEVER repeat the same acknowledgment templates (e.g., 'I understand', 'It's normal', 'Understandable').\n"
|
| 485 |
+
"- confusion: Acknowledge confusion + hint + guiding question.\n"
|
| 486 |
+
"- frustration: Validate frustration + alternative view + guiding question.\n"
|
| 487 |
+
"- boredom: Acknowledge boredom + analogy/challenge + guiding question.\n"
|
| 488 |
+
"- confidence: Praise + challenge question."
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
messages = [SystemMessage(content=f"{custom_system}\n{tone_instruction}")]
|
| 492 |
+
|
| 493 |
+
if history:
|
| 494 |
+
for msg in history:
|
| 495 |
+
if msg.role == "user":
|
| 496 |
+
messages.append(HumanMessage(content=msg.content))
|
| 497 |
+
else:
|
| 498 |
+
messages.append(AIMessage(content=msg.content))
|
| 499 |
+
|
| 500 |
+
messages.append(HumanMessage(content=message))
|
| 501 |
+
|
| 502 |
+
res = llm.invoke(messages)
|
| 503 |
+
raw_response = get_text_content(res.content)
|
| 504 |
+
|
| 505 |
+
# OPTIMIZATION 3: With response_mime_type active, markdown fences (```json) are bypassed entirely.
|
| 506 |
+
cleaned_json = raw_response.strip()
|
| 507 |
+
|
| 508 |
+
try:
|
| 509 |
+
parsed = json.loads(cleaned_json)
|
| 510 |
+
state_val = parsed.get("state", "confusion")
|
| 511 |
+
reply_val = parsed.get("reply", "")
|
| 512 |
+
|
| 513 |
+
supabase_payload = {
|
| 514 |
+
"state": state_val,
|
| 515 |
+
"reply": reply_val,
|
| 516 |
+
"query": message,
|
| 517 |
+
"timestamp": datetime.now().isoformat()
|
| 518 |
+
}
|
| 519 |
+
print(f"[Supabase Prototype] Directly writing payload to tracking table: {json.dumps(supabase_payload)}")
|
| 520 |
+
except Exception as e:
|
| 521 |
+
print(f"Failed to parse LLM JSON response: {e}. Raw response: {raw_response}")
|
| 522 |
+
state_val = "confusion"
|
| 523 |
+
reply_val = "Let's take a look at this concept step by step. What do you think is the first part?"
|
| 524 |
+
|
| 525 |
+
prompt_context = f"{custom_system}\n{tone_instruction}\nUser Query: {message}"
|
| 526 |
+
est_in = estimate_tokens(prompt_context)
|
| 527 |
+
est_out = estimate_tokens(raw_response)
|
| 528 |
+
|
| 529 |
+
return state_val, reply_val, prompt_context, est_in, est_out
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
# Option C response helper using raw DistilRoBERTa emotion scores directly as LLM prompt context
|
| 533 |
+
def run_flow_c(message: str, system_prompt: Optional[str], api_key: str, raw_emotions: List[EmotionScore], history: Optional[List[ChatMessage]] = None):
|
| 534 |
+
llm = ChatGoogleGenerativeAI(
|
| 535 |
+
model="gemini-3.1-flash-lite",
|
| 536 |
+
google_api_key=api_key,
|
| 537 |
+
temperature=0.0,
|
| 538 |
+
max_tokens=300
|
| 539 |
+
)
|
| 540 |
+
custom_system = system_prompt or (
|
| 541 |
+
"You are a concise, Socratic educational tutor. Your focus is strictly to teach. "
|
| 542 |
+
"NEVER give the user the direct answer or solution. Instead, guide them, nudge them, and ask leading questions to help them figure it out. "
|
| 543 |
+
"Adjust your behavior and tone based on the user's emotional state. Keep responses brief (max 5 sentences)."
|
| 544 |
+
)
|
| 545 |
+
|
| 546 |
+
# Format raw emotions nicely for the model's context
|
| 547 |
+
emotion_context_str = ", ".join([f"{item.label}: {item.score:.3f}" for item in raw_emotions])
|
| 548 |
+
|
| 549 |
+
tone_instruction = (
|
| 550 |
+
"IMPORTANT: Socratic tutor. NEVER state answer/definition/solution. Nudge/guide using questions. "
|
| 551 |
+
"Max 5 sentences.\n"
|
| 552 |
+
f"User emotions: {emotion_context_str}.\n"
|
| 553 |
+
"Synthesize: If confusion (surprise/fear), give a stronger hint. If frustration (anger/disgust), be empathetic. "
|
| 554 |
+
"If boredom (neutral), suggest alternative hands-on/analogy path. If sadness, offer quick warm tips. If confidence (joy), challenge them."
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
prompt_context = f"{tone_instruction}\n\nUser Query: {message}"
|
| 558 |
+
|
| 559 |
+
messages = [SystemMessage(content=custom_system)]
|
| 560 |
+
|
| 561 |
+
# Prepend history if exists
|
| 562 |
+
if history:
|
| 563 |
+
for msg in history:
|
| 564 |
+
if msg.role == "user":
|
| 565 |
+
messages.append(HumanMessage(content=msg.content))
|
| 566 |
+
else:
|
| 567 |
+
messages.append(AIMessage(content=msg.content))
|
| 568 |
+
|
| 569 |
+
messages.append(HumanMessage(content=prompt_context))
|
| 570 |
+
|
| 571 |
+
res = llm.invoke(messages)
|
| 572 |
+
response_text = get_text_content(res.content)
|
| 573 |
+
|
| 574 |
+
# Estimate tokens
|
| 575 |
+
est_input = estimate_tokens(custom_system) + estimate_tokens(prompt_context)
|
| 576 |
+
est_output = estimate_tokens(response_text)
|
| 577 |
+
|
| 578 |
+
return response_text, prompt_context, est_input, est_output
|
| 579 |
+
|
| 580 |
+
# Option D response helper using DistilRoBERTa mapped sentiment
|
| 581 |
+
def run_flow_d(message: str, system_prompt: Optional[str], api_key: str, mapped_sentiment: str, history: Optional[List[ChatMessage]] = None):
|
| 582 |
+
llm = ChatGoogleGenerativeAI(
|
| 583 |
+
model="gemini-3.1-flash-lite",
|
| 584 |
+
google_api_key=api_key,
|
| 585 |
+
temperature=0.0,
|
| 586 |
+
max_tokens=300
|
| 587 |
+
)
|
| 588 |
+
custom_system = system_prompt or (
|
| 589 |
+
"You are a concise, Socratic educational tutor. Your focus is strictly to teach. "
|
| 590 |
+
"NEVER give the user the direct answer or solution. Instead, guide them, nudge them, and ask leading questions to help them figure it out. "
|
| 591 |
+
"Adjust your behavior and tone based on the user's sentiment. Keep responses brief (max 5 sentences)."
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
tone_instruction = (
|
| 595 |
+
"IMPORTANT: You are a Socratic tutor. NEVER directly state the answer, definition, or solution. "
|
| 596 |
+
"Instead, nudge the user and guide them to find the answer themselves through questions. "
|
| 597 |
+
"Be extremely concise and direct (strictly limit your response to max 5 sentences).\n"
|
| 598 |
+
)
|
| 599 |
+
if mapped_sentiment == "confusion":
|
| 600 |
+
tone_instruction += "The user is confused. Give them a stronger, clearer hint to guide them, and ask a direct question to help them take the next step towards the answer without telling it to them."
|
| 601 |
+
elif mapped_sentiment == "sadness":
|
| 602 |
+
tone_instruction += "The user is sad. Give them brief, warm, empathetic encouragement and practical tips to overcome it (like taking a micro-break or focusing on progress), and ask a gentle guiding question to continue."
|
| 603 |
+
elif mapped_sentiment == "frustration":
|
| 604 |
+
tone_instruction += "The user is frustrated. Empathetically acknowledge their frustration, give them a helpful hint or alternative perspective, and ask a guiding question to help them work through it."
|
| 605 |
+
elif mapped_sentiment == "boredom":
|
| 606 |
+
tone_instruction += "The user is bored. Suggest a completely different way to learn this concept (e.g., through a hands-on project, analogy, or challenge) to spark interest, and ask a guiding question to get them started."
|
| 607 |
+
elif mapped_sentiment == "confidence":
|
| 608 |
+
tone_instruction += "The user is confident. Celebrate their success briefly, and offer a quick challenge or question to test their understanding."
|
| 609 |
+
else:
|
| 610 |
+
tone_instruction += "Ask a guiding question to nudge them towards the answer."
|
| 611 |
+
|
| 612 |
+
prompt_context = f"{tone_instruction}\n\nUser Query: {message}"
|
| 613 |
+
|
| 614 |
+
messages = [SystemMessage(content=custom_system)]
|
| 615 |
+
if history:
|
| 616 |
+
for msg in history:
|
| 617 |
+
if msg.role == "user":
|
| 618 |
+
messages.append(HumanMessage(content=msg.content))
|
| 619 |
+
else:
|
| 620 |
+
messages.append(AIMessage(content=msg.content))
|
| 621 |
+
messages.append(HumanMessage(content=prompt_context))
|
| 622 |
+
|
| 623 |
+
res = llm.invoke(messages)
|
| 624 |
+
response_text = get_text_content(res.content)
|
| 625 |
+
|
| 626 |
+
est_input = estimate_tokens(custom_system) + estimate_tokens(prompt_context)
|
| 627 |
+
est_output = estimate_tokens(response_text)
|
| 628 |
+
|
| 629 |
+
return response_text, prompt_context, est_input, est_output
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
# API Routes
|
| 633 |
+
@app.get("/api/status")
|
| 634 |
+
def get_status():
|
| 635 |
+
return {
|
| 636 |
+
"roberta_status": model_status,
|
| 637 |
+
"roberta_error": model_error,
|
| 638 |
+
"ner_status": ner_status,
|
| 639 |
+
"ner_error": ner_error,
|
| 640 |
+
"gemini_api_key_configured": bool(os.environ.get("GEMINI_API_KEY"))
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
@app.post("/api/chat", response_model=ChatResponse)
|
| 644 |
+
def chat_endpoint(request: ChatRequest):
|
| 645 |
+
# Retrieve Gemini API Key
|
| 646 |
+
api_key = request.gemini_api_key or os.environ.get("GEMINI_API_KEY")
|
| 647 |
+
if not api_key:
|
| 648 |
+
raise HTTPException(
|
| 649 |
+
status_code=400,
|
| 650 |
+
detail="Gemini API Key is missing. Please provide it in the Settings panel."
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
# Scrub PII from user query
|
| 654 |
+
request.message = scrub_pii(request.message)
|
| 655 |
+
|
| 656 |
+
# Initialize all option return variables
|
| 657 |
+
sentiment_details_a = None
|
| 658 |
+
response_a = None
|
| 659 |
+
latency_a = None
|
| 660 |
+
prompt_context_a = None
|
| 661 |
+
tokens_a = None
|
| 662 |
+
|
| 663 |
+
sentiment_details_b = None
|
| 664 |
+
response_b = None
|
| 665 |
+
latency_b = None
|
| 666 |
+
prompt_context_b = None
|
| 667 |
+
tokens_b = None
|
| 668 |
+
|
| 669 |
+
response_c = None
|
| 670 |
+
latency_c = None
|
| 671 |
+
prompt_context_c = None
|
| 672 |
+
tokens_c = None
|
| 673 |
+
|
| 674 |
+
sentiment_details_d = None
|
| 675 |
+
response_d = None
|
| 676 |
+
latency_d = None
|
| 677 |
+
prompt_context_d = None
|
| 678 |
+
tokens_d = None
|
| 679 |
+
|
| 680 |
+
# Track metrics for logging
|
| 681 |
+
detected_sentiment_a = "N/A"
|
| 682 |
+
mapped_sentiment_b = "N/A"
|
| 683 |
+
mapped_sentiment_d = "N/A"
|
| 684 |
+
cost_a = 0.0
|
| 685 |
+
cost_b = 0.0
|
| 686 |
+
cost_c = 0.0
|
| 687 |
+
cost_d = 0.0
|
| 688 |
+
est_in_b = 0
|
| 689 |
+
est_out_b = 0
|
| 690 |
+
est_in_c = 0
|
| 691 |
+
est_out_c = 0
|
| 692 |
+
est_in_d = 0
|
| 693 |
+
est_out_d = 0
|
| 694 |
+
|
| 695 |
+
selected = request.selected_option.lower() if request.selected_option else "all"
|
| 696 |
+
run_a = (selected == "all" or selected == "a")
|
| 697 |
+
run_b = (selected == "all" or selected == "b")
|
| 698 |
+
run_c = (selected == "all" or selected == "c")
|
| 699 |
+
run_d = (selected == "all" or selected == "d")
|
| 700 |
+
|
| 701 |
+
# ------------------
|
| 702 |
+
# FLOW A: LangGraph + LangChain Sentiment & Response
|
| 703 |
+
# ------------------
|
| 704 |
+
if run_a:
|
| 705 |
+
start_a = time.time()
|
| 706 |
+
try:
|
| 707 |
+
final_state_a = run_flow_a_langgraph(
|
| 708 |
+
message=request.message,
|
| 709 |
+
system_prompt=request.system_prompt,
|
| 710 |
+
api_key=api_key,
|
| 711 |
+
history=request.history_a
|
| 712 |
+
)
|
| 713 |
+
|
| 714 |
+
detected_sentiment_a = final_state_a["sentiment"]
|
| 715 |
+
explanation_a = final_state_a["explanation"]
|
| 716 |
+
response_a = final_state_a["response"]
|
| 717 |
+
|
| 718 |
+
prompt_context_a = f"Detected Sentiment (LangGraph): {detected_sentiment_a}\nExplanation: {explanation_a}"
|
| 719 |
+
tokens_a = final_state_a.get("input_tokens", 0) + final_state_a.get("output_tokens", 0)
|
| 720 |
+
cost_a = calculate_cost(final_state_a["input_tokens"], final_state_a["output_tokens"])
|
| 721 |
+
|
| 722 |
+
sentiment_details_a = SentimentDetailsA(
|
| 723 |
+
detected_sentiment=detected_sentiment_a,
|
| 724 |
+
explanation=explanation_a
|
| 725 |
+
)
|
| 726 |
+
except Exception as e:
|
| 727 |
+
print(f"Error in Flow A (LangGraph): {e}")
|
| 728 |
+
detected_sentiment_a = "neutral"
|
| 729 |
+
explanation_a = f"Error: {str(e)}"
|
| 730 |
+
response_a = "An error occurred during Flow A generation."
|
| 731 |
+
prompt_context_a = "N/A"
|
| 732 |
+
cost_a = 0.0
|
| 733 |
+
sentiment_details_a = SentimentDetailsA(
|
| 734 |
+
detected_sentiment="neutral",
|
| 735 |
+
explanation=explanation_a
|
| 736 |
+
)
|
| 737 |
+
latency_a = time.time() - start_a
|
| 738 |
+
|
| 739 |
+
# ------------------
|
| 740 |
+
# FLOW B: Gemini Single-Pass (Sentiment & Response in one call)
|
| 741 |
+
# ------------------
|
| 742 |
+
if run_b:
|
| 743 |
+
start_b = time.time()
|
| 744 |
+
try:
|
| 745 |
+
mapped_sentiment_b, response_b, prompt_context_b, est_in_b, est_out_b = run_flow_b(
|
| 746 |
+
message=request.message,
|
| 747 |
+
system_prompt=request.system_prompt,
|
| 748 |
+
api_key=api_key,
|
| 749 |
+
history=request.history_b
|
| 750 |
+
)
|
| 751 |
+
cost_b = calculate_cost(est_in_b, est_out_b)
|
| 752 |
+
tokens_b = est_in_b + est_out_b
|
| 753 |
+
sentiment_details_b = SentimentDetailsB(
|
| 754 |
+
mapped_sentiment=mapped_sentiment_b,
|
| 755 |
+
raw_emotions=[]
|
| 756 |
+
)
|
| 757 |
+
except Exception as e:
|
| 758 |
+
print(f"Flow B single-pass error: {e}")
|
| 759 |
+
mapped_sentiment_b = "neutral"
|
| 760 |
+
response_b = "An error occurred during Flow B generation."
|
| 761 |
+
prompt_context_b = "N/A"
|
| 762 |
+
cost_b = 0.0
|
| 763 |
+
est_in_b = 0
|
| 764 |
+
est_out_b = 0
|
| 765 |
+
sentiment_details_b = SentimentDetailsB(
|
| 766 |
+
mapped_sentiment="neutral",
|
| 767 |
+
raw_emotions=[]
|
| 768 |
+
)
|
| 769 |
+
latency_b = time.time() - start_b
|
| 770 |
+
|
| 771 |
+
# ------------------
|
| 772 |
+
# FLOW C & D: DistilRoBERTa Classifier Setup
|
| 773 |
+
# ------------------
|
| 774 |
+
raw_emotions = []
|
| 775 |
+
classifier_ran = False
|
| 776 |
+
|
| 777 |
+
if run_c or run_d:
|
| 778 |
+
try:
|
| 779 |
+
if model_status == "loading":
|
| 780 |
+
raise HTTPException(
|
| 781 |
+
status_code=503,
|
| 782 |
+
detail="DistilRoBERTa model is still downloading/loading. Please wait a few seconds and try again."
|
| 783 |
+
)
|
| 784 |
+
elif model_status == "failed" or classifier is None:
|
| 785 |
+
raise HTTPException(
|
| 786 |
+
status_code=500,
|
| 787 |
+
detail=f"DistilRoBERTa model is unavailable. Load error: {model_error}"
|
| 788 |
+
)
|
| 789 |
+
|
| 790 |
+
# Run local classifier once
|
| 791 |
+
classifier_results = classifier(request.message)[0]
|
| 792 |
+
raw_emotions = [
|
| 793 |
+
EmotionScore(label=item["label"], score=float(item["score"]))
|
| 794 |
+
for item in classifier_results
|
| 795 |
+
]
|
| 796 |
+
mapped_sentiment_d = map_distilroberta_emotions(classifier_results)
|
| 797 |
+
classifier_ran = True
|
| 798 |
+
except HTTPException as he:
|
| 799 |
+
raise he
|
| 800 |
+
except Exception as e:
|
| 801 |
+
print(f"DistilRoBERTa classification error: {e}")
|
| 802 |
+
|
| 803 |
+
# ------------------
|
| 804 |
+
# FLOW C: Local DistilRoBERTa Raw Scores + Gemini Reply
|
| 805 |
+
# ------------------
|
| 806 |
+
if run_c:
|
| 807 |
+
start_c = time.time()
|
| 808 |
+
try:
|
| 809 |
+
if not classifier_ran:
|
| 810 |
+
raise Exception("Classifier did not run successfully.")
|
| 811 |
+
response_c, prompt_context_c, est_in_c, est_out_c = run_flow_c(
|
| 812 |
+
message=request.message,
|
| 813 |
+
system_prompt=request.system_prompt,
|
| 814 |
+
api_key=api_key,
|
| 815 |
+
raw_emotions=raw_emotions,
|
| 816 |
+
history=request.history_c
|
| 817 |
+
)
|
| 818 |
+
cost_c = calculate_cost(est_in_c, est_out_c)
|
| 819 |
+
tokens_c = est_in_c + est_out_c
|
| 820 |
+
except Exception as e:
|
| 821 |
+
print(f"Flow C error: {e}")
|
| 822 |
+
response_c = "An error occurred during Flow C generation."
|
| 823 |
+
prompt_context_c = "N/A"
|
| 824 |
+
cost_c = 0.0
|
| 825 |
+
est_in_c = 0
|
| 826 |
+
est_out_c = 0
|
| 827 |
+
latency_c = time.time() - start_c
|
| 828 |
+
|
| 829 |
+
# ------------------
|
| 830 |
+
# FLOW D: Local DistilRoBERTa Classifier + Gemini Reply (Old Option B)
|
| 831 |
+
# ------------------
|
| 832 |
+
if run_d:
|
| 833 |
+
start_d = time.time()
|
| 834 |
+
try:
|
| 835 |
+
if not classifier_ran:
|
| 836 |
+
raise Exception("Classifier did not run successfully.")
|
| 837 |
+
response_d, prompt_context_d, est_in_d, est_out_d = run_flow_d(
|
| 838 |
+
message=request.message,
|
| 839 |
+
system_prompt=request.system_prompt,
|
| 840 |
+
api_key=api_key,
|
| 841 |
+
mapped_sentiment=mapped_sentiment_d,
|
| 842 |
+
history=request.history_d
|
| 843 |
+
)
|
| 844 |
+
cost_d = calculate_cost(est_in_d, est_out_d)
|
| 845 |
+
tokens_d = est_in_d + est_out_d
|
| 846 |
+
sentiment_details_d = SentimentDetailsB(
|
| 847 |
+
mapped_sentiment=mapped_sentiment_d,
|
| 848 |
+
raw_emotions=raw_emotions
|
| 849 |
+
)
|
| 850 |
+
except Exception as e:
|
| 851 |
+
print(f"Flow D error: {e}")
|
| 852 |
+
response_d = "An error occurred during Flow D generation."
|
| 853 |
+
prompt_context_d = "N/A"
|
| 854 |
+
cost_d = 0.0
|
| 855 |
+
est_in_d = 0
|
| 856 |
+
est_out_d = 0
|
| 857 |
+
sentiment_details_d = SentimentDetailsB(
|
| 858 |
+
mapped_sentiment="neutral",
|
| 859 |
+
raw_emotions=[]
|
| 860 |
+
)
|
| 861 |
+
latency_d = time.time() - start_d
|
| 862 |
+
|
| 863 |
+
# Log to Markdown file asynchronously or directly (only log values if ran)
|
| 864 |
+
log_to_md(
|
| 865 |
+
question=request.message,
|
| 866 |
+
sentiment_a=detected_sentiment_a,
|
| 867 |
+
sentiment_b=mapped_sentiment_b,
|
| 868 |
+
sentiment_d=mapped_sentiment_d,
|
| 869 |
+
latency_a=latency_a or 0.0,
|
| 870 |
+
latency_b=latency_b or 0.0,
|
| 871 |
+
latency_c=latency_c or 0.0,
|
| 872 |
+
latency_d=latency_d or 0.0,
|
| 873 |
+
cost_a=cost_a,
|
| 874 |
+
cost_b=cost_b,
|
| 875 |
+
cost_c=cost_c,
|
| 876 |
+
cost_d=cost_d,
|
| 877 |
+
tokens_in_a=final_state_a.get("input_tokens", 0) if (run_a and "final_state_a" in locals()) else 0,
|
| 878 |
+
tokens_out_a=final_state_a.get("output_tokens", 0) if (run_a and "final_state_a" in locals()) else 0,
|
| 879 |
+
tokens_in_b=est_in_b,
|
| 880 |
+
tokens_out_b=est_out_b,
|
| 881 |
+
tokens_in_c=est_in_c,
|
| 882 |
+
tokens_out_c=est_out_c,
|
| 883 |
+
tokens_in_d=est_in_d,
|
| 884 |
+
tokens_out_d=est_out_d,
|
| 885 |
+
answer_a=response_a or "Skipped",
|
| 886 |
+
answer_b=response_b or "Skipped",
|
| 887 |
+
answer_c=response_c or "Skipped",
|
| 888 |
+
answer_d=response_d or "Skipped",
|
| 889 |
+
selected_option=selected
|
| 890 |
+
)
|
| 891 |
+
|
| 892 |
+
return ChatResponse(
|
| 893 |
+
sentiment_a=sentiment_details_a,
|
| 894 |
+
response_a=response_a,
|
| 895 |
+
latency_a=round(latency_a, 3) if latency_a is not None else None,
|
| 896 |
+
prompt_context_a=prompt_context_a,
|
| 897 |
+
|
| 898 |
+
sentiment_b=sentiment_details_b,
|
| 899 |
+
response_b=response_b,
|
| 900 |
+
latency_b=round(latency_b, 3) if latency_b is not None else None,
|
| 901 |
+
prompt_context_b=prompt_context_b,
|
| 902 |
+
|
| 903 |
+
response_c=response_c,
|
| 904 |
+
latency_c=round(latency_c, 3) if latency_c is not None else None,
|
| 905 |
+
prompt_context_c=prompt_context_c,
|
| 906 |
+
|
| 907 |
+
sentiment_d=sentiment_details_d,
|
| 908 |
+
response_d=response_d,
|
| 909 |
+
latency_d=round(latency_d, 3) if latency_d is not None else None,
|
| 910 |
+
prompt_context_d=prompt_context_d,
|
| 911 |
+
|
| 912 |
+
tokens_a=tokens_a,
|
| 913 |
+
tokens_b=tokens_b,
|
| 914 |
+
tokens_c=tokens_c,
|
| 915 |
+
tokens_d=tokens_d
|
| 916 |
+
)
|
| 917 |
+
|
| 918 |
+
# Mount frontend static files in production if dist folder is built
|
| 919 |
+
frontend_dist_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "dist")
|
| 920 |
+
if os.path.exists(frontend_dist_path):
|
| 921 |
+
app.mount("/", StaticFiles(directory=frontend_dist_path, html=True), name="frontend")
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 2 |
+
torch
|
| 3 |
+
transformers
|
| 4 |
+
fastapi
|
| 5 |
+
uvicorn[standard]
|
| 6 |
+
pydantic
|
| 7 |
+
google-genai
|
| 8 |
+
python-dotenv
|
| 9 |
+
langchain
|
| 10 |
+
langgraph
|
| 11 |
+
langchain-google-genai
|
| 12 |
+
|
backend/test_sentiment.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Add current directory to path for imports
|
| 5 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
|
| 7 |
+
from main import map_distilroberta_emotions
|
| 8 |
+
|
| 9 |
+
def test_mapping():
|
| 10 |
+
print("Testing DistilRoBERTa emotion mapping logic...")
|
| 11 |
+
|
| 12 |
+
# 1. Frustration tests
|
| 13 |
+
anger_test = [{"label": "anger", "score": 0.8}, {"label": "neutral", "score": 0.2}]
|
| 14 |
+
assert map_distilroberta_emotions(anger_test) == "frustration", f"Expected frustration, got {map_distilroberta_emotions(anger_test)}"
|
| 15 |
+
|
| 16 |
+
disgust_test = [{"label": "disgust", "score": 0.6}]
|
| 17 |
+
assert map_distilroberta_emotions(disgust_test) == "frustration", f"Expected frustration, got {map_distilroberta_emotions(disgust_test)}"
|
| 18 |
+
|
| 19 |
+
# 2. Confusion tests
|
| 20 |
+
sadness_test = [{"label": "sadness", "score": 0.9}]
|
| 21 |
+
assert map_distilroberta_emotions(sadness_test) == "confusion", f"Expected confusion, got {map_distilroberta_emotions(sadness_test)}"
|
| 22 |
+
|
| 23 |
+
surprise_test = [{"label": "surprise", "score": 0.55}]
|
| 24 |
+
assert map_distilroberta_emotions(surprise_test) == "confusion", f"Expected confusion, got {map_distilroberta_emotions(surprise_test)}"
|
| 25 |
+
|
| 26 |
+
fear_test = [{"label": "fear", "score": 0.7}]
|
| 27 |
+
assert map_distilroberta_emotions(fear_test) == "confusion", f"Expected confusion, got {map_distilroberta_emotions(fear_test)}"
|
| 28 |
+
|
| 29 |
+
# 3. Confidence tests
|
| 30 |
+
joy_test = [{"label": "joy", "score": 0.95}]
|
| 31 |
+
assert map_distilroberta_emotions(joy_test) == "confidence", f"Expected confidence, got {map_distilroberta_emotions(joy_test)}"
|
| 32 |
+
|
| 33 |
+
# 4. Boredom tests
|
| 34 |
+
neutral_test = [{"label": "neutral", "score": 0.9}]
|
| 35 |
+
assert map_distilroberta_emotions(neutral_test) == "boredom", f"Expected boredom, got {map_distilroberta_emotions(neutral_test)}"
|
| 36 |
+
|
| 37 |
+
print("All DistilRoBERTa mapping assertions passed successfully!")
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
test_mapping()
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/.oxlintrc.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
| 3 |
+
"plugins": ["react", "oxc"],
|
| 4 |
+
"rules": {
|
| 5 |
+
"react/rules-of-hooks": "error",
|
| 6 |
+
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
| 7 |
+
}
|
| 8 |
+
}
|
frontend/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the Oxlint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and Oxlint's TypeScript related rules in your project.
|
frontend/index.html
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎓</text></svg>" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>Sentiment Detection Demo</title>
|
| 8 |
+
<meta name="description" content="Demo educational chatbot that compares sentiment analysis results between Gemini and a local RoBERTa model to tailor responses." />
|
| 9 |
+
<!-- Google Fonts Inter -->
|
| 10 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 13 |
+
<!-- KaTeX for LaTeX rendering -->
|
| 14 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css">
|
| 15 |
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"></script>
|
| 16 |
+
</head>
|
| 17 |
+
<body>
|
| 18 |
+
<div id="root"></div>
|
| 19 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 20 |
+
</body>
|
| 21 |
+
</html>
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,2074 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "frontend",
|
| 9 |
+
"version": "0.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"lucide-react": "^1.21.0",
|
| 12 |
+
"react": "^19.2.7",
|
| 13 |
+
"react-dom": "^19.2.7"
|
| 14 |
+
},
|
| 15 |
+
"devDependencies": {
|
| 16 |
+
"@types/react": "^19.2.17",
|
| 17 |
+
"@types/react-dom": "^19.2.3",
|
| 18 |
+
"@vitejs/plugin-react": "^5.1.1",
|
| 19 |
+
"oxlint": "^1.69.0",
|
| 20 |
+
"vite": "^5.4.11"
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"node_modules/@babel/code-frame": {
|
| 24 |
+
"version": "7.29.7",
|
| 25 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
| 26 |
+
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
| 27 |
+
"dev": true,
|
| 28 |
+
"license": "MIT",
|
| 29 |
+
"dependencies": {
|
| 30 |
+
"@babel/helper-validator-identifier": "^7.29.7",
|
| 31 |
+
"js-tokens": "^4.0.0",
|
| 32 |
+
"picocolors": "^1.1.1"
|
| 33 |
+
},
|
| 34 |
+
"engines": {
|
| 35 |
+
"node": ">=6.9.0"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"node_modules/@babel/compat-data": {
|
| 39 |
+
"version": "7.29.7",
|
| 40 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
| 41 |
+
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
| 42 |
+
"dev": true,
|
| 43 |
+
"license": "MIT",
|
| 44 |
+
"engines": {
|
| 45 |
+
"node": ">=6.9.0"
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"node_modules/@babel/core": {
|
| 49 |
+
"version": "7.29.7",
|
| 50 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
| 51 |
+
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
| 52 |
+
"dev": true,
|
| 53 |
+
"license": "MIT",
|
| 54 |
+
"dependencies": {
|
| 55 |
+
"@babel/code-frame": "^7.29.7",
|
| 56 |
+
"@babel/generator": "^7.29.7",
|
| 57 |
+
"@babel/helper-compilation-targets": "^7.29.7",
|
| 58 |
+
"@babel/helper-module-transforms": "^7.29.7",
|
| 59 |
+
"@babel/helpers": "^7.29.7",
|
| 60 |
+
"@babel/parser": "^7.29.7",
|
| 61 |
+
"@babel/template": "^7.29.7",
|
| 62 |
+
"@babel/traverse": "^7.29.7",
|
| 63 |
+
"@babel/types": "^7.29.7",
|
| 64 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 65 |
+
"convert-source-map": "^2.0.0",
|
| 66 |
+
"debug": "^4.1.0",
|
| 67 |
+
"gensync": "^1.0.0-beta.2",
|
| 68 |
+
"json5": "^2.2.3",
|
| 69 |
+
"semver": "^6.3.1"
|
| 70 |
+
},
|
| 71 |
+
"engines": {
|
| 72 |
+
"node": ">=6.9.0"
|
| 73 |
+
},
|
| 74 |
+
"funding": {
|
| 75 |
+
"type": "opencollective",
|
| 76 |
+
"url": "https://opencollective.com/babel"
|
| 77 |
+
}
|
| 78 |
+
},
|
| 79 |
+
"node_modules/@babel/generator": {
|
| 80 |
+
"version": "7.29.7",
|
| 81 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
| 82 |
+
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
| 83 |
+
"dev": true,
|
| 84 |
+
"license": "MIT",
|
| 85 |
+
"dependencies": {
|
| 86 |
+
"@babel/parser": "^7.29.7",
|
| 87 |
+
"@babel/types": "^7.29.7",
|
| 88 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 89 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 90 |
+
"jsesc": "^3.0.2"
|
| 91 |
+
},
|
| 92 |
+
"engines": {
|
| 93 |
+
"node": ">=6.9.0"
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 97 |
+
"version": "7.29.7",
|
| 98 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
| 99 |
+
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
| 100 |
+
"dev": true,
|
| 101 |
+
"license": "MIT",
|
| 102 |
+
"dependencies": {
|
| 103 |
+
"@babel/compat-data": "^7.29.7",
|
| 104 |
+
"@babel/helper-validator-option": "^7.29.7",
|
| 105 |
+
"browserslist": "^4.24.0",
|
| 106 |
+
"lru-cache": "^5.1.1",
|
| 107 |
+
"semver": "^6.3.1"
|
| 108 |
+
},
|
| 109 |
+
"engines": {
|
| 110 |
+
"node": ">=6.9.0"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"node_modules/@babel/helper-globals": {
|
| 114 |
+
"version": "7.29.7",
|
| 115 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
| 116 |
+
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
|
| 117 |
+
"dev": true,
|
| 118 |
+
"license": "MIT",
|
| 119 |
+
"engines": {
|
| 120 |
+
"node": ">=6.9.0"
|
| 121 |
+
}
|
| 122 |
+
},
|
| 123 |
+
"node_modules/@babel/helper-module-imports": {
|
| 124 |
+
"version": "7.29.7",
|
| 125 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
| 126 |
+
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
|
| 127 |
+
"dev": true,
|
| 128 |
+
"license": "MIT",
|
| 129 |
+
"dependencies": {
|
| 130 |
+
"@babel/traverse": "^7.29.7",
|
| 131 |
+
"@babel/types": "^7.29.7"
|
| 132 |
+
},
|
| 133 |
+
"engines": {
|
| 134 |
+
"node": ">=6.9.0"
|
| 135 |
+
}
|
| 136 |
+
},
|
| 137 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 138 |
+
"version": "7.29.7",
|
| 139 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
|
| 140 |
+
"integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
|
| 141 |
+
"dev": true,
|
| 142 |
+
"license": "MIT",
|
| 143 |
+
"dependencies": {
|
| 144 |
+
"@babel/helper-module-imports": "^7.29.7",
|
| 145 |
+
"@babel/helper-validator-identifier": "^7.29.7",
|
| 146 |
+
"@babel/traverse": "^7.29.7"
|
| 147 |
+
},
|
| 148 |
+
"engines": {
|
| 149 |
+
"node": ">=6.9.0"
|
| 150 |
+
},
|
| 151 |
+
"peerDependencies": {
|
| 152 |
+
"@babel/core": "^7.0.0"
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
"node_modules/@babel/helper-plugin-utils": {
|
| 156 |
+
"version": "7.29.7",
|
| 157 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
|
| 158 |
+
"integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
|
| 159 |
+
"dev": true,
|
| 160 |
+
"license": "MIT",
|
| 161 |
+
"engines": {
|
| 162 |
+
"node": ">=6.9.0"
|
| 163 |
+
}
|
| 164 |
+
},
|
| 165 |
+
"node_modules/@babel/helper-string-parser": {
|
| 166 |
+
"version": "7.29.7",
|
| 167 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
| 168 |
+
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
| 169 |
+
"dev": true,
|
| 170 |
+
"license": "MIT",
|
| 171 |
+
"engines": {
|
| 172 |
+
"node": ">=6.9.0"
|
| 173 |
+
}
|
| 174 |
+
},
|
| 175 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 176 |
+
"version": "7.29.7",
|
| 177 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
| 178 |
+
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
| 179 |
+
"dev": true,
|
| 180 |
+
"license": "MIT",
|
| 181 |
+
"engines": {
|
| 182 |
+
"node": ">=6.9.0"
|
| 183 |
+
}
|
| 184 |
+
},
|
| 185 |
+
"node_modules/@babel/helper-validator-option": {
|
| 186 |
+
"version": "7.29.7",
|
| 187 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
| 188 |
+
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
| 189 |
+
"dev": true,
|
| 190 |
+
"license": "MIT",
|
| 191 |
+
"engines": {
|
| 192 |
+
"node": ">=6.9.0"
|
| 193 |
+
}
|
| 194 |
+
},
|
| 195 |
+
"node_modules/@babel/helpers": {
|
| 196 |
+
"version": "7.29.7",
|
| 197 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
| 198 |
+
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
| 199 |
+
"dev": true,
|
| 200 |
+
"license": "MIT",
|
| 201 |
+
"dependencies": {
|
| 202 |
+
"@babel/template": "^7.29.7",
|
| 203 |
+
"@babel/types": "^7.29.7"
|
| 204 |
+
},
|
| 205 |
+
"engines": {
|
| 206 |
+
"node": ">=6.9.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"node_modules/@babel/parser": {
|
| 210 |
+
"version": "7.29.7",
|
| 211 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
| 212 |
+
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
| 213 |
+
"dev": true,
|
| 214 |
+
"license": "MIT",
|
| 215 |
+
"dependencies": {
|
| 216 |
+
"@babel/types": "^7.29.7"
|
| 217 |
+
},
|
| 218 |
+
"bin": {
|
| 219 |
+
"parser": "bin/babel-parser.js"
|
| 220 |
+
},
|
| 221 |
+
"engines": {
|
| 222 |
+
"node": ">=6.0.0"
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
| 226 |
+
"version": "7.29.7",
|
| 227 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
|
| 228 |
+
"integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
|
| 229 |
+
"dev": true,
|
| 230 |
+
"license": "MIT",
|
| 231 |
+
"dependencies": {
|
| 232 |
+
"@babel/helper-plugin-utils": "^7.29.7"
|
| 233 |
+
},
|
| 234 |
+
"engines": {
|
| 235 |
+
"node": ">=6.9.0"
|
| 236 |
+
},
|
| 237 |
+
"peerDependencies": {
|
| 238 |
+
"@babel/core": "^7.0.0-0"
|
| 239 |
+
}
|
| 240 |
+
},
|
| 241 |
+
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
| 242 |
+
"version": "7.29.7",
|
| 243 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
|
| 244 |
+
"integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
|
| 245 |
+
"dev": true,
|
| 246 |
+
"license": "MIT",
|
| 247 |
+
"dependencies": {
|
| 248 |
+
"@babel/helper-plugin-utils": "^7.29.7"
|
| 249 |
+
},
|
| 250 |
+
"engines": {
|
| 251 |
+
"node": ">=6.9.0"
|
| 252 |
+
},
|
| 253 |
+
"peerDependencies": {
|
| 254 |
+
"@babel/core": "^7.0.0-0"
|
| 255 |
+
}
|
| 256 |
+
},
|
| 257 |
+
"node_modules/@babel/template": {
|
| 258 |
+
"version": "7.29.7",
|
| 259 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
| 260 |
+
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
| 261 |
+
"dev": true,
|
| 262 |
+
"license": "MIT",
|
| 263 |
+
"dependencies": {
|
| 264 |
+
"@babel/code-frame": "^7.29.7",
|
| 265 |
+
"@babel/parser": "^7.29.7",
|
| 266 |
+
"@babel/types": "^7.29.7"
|
| 267 |
+
},
|
| 268 |
+
"engines": {
|
| 269 |
+
"node": ">=6.9.0"
|
| 270 |
+
}
|
| 271 |
+
},
|
| 272 |
+
"node_modules/@babel/traverse": {
|
| 273 |
+
"version": "7.29.7",
|
| 274 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
|
| 275 |
+
"integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
|
| 276 |
+
"dev": true,
|
| 277 |
+
"license": "MIT",
|
| 278 |
+
"dependencies": {
|
| 279 |
+
"@babel/code-frame": "^7.29.7",
|
| 280 |
+
"@babel/generator": "^7.29.7",
|
| 281 |
+
"@babel/helper-globals": "^7.29.7",
|
| 282 |
+
"@babel/parser": "^7.29.7",
|
| 283 |
+
"@babel/template": "^7.29.7",
|
| 284 |
+
"@babel/types": "^7.29.7",
|
| 285 |
+
"debug": "^4.3.1"
|
| 286 |
+
},
|
| 287 |
+
"engines": {
|
| 288 |
+
"node": ">=6.9.0"
|
| 289 |
+
}
|
| 290 |
+
},
|
| 291 |
+
"node_modules/@babel/types": {
|
| 292 |
+
"version": "7.29.7",
|
| 293 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
| 294 |
+
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
| 295 |
+
"dev": true,
|
| 296 |
+
"license": "MIT",
|
| 297 |
+
"dependencies": {
|
| 298 |
+
"@babel/helper-string-parser": "^7.29.7",
|
| 299 |
+
"@babel/helper-validator-identifier": "^7.29.7"
|
| 300 |
+
},
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": ">=6.9.0"
|
| 303 |
+
}
|
| 304 |
+
},
|
| 305 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 306 |
+
"version": "0.21.5",
|
| 307 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
| 308 |
+
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
| 309 |
+
"cpu": [
|
| 310 |
+
"ppc64"
|
| 311 |
+
],
|
| 312 |
+
"dev": true,
|
| 313 |
+
"license": "MIT",
|
| 314 |
+
"optional": true,
|
| 315 |
+
"os": [
|
| 316 |
+
"aix"
|
| 317 |
+
],
|
| 318 |
+
"engines": {
|
| 319 |
+
"node": ">=12"
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"node_modules/@esbuild/android-arm": {
|
| 323 |
+
"version": "0.21.5",
|
| 324 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
| 325 |
+
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
| 326 |
+
"cpu": [
|
| 327 |
+
"arm"
|
| 328 |
+
],
|
| 329 |
+
"dev": true,
|
| 330 |
+
"license": "MIT",
|
| 331 |
+
"optional": true,
|
| 332 |
+
"os": [
|
| 333 |
+
"android"
|
| 334 |
+
],
|
| 335 |
+
"engines": {
|
| 336 |
+
"node": ">=12"
|
| 337 |
+
}
|
| 338 |
+
},
|
| 339 |
+
"node_modules/@esbuild/android-arm64": {
|
| 340 |
+
"version": "0.21.5",
|
| 341 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
| 342 |
+
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
| 343 |
+
"cpu": [
|
| 344 |
+
"arm64"
|
| 345 |
+
],
|
| 346 |
+
"dev": true,
|
| 347 |
+
"license": "MIT",
|
| 348 |
+
"optional": true,
|
| 349 |
+
"os": [
|
| 350 |
+
"android"
|
| 351 |
+
],
|
| 352 |
+
"engines": {
|
| 353 |
+
"node": ">=12"
|
| 354 |
+
}
|
| 355 |
+
},
|
| 356 |
+
"node_modules/@esbuild/android-x64": {
|
| 357 |
+
"version": "0.21.5",
|
| 358 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
| 359 |
+
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
| 360 |
+
"cpu": [
|
| 361 |
+
"x64"
|
| 362 |
+
],
|
| 363 |
+
"dev": true,
|
| 364 |
+
"license": "MIT",
|
| 365 |
+
"optional": true,
|
| 366 |
+
"os": [
|
| 367 |
+
"android"
|
| 368 |
+
],
|
| 369 |
+
"engines": {
|
| 370 |
+
"node": ">=12"
|
| 371 |
+
}
|
| 372 |
+
},
|
| 373 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 374 |
+
"version": "0.21.5",
|
| 375 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
| 376 |
+
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
| 377 |
+
"cpu": [
|
| 378 |
+
"arm64"
|
| 379 |
+
],
|
| 380 |
+
"dev": true,
|
| 381 |
+
"license": "MIT",
|
| 382 |
+
"optional": true,
|
| 383 |
+
"os": [
|
| 384 |
+
"darwin"
|
| 385 |
+
],
|
| 386 |
+
"engines": {
|
| 387 |
+
"node": ">=12"
|
| 388 |
+
}
|
| 389 |
+
},
|
| 390 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 391 |
+
"version": "0.21.5",
|
| 392 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
| 393 |
+
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
| 394 |
+
"cpu": [
|
| 395 |
+
"x64"
|
| 396 |
+
],
|
| 397 |
+
"dev": true,
|
| 398 |
+
"license": "MIT",
|
| 399 |
+
"optional": true,
|
| 400 |
+
"os": [
|
| 401 |
+
"darwin"
|
| 402 |
+
],
|
| 403 |
+
"engines": {
|
| 404 |
+
"node": ">=12"
|
| 405 |
+
}
|
| 406 |
+
},
|
| 407 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 408 |
+
"version": "0.21.5",
|
| 409 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
| 410 |
+
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
| 411 |
+
"cpu": [
|
| 412 |
+
"arm64"
|
| 413 |
+
],
|
| 414 |
+
"dev": true,
|
| 415 |
+
"license": "MIT",
|
| 416 |
+
"optional": true,
|
| 417 |
+
"os": [
|
| 418 |
+
"freebsd"
|
| 419 |
+
],
|
| 420 |
+
"engines": {
|
| 421 |
+
"node": ">=12"
|
| 422 |
+
}
|
| 423 |
+
},
|
| 424 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 425 |
+
"version": "0.21.5",
|
| 426 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
| 427 |
+
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
| 428 |
+
"cpu": [
|
| 429 |
+
"x64"
|
| 430 |
+
],
|
| 431 |
+
"dev": true,
|
| 432 |
+
"license": "MIT",
|
| 433 |
+
"optional": true,
|
| 434 |
+
"os": [
|
| 435 |
+
"freebsd"
|
| 436 |
+
],
|
| 437 |
+
"engines": {
|
| 438 |
+
"node": ">=12"
|
| 439 |
+
}
|
| 440 |
+
},
|
| 441 |
+
"node_modules/@esbuild/linux-arm": {
|
| 442 |
+
"version": "0.21.5",
|
| 443 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
| 444 |
+
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
| 445 |
+
"cpu": [
|
| 446 |
+
"arm"
|
| 447 |
+
],
|
| 448 |
+
"dev": true,
|
| 449 |
+
"license": "MIT",
|
| 450 |
+
"optional": true,
|
| 451 |
+
"os": [
|
| 452 |
+
"linux"
|
| 453 |
+
],
|
| 454 |
+
"engines": {
|
| 455 |
+
"node": ">=12"
|
| 456 |
+
}
|
| 457 |
+
},
|
| 458 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 459 |
+
"version": "0.21.5",
|
| 460 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
| 461 |
+
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
| 462 |
+
"cpu": [
|
| 463 |
+
"arm64"
|
| 464 |
+
],
|
| 465 |
+
"dev": true,
|
| 466 |
+
"license": "MIT",
|
| 467 |
+
"optional": true,
|
| 468 |
+
"os": [
|
| 469 |
+
"linux"
|
| 470 |
+
],
|
| 471 |
+
"engines": {
|
| 472 |
+
"node": ">=12"
|
| 473 |
+
}
|
| 474 |
+
},
|
| 475 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 476 |
+
"version": "0.21.5",
|
| 477 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
| 478 |
+
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
| 479 |
+
"cpu": [
|
| 480 |
+
"ia32"
|
| 481 |
+
],
|
| 482 |
+
"dev": true,
|
| 483 |
+
"license": "MIT",
|
| 484 |
+
"optional": true,
|
| 485 |
+
"os": [
|
| 486 |
+
"linux"
|
| 487 |
+
],
|
| 488 |
+
"engines": {
|
| 489 |
+
"node": ">=12"
|
| 490 |
+
}
|
| 491 |
+
},
|
| 492 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 493 |
+
"version": "0.21.5",
|
| 494 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
| 495 |
+
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
| 496 |
+
"cpu": [
|
| 497 |
+
"loong64"
|
| 498 |
+
],
|
| 499 |
+
"dev": true,
|
| 500 |
+
"license": "MIT",
|
| 501 |
+
"optional": true,
|
| 502 |
+
"os": [
|
| 503 |
+
"linux"
|
| 504 |
+
],
|
| 505 |
+
"engines": {
|
| 506 |
+
"node": ">=12"
|
| 507 |
+
}
|
| 508 |
+
},
|
| 509 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 510 |
+
"version": "0.21.5",
|
| 511 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
| 512 |
+
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
| 513 |
+
"cpu": [
|
| 514 |
+
"mips64el"
|
| 515 |
+
],
|
| 516 |
+
"dev": true,
|
| 517 |
+
"license": "MIT",
|
| 518 |
+
"optional": true,
|
| 519 |
+
"os": [
|
| 520 |
+
"linux"
|
| 521 |
+
],
|
| 522 |
+
"engines": {
|
| 523 |
+
"node": ">=12"
|
| 524 |
+
}
|
| 525 |
+
},
|
| 526 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 527 |
+
"version": "0.21.5",
|
| 528 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
| 529 |
+
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
| 530 |
+
"cpu": [
|
| 531 |
+
"ppc64"
|
| 532 |
+
],
|
| 533 |
+
"dev": true,
|
| 534 |
+
"license": "MIT",
|
| 535 |
+
"optional": true,
|
| 536 |
+
"os": [
|
| 537 |
+
"linux"
|
| 538 |
+
],
|
| 539 |
+
"engines": {
|
| 540 |
+
"node": ">=12"
|
| 541 |
+
}
|
| 542 |
+
},
|
| 543 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 544 |
+
"version": "0.21.5",
|
| 545 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
| 546 |
+
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
| 547 |
+
"cpu": [
|
| 548 |
+
"riscv64"
|
| 549 |
+
],
|
| 550 |
+
"dev": true,
|
| 551 |
+
"license": "MIT",
|
| 552 |
+
"optional": true,
|
| 553 |
+
"os": [
|
| 554 |
+
"linux"
|
| 555 |
+
],
|
| 556 |
+
"engines": {
|
| 557 |
+
"node": ">=12"
|
| 558 |
+
}
|
| 559 |
+
},
|
| 560 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 561 |
+
"version": "0.21.5",
|
| 562 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
| 563 |
+
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
| 564 |
+
"cpu": [
|
| 565 |
+
"s390x"
|
| 566 |
+
],
|
| 567 |
+
"dev": true,
|
| 568 |
+
"license": "MIT",
|
| 569 |
+
"optional": true,
|
| 570 |
+
"os": [
|
| 571 |
+
"linux"
|
| 572 |
+
],
|
| 573 |
+
"engines": {
|
| 574 |
+
"node": ">=12"
|
| 575 |
+
}
|
| 576 |
+
},
|
| 577 |
+
"node_modules/@esbuild/linux-x64": {
|
| 578 |
+
"version": "0.21.5",
|
| 579 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
| 580 |
+
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
| 581 |
+
"cpu": [
|
| 582 |
+
"x64"
|
| 583 |
+
],
|
| 584 |
+
"dev": true,
|
| 585 |
+
"license": "MIT",
|
| 586 |
+
"optional": true,
|
| 587 |
+
"os": [
|
| 588 |
+
"linux"
|
| 589 |
+
],
|
| 590 |
+
"engines": {
|
| 591 |
+
"node": ">=12"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 595 |
+
"version": "0.21.5",
|
| 596 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
| 597 |
+
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
| 598 |
+
"cpu": [
|
| 599 |
+
"x64"
|
| 600 |
+
],
|
| 601 |
+
"dev": true,
|
| 602 |
+
"license": "MIT",
|
| 603 |
+
"optional": true,
|
| 604 |
+
"os": [
|
| 605 |
+
"netbsd"
|
| 606 |
+
],
|
| 607 |
+
"engines": {
|
| 608 |
+
"node": ">=12"
|
| 609 |
+
}
|
| 610 |
+
},
|
| 611 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 612 |
+
"version": "0.21.5",
|
| 613 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
| 614 |
+
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
| 615 |
+
"cpu": [
|
| 616 |
+
"x64"
|
| 617 |
+
],
|
| 618 |
+
"dev": true,
|
| 619 |
+
"license": "MIT",
|
| 620 |
+
"optional": true,
|
| 621 |
+
"os": [
|
| 622 |
+
"openbsd"
|
| 623 |
+
],
|
| 624 |
+
"engines": {
|
| 625 |
+
"node": ">=12"
|
| 626 |
+
}
|
| 627 |
+
},
|
| 628 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 629 |
+
"version": "0.21.5",
|
| 630 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
| 631 |
+
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
| 632 |
+
"cpu": [
|
| 633 |
+
"x64"
|
| 634 |
+
],
|
| 635 |
+
"dev": true,
|
| 636 |
+
"license": "MIT",
|
| 637 |
+
"optional": true,
|
| 638 |
+
"os": [
|
| 639 |
+
"sunos"
|
| 640 |
+
],
|
| 641 |
+
"engines": {
|
| 642 |
+
"node": ">=12"
|
| 643 |
+
}
|
| 644 |
+
},
|
| 645 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 646 |
+
"version": "0.21.5",
|
| 647 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
| 648 |
+
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
| 649 |
+
"cpu": [
|
| 650 |
+
"arm64"
|
| 651 |
+
],
|
| 652 |
+
"dev": true,
|
| 653 |
+
"license": "MIT",
|
| 654 |
+
"optional": true,
|
| 655 |
+
"os": [
|
| 656 |
+
"win32"
|
| 657 |
+
],
|
| 658 |
+
"engines": {
|
| 659 |
+
"node": ">=12"
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 663 |
+
"version": "0.21.5",
|
| 664 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
| 665 |
+
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
| 666 |
+
"cpu": [
|
| 667 |
+
"ia32"
|
| 668 |
+
],
|
| 669 |
+
"dev": true,
|
| 670 |
+
"license": "MIT",
|
| 671 |
+
"optional": true,
|
| 672 |
+
"os": [
|
| 673 |
+
"win32"
|
| 674 |
+
],
|
| 675 |
+
"engines": {
|
| 676 |
+
"node": ">=12"
|
| 677 |
+
}
|
| 678 |
+
},
|
| 679 |
+
"node_modules/@esbuild/win32-x64": {
|
| 680 |
+
"version": "0.21.5",
|
| 681 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
| 682 |
+
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
| 683 |
+
"cpu": [
|
| 684 |
+
"x64"
|
| 685 |
+
],
|
| 686 |
+
"dev": true,
|
| 687 |
+
"license": "MIT",
|
| 688 |
+
"optional": true,
|
| 689 |
+
"os": [
|
| 690 |
+
"win32"
|
| 691 |
+
],
|
| 692 |
+
"engines": {
|
| 693 |
+
"node": ">=12"
|
| 694 |
+
}
|
| 695 |
+
},
|
| 696 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 697 |
+
"version": "0.3.13",
|
| 698 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 699 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 700 |
+
"dev": true,
|
| 701 |
+
"license": "MIT",
|
| 702 |
+
"dependencies": {
|
| 703 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 704 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 705 |
+
}
|
| 706 |
+
},
|
| 707 |
+
"node_modules/@jridgewell/remapping": {
|
| 708 |
+
"version": "2.3.5",
|
| 709 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 710 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 711 |
+
"dev": true,
|
| 712 |
+
"license": "MIT",
|
| 713 |
+
"dependencies": {
|
| 714 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 715 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 716 |
+
}
|
| 717 |
+
},
|
| 718 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 719 |
+
"version": "3.1.2",
|
| 720 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 721 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 722 |
+
"dev": true,
|
| 723 |
+
"license": "MIT",
|
| 724 |
+
"engines": {
|
| 725 |
+
"node": ">=6.0.0"
|
| 726 |
+
}
|
| 727 |
+
},
|
| 728 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 729 |
+
"version": "1.5.5",
|
| 730 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 731 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 732 |
+
"dev": true,
|
| 733 |
+
"license": "MIT"
|
| 734 |
+
},
|
| 735 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 736 |
+
"version": "0.3.31",
|
| 737 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 738 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 739 |
+
"dev": true,
|
| 740 |
+
"license": "MIT",
|
| 741 |
+
"dependencies": {
|
| 742 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 743 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 744 |
+
}
|
| 745 |
+
},
|
| 746 |
+
"node_modules/@oxlint/binding-android-arm-eabi": {
|
| 747 |
+
"version": "1.71.0",
|
| 748 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.71.0.tgz",
|
| 749 |
+
"integrity": "sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==",
|
| 750 |
+
"cpu": [
|
| 751 |
+
"arm"
|
| 752 |
+
],
|
| 753 |
+
"dev": true,
|
| 754 |
+
"license": "MIT",
|
| 755 |
+
"optional": true,
|
| 756 |
+
"os": [
|
| 757 |
+
"android"
|
| 758 |
+
],
|
| 759 |
+
"engines": {
|
| 760 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 761 |
+
}
|
| 762 |
+
},
|
| 763 |
+
"node_modules/@oxlint/binding-android-arm64": {
|
| 764 |
+
"version": "1.71.0",
|
| 765 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.71.0.tgz",
|
| 766 |
+
"integrity": "sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==",
|
| 767 |
+
"cpu": [
|
| 768 |
+
"arm64"
|
| 769 |
+
],
|
| 770 |
+
"dev": true,
|
| 771 |
+
"license": "MIT",
|
| 772 |
+
"optional": true,
|
| 773 |
+
"os": [
|
| 774 |
+
"android"
|
| 775 |
+
],
|
| 776 |
+
"engines": {
|
| 777 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 778 |
+
}
|
| 779 |
+
},
|
| 780 |
+
"node_modules/@oxlint/binding-darwin-arm64": {
|
| 781 |
+
"version": "1.71.0",
|
| 782 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.71.0.tgz",
|
| 783 |
+
"integrity": "sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==",
|
| 784 |
+
"cpu": [
|
| 785 |
+
"arm64"
|
| 786 |
+
],
|
| 787 |
+
"dev": true,
|
| 788 |
+
"license": "MIT",
|
| 789 |
+
"optional": true,
|
| 790 |
+
"os": [
|
| 791 |
+
"darwin"
|
| 792 |
+
],
|
| 793 |
+
"engines": {
|
| 794 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 795 |
+
}
|
| 796 |
+
},
|
| 797 |
+
"node_modules/@oxlint/binding-darwin-x64": {
|
| 798 |
+
"version": "1.71.0",
|
| 799 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.71.0.tgz",
|
| 800 |
+
"integrity": "sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==",
|
| 801 |
+
"cpu": [
|
| 802 |
+
"x64"
|
| 803 |
+
],
|
| 804 |
+
"dev": true,
|
| 805 |
+
"license": "MIT",
|
| 806 |
+
"optional": true,
|
| 807 |
+
"os": [
|
| 808 |
+
"darwin"
|
| 809 |
+
],
|
| 810 |
+
"engines": {
|
| 811 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 812 |
+
}
|
| 813 |
+
},
|
| 814 |
+
"node_modules/@oxlint/binding-freebsd-x64": {
|
| 815 |
+
"version": "1.71.0",
|
| 816 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.71.0.tgz",
|
| 817 |
+
"integrity": "sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==",
|
| 818 |
+
"cpu": [
|
| 819 |
+
"x64"
|
| 820 |
+
],
|
| 821 |
+
"dev": true,
|
| 822 |
+
"license": "MIT",
|
| 823 |
+
"optional": true,
|
| 824 |
+
"os": [
|
| 825 |
+
"freebsd"
|
| 826 |
+
],
|
| 827 |
+
"engines": {
|
| 828 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 829 |
+
}
|
| 830 |
+
},
|
| 831 |
+
"node_modules/@oxlint/binding-linux-arm-gnueabihf": {
|
| 832 |
+
"version": "1.71.0",
|
| 833 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.71.0.tgz",
|
| 834 |
+
"integrity": "sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==",
|
| 835 |
+
"cpu": [
|
| 836 |
+
"arm"
|
| 837 |
+
],
|
| 838 |
+
"dev": true,
|
| 839 |
+
"license": "MIT",
|
| 840 |
+
"optional": true,
|
| 841 |
+
"os": [
|
| 842 |
+
"linux"
|
| 843 |
+
],
|
| 844 |
+
"engines": {
|
| 845 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 846 |
+
}
|
| 847 |
+
},
|
| 848 |
+
"node_modules/@oxlint/binding-linux-arm-musleabihf": {
|
| 849 |
+
"version": "1.71.0",
|
| 850 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.71.0.tgz",
|
| 851 |
+
"integrity": "sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==",
|
| 852 |
+
"cpu": [
|
| 853 |
+
"arm"
|
| 854 |
+
],
|
| 855 |
+
"dev": true,
|
| 856 |
+
"license": "MIT",
|
| 857 |
+
"optional": true,
|
| 858 |
+
"os": [
|
| 859 |
+
"linux"
|
| 860 |
+
],
|
| 861 |
+
"engines": {
|
| 862 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 863 |
+
}
|
| 864 |
+
},
|
| 865 |
+
"node_modules/@oxlint/binding-linux-arm64-gnu": {
|
| 866 |
+
"version": "1.71.0",
|
| 867 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.71.0.tgz",
|
| 868 |
+
"integrity": "sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==",
|
| 869 |
+
"cpu": [
|
| 870 |
+
"arm64"
|
| 871 |
+
],
|
| 872 |
+
"dev": true,
|
| 873 |
+
"license": "MIT",
|
| 874 |
+
"optional": true,
|
| 875 |
+
"os": [
|
| 876 |
+
"linux"
|
| 877 |
+
],
|
| 878 |
+
"engines": {
|
| 879 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 880 |
+
}
|
| 881 |
+
},
|
| 882 |
+
"node_modules/@oxlint/binding-linux-arm64-musl": {
|
| 883 |
+
"version": "1.71.0",
|
| 884 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.71.0.tgz",
|
| 885 |
+
"integrity": "sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==",
|
| 886 |
+
"cpu": [
|
| 887 |
+
"arm64"
|
| 888 |
+
],
|
| 889 |
+
"dev": true,
|
| 890 |
+
"license": "MIT",
|
| 891 |
+
"optional": true,
|
| 892 |
+
"os": [
|
| 893 |
+
"linux"
|
| 894 |
+
],
|
| 895 |
+
"engines": {
|
| 896 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 897 |
+
}
|
| 898 |
+
},
|
| 899 |
+
"node_modules/@oxlint/binding-linux-ppc64-gnu": {
|
| 900 |
+
"version": "1.71.0",
|
| 901 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.71.0.tgz",
|
| 902 |
+
"integrity": "sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==",
|
| 903 |
+
"cpu": [
|
| 904 |
+
"ppc64"
|
| 905 |
+
],
|
| 906 |
+
"dev": true,
|
| 907 |
+
"license": "MIT",
|
| 908 |
+
"optional": true,
|
| 909 |
+
"os": [
|
| 910 |
+
"linux"
|
| 911 |
+
],
|
| 912 |
+
"engines": {
|
| 913 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 914 |
+
}
|
| 915 |
+
},
|
| 916 |
+
"node_modules/@oxlint/binding-linux-riscv64-gnu": {
|
| 917 |
+
"version": "1.71.0",
|
| 918 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.71.0.tgz",
|
| 919 |
+
"integrity": "sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==",
|
| 920 |
+
"cpu": [
|
| 921 |
+
"riscv64"
|
| 922 |
+
],
|
| 923 |
+
"dev": true,
|
| 924 |
+
"license": "MIT",
|
| 925 |
+
"optional": true,
|
| 926 |
+
"os": [
|
| 927 |
+
"linux"
|
| 928 |
+
],
|
| 929 |
+
"engines": {
|
| 930 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 931 |
+
}
|
| 932 |
+
},
|
| 933 |
+
"node_modules/@oxlint/binding-linux-riscv64-musl": {
|
| 934 |
+
"version": "1.71.0",
|
| 935 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.71.0.tgz",
|
| 936 |
+
"integrity": "sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==",
|
| 937 |
+
"cpu": [
|
| 938 |
+
"riscv64"
|
| 939 |
+
],
|
| 940 |
+
"dev": true,
|
| 941 |
+
"license": "MIT",
|
| 942 |
+
"optional": true,
|
| 943 |
+
"os": [
|
| 944 |
+
"linux"
|
| 945 |
+
],
|
| 946 |
+
"engines": {
|
| 947 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 948 |
+
}
|
| 949 |
+
},
|
| 950 |
+
"node_modules/@oxlint/binding-linux-s390x-gnu": {
|
| 951 |
+
"version": "1.71.0",
|
| 952 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.71.0.tgz",
|
| 953 |
+
"integrity": "sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==",
|
| 954 |
+
"cpu": [
|
| 955 |
+
"s390x"
|
| 956 |
+
],
|
| 957 |
+
"dev": true,
|
| 958 |
+
"license": "MIT",
|
| 959 |
+
"optional": true,
|
| 960 |
+
"os": [
|
| 961 |
+
"linux"
|
| 962 |
+
],
|
| 963 |
+
"engines": {
|
| 964 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 965 |
+
}
|
| 966 |
+
},
|
| 967 |
+
"node_modules/@oxlint/binding-linux-x64-gnu": {
|
| 968 |
+
"version": "1.71.0",
|
| 969 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.71.0.tgz",
|
| 970 |
+
"integrity": "sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==",
|
| 971 |
+
"cpu": [
|
| 972 |
+
"x64"
|
| 973 |
+
],
|
| 974 |
+
"dev": true,
|
| 975 |
+
"license": "MIT",
|
| 976 |
+
"optional": true,
|
| 977 |
+
"os": [
|
| 978 |
+
"linux"
|
| 979 |
+
],
|
| 980 |
+
"engines": {
|
| 981 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 982 |
+
}
|
| 983 |
+
},
|
| 984 |
+
"node_modules/@oxlint/binding-linux-x64-musl": {
|
| 985 |
+
"version": "1.71.0",
|
| 986 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.71.0.tgz",
|
| 987 |
+
"integrity": "sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==",
|
| 988 |
+
"cpu": [
|
| 989 |
+
"x64"
|
| 990 |
+
],
|
| 991 |
+
"dev": true,
|
| 992 |
+
"license": "MIT",
|
| 993 |
+
"optional": true,
|
| 994 |
+
"os": [
|
| 995 |
+
"linux"
|
| 996 |
+
],
|
| 997 |
+
"engines": {
|
| 998 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 999 |
+
}
|
| 1000 |
+
},
|
| 1001 |
+
"node_modules/@oxlint/binding-openharmony-arm64": {
|
| 1002 |
+
"version": "1.71.0",
|
| 1003 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.71.0.tgz",
|
| 1004 |
+
"integrity": "sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==",
|
| 1005 |
+
"cpu": [
|
| 1006 |
+
"arm64"
|
| 1007 |
+
],
|
| 1008 |
+
"dev": true,
|
| 1009 |
+
"license": "MIT",
|
| 1010 |
+
"optional": true,
|
| 1011 |
+
"os": [
|
| 1012 |
+
"openharmony"
|
| 1013 |
+
],
|
| 1014 |
+
"engines": {
|
| 1015 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1016 |
+
}
|
| 1017 |
+
},
|
| 1018 |
+
"node_modules/@oxlint/binding-win32-arm64-msvc": {
|
| 1019 |
+
"version": "1.71.0",
|
| 1020 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.71.0.tgz",
|
| 1021 |
+
"integrity": "sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==",
|
| 1022 |
+
"cpu": [
|
| 1023 |
+
"arm64"
|
| 1024 |
+
],
|
| 1025 |
+
"dev": true,
|
| 1026 |
+
"license": "MIT",
|
| 1027 |
+
"optional": true,
|
| 1028 |
+
"os": [
|
| 1029 |
+
"win32"
|
| 1030 |
+
],
|
| 1031 |
+
"engines": {
|
| 1032 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1033 |
+
}
|
| 1034 |
+
},
|
| 1035 |
+
"node_modules/@oxlint/binding-win32-ia32-msvc": {
|
| 1036 |
+
"version": "1.71.0",
|
| 1037 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.71.0.tgz",
|
| 1038 |
+
"integrity": "sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==",
|
| 1039 |
+
"cpu": [
|
| 1040 |
+
"ia32"
|
| 1041 |
+
],
|
| 1042 |
+
"dev": true,
|
| 1043 |
+
"license": "MIT",
|
| 1044 |
+
"optional": true,
|
| 1045 |
+
"os": [
|
| 1046 |
+
"win32"
|
| 1047 |
+
],
|
| 1048 |
+
"engines": {
|
| 1049 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1050 |
+
}
|
| 1051 |
+
},
|
| 1052 |
+
"node_modules/@oxlint/binding-win32-x64-msvc": {
|
| 1053 |
+
"version": "1.71.0",
|
| 1054 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.71.0.tgz",
|
| 1055 |
+
"integrity": "sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==",
|
| 1056 |
+
"cpu": [
|
| 1057 |
+
"x64"
|
| 1058 |
+
],
|
| 1059 |
+
"dev": true,
|
| 1060 |
+
"license": "MIT",
|
| 1061 |
+
"optional": true,
|
| 1062 |
+
"os": [
|
| 1063 |
+
"win32"
|
| 1064 |
+
],
|
| 1065 |
+
"engines": {
|
| 1066 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1067 |
+
}
|
| 1068 |
+
},
|
| 1069 |
+
"node_modules/@rolldown/pluginutils": {
|
| 1070 |
+
"version": "1.0.0-beta.47",
|
| 1071 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
|
| 1072 |
+
"integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
|
| 1073 |
+
"dev": true,
|
| 1074 |
+
"license": "MIT"
|
| 1075 |
+
},
|
| 1076 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 1077 |
+
"version": "4.62.2",
|
| 1078 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
| 1079 |
+
"integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
|
| 1080 |
+
"cpu": [
|
| 1081 |
+
"arm"
|
| 1082 |
+
],
|
| 1083 |
+
"dev": true,
|
| 1084 |
+
"license": "MIT",
|
| 1085 |
+
"optional": true,
|
| 1086 |
+
"os": [
|
| 1087 |
+
"android"
|
| 1088 |
+
]
|
| 1089 |
+
},
|
| 1090 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 1091 |
+
"version": "4.62.2",
|
| 1092 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
|
| 1093 |
+
"integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
|
| 1094 |
+
"cpu": [
|
| 1095 |
+
"arm64"
|
| 1096 |
+
],
|
| 1097 |
+
"dev": true,
|
| 1098 |
+
"license": "MIT",
|
| 1099 |
+
"optional": true,
|
| 1100 |
+
"os": [
|
| 1101 |
+
"android"
|
| 1102 |
+
]
|
| 1103 |
+
},
|
| 1104 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 1105 |
+
"version": "4.62.2",
|
| 1106 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
|
| 1107 |
+
"integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
|
| 1108 |
+
"cpu": [
|
| 1109 |
+
"arm64"
|
| 1110 |
+
],
|
| 1111 |
+
"dev": true,
|
| 1112 |
+
"license": "MIT",
|
| 1113 |
+
"optional": true,
|
| 1114 |
+
"os": [
|
| 1115 |
+
"darwin"
|
| 1116 |
+
]
|
| 1117 |
+
},
|
| 1118 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 1119 |
+
"version": "4.62.2",
|
| 1120 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
|
| 1121 |
+
"integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
|
| 1122 |
+
"cpu": [
|
| 1123 |
+
"x64"
|
| 1124 |
+
],
|
| 1125 |
+
"dev": true,
|
| 1126 |
+
"license": "MIT",
|
| 1127 |
+
"optional": true,
|
| 1128 |
+
"os": [
|
| 1129 |
+
"darwin"
|
| 1130 |
+
]
|
| 1131 |
+
},
|
| 1132 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 1133 |
+
"version": "4.62.2",
|
| 1134 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
|
| 1135 |
+
"integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
|
| 1136 |
+
"cpu": [
|
| 1137 |
+
"arm64"
|
| 1138 |
+
],
|
| 1139 |
+
"dev": true,
|
| 1140 |
+
"license": "MIT",
|
| 1141 |
+
"optional": true,
|
| 1142 |
+
"os": [
|
| 1143 |
+
"freebsd"
|
| 1144 |
+
]
|
| 1145 |
+
},
|
| 1146 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 1147 |
+
"version": "4.62.2",
|
| 1148 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
|
| 1149 |
+
"integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
|
| 1150 |
+
"cpu": [
|
| 1151 |
+
"x64"
|
| 1152 |
+
],
|
| 1153 |
+
"dev": true,
|
| 1154 |
+
"license": "MIT",
|
| 1155 |
+
"optional": true,
|
| 1156 |
+
"os": [
|
| 1157 |
+
"freebsd"
|
| 1158 |
+
]
|
| 1159 |
+
},
|
| 1160 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 1161 |
+
"version": "4.62.2",
|
| 1162 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
|
| 1163 |
+
"integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
|
| 1164 |
+
"cpu": [
|
| 1165 |
+
"arm"
|
| 1166 |
+
],
|
| 1167 |
+
"dev": true,
|
| 1168 |
+
"license": "MIT",
|
| 1169 |
+
"optional": true,
|
| 1170 |
+
"os": [
|
| 1171 |
+
"linux"
|
| 1172 |
+
]
|
| 1173 |
+
},
|
| 1174 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 1175 |
+
"version": "4.62.2",
|
| 1176 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
|
| 1177 |
+
"integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
|
| 1178 |
+
"cpu": [
|
| 1179 |
+
"arm"
|
| 1180 |
+
],
|
| 1181 |
+
"dev": true,
|
| 1182 |
+
"license": "MIT",
|
| 1183 |
+
"optional": true,
|
| 1184 |
+
"os": [
|
| 1185 |
+
"linux"
|
| 1186 |
+
]
|
| 1187 |
+
},
|
| 1188 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 1189 |
+
"version": "4.62.2",
|
| 1190 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
|
| 1191 |
+
"integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
|
| 1192 |
+
"cpu": [
|
| 1193 |
+
"arm64"
|
| 1194 |
+
],
|
| 1195 |
+
"dev": true,
|
| 1196 |
+
"license": "MIT",
|
| 1197 |
+
"optional": true,
|
| 1198 |
+
"os": [
|
| 1199 |
+
"linux"
|
| 1200 |
+
]
|
| 1201 |
+
},
|
| 1202 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 1203 |
+
"version": "4.62.2",
|
| 1204 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
|
| 1205 |
+
"integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
|
| 1206 |
+
"cpu": [
|
| 1207 |
+
"arm64"
|
| 1208 |
+
],
|
| 1209 |
+
"dev": true,
|
| 1210 |
+
"license": "MIT",
|
| 1211 |
+
"optional": true,
|
| 1212 |
+
"os": [
|
| 1213 |
+
"linux"
|
| 1214 |
+
]
|
| 1215 |
+
},
|
| 1216 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 1217 |
+
"version": "4.62.2",
|
| 1218 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
|
| 1219 |
+
"integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
|
| 1220 |
+
"cpu": [
|
| 1221 |
+
"loong64"
|
| 1222 |
+
],
|
| 1223 |
+
"dev": true,
|
| 1224 |
+
"license": "MIT",
|
| 1225 |
+
"optional": true,
|
| 1226 |
+
"os": [
|
| 1227 |
+
"linux"
|
| 1228 |
+
]
|
| 1229 |
+
},
|
| 1230 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 1231 |
+
"version": "4.62.2",
|
| 1232 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
|
| 1233 |
+
"integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
|
| 1234 |
+
"cpu": [
|
| 1235 |
+
"loong64"
|
| 1236 |
+
],
|
| 1237 |
+
"dev": true,
|
| 1238 |
+
"license": "MIT",
|
| 1239 |
+
"optional": true,
|
| 1240 |
+
"os": [
|
| 1241 |
+
"linux"
|
| 1242 |
+
]
|
| 1243 |
+
},
|
| 1244 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 1245 |
+
"version": "4.62.2",
|
| 1246 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
|
| 1247 |
+
"integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
|
| 1248 |
+
"cpu": [
|
| 1249 |
+
"ppc64"
|
| 1250 |
+
],
|
| 1251 |
+
"dev": true,
|
| 1252 |
+
"license": "MIT",
|
| 1253 |
+
"optional": true,
|
| 1254 |
+
"os": [
|
| 1255 |
+
"linux"
|
| 1256 |
+
]
|
| 1257 |
+
},
|
| 1258 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 1259 |
+
"version": "4.62.2",
|
| 1260 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
|
| 1261 |
+
"integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
|
| 1262 |
+
"cpu": [
|
| 1263 |
+
"ppc64"
|
| 1264 |
+
],
|
| 1265 |
+
"dev": true,
|
| 1266 |
+
"license": "MIT",
|
| 1267 |
+
"optional": true,
|
| 1268 |
+
"os": [
|
| 1269 |
+
"linux"
|
| 1270 |
+
]
|
| 1271 |
+
},
|
| 1272 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 1273 |
+
"version": "4.62.2",
|
| 1274 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
|
| 1275 |
+
"integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
|
| 1276 |
+
"cpu": [
|
| 1277 |
+
"riscv64"
|
| 1278 |
+
],
|
| 1279 |
+
"dev": true,
|
| 1280 |
+
"license": "MIT",
|
| 1281 |
+
"optional": true,
|
| 1282 |
+
"os": [
|
| 1283 |
+
"linux"
|
| 1284 |
+
]
|
| 1285 |
+
},
|
| 1286 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 1287 |
+
"version": "4.62.2",
|
| 1288 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
|
| 1289 |
+
"integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
|
| 1290 |
+
"cpu": [
|
| 1291 |
+
"riscv64"
|
| 1292 |
+
],
|
| 1293 |
+
"dev": true,
|
| 1294 |
+
"license": "MIT",
|
| 1295 |
+
"optional": true,
|
| 1296 |
+
"os": [
|
| 1297 |
+
"linux"
|
| 1298 |
+
]
|
| 1299 |
+
},
|
| 1300 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 1301 |
+
"version": "4.62.2",
|
| 1302 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
|
| 1303 |
+
"integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
|
| 1304 |
+
"cpu": [
|
| 1305 |
+
"s390x"
|
| 1306 |
+
],
|
| 1307 |
+
"dev": true,
|
| 1308 |
+
"license": "MIT",
|
| 1309 |
+
"optional": true,
|
| 1310 |
+
"os": [
|
| 1311 |
+
"linux"
|
| 1312 |
+
]
|
| 1313 |
+
},
|
| 1314 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 1315 |
+
"version": "4.62.2",
|
| 1316 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
|
| 1317 |
+
"integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
|
| 1318 |
+
"cpu": [
|
| 1319 |
+
"x64"
|
| 1320 |
+
],
|
| 1321 |
+
"dev": true,
|
| 1322 |
+
"license": "MIT",
|
| 1323 |
+
"optional": true,
|
| 1324 |
+
"os": [
|
| 1325 |
+
"linux"
|
| 1326 |
+
]
|
| 1327 |
+
},
|
| 1328 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 1329 |
+
"version": "4.62.2",
|
| 1330 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
|
| 1331 |
+
"integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
|
| 1332 |
+
"cpu": [
|
| 1333 |
+
"x64"
|
| 1334 |
+
],
|
| 1335 |
+
"dev": true,
|
| 1336 |
+
"license": "MIT",
|
| 1337 |
+
"optional": true,
|
| 1338 |
+
"os": [
|
| 1339 |
+
"linux"
|
| 1340 |
+
]
|
| 1341 |
+
},
|
| 1342 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 1343 |
+
"version": "4.62.2",
|
| 1344 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
|
| 1345 |
+
"integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
|
| 1346 |
+
"cpu": [
|
| 1347 |
+
"x64"
|
| 1348 |
+
],
|
| 1349 |
+
"dev": true,
|
| 1350 |
+
"license": "MIT",
|
| 1351 |
+
"optional": true,
|
| 1352 |
+
"os": [
|
| 1353 |
+
"openbsd"
|
| 1354 |
+
]
|
| 1355 |
+
},
|
| 1356 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 1357 |
+
"version": "4.62.2",
|
| 1358 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
|
| 1359 |
+
"integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
|
| 1360 |
+
"cpu": [
|
| 1361 |
+
"arm64"
|
| 1362 |
+
],
|
| 1363 |
+
"dev": true,
|
| 1364 |
+
"license": "MIT",
|
| 1365 |
+
"optional": true,
|
| 1366 |
+
"os": [
|
| 1367 |
+
"openharmony"
|
| 1368 |
+
]
|
| 1369 |
+
},
|
| 1370 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 1371 |
+
"version": "4.62.2",
|
| 1372 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
|
| 1373 |
+
"integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
|
| 1374 |
+
"cpu": [
|
| 1375 |
+
"arm64"
|
| 1376 |
+
],
|
| 1377 |
+
"dev": true,
|
| 1378 |
+
"license": "MIT",
|
| 1379 |
+
"optional": true,
|
| 1380 |
+
"os": [
|
| 1381 |
+
"win32"
|
| 1382 |
+
]
|
| 1383 |
+
},
|
| 1384 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 1385 |
+
"version": "4.62.2",
|
| 1386 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
|
| 1387 |
+
"integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
|
| 1388 |
+
"cpu": [
|
| 1389 |
+
"ia32"
|
| 1390 |
+
],
|
| 1391 |
+
"dev": true,
|
| 1392 |
+
"license": "MIT",
|
| 1393 |
+
"optional": true,
|
| 1394 |
+
"os": [
|
| 1395 |
+
"win32"
|
| 1396 |
+
]
|
| 1397 |
+
},
|
| 1398 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1399 |
+
"version": "4.62.2",
|
| 1400 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
|
| 1401 |
+
"integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
|
| 1402 |
+
"cpu": [
|
| 1403 |
+
"x64"
|
| 1404 |
+
],
|
| 1405 |
+
"dev": true,
|
| 1406 |
+
"license": "MIT",
|
| 1407 |
+
"optional": true,
|
| 1408 |
+
"os": [
|
| 1409 |
+
"win32"
|
| 1410 |
+
]
|
| 1411 |
+
},
|
| 1412 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1413 |
+
"version": "4.62.2",
|
| 1414 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
|
| 1415 |
+
"integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
|
| 1416 |
+
"cpu": [
|
| 1417 |
+
"x64"
|
| 1418 |
+
],
|
| 1419 |
+
"dev": true,
|
| 1420 |
+
"license": "MIT",
|
| 1421 |
+
"optional": true,
|
| 1422 |
+
"os": [
|
| 1423 |
+
"win32"
|
| 1424 |
+
]
|
| 1425 |
+
},
|
| 1426 |
+
"node_modules/@types/babel__core": {
|
| 1427 |
+
"version": "7.20.5",
|
| 1428 |
+
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
| 1429 |
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
| 1430 |
+
"dev": true,
|
| 1431 |
+
"license": "MIT",
|
| 1432 |
+
"dependencies": {
|
| 1433 |
+
"@babel/parser": "^7.20.7",
|
| 1434 |
+
"@babel/types": "^7.20.7",
|
| 1435 |
+
"@types/babel__generator": "*",
|
| 1436 |
+
"@types/babel__template": "*",
|
| 1437 |
+
"@types/babel__traverse": "*"
|
| 1438 |
+
}
|
| 1439 |
+
},
|
| 1440 |
+
"node_modules/@types/babel__generator": {
|
| 1441 |
+
"version": "7.27.0",
|
| 1442 |
+
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
| 1443 |
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
| 1444 |
+
"dev": true,
|
| 1445 |
+
"license": "MIT",
|
| 1446 |
+
"dependencies": {
|
| 1447 |
+
"@babel/types": "^7.0.0"
|
| 1448 |
+
}
|
| 1449 |
+
},
|
| 1450 |
+
"node_modules/@types/babel__template": {
|
| 1451 |
+
"version": "7.4.4",
|
| 1452 |
+
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
| 1453 |
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
| 1454 |
+
"dev": true,
|
| 1455 |
+
"license": "MIT",
|
| 1456 |
+
"dependencies": {
|
| 1457 |
+
"@babel/parser": "^7.1.0",
|
| 1458 |
+
"@babel/types": "^7.0.0"
|
| 1459 |
+
}
|
| 1460 |
+
},
|
| 1461 |
+
"node_modules/@types/babel__traverse": {
|
| 1462 |
+
"version": "7.28.0",
|
| 1463 |
+
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
| 1464 |
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
| 1465 |
+
"dev": true,
|
| 1466 |
+
"license": "MIT",
|
| 1467 |
+
"dependencies": {
|
| 1468 |
+
"@babel/types": "^7.28.2"
|
| 1469 |
+
}
|
| 1470 |
+
},
|
| 1471 |
+
"node_modules/@types/estree": {
|
| 1472 |
+
"version": "1.0.9",
|
| 1473 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
| 1474 |
+
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
| 1475 |
+
"dev": true,
|
| 1476 |
+
"license": "MIT"
|
| 1477 |
+
},
|
| 1478 |
+
"node_modules/@types/react": {
|
| 1479 |
+
"version": "19.2.17",
|
| 1480 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
| 1481 |
+
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
| 1482 |
+
"dev": true,
|
| 1483 |
+
"license": "MIT",
|
| 1484 |
+
"dependencies": {
|
| 1485 |
+
"csstype": "^3.2.2"
|
| 1486 |
+
}
|
| 1487 |
+
},
|
| 1488 |
+
"node_modules/@types/react-dom": {
|
| 1489 |
+
"version": "19.2.3",
|
| 1490 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 1491 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 1492 |
+
"dev": true,
|
| 1493 |
+
"license": "MIT",
|
| 1494 |
+
"peerDependencies": {
|
| 1495 |
+
"@types/react": "^19.2.0"
|
| 1496 |
+
}
|
| 1497 |
+
},
|
| 1498 |
+
"node_modules/@vitejs/plugin-react": {
|
| 1499 |
+
"version": "5.1.1",
|
| 1500 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
|
| 1501 |
+
"integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==",
|
| 1502 |
+
"dev": true,
|
| 1503 |
+
"license": "MIT",
|
| 1504 |
+
"dependencies": {
|
| 1505 |
+
"@babel/core": "^7.28.5",
|
| 1506 |
+
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
| 1507 |
+
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
| 1508 |
+
"@rolldown/pluginutils": "1.0.0-beta.47",
|
| 1509 |
+
"@types/babel__core": "^7.20.5",
|
| 1510 |
+
"react-refresh": "^0.18.0"
|
| 1511 |
+
},
|
| 1512 |
+
"engines": {
|
| 1513 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1514 |
+
},
|
| 1515 |
+
"peerDependencies": {
|
| 1516 |
+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1517 |
+
}
|
| 1518 |
+
},
|
| 1519 |
+
"node_modules/baseline-browser-mapping": {
|
| 1520 |
+
"version": "2.10.40",
|
| 1521 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
|
| 1522 |
+
"integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
|
| 1523 |
+
"dev": true,
|
| 1524 |
+
"license": "Apache-2.0",
|
| 1525 |
+
"bin": {
|
| 1526 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 1527 |
+
},
|
| 1528 |
+
"engines": {
|
| 1529 |
+
"node": ">=6.0.0"
|
| 1530 |
+
}
|
| 1531 |
+
},
|
| 1532 |
+
"node_modules/browserslist": {
|
| 1533 |
+
"version": "4.28.4",
|
| 1534 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
|
| 1535 |
+
"integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
|
| 1536 |
+
"dev": true,
|
| 1537 |
+
"funding": [
|
| 1538 |
+
{
|
| 1539 |
+
"type": "opencollective",
|
| 1540 |
+
"url": "https://opencollective.com/browserslist"
|
| 1541 |
+
},
|
| 1542 |
+
{
|
| 1543 |
+
"type": "tidelift",
|
| 1544 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1545 |
+
},
|
| 1546 |
+
{
|
| 1547 |
+
"type": "github",
|
| 1548 |
+
"url": "https://github.com/sponsors/ai"
|
| 1549 |
+
}
|
| 1550 |
+
],
|
| 1551 |
+
"license": "MIT",
|
| 1552 |
+
"dependencies": {
|
| 1553 |
+
"baseline-browser-mapping": "^2.10.38",
|
| 1554 |
+
"caniuse-lite": "^1.0.30001799",
|
| 1555 |
+
"electron-to-chromium": "^1.5.376",
|
| 1556 |
+
"node-releases": "^2.0.48",
|
| 1557 |
+
"update-browserslist-db": "^1.2.3"
|
| 1558 |
+
},
|
| 1559 |
+
"bin": {
|
| 1560 |
+
"browserslist": "cli.js"
|
| 1561 |
+
},
|
| 1562 |
+
"engines": {
|
| 1563 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1564 |
+
}
|
| 1565 |
+
},
|
| 1566 |
+
"node_modules/caniuse-lite": {
|
| 1567 |
+
"version": "1.0.30001799",
|
| 1568 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
| 1569 |
+
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
|
| 1570 |
+
"dev": true,
|
| 1571 |
+
"funding": [
|
| 1572 |
+
{
|
| 1573 |
+
"type": "opencollective",
|
| 1574 |
+
"url": "https://opencollective.com/browserslist"
|
| 1575 |
+
},
|
| 1576 |
+
{
|
| 1577 |
+
"type": "tidelift",
|
| 1578 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1579 |
+
},
|
| 1580 |
+
{
|
| 1581 |
+
"type": "github",
|
| 1582 |
+
"url": "https://github.com/sponsors/ai"
|
| 1583 |
+
}
|
| 1584 |
+
],
|
| 1585 |
+
"license": "CC-BY-4.0"
|
| 1586 |
+
},
|
| 1587 |
+
"node_modules/convert-source-map": {
|
| 1588 |
+
"version": "2.0.0",
|
| 1589 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1590 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1591 |
+
"dev": true,
|
| 1592 |
+
"license": "MIT"
|
| 1593 |
+
},
|
| 1594 |
+
"node_modules/csstype": {
|
| 1595 |
+
"version": "3.2.3",
|
| 1596 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1597 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1598 |
+
"dev": true,
|
| 1599 |
+
"license": "MIT"
|
| 1600 |
+
},
|
| 1601 |
+
"node_modules/debug": {
|
| 1602 |
+
"version": "4.4.3",
|
| 1603 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1604 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1605 |
+
"dev": true,
|
| 1606 |
+
"license": "MIT",
|
| 1607 |
+
"dependencies": {
|
| 1608 |
+
"ms": "^2.1.3"
|
| 1609 |
+
},
|
| 1610 |
+
"engines": {
|
| 1611 |
+
"node": ">=6.0"
|
| 1612 |
+
},
|
| 1613 |
+
"peerDependenciesMeta": {
|
| 1614 |
+
"supports-color": {
|
| 1615 |
+
"optional": true
|
| 1616 |
+
}
|
| 1617 |
+
}
|
| 1618 |
+
},
|
| 1619 |
+
"node_modules/electron-to-chromium": {
|
| 1620 |
+
"version": "1.5.378",
|
| 1621 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz",
|
| 1622 |
+
"integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==",
|
| 1623 |
+
"dev": true,
|
| 1624 |
+
"license": "ISC"
|
| 1625 |
+
},
|
| 1626 |
+
"node_modules/esbuild": {
|
| 1627 |
+
"version": "0.21.5",
|
| 1628 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
| 1629 |
+
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
| 1630 |
+
"dev": true,
|
| 1631 |
+
"hasInstallScript": true,
|
| 1632 |
+
"license": "MIT",
|
| 1633 |
+
"bin": {
|
| 1634 |
+
"esbuild": "bin/esbuild"
|
| 1635 |
+
},
|
| 1636 |
+
"engines": {
|
| 1637 |
+
"node": ">=12"
|
| 1638 |
+
},
|
| 1639 |
+
"optionalDependencies": {
|
| 1640 |
+
"@esbuild/aix-ppc64": "0.21.5",
|
| 1641 |
+
"@esbuild/android-arm": "0.21.5",
|
| 1642 |
+
"@esbuild/android-arm64": "0.21.5",
|
| 1643 |
+
"@esbuild/android-x64": "0.21.5",
|
| 1644 |
+
"@esbuild/darwin-arm64": "0.21.5",
|
| 1645 |
+
"@esbuild/darwin-x64": "0.21.5",
|
| 1646 |
+
"@esbuild/freebsd-arm64": "0.21.5",
|
| 1647 |
+
"@esbuild/freebsd-x64": "0.21.5",
|
| 1648 |
+
"@esbuild/linux-arm": "0.21.5",
|
| 1649 |
+
"@esbuild/linux-arm64": "0.21.5",
|
| 1650 |
+
"@esbuild/linux-ia32": "0.21.5",
|
| 1651 |
+
"@esbuild/linux-loong64": "0.21.5",
|
| 1652 |
+
"@esbuild/linux-mips64el": "0.21.5",
|
| 1653 |
+
"@esbuild/linux-ppc64": "0.21.5",
|
| 1654 |
+
"@esbuild/linux-riscv64": "0.21.5",
|
| 1655 |
+
"@esbuild/linux-s390x": "0.21.5",
|
| 1656 |
+
"@esbuild/linux-x64": "0.21.5",
|
| 1657 |
+
"@esbuild/netbsd-x64": "0.21.5",
|
| 1658 |
+
"@esbuild/openbsd-x64": "0.21.5",
|
| 1659 |
+
"@esbuild/sunos-x64": "0.21.5",
|
| 1660 |
+
"@esbuild/win32-arm64": "0.21.5",
|
| 1661 |
+
"@esbuild/win32-ia32": "0.21.5",
|
| 1662 |
+
"@esbuild/win32-x64": "0.21.5"
|
| 1663 |
+
}
|
| 1664 |
+
},
|
| 1665 |
+
"node_modules/escalade": {
|
| 1666 |
+
"version": "3.2.0",
|
| 1667 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1668 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1669 |
+
"dev": true,
|
| 1670 |
+
"license": "MIT",
|
| 1671 |
+
"engines": {
|
| 1672 |
+
"node": ">=6"
|
| 1673 |
+
}
|
| 1674 |
+
},
|
| 1675 |
+
"node_modules/fsevents": {
|
| 1676 |
+
"version": "2.3.3",
|
| 1677 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1678 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1679 |
+
"dev": true,
|
| 1680 |
+
"hasInstallScript": true,
|
| 1681 |
+
"license": "MIT",
|
| 1682 |
+
"optional": true,
|
| 1683 |
+
"os": [
|
| 1684 |
+
"darwin"
|
| 1685 |
+
],
|
| 1686 |
+
"engines": {
|
| 1687 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1688 |
+
}
|
| 1689 |
+
},
|
| 1690 |
+
"node_modules/gensync": {
|
| 1691 |
+
"version": "1.0.0-beta.2",
|
| 1692 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1693 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1694 |
+
"dev": true,
|
| 1695 |
+
"license": "MIT",
|
| 1696 |
+
"engines": {
|
| 1697 |
+
"node": ">=6.9.0"
|
| 1698 |
+
}
|
| 1699 |
+
},
|
| 1700 |
+
"node_modules/js-tokens": {
|
| 1701 |
+
"version": "4.0.0",
|
| 1702 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1703 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1704 |
+
"dev": true,
|
| 1705 |
+
"license": "MIT"
|
| 1706 |
+
},
|
| 1707 |
+
"node_modules/jsesc": {
|
| 1708 |
+
"version": "3.1.0",
|
| 1709 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1710 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1711 |
+
"dev": true,
|
| 1712 |
+
"license": "MIT",
|
| 1713 |
+
"bin": {
|
| 1714 |
+
"jsesc": "bin/jsesc"
|
| 1715 |
+
},
|
| 1716 |
+
"engines": {
|
| 1717 |
+
"node": ">=6"
|
| 1718 |
+
}
|
| 1719 |
+
},
|
| 1720 |
+
"node_modules/json5": {
|
| 1721 |
+
"version": "2.2.3",
|
| 1722 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1723 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1724 |
+
"dev": true,
|
| 1725 |
+
"license": "MIT",
|
| 1726 |
+
"bin": {
|
| 1727 |
+
"json5": "lib/cli.js"
|
| 1728 |
+
},
|
| 1729 |
+
"engines": {
|
| 1730 |
+
"node": ">=6"
|
| 1731 |
+
}
|
| 1732 |
+
},
|
| 1733 |
+
"node_modules/lru-cache": {
|
| 1734 |
+
"version": "5.1.1",
|
| 1735 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 1736 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 1737 |
+
"dev": true,
|
| 1738 |
+
"license": "ISC",
|
| 1739 |
+
"dependencies": {
|
| 1740 |
+
"yallist": "^3.0.2"
|
| 1741 |
+
}
|
| 1742 |
+
},
|
| 1743 |
+
"node_modules/lucide-react": {
|
| 1744 |
+
"version": "1.21.0",
|
| 1745 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz",
|
| 1746 |
+
"integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==",
|
| 1747 |
+
"license": "ISC",
|
| 1748 |
+
"peerDependencies": {
|
| 1749 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1750 |
+
}
|
| 1751 |
+
},
|
| 1752 |
+
"node_modules/ms": {
|
| 1753 |
+
"version": "2.1.3",
|
| 1754 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1755 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1756 |
+
"dev": true,
|
| 1757 |
+
"license": "MIT"
|
| 1758 |
+
},
|
| 1759 |
+
"node_modules/nanoid": {
|
| 1760 |
+
"version": "3.3.15",
|
| 1761 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
| 1762 |
+
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
| 1763 |
+
"dev": true,
|
| 1764 |
+
"funding": [
|
| 1765 |
+
{
|
| 1766 |
+
"type": "github",
|
| 1767 |
+
"url": "https://github.com/sponsors/ai"
|
| 1768 |
+
}
|
| 1769 |
+
],
|
| 1770 |
+
"license": "MIT",
|
| 1771 |
+
"bin": {
|
| 1772 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1773 |
+
},
|
| 1774 |
+
"engines": {
|
| 1775 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1776 |
+
}
|
| 1777 |
+
},
|
| 1778 |
+
"node_modules/node-releases": {
|
| 1779 |
+
"version": "2.0.50",
|
| 1780 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
|
| 1781 |
+
"integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
|
| 1782 |
+
"dev": true,
|
| 1783 |
+
"license": "MIT",
|
| 1784 |
+
"engines": {
|
| 1785 |
+
"node": ">=18"
|
| 1786 |
+
}
|
| 1787 |
+
},
|
| 1788 |
+
"node_modules/oxlint": {
|
| 1789 |
+
"version": "1.71.0",
|
| 1790 |
+
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.71.0.tgz",
|
| 1791 |
+
"integrity": "sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==",
|
| 1792 |
+
"dev": true,
|
| 1793 |
+
"license": "MIT",
|
| 1794 |
+
"bin": {
|
| 1795 |
+
"oxlint": "bin/oxlint"
|
| 1796 |
+
},
|
| 1797 |
+
"engines": {
|
| 1798 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1799 |
+
},
|
| 1800 |
+
"funding": {
|
| 1801 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 1802 |
+
},
|
| 1803 |
+
"optionalDependencies": {
|
| 1804 |
+
"@oxlint/binding-android-arm-eabi": "1.71.0",
|
| 1805 |
+
"@oxlint/binding-android-arm64": "1.71.0",
|
| 1806 |
+
"@oxlint/binding-darwin-arm64": "1.71.0",
|
| 1807 |
+
"@oxlint/binding-darwin-x64": "1.71.0",
|
| 1808 |
+
"@oxlint/binding-freebsd-x64": "1.71.0",
|
| 1809 |
+
"@oxlint/binding-linux-arm-gnueabihf": "1.71.0",
|
| 1810 |
+
"@oxlint/binding-linux-arm-musleabihf": "1.71.0",
|
| 1811 |
+
"@oxlint/binding-linux-arm64-gnu": "1.71.0",
|
| 1812 |
+
"@oxlint/binding-linux-arm64-musl": "1.71.0",
|
| 1813 |
+
"@oxlint/binding-linux-ppc64-gnu": "1.71.0",
|
| 1814 |
+
"@oxlint/binding-linux-riscv64-gnu": "1.71.0",
|
| 1815 |
+
"@oxlint/binding-linux-riscv64-musl": "1.71.0",
|
| 1816 |
+
"@oxlint/binding-linux-s390x-gnu": "1.71.0",
|
| 1817 |
+
"@oxlint/binding-linux-x64-gnu": "1.71.0",
|
| 1818 |
+
"@oxlint/binding-linux-x64-musl": "1.71.0",
|
| 1819 |
+
"@oxlint/binding-openharmony-arm64": "1.71.0",
|
| 1820 |
+
"@oxlint/binding-win32-arm64-msvc": "1.71.0",
|
| 1821 |
+
"@oxlint/binding-win32-ia32-msvc": "1.71.0",
|
| 1822 |
+
"@oxlint/binding-win32-x64-msvc": "1.71.0"
|
| 1823 |
+
},
|
| 1824 |
+
"peerDependencies": {
|
| 1825 |
+
"oxlint-tsgolint": ">=0.22.1",
|
| 1826 |
+
"vite-plus": "*"
|
| 1827 |
+
},
|
| 1828 |
+
"peerDependenciesMeta": {
|
| 1829 |
+
"oxlint-tsgolint": {
|
| 1830 |
+
"optional": true
|
| 1831 |
+
},
|
| 1832 |
+
"vite-plus": {
|
| 1833 |
+
"optional": true
|
| 1834 |
+
}
|
| 1835 |
+
}
|
| 1836 |
+
},
|
| 1837 |
+
"node_modules/picocolors": {
|
| 1838 |
+
"version": "1.1.1",
|
| 1839 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1840 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1841 |
+
"dev": true,
|
| 1842 |
+
"license": "ISC"
|
| 1843 |
+
},
|
| 1844 |
+
"node_modules/postcss": {
|
| 1845 |
+
"version": "8.5.15",
|
| 1846 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
| 1847 |
+
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
| 1848 |
+
"dev": true,
|
| 1849 |
+
"funding": [
|
| 1850 |
+
{
|
| 1851 |
+
"type": "opencollective",
|
| 1852 |
+
"url": "https://opencollective.com/postcss/"
|
| 1853 |
+
},
|
| 1854 |
+
{
|
| 1855 |
+
"type": "tidelift",
|
| 1856 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1857 |
+
},
|
| 1858 |
+
{
|
| 1859 |
+
"type": "github",
|
| 1860 |
+
"url": "https://github.com/sponsors/ai"
|
| 1861 |
+
}
|
| 1862 |
+
],
|
| 1863 |
+
"license": "MIT",
|
| 1864 |
+
"dependencies": {
|
| 1865 |
+
"nanoid": "^3.3.12",
|
| 1866 |
+
"picocolors": "^1.1.1",
|
| 1867 |
+
"source-map-js": "^1.2.1"
|
| 1868 |
+
},
|
| 1869 |
+
"engines": {
|
| 1870 |
+
"node": "^10 || ^12 || >=14"
|
| 1871 |
+
}
|
| 1872 |
+
},
|
| 1873 |
+
"node_modules/react": {
|
| 1874 |
+
"version": "19.2.7",
|
| 1875 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
| 1876 |
+
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
| 1877 |
+
"license": "MIT",
|
| 1878 |
+
"engines": {
|
| 1879 |
+
"node": ">=0.10.0"
|
| 1880 |
+
}
|
| 1881 |
+
},
|
| 1882 |
+
"node_modules/react-dom": {
|
| 1883 |
+
"version": "19.2.7",
|
| 1884 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
| 1885 |
+
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
| 1886 |
+
"license": "MIT",
|
| 1887 |
+
"dependencies": {
|
| 1888 |
+
"scheduler": "^0.27.0"
|
| 1889 |
+
},
|
| 1890 |
+
"peerDependencies": {
|
| 1891 |
+
"react": "^19.2.7"
|
| 1892 |
+
}
|
| 1893 |
+
},
|
| 1894 |
+
"node_modules/react-refresh": {
|
| 1895 |
+
"version": "0.18.0",
|
| 1896 |
+
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
| 1897 |
+
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
|
| 1898 |
+
"dev": true,
|
| 1899 |
+
"license": "MIT",
|
| 1900 |
+
"engines": {
|
| 1901 |
+
"node": ">=0.10.0"
|
| 1902 |
+
}
|
| 1903 |
+
},
|
| 1904 |
+
"node_modules/rollup": {
|
| 1905 |
+
"version": "4.62.2",
|
| 1906 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
| 1907 |
+
"integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
|
| 1908 |
+
"dev": true,
|
| 1909 |
+
"license": "MIT",
|
| 1910 |
+
"dependencies": {
|
| 1911 |
+
"@types/estree": "1.0.9"
|
| 1912 |
+
},
|
| 1913 |
+
"bin": {
|
| 1914 |
+
"rollup": "dist/bin/rollup"
|
| 1915 |
+
},
|
| 1916 |
+
"engines": {
|
| 1917 |
+
"node": ">=18.0.0",
|
| 1918 |
+
"npm": ">=8.0.0"
|
| 1919 |
+
},
|
| 1920 |
+
"optionalDependencies": {
|
| 1921 |
+
"@rollup/rollup-android-arm-eabi": "4.62.2",
|
| 1922 |
+
"@rollup/rollup-android-arm64": "4.62.2",
|
| 1923 |
+
"@rollup/rollup-darwin-arm64": "4.62.2",
|
| 1924 |
+
"@rollup/rollup-darwin-x64": "4.62.2",
|
| 1925 |
+
"@rollup/rollup-freebsd-arm64": "4.62.2",
|
| 1926 |
+
"@rollup/rollup-freebsd-x64": "4.62.2",
|
| 1927 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
|
| 1928 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.62.2",
|
| 1929 |
+
"@rollup/rollup-linux-arm64-gnu": "4.62.2",
|
| 1930 |
+
"@rollup/rollup-linux-arm64-musl": "4.62.2",
|
| 1931 |
+
"@rollup/rollup-linux-loong64-gnu": "4.62.2",
|
| 1932 |
+
"@rollup/rollup-linux-loong64-musl": "4.62.2",
|
| 1933 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.62.2",
|
| 1934 |
+
"@rollup/rollup-linux-ppc64-musl": "4.62.2",
|
| 1935 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.62.2",
|
| 1936 |
+
"@rollup/rollup-linux-riscv64-musl": "4.62.2",
|
| 1937 |
+
"@rollup/rollup-linux-s390x-gnu": "4.62.2",
|
| 1938 |
+
"@rollup/rollup-linux-x64-gnu": "4.62.2",
|
| 1939 |
+
"@rollup/rollup-linux-x64-musl": "4.62.2",
|
| 1940 |
+
"@rollup/rollup-openbsd-x64": "4.62.2",
|
| 1941 |
+
"@rollup/rollup-openharmony-arm64": "4.62.2",
|
| 1942 |
+
"@rollup/rollup-win32-arm64-msvc": "4.62.2",
|
| 1943 |
+
"@rollup/rollup-win32-ia32-msvc": "4.62.2",
|
| 1944 |
+
"@rollup/rollup-win32-x64-gnu": "4.62.2",
|
| 1945 |
+
"@rollup/rollup-win32-x64-msvc": "4.62.2",
|
| 1946 |
+
"fsevents": "~2.3.2"
|
| 1947 |
+
}
|
| 1948 |
+
},
|
| 1949 |
+
"node_modules/scheduler": {
|
| 1950 |
+
"version": "0.27.0",
|
| 1951 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 1952 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 1953 |
+
"license": "MIT"
|
| 1954 |
+
},
|
| 1955 |
+
"node_modules/semver": {
|
| 1956 |
+
"version": "6.3.1",
|
| 1957 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 1958 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 1959 |
+
"dev": true,
|
| 1960 |
+
"license": "ISC",
|
| 1961 |
+
"bin": {
|
| 1962 |
+
"semver": "bin/semver.js"
|
| 1963 |
+
}
|
| 1964 |
+
},
|
| 1965 |
+
"node_modules/source-map-js": {
|
| 1966 |
+
"version": "1.2.1",
|
| 1967 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1968 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1969 |
+
"dev": true,
|
| 1970 |
+
"license": "BSD-3-Clause",
|
| 1971 |
+
"engines": {
|
| 1972 |
+
"node": ">=0.10.0"
|
| 1973 |
+
}
|
| 1974 |
+
},
|
| 1975 |
+
"node_modules/update-browserslist-db": {
|
| 1976 |
+
"version": "1.2.3",
|
| 1977 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 1978 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 1979 |
+
"dev": true,
|
| 1980 |
+
"funding": [
|
| 1981 |
+
{
|
| 1982 |
+
"type": "opencollective",
|
| 1983 |
+
"url": "https://opencollective.com/browserslist"
|
| 1984 |
+
},
|
| 1985 |
+
{
|
| 1986 |
+
"type": "tidelift",
|
| 1987 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1988 |
+
},
|
| 1989 |
+
{
|
| 1990 |
+
"type": "github",
|
| 1991 |
+
"url": "https://github.com/sponsors/ai"
|
| 1992 |
+
}
|
| 1993 |
+
],
|
| 1994 |
+
"license": "MIT",
|
| 1995 |
+
"dependencies": {
|
| 1996 |
+
"escalade": "^3.2.0",
|
| 1997 |
+
"picocolors": "^1.1.1"
|
| 1998 |
+
},
|
| 1999 |
+
"bin": {
|
| 2000 |
+
"update-browserslist-db": "cli.js"
|
| 2001 |
+
},
|
| 2002 |
+
"peerDependencies": {
|
| 2003 |
+
"browserslist": ">= 4.21.0"
|
| 2004 |
+
}
|
| 2005 |
+
},
|
| 2006 |
+
"node_modules/vite": {
|
| 2007 |
+
"version": "5.4.11",
|
| 2008 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
| 2009 |
+
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
|
| 2010 |
+
"dev": true,
|
| 2011 |
+
"license": "MIT",
|
| 2012 |
+
"dependencies": {
|
| 2013 |
+
"esbuild": "^0.21.3",
|
| 2014 |
+
"postcss": "^8.4.43",
|
| 2015 |
+
"rollup": "^4.20.0"
|
| 2016 |
+
},
|
| 2017 |
+
"bin": {
|
| 2018 |
+
"vite": "bin/vite.js"
|
| 2019 |
+
},
|
| 2020 |
+
"engines": {
|
| 2021 |
+
"node": "^18.0.0 || >=20.0.0"
|
| 2022 |
+
},
|
| 2023 |
+
"funding": {
|
| 2024 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2025 |
+
},
|
| 2026 |
+
"optionalDependencies": {
|
| 2027 |
+
"fsevents": "~2.3.3"
|
| 2028 |
+
},
|
| 2029 |
+
"peerDependencies": {
|
| 2030 |
+
"@types/node": "^18.0.0 || >=20.0.0",
|
| 2031 |
+
"less": "*",
|
| 2032 |
+
"lightningcss": "^1.21.0",
|
| 2033 |
+
"sass": "*",
|
| 2034 |
+
"sass-embedded": "*",
|
| 2035 |
+
"stylus": "*",
|
| 2036 |
+
"sugarss": "*",
|
| 2037 |
+
"terser": "^5.4.0"
|
| 2038 |
+
},
|
| 2039 |
+
"peerDependenciesMeta": {
|
| 2040 |
+
"@types/node": {
|
| 2041 |
+
"optional": true
|
| 2042 |
+
},
|
| 2043 |
+
"less": {
|
| 2044 |
+
"optional": true
|
| 2045 |
+
},
|
| 2046 |
+
"lightningcss": {
|
| 2047 |
+
"optional": true
|
| 2048 |
+
},
|
| 2049 |
+
"sass": {
|
| 2050 |
+
"optional": true
|
| 2051 |
+
},
|
| 2052 |
+
"sass-embedded": {
|
| 2053 |
+
"optional": true
|
| 2054 |
+
},
|
| 2055 |
+
"stylus": {
|
| 2056 |
+
"optional": true
|
| 2057 |
+
},
|
| 2058 |
+
"sugarss": {
|
| 2059 |
+
"optional": true
|
| 2060 |
+
},
|
| 2061 |
+
"terser": {
|
| 2062 |
+
"optional": true
|
| 2063 |
+
}
|
| 2064 |
+
}
|
| 2065 |
+
},
|
| 2066 |
+
"node_modules/yallist": {
|
| 2067 |
+
"version": "3.1.1",
|
| 2068 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2069 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2070 |
+
"dev": true,
|
| 2071 |
+
"license": "ISC"
|
| 2072 |
+
}
|
| 2073 |
+
}
|
| 2074 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "oxlint",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"lucide-react": "^1.21.0",
|
| 14 |
+
"react": "^19.2.7",
|
| 15 |
+
"react-dom": "^19.2.7"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@types/react": "^19.2.17",
|
| 19 |
+
"@types/react-dom": "^19.2.3",
|
| 20 |
+
"@vitejs/plugin-react": "^5.1.1",
|
| 21 |
+
"oxlint": "^1.69.0",
|
| 22 |
+
"vite": "^5.4.11"
|
| 23 |
+
}
|
| 24 |
+
}
|
frontend/public/favicon.svg
ADDED
|
|
frontend/public/icons.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-color: #0b0c10;
|
| 3 |
+
--bg-card: rgba(22, 24, 33, 0.7);
|
| 4 |
+
--bg-card-hover: rgba(30, 33, 45, 0.8);
|
| 5 |
+
--border-color: rgba(255, 255, 255, 0.08);
|
| 6 |
+
--border-glow: rgba(99, 102, 241, 0.15);
|
| 7 |
+
|
| 8 |
+
--text-primary: #f3f4f6;
|
| 9 |
+
--text-secondary: #9ca3af;
|
| 10 |
+
--text-muted: #6b7280;
|
| 11 |
+
|
| 12 |
+
--primary: #6366f1;
|
| 13 |
+
--primary-glow: rgba(99, 102, 241, 0.35);
|
| 14 |
+
--primary-dark: #4f46e5;
|
| 15 |
+
--secondary: #a855f7;
|
| 16 |
+
|
| 17 |
+
/* Sentiment Colors */
|
| 18 |
+
--color-happy: #10b981;
|
| 19 |
+
--color-happy-bg: rgba(16, 185, 129, 0.1);
|
| 20 |
+
--color-sad: #0ea5e9;
|
| 21 |
+
--color-sad-bg: rgba(14, 165, 233, 0.1);
|
| 22 |
+
--color-frustrated: #f43f5e;
|
| 23 |
+
--color-frustrated-bg: rgba(244, 63, 94, 0.1);
|
| 24 |
+
--color-neutral: #f59e0b;
|
| 25 |
+
--color-neutral-bg: rgba(245, 158, 11, 0.1);
|
| 26 |
+
|
| 27 |
+
--font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
| 28 |
+
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
* {
|
| 32 |
+
box-sizing: border-box;
|
| 33 |
+
margin: 0;
|
| 34 |
+
padding: 0;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
body {
|
| 38 |
+
background-color: var(--bg-color);
|
| 39 |
+
color: var(--text-primary);
|
| 40 |
+
font-family: var(--font-family);
|
| 41 |
+
min-height: 100vh;
|
| 42 |
+
overflow-x: hidden;
|
| 43 |
+
background-image:
|
| 44 |
+
radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.08) 0%, transparent 40%),
|
| 45 |
+
radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.08) 0%, transparent 40%);
|
| 46 |
+
background-attachment: fixed;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/* Custom Scrollbar */
|
| 50 |
+
::-webkit-scrollbar {
|
| 51 |
+
width: 8px;
|
| 52 |
+
height: 8px;
|
| 53 |
+
}
|
| 54 |
+
::-webkit-scrollbar-track {
|
| 55 |
+
background: rgba(0, 0, 0, 0.2);
|
| 56 |
+
}
|
| 57 |
+
::-webkit-scrollbar-thumb {
|
| 58 |
+
background: rgba(255, 255, 255, 0.1);
|
| 59 |
+
border-radius: 4px;
|
| 60 |
+
}
|
| 61 |
+
::-webkit-scrollbar-thumb:hover {
|
| 62 |
+
background: rgba(255, 255, 255, 0.2);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/* App Container */
|
| 66 |
+
.app-container {
|
| 67 |
+
max-width: 1400px;
|
| 68 |
+
margin: 0 auto;
|
| 69 |
+
padding: 2rem 1.5rem;
|
| 70 |
+
display: flex;
|
| 71 |
+
flex-direction: column;
|
| 72 |
+
gap: 2rem;
|
| 73 |
+
min-height: 100vh;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/* Header */
|
| 77 |
+
.app-header {
|
| 78 |
+
display: flex;
|
| 79 |
+
justify-content: space-between;
|
| 80 |
+
align-items: center;
|
| 81 |
+
border-bottom: 1px solid var(--border-color);
|
| 82 |
+
padding-bottom: 1.5rem;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.header-title-section h1 {
|
| 86 |
+
font-size: 2.2rem;
|
| 87 |
+
font-weight: 800;
|
| 88 |
+
background: linear-gradient(135deg, #fff 30%, #a855f7 100%);
|
| 89 |
+
-webkit-background-clip: text;
|
| 90 |
+
-webkit-text-fill-color: transparent;
|
| 91 |
+
letter-spacing: -0.5px;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.header-title-section p {
|
| 95 |
+
color: var(--text-secondary);
|
| 96 |
+
font-size: 0.95rem;
|
| 97 |
+
margin-top: 0.4rem;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.header-status {
|
| 101 |
+
display: flex;
|
| 102 |
+
align-items: center;
|
| 103 |
+
gap: 0.8rem;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.status-badge {
|
| 107 |
+
display: flex;
|
| 108 |
+
align-items: center;
|
| 109 |
+
gap: 0.5rem;
|
| 110 |
+
background: rgba(255, 255, 255, 0.04);
|
| 111 |
+
border: 1px solid var(--border-color);
|
| 112 |
+
padding: 0.4rem 0.8rem;
|
| 113 |
+
border-radius: 9999px;
|
| 114 |
+
font-size: 0.85rem;
|
| 115 |
+
color: var(--text-secondary);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.status-dot {
|
| 119 |
+
width: 8px;
|
| 120 |
+
height: 8px;
|
| 121 |
+
border-radius: 50%;
|
| 122 |
+
display: inline-block;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.status-dot.loading {
|
| 126 |
+
background-color: var(--color-neutral);
|
| 127 |
+
box-shadow: 0 0 8px var(--color-neutral);
|
| 128 |
+
animation: pulse 1.5s infinite;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.status-dot.ready {
|
| 132 |
+
background-color: var(--color-happy);
|
| 133 |
+
box-shadow: 0 0 8px var(--color-happy);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.status-dot.failed {
|
| 137 |
+
background-color: var(--color-frustrated);
|
| 138 |
+
box-shadow: 0 0 8px var(--color-frustrated);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/* Settings Bar */
|
| 142 |
+
.settings-toggle-btn {
|
| 143 |
+
background: rgba(255, 255, 255, 0.05);
|
| 144 |
+
border: 1px solid var(--border-color);
|
| 145 |
+
color: var(--text-primary);
|
| 146 |
+
padding: 0.5rem 1rem;
|
| 147 |
+
border-radius: 8px;
|
| 148 |
+
cursor: pointer;
|
| 149 |
+
display: flex;
|
| 150 |
+
align-items: center;
|
| 151 |
+
gap: 0.5rem;
|
| 152 |
+
font-size: 0.9rem;
|
| 153 |
+
transition: var(--transition);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.settings-toggle-btn:hover {
|
| 157 |
+
background: rgba(255, 255, 255, 0.1);
|
| 158 |
+
border-color: var(--primary);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.settings-drawer {
|
| 162 |
+
background: var(--bg-card);
|
| 163 |
+
backdrop-filter: blur(12px);
|
| 164 |
+
border: 1px solid var(--border-color);
|
| 165 |
+
border-radius: 12px;
|
| 166 |
+
padding: 1.5rem;
|
| 167 |
+
display: flex;
|
| 168 |
+
flex-direction: column;
|
| 169 |
+
gap: 1.2rem;
|
| 170 |
+
animation: slideDown 0.3s ease-out;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.settings-group {
|
| 174 |
+
display: flex;
|
| 175 |
+
flex-direction: column;
|
| 176 |
+
gap: 0.5rem;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.settings-group label {
|
| 180 |
+
font-size: 0.85rem;
|
| 181 |
+
font-weight: 600;
|
| 182 |
+
color: var(--text-secondary);
|
| 183 |
+
text-transform: uppercase;
|
| 184 |
+
letter-spacing: 0.5px;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.settings-group input, .settings-group textarea {
|
| 188 |
+
background: rgba(0, 0, 0, 0.2);
|
| 189 |
+
border: 1px solid var(--border-color);
|
| 190 |
+
border-radius: 8px;
|
| 191 |
+
color: var(--text-primary);
|
| 192 |
+
padding: 0.75rem 1rem;
|
| 193 |
+
font-family: inherit;
|
| 194 |
+
font-size: 0.95rem;
|
| 195 |
+
transition: var(--transition);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.settings-group input:focus, .settings-group textarea:focus {
|
| 199 |
+
outline: none;
|
| 200 |
+
border-color: var(--primary);
|
| 201 |
+
box-shadow: 0 0 10px var(--primary-glow);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
/* Main Layout Grid */
|
| 205 |
+
.main-grid {
|
| 206 |
+
display: grid;
|
| 207 |
+
grid-template-columns: 1fr;
|
| 208 |
+
gap: 2rem;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/* Query Input Section */
|
| 212 |
+
.query-card {
|
| 213 |
+
background: var(--bg-card);
|
| 214 |
+
backdrop-filter: blur(12px);
|
| 215 |
+
border: 1px solid var(--border-color);
|
| 216 |
+
border-radius: 16px;
|
| 217 |
+
padding: 1.5rem;
|
| 218 |
+
display: flex;
|
| 219 |
+
flex-direction: column;
|
| 220 |
+
gap: 1rem;
|
| 221 |
+
position: relative;
|
| 222 |
+
overflow: hidden;
|
| 223 |
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.query-card::before {
|
| 227 |
+
content: '';
|
| 228 |
+
position: absolute;
|
| 229 |
+
top: 0;
|
| 230 |
+
left: 0;
|
| 231 |
+
right: 0;
|
| 232 |
+
height: 2px;
|
| 233 |
+
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
.query-input-wrapper {
|
| 237 |
+
display: flex;
|
| 238 |
+
gap: 1rem;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.query-textarea {
|
| 242 |
+
flex: 1;
|
| 243 |
+
background: rgba(0, 0, 0, 0.25);
|
| 244 |
+
border: 1px solid var(--border-color);
|
| 245 |
+
border-radius: 12px;
|
| 246 |
+
color: var(--text-primary);
|
| 247 |
+
padding: 1rem;
|
| 248 |
+
font-family: inherit;
|
| 249 |
+
font-size: 1.05rem;
|
| 250 |
+
resize: vertical;
|
| 251 |
+
min-height: 80px;
|
| 252 |
+
transition: var(--transition);
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.query-textarea:focus {
|
| 256 |
+
outline: none;
|
| 257 |
+
border-color: var(--primary);
|
| 258 |
+
box-shadow: 0 0 12px var(--primary-glow);
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.send-button {
|
| 262 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
| 263 |
+
border: none;
|
| 264 |
+
color: white;
|
| 265 |
+
border-radius: 12px;
|
| 266 |
+
padding: 0 1.8rem;
|
| 267 |
+
font-weight: 600;
|
| 268 |
+
font-size: 1rem;
|
| 269 |
+
cursor: pointer;
|
| 270 |
+
display: flex;
|
| 271 |
+
align-items: center;
|
| 272 |
+
justify-content: center;
|
| 273 |
+
gap: 0.6rem;
|
| 274 |
+
transition: var(--transition);
|
| 275 |
+
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.3);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.send-button:hover {
|
| 279 |
+
transform: translateY(-2px);
|
| 280 |
+
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.5);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.send-button:disabled {
|
| 284 |
+
background: var(--text-muted);
|
| 285 |
+
cursor: not-allowed;
|
| 286 |
+
transform: none;
|
| 287 |
+
box-shadow: none;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
/* Quick Prompts */
|
| 291 |
+
.quick-prompts {
|
| 292 |
+
display: flex;
|
| 293 |
+
flex-wrap: wrap;
|
| 294 |
+
gap: 0.6rem;
|
| 295 |
+
align-items: center;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.quick-prompt-label {
|
| 299 |
+
font-size: 0.8rem;
|
| 300 |
+
color: var(--text-muted);
|
| 301 |
+
font-weight: 600;
|
| 302 |
+
text-transform: uppercase;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.quick-prompt-btn {
|
| 306 |
+
background: rgba(255, 255, 255, 0.03);
|
| 307 |
+
border: 1px solid var(--border-color);
|
| 308 |
+
color: var(--text-secondary);
|
| 309 |
+
padding: 0.4rem 0.8rem;
|
| 310 |
+
border-radius: 20px;
|
| 311 |
+
font-size: 0.85rem;
|
| 312 |
+
cursor: pointer;
|
| 313 |
+
transition: var(--transition);
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
.quick-prompt-btn:hover {
|
| 317 |
+
background: rgba(99, 102, 241, 0.1);
|
| 318 |
+
border-color: var(--primary);
|
| 319 |
+
color: var(--text-primary);
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/* Chat Comparison Columns */
|
| 323 |
+
.comparison-grid {
|
| 324 |
+
display: grid;
|
| 325 |
+
grid-template-columns: 1fr;
|
| 326 |
+
gap: 2rem;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
@media (min-width: 900px) {
|
| 330 |
+
.comparison-grid {
|
| 331 |
+
grid-template-columns: 1fr 1fr;
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
@media (min-width: 1300px) {
|
| 336 |
+
.comparison-grid {
|
| 337 |
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
.column-card {
|
| 342 |
+
background: var(--bg-card);
|
| 343 |
+
backdrop-filter: blur(12px);
|
| 344 |
+
border: 1px solid var(--border-color);
|
| 345 |
+
border-radius: 16px;
|
| 346 |
+
display: flex;
|
| 347 |
+
flex-direction: column;
|
| 348 |
+
min-height: 500px;
|
| 349 |
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
| 350 |
+
overflow: hidden;
|
| 351 |
+
transition: var(--transition);
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.column-card:hover {
|
| 355 |
+
border-color: rgba(255, 255, 255, 0.12);
|
| 356 |
+
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.4);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.column-header {
|
| 360 |
+
padding: 1.25rem 1.5rem;
|
| 361 |
+
border-bottom: 1px solid var(--border-color);
|
| 362 |
+
display: flex;
|
| 363 |
+
justify-content: space-between;
|
| 364 |
+
align-items: center;
|
| 365 |
+
flex-wrap: wrap;
|
| 366 |
+
gap: 0.8rem;
|
| 367 |
+
background: rgba(0, 0, 0, 0.15);
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.column-meta {
|
| 371 |
+
display: flex;
|
| 372 |
+
align-items: center;
|
| 373 |
+
flex-wrap: wrap;
|
| 374 |
+
gap: 0.6rem;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
.column-title-wrapper h2 {
|
| 378 |
+
font-size: 1.15rem;
|
| 379 |
+
font-weight: 700;
|
| 380 |
+
display: flex;
|
| 381 |
+
align-items: center;
|
| 382 |
+
gap: 0.5rem;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.column-title-wrapper p {
|
| 386 |
+
font-size: 0.75rem;
|
| 387 |
+
color: var(--text-muted);
|
| 388 |
+
margin-top: 0.2rem;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
.latency-badge {
|
| 392 |
+
background: rgba(0, 0, 0, 0.3);
|
| 393 |
+
border: 1px solid var(--border-color);
|
| 394 |
+
color: var(--text-secondary);
|
| 395 |
+
font-size: 0.75rem;
|
| 396 |
+
padding: 0.3rem 0.6rem;
|
| 397 |
+
border-radius: 6px;
|
| 398 |
+
font-family: monospace;
|
| 399 |
+
white-space: nowrap;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
/* Sentiment Details Badge */
|
| 403 |
+
.sentiment-badge {
|
| 404 |
+
display: inline-flex;
|
| 405 |
+
align-items: center;
|
| 406 |
+
gap: 0.4rem;
|
| 407 |
+
padding: 0.4rem 0.8rem;
|
| 408 |
+
border-radius: 8px;
|
| 409 |
+
font-size: 0.85rem;
|
| 410 |
+
font-weight: 700;
|
| 411 |
+
text-transform: uppercase;
|
| 412 |
+
letter-spacing: 0.5px;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.sentiment-badge.happy {
|
| 416 |
+
background-color: var(--color-happy-bg);
|
| 417 |
+
border: 1px solid var(--color-happy);
|
| 418 |
+
color: var(--color-happy);
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
.sentiment-badge.sad {
|
| 422 |
+
background-color: var(--color-sad-bg);
|
| 423 |
+
border: 1px solid var(--color-sad);
|
| 424 |
+
color: var(--color-sad);
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.sentiment-badge.frustrated {
|
| 428 |
+
background-color: var(--color-frustrated-bg);
|
| 429 |
+
border: 1px solid var(--color-frustrated);
|
| 430 |
+
color: var(--color-frustrated);
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
.sentiment-badge.neutral {
|
| 434 |
+
background-color: var(--color-neutral-bg);
|
| 435 |
+
border: 1px solid var(--color-neutral);
|
| 436 |
+
color: var(--color-neutral);
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
/* Chat Body Response */
|
| 440 |
+
.column-body {
|
| 441 |
+
padding: 1.5rem;
|
| 442 |
+
flex: 1;
|
| 443 |
+
display: flex;
|
| 444 |
+
flex-direction: column;
|
| 445 |
+
gap: 1.5rem;
|
| 446 |
+
overflow-y: auto;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.sentiment-analysis-box {
|
| 450 |
+
background: rgba(255, 255, 255, 0.02);
|
| 451 |
+
border: 1px solid var(--border-color);
|
| 452 |
+
border-radius: 12px;
|
| 453 |
+
padding: 1rem;
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
.sentiment-analysis-box h3 {
|
| 457 |
+
font-size: 0.85rem;
|
| 458 |
+
color: var(--text-muted);
|
| 459 |
+
text-transform: uppercase;
|
| 460 |
+
letter-spacing: 0.5px;
|
| 461 |
+
margin-bottom: 0.5rem;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
.sentiment-analysis-box p {
|
| 465 |
+
font-size: 0.95rem;
|
| 466 |
+
line-height: 1.5;
|
| 467 |
+
color: var(--text-secondary);
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.chat-response-content {
|
| 471 |
+
flex: 1;
|
| 472 |
+
font-size: 1rem;
|
| 473 |
+
line-height: 1.6;
|
| 474 |
+
color: var(--text-primary);
|
| 475 |
+
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
| 476 |
+
padding-top: 1.5rem;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
.chat-response-content p {
|
| 480 |
+
margin-bottom: 1rem;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
.chat-response-content ul, .chat-response-content ol {
|
| 484 |
+
margin-bottom: 1rem;
|
| 485 |
+
padding-left: 1.5rem;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
.chat-response-content li {
|
| 489 |
+
margin-bottom: 0.4rem;
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
.chat-response-content code {
|
| 493 |
+
background: rgba(0, 0, 0, 0.3);
|
| 494 |
+
padding: 0.2rem 0.4rem;
|
| 495 |
+
border-radius: 4px;
|
| 496 |
+
font-family: monospace;
|
| 497 |
+
font-size: 0.9rem;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
.chat-response-content pre {
|
| 501 |
+
background: rgba(0, 0, 0, 0.4);
|
| 502 |
+
padding: 1rem;
|
| 503 |
+
border-radius: 8px;
|
| 504 |
+
overflow-x: auto;
|
| 505 |
+
margin: 1rem 0;
|
| 506 |
+
border: 1px solid var(--border-color);
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
.chat-response-content pre code {
|
| 510 |
+
background: transparent;
|
| 511 |
+
padding: 0;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
/* Prompt Context Inspector */
|
| 515 |
+
.inspector-section {
|
| 516 |
+
border-top: 1px solid var(--border-color);
|
| 517 |
+
background: rgba(0, 0, 0, 0.2);
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
.inspector-header {
|
| 521 |
+
padding: 0.75rem 1.5rem;
|
| 522 |
+
display: flex;
|
| 523 |
+
justify-content: space-between;
|
| 524 |
+
align-items: center;
|
| 525 |
+
cursor: pointer;
|
| 526 |
+
user-select: none;
|
| 527 |
+
transition: var(--transition);
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.inspector-header:hover {
|
| 531 |
+
background: rgba(255, 255, 255, 0.02);
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
.inspector-header span {
|
| 535 |
+
font-size: 0.8rem;
|
| 536 |
+
font-weight: 600;
|
| 537 |
+
color: var(--text-muted);
|
| 538 |
+
text-transform: uppercase;
|
| 539 |
+
letter-spacing: 0.5px;
|
| 540 |
+
display: flex;
|
| 541 |
+
align-items: center;
|
| 542 |
+
gap: 0.4rem;
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
.inspector-body {
|
| 546 |
+
padding: 1.2rem 1.5rem;
|
| 547 |
+
border-top: 1px solid var(--border-color);
|
| 548 |
+
background: rgba(0, 0, 0, 0.3);
|
| 549 |
+
font-family: monospace;
|
| 550 |
+
font-size: 0.85rem;
|
| 551 |
+
line-height: 1.5;
|
| 552 |
+
color: var(--text-secondary);
|
| 553 |
+
white-space: pre-wrap;
|
| 554 |
+
max-height: 250px;
|
| 555 |
+
overflow-y: auto;
|
| 556 |
+
animation: slideDown 0.2s ease-out;
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
/* RoBERTa Custom Emotion List & Chart */
|
| 560 |
+
.emotion-chart-container {
|
| 561 |
+
display: flex;
|
| 562 |
+
flex-direction: column;
|
| 563 |
+
gap: 0.5rem;
|
| 564 |
+
margin-top: 0.8rem;
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
.emotion-bar-row {
|
| 568 |
+
display: flex;
|
| 569 |
+
align-items: center;
|
| 570 |
+
gap: 0.8rem;
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
.emotion-bar-label {
|
| 574 |
+
width: 110px;
|
| 575 |
+
font-size: 0.8rem;
|
| 576 |
+
color: var(--text-secondary);
|
| 577 |
+
text-align: right;
|
| 578 |
+
text-transform: capitalize;
|
| 579 |
+
white-space: nowrap;
|
| 580 |
+
overflow: hidden;
|
| 581 |
+
text-overflow: ellipsis;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.emotion-bar-track {
|
| 585 |
+
flex: 1;
|
| 586 |
+
height: 8px;
|
| 587 |
+
background: rgba(255, 255, 255, 0.04);
|
| 588 |
+
border-radius: 4px;
|
| 589 |
+
overflow: hidden;
|
| 590 |
+
position: relative;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.emotion-bar-fill {
|
| 594 |
+
height: 100%;
|
| 595 |
+
border-radius: 4px;
|
| 596 |
+
transition: width 0.8s cubic-bezier(0.1, 0.8, 0.2, 1);
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
.emotion-bar-value {
|
| 600 |
+
width: 45px;
|
| 601 |
+
font-size: 0.75rem;
|
| 602 |
+
color: var(--text-muted);
|
| 603 |
+
font-family: monospace;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
/* Loading Skeletons */
|
| 607 |
+
.skeleton-wrapper {
|
| 608 |
+
display: flex;
|
| 609 |
+
flex-direction: column;
|
| 610 |
+
gap: 1rem;
|
| 611 |
+
width: 100%;
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
.skeleton-line {
|
| 615 |
+
height: 16px;
|
| 616 |
+
background: linear-gradient(90deg, rgba(255,255,255,0.03) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 75%);
|
| 617 |
+
background-size: 200% 100%;
|
| 618 |
+
animation: shimmer 1.5s infinite;
|
| 619 |
+
border-radius: 4px;
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
.skeleton-line.short { width: 40%; }
|
| 623 |
+
.skeleton-line.medium { width: 70%; }
|
| 624 |
+
.skeleton-line.long { width: 100%; }
|
| 625 |
+
|
| 626 |
+
.skeleton-box {
|
| 627 |
+
height: 80px;
|
| 628 |
+
background: linear-gradient(90deg, rgba(255,255,255,0.03) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 75%);
|
| 629 |
+
background-size: 200% 100%;
|
| 630 |
+
animation: shimmer 1.5s infinite;
|
| 631 |
+
border-radius: 8px;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
/* Animations */
|
| 635 |
+
@keyframes pulse {
|
| 636 |
+
0% { transform: scale(1); opacity: 1; }
|
| 637 |
+
50% { transform: scale(1.15); opacity: 0.6; }
|
| 638 |
+
100% { transform: scale(1); opacity: 1; }
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
@keyframes shimmer {
|
| 642 |
+
0% { background-position: 200% 0; }
|
| 643 |
+
100% { background-position: -200% 0; }
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
@keyframes slideDown {
|
| 647 |
+
from { opacity: 0; transform: translateY(-10px); }
|
| 648 |
+
to { opacity: 1; transform: translateY(0); }
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
/* Empty State / Welcome Screen */
|
| 652 |
+
.empty-state {
|
| 653 |
+
display: flex;
|
| 654 |
+
flex-direction: column;
|
| 655 |
+
align-items: center;
|
| 656 |
+
justify-content: center;
|
| 657 |
+
padding: 4rem 2rem;
|
| 658 |
+
text-align: center;
|
| 659 |
+
color: var(--text-secondary);
|
| 660 |
+
gap: 1rem;
|
| 661 |
+
flex: 1;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.empty-state-icon {
|
| 665 |
+
color: var(--text-muted);
|
| 666 |
+
margin-bottom: 0.5rem;
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
.empty-state h3 {
|
| 670 |
+
font-size: 1.3rem;
|
| 671 |
+
color: var(--text-primary);
|
| 672 |
+
font-weight: 600;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.empty-state p {
|
| 676 |
+
max-width: 500px;
|
| 677 |
+
font-size: 0.95rem;
|
| 678 |
+
line-height: 1.5;
|
| 679 |
+
color: var(--text-muted);
|
| 680 |
+
}
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import { Send, Settings as SettingsIcon, GraduationCap, AlertCircle, HelpCircle } from 'lucide-react';
|
| 3 |
+
import Settings from './components/Settings';
|
| 4 |
+
import ChatWindow from './components/ChatWindow';
|
| 5 |
+
import './App.css';
|
| 6 |
+
|
| 7 |
+
const API_BASE = window.location.origin === 'http://localhost:5173' ? 'http://localhost:8000' : '';
|
| 8 |
+
|
| 9 |
+
const samples = {
|
| 10 |
+
confusion: [
|
| 11 |
+
"Wait, why does a negative times a negative make a positive? I don't get it.",
|
| 12 |
+
"I'm looking at this cell diagram and I can't tell the difference between the cell wall and the cell membrane.",
|
| 13 |
+
"Our teacher said the Earth is tilted, but how does that make summer and winter? It doesn't make sense.",
|
| 14 |
+
"Is a virus alive or is it not? My textbook says both and I'm really mixed up.",
|
| 15 |
+
"What is the difference between a variable and a constant in algebra? I'm lost."
|
| 16 |
+
],
|
| 17 |
+
frustration: [
|
| 18 |
+
"I've reread this history chapter about the constitution three times and I still don't understand it! This is so annoying!",
|
| 19 |
+
"My science experiment failed again! The volcano didn't bubble at all and I did everything exactly right!",
|
| 20 |
+
"This long division with decimals is taking forever and I keep getting the wrong remainder! I hate this!",
|
| 21 |
+
"I'm trying to write this paragraph about Shakespeare but I have absolutely no idea what to write. I give up!",
|
| 22 |
+
"Why is English spelling so weird? These rules make no sense and I keep failing my vocabulary checks!"
|
| 23 |
+
],
|
| 24 |
+
boredom: [
|
| 25 |
+
"Ugh, why do we have to learn about sedimentary rocks? They just sit there. Who cares?",
|
| 26 |
+
"This math worksheet is just 50 of the same exact addition problems. This is so boring.",
|
| 27 |
+
"Reading about the ancient Egyptians is okay, but copying these definitions is putting me to sleep.",
|
| 28 |
+
"We are just sitting here listening to a long lecture about grammar. When do we get to do something fun?",
|
| 29 |
+
"I finished all my science reading early. There's nothing else to do except stare at the wall."
|
| 30 |
+
],
|
| 31 |
+
confidence: [
|
| 32 |
+
"I totally mastered multiplying fractions! Give me a hard practice problem to try!",
|
| 33 |
+
"I just finished writing my first short story in English and it has a really cool plot twist!",
|
| 34 |
+
"I know exactly why the American Revolution started. It was because of tax without representation!",
|
| 35 |
+
"I got a 100 on my spelling quiz today! I finally studied the right way!",
|
| 36 |
+
"I can explain the entire water cycle in my sleep! Evaporation, condensation, precipitation, easy!"
|
| 37 |
+
]
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
export default function App() {
|
| 41 |
+
const [apiKey, setApiKeyState] = useState(() => localStorage.getItem('gemini_api_key') || '');
|
| 42 |
+
const [systemPrompt, setSystemPromptState] = useState(() => localStorage.getItem('system_prompt') || '');
|
| 43 |
+
const [selectedSampleCategory, setSelectedSampleCategory] = useState('confusion');
|
| 44 |
+
const [message, setMessage] = useState('');
|
| 45 |
+
const [result, setResult] = useState(null);
|
| 46 |
+
const [loading, setLoading] = useState(false);
|
| 47 |
+
const [error, setError] = useState(null);
|
| 48 |
+
const [historyA, setHistoryA] = useState([]);
|
| 49 |
+
const [historyB, setHistoryB] = useState([]);
|
| 50 |
+
const [historyC, setHistoryC] = useState([]);
|
| 51 |
+
const [historyD, setHistoryD] = useState([]);
|
| 52 |
+
const [selectedOption, setSelectedOption] = useState('all');
|
| 53 |
+
const [showSettings, setShowSettings] = useState(false);
|
| 54 |
+
const [backendStatus, setBackendStatus] = useState({
|
| 55 |
+
roberta_status: 'loading',
|
| 56 |
+
roberta_error: null,
|
| 57 |
+
gemini_api_key_configured: false
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
const setApiKey = (val) => {
|
| 61 |
+
setApiKeyState(val);
|
| 62 |
+
localStorage.setItem('gemini_api_key', val);
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
const setSystemPrompt = (val) => {
|
| 66 |
+
setSystemPromptState(val);
|
| 67 |
+
localStorage.setItem('system_prompt', val);
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
const checkBackendStatus = async () => {
|
| 71 |
+
try {
|
| 72 |
+
const res = await fetch(`${API_BASE}/api/status`);
|
| 73 |
+
if (res.ok) {
|
| 74 |
+
const data = await res.json();
|
| 75 |
+
setBackendStatus(data);
|
| 76 |
+
return data.roberta_status;
|
| 77 |
+
}
|
| 78 |
+
} catch (err) {
|
| 79 |
+
console.error("Failed to fetch backend status:", err);
|
| 80 |
+
setBackendStatus({
|
| 81 |
+
roberta_status: 'failed',
|
| 82 |
+
roberta_error: 'Backend is offline or unreachable',
|
| 83 |
+
gemini_api_key_configured: false
|
| 84 |
+
});
|
| 85 |
+
}
|
| 86 |
+
return 'failed';
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
// Poll status on startup if model is loading
|
| 90 |
+
useEffect(() => {
|
| 91 |
+
checkBackendStatus();
|
| 92 |
+
|
| 93 |
+
const interval = setInterval(async () => {
|
| 94 |
+
const status = await checkBackendStatus();
|
| 95 |
+
if (status === 'ready' || status === 'failed') {
|
| 96 |
+
clearInterval(interval);
|
| 97 |
+
}
|
| 98 |
+
}, 4000);
|
| 99 |
+
|
| 100 |
+
return () => clearInterval(interval);
|
| 101 |
+
}, []);
|
| 102 |
+
|
| 103 |
+
const handleSubmit = async (e, followUpText = null) => {
|
| 104 |
+
if (e) e.preventDefault();
|
| 105 |
+
|
| 106 |
+
const activeMessage = followUpText !== null ? followUpText : message;
|
| 107 |
+
if (!activeMessage.trim() || loading) return;
|
| 108 |
+
|
| 109 |
+
setLoading(true);
|
| 110 |
+
setError(null);
|
| 111 |
+
|
| 112 |
+
// Reset histories and results on a brand new query
|
| 113 |
+
let currentHistoryA = historyA;
|
| 114 |
+
let currentHistoryB = historyB;
|
| 115 |
+
let currentHistoryC = historyC;
|
| 116 |
+
let currentHistoryD = historyD;
|
| 117 |
+
|
| 118 |
+
if (followUpText === null) {
|
| 119 |
+
setResult(null);
|
| 120 |
+
currentHistoryA = [];
|
| 121 |
+
currentHistoryB = [];
|
| 122 |
+
currentHistoryC = [];
|
| 123 |
+
currentHistoryD = [];
|
| 124 |
+
setHistoryA([]);
|
| 125 |
+
setHistoryB([]);
|
| 126 |
+
setHistoryC([]);
|
| 127 |
+
setHistoryD([]);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Prepend user message locally to create optimistic user bubble
|
| 131 |
+
const nextHistoryA = [...currentHistoryA, { role: 'user', content: activeMessage }];
|
| 132 |
+
const nextHistoryB = [...currentHistoryB, { role: 'user', content: activeMessage }];
|
| 133 |
+
const nextHistoryC = [...currentHistoryC, { role: 'user', content: activeMessage }];
|
| 134 |
+
const nextHistoryD = [...currentHistoryD, { role: 'user', content: activeMessage }];
|
| 135 |
+
|
| 136 |
+
setHistoryA(nextHistoryA);
|
| 137 |
+
setHistoryB(nextHistoryB);
|
| 138 |
+
setHistoryC(nextHistoryC);
|
| 139 |
+
setHistoryD(nextHistoryD);
|
| 140 |
+
|
| 141 |
+
try {
|
| 142 |
+
const res = await fetch(`${API_BASE}/api/chat`, {
|
| 143 |
+
method: 'POST',
|
| 144 |
+
headers: { 'Content-Type': 'application/json' },
|
| 145 |
+
body: JSON.stringify({
|
| 146 |
+
message: activeMessage,
|
| 147 |
+
gemini_api_key: apiKey || null,
|
| 148 |
+
system_prompt: systemPrompt || null,
|
| 149 |
+
history_a: currentHistoryA,
|
| 150 |
+
history_b: currentHistoryB,
|
| 151 |
+
history_c: currentHistoryC,
|
| 152 |
+
history_d: currentHistoryD,
|
| 153 |
+
selected_option: selectedOption
|
| 154 |
+
})
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
if (!res.ok) {
|
| 158 |
+
const data = await res.json();
|
| 159 |
+
throw new Error(data.detail || `Server error: ${res.statusText}`);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
const data = await res.json();
|
| 163 |
+
setResult(data);
|
| 164 |
+
|
| 165 |
+
// Append backend assistant responses to respective histories
|
| 166 |
+
setHistoryA([...nextHistoryA, { role: 'assistant', content: data.response_a }]);
|
| 167 |
+
setHistoryB([...nextHistoryB, { role: 'assistant', content: data.response_b }]);
|
| 168 |
+
setHistoryC([...nextHistoryC, { role: 'assistant', content: data.response_c }]);
|
| 169 |
+
setHistoryD([...nextHistoryD, { role: 'assistant', content: data.response_d }]);
|
| 170 |
+
|
| 171 |
+
if (followUpText === null) {
|
| 172 |
+
setMessage('');
|
| 173 |
+
}
|
| 174 |
+
} catch (err) {
|
| 175 |
+
setError(err.message || 'An unexpected error occurred while communicating with the server.');
|
| 176 |
+
console.error(err);
|
| 177 |
+
} finally {
|
| 178 |
+
setLoading(false);
|
| 179 |
+
}
|
| 180 |
+
};
|
| 181 |
+
|
| 182 |
+
const handleQuickPrompt = (promptText) => {
|
| 183 |
+
setMessage(promptText);
|
| 184 |
+
};
|
| 185 |
+
|
| 186 |
+
const isRobertaReady = backendStatus.roberta_status === 'ready';
|
| 187 |
+
|
| 188 |
+
return (
|
| 189 |
+
<div className="app-container">
|
| 190 |
+
{/* Header */}
|
| 191 |
+
<header className="app-header">
|
| 192 |
+
<div className="header-title-section">
|
| 193 |
+
<h1 style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
|
| 194 |
+
<GraduationCap size={36} color="var(--primary)" />
|
| 195 |
+
Sentiment Detection
|
| 196 |
+
</h1>
|
| 197 |
+
<p>Sentiment Analysis Comparison (Gemini vs. DistilRoBERTa)</p>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
<div className="header-status">
|
| 201 |
+
<div className="status-badge" style={{ padding: '0.4rem 0.8rem' }}>
|
| 202 |
+
<span className={`status-dot ${isRobertaReady ? 'ready' : 'loading'}`}></span>
|
| 203 |
+
<span style={{ fontSize: '0.85rem' }}>
|
| 204 |
+
DistilRoBERTa: {isRobertaReady ? 'Loaded' : 'Initializing...'}
|
| 205 |
+
</span>
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<button
|
| 209 |
+
className="settings-toggle-btn"
|
| 210 |
+
onClick={() => setShowSettings(!showSettings)}
|
| 211 |
+
>
|
| 212 |
+
<SettingsIcon size={18} />
|
| 213 |
+
Config Settings
|
| 214 |
+
</button>
|
| 215 |
+
</div>
|
| 216 |
+
</header>
|
| 217 |
+
|
| 218 |
+
{/* Settings Panel */}
|
| 219 |
+
{showSettings && (
|
| 220 |
+
<Settings
|
| 221 |
+
apiKey={apiKey}
|
| 222 |
+
setApiKey={setApiKey}
|
| 223 |
+
systemPrompt={systemPrompt}
|
| 224 |
+
setSystemPrompt={setSystemPrompt}
|
| 225 |
+
backendStatus={backendStatus}
|
| 226 |
+
checkBackendStatus={checkBackendStatus}
|
| 227 |
+
/>
|
| 228 |
+
)}
|
| 229 |
+
|
| 230 |
+
{/* Main Grid */}
|
| 231 |
+
<main className="main-grid">
|
| 232 |
+
{/* Chat input box */}
|
| 233 |
+
<section className="query-card">
|
| 234 |
+
{/* Pipeline Selector */}
|
| 235 |
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '0.8rem' }}>
|
| 236 |
+
<span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', fontWeight: 600, textTransform: 'uppercase' }}>Active Pipeline:</span>
|
| 237 |
+
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
| 238 |
+
{[
|
| 239 |
+
{ id: 'all', label: 'All (Compare)' },
|
| 240 |
+
{ id: 'A', label: 'Option A (LangGraph 2-Pass)' },
|
| 241 |
+
{ id: 'B', label: 'Option B (Gemini Single-Pass)' },
|
| 242 |
+
{ id: 'C', label: 'Option C (DistilRoBERTa Dist.)' },
|
| 243 |
+
{ id: 'D', label: 'Option D (DistilRoBERTa Class.)' }
|
| 244 |
+
].map((opt) => (
|
| 245 |
+
<button
|
| 246 |
+
key={opt.id}
|
| 247 |
+
type="button"
|
| 248 |
+
onClick={() => setSelectedOption(opt.id)}
|
| 249 |
+
style={{
|
| 250 |
+
background: selectedOption === opt.id ? 'var(--primary)' : 'rgba(255,255,255,0.03)',
|
| 251 |
+
border: '1px solid ' + (selectedOption === opt.id ? 'var(--primary)' : 'var(--border-color)'),
|
| 252 |
+
color: selectedOption === opt.id ? '#fff' : 'var(--text-secondary)',
|
| 253 |
+
padding: '0.4rem 0.8rem',
|
| 254 |
+
borderRadius: '8px',
|
| 255 |
+
fontSize: '0.85rem',
|
| 256 |
+
fontWeight: 600,
|
| 257 |
+
cursor: 'pointer',
|
| 258 |
+
transition: 'var(--transition)'
|
| 259 |
+
}}
|
| 260 |
+
>
|
| 261 |
+
{opt.label}
|
| 262 |
+
</button>
|
| 263 |
+
))}
|
| 264 |
+
</div>
|
| 265 |
+
</div>
|
| 266 |
+
|
| 267 |
+
<form onSubmit={handleSubmit} className="query-input-wrapper">
|
| 268 |
+
<textarea
|
| 269 |
+
value={message}
|
| 270 |
+
onChange={(e) => setMessage(e.target.value)}
|
| 271 |
+
placeholder="Ask an educational question... (e.g. 'I don't understand how recursion works!')"
|
| 272 |
+
className="query-textarea"
|
| 273 |
+
onKeyDown={(e) => {
|
| 274 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 275 |
+
e.preventDefault();
|
| 276 |
+
handleSubmit();
|
| 277 |
+
}
|
| 278 |
+
}}
|
| 279 |
+
/>
|
| 280 |
+
<button
|
| 281 |
+
type="submit"
|
| 282 |
+
className="send-button"
|
| 283 |
+
disabled={loading || !message.trim()}
|
| 284 |
+
>
|
| 285 |
+
<Send size={18} />
|
| 286 |
+
Analyze & Chat
|
| 287 |
+
</button>
|
| 288 |
+
</form>
|
| 289 |
+
|
| 290 |
+
{/* Quick prompts */}
|
| 291 |
+
<div className="quick-prompts-section" style={{ display: 'flex', flexDirection: 'column', gap: '0.8rem', marginTop: '0.5rem' }}>
|
| 292 |
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem', flexWrap: 'wrap', borderBottom: '1px solid var(--border-color)', paddingBottom: '0.6rem' }}>
|
| 293 |
+
<span className="quick-prompt-label" style={{ marginRight: 'auto' }}>8th Grade Sample Prompts:</span>
|
| 294 |
+
<div style={{ display: 'flex', gap: '0.4rem' }}>
|
| 295 |
+
{Object.keys(samples).map((cat) => (
|
| 296 |
+
<button
|
| 297 |
+
key={cat}
|
| 298 |
+
type="button"
|
| 299 |
+
onClick={() => setSelectedSampleCategory(cat)}
|
| 300 |
+
style={{
|
| 301 |
+
background: selectedSampleCategory === cat ? 'var(--primary)' : 'rgba(255,255,255,0.03)',
|
| 302 |
+
border: '1px solid ' + (selectedSampleCategory === cat ? 'var(--primary)' : 'var(--border-color)'),
|
| 303 |
+
color: selectedSampleCategory === cat ? '#fff' : 'var(--text-secondary)',
|
| 304 |
+
padding: '0.3rem 0.75rem',
|
| 305 |
+
borderRadius: '16px',
|
| 306 |
+
fontSize: '0.8rem',
|
| 307 |
+
fontWeight: 600,
|
| 308 |
+
cursor: 'pointer',
|
| 309 |
+
textTransform: 'capitalize',
|
| 310 |
+
transition: 'var(--transition)'
|
| 311 |
+
}}
|
| 312 |
+
>
|
| 313 |
+
{cat}
|
| 314 |
+
</button>
|
| 315 |
+
))}
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '0.5rem' }}>
|
| 319 |
+
{samples[selectedSampleCategory].map((promptText, i) => (
|
| 320 |
+
<button
|
| 321 |
+
key={i}
|
| 322 |
+
type="button"
|
| 323 |
+
className="quick-prompt-btn"
|
| 324 |
+
onClick={() => handleQuickPrompt(promptText)}
|
| 325 |
+
style={{ textAlign: 'left', width: '100%', whiteSpace: 'normal', lineHeight: '1.4', padding: '0.6rem 1rem', borderRadius: '8px' }}
|
| 326 |
+
>
|
| 327 |
+
{promptText}
|
| 328 |
+
</button>
|
| 329 |
+
))}
|
| 330 |
+
</div>
|
| 331 |
+
</div>
|
| 332 |
+
</section>
|
| 333 |
+
|
| 334 |
+
{/* Comparison Chat Output */}
|
| 335 |
+
<section>
|
| 336 |
+
<ChatWindow
|
| 337 |
+
result={result}
|
| 338 |
+
loading={loading}
|
| 339 |
+
error={error}
|
| 340 |
+
historyA={historyA}
|
| 341 |
+
historyB={historyB}
|
| 342 |
+
historyC={historyC}
|
| 343 |
+
historyD={historyD}
|
| 344 |
+
selectedOption={selectedOption}
|
| 345 |
+
onSubmitFollowUp={(text) => handleSubmit(null, text)}
|
| 346 |
+
/>
|
| 347 |
+
</section>
|
| 348 |
+
</main>
|
| 349 |
+
</div>
|
| 350 |
+
);
|
| 351 |
+
}
|
frontend/src/assets/hero.png
ADDED
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/assets/vite.svg
ADDED
|
|
frontend/src/components/ChatWindow.jsx
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { Sparkles, Cpu, Clock, Terminal, ChevronDown, ChevronUp, AlertCircle, Brain } from 'lucide-react';
|
| 3 |
+
import EmotionChart from './EmotionChart';
|
| 4 |
+
|
| 5 |
+
// Render LaTeX and text mixed together
|
| 6 |
+
function RenderLatex({ text }) {
|
| 7 |
+
if (!text) return null;
|
| 8 |
+
|
| 9 |
+
// Split text by $$ (block math) first, then by $ (inline math)
|
| 10 |
+
const blockParts = text.split(/(\$\$.*?\$\$)/g);
|
| 11 |
+
|
| 12 |
+
return (
|
| 13 |
+
<>
|
| 14 |
+
{blockParts.map((bp, bpIdx) => {
|
| 15 |
+
if (bp.startsWith('$$') && bp.endsWith('$$')) {
|
| 16 |
+
const formula = bp.slice(2, -2);
|
| 17 |
+
try {
|
| 18 |
+
if (window.katex) {
|
| 19 |
+
const html = window.katex.renderToString(formula, { displayMode: true, throwOnError: false });
|
| 20 |
+
return <div key={bpIdx} dangerouslySetInnerHTML={{ __html: html }} style={{ margin: '0.8rem 0' }} />;
|
| 21 |
+
}
|
| 22 |
+
} catch (e) {
|
| 23 |
+
console.error(e);
|
| 24 |
+
}
|
| 25 |
+
return <div key={bpIdx} style={{ margin: '0.8rem 0', fontFamily: 'monospace' }}>{bp}</div>;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
// Inline math split
|
| 29 |
+
const inlineParts = bp.split(/(\$.*?\$)/g);
|
| 30 |
+
return (
|
| 31 |
+
<React.Fragment key={bpIdx}>
|
| 32 |
+
{inlineParts.map((ip, ipIdx) => {
|
| 33 |
+
if (ip.startsWith('$') && ip.endsWith('$')) {
|
| 34 |
+
const formula = ip.slice(1, -1);
|
| 35 |
+
// Make sure it's not a lonely single dollar sign or empty
|
| 36 |
+
if (formula.trim()) {
|
| 37 |
+
try {
|
| 38 |
+
if (window.katex) {
|
| 39 |
+
const html = window.katex.renderToString(formula, { displayMode: false, throwOnError: false });
|
| 40 |
+
return <span key={ipIdx} dangerouslySetInnerHTML={{ __html: html }} />;
|
| 41 |
+
}
|
| 42 |
+
} catch (e) {
|
| 43 |
+
console.error(e);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
return <span key={ipIdx}>{ip}</span>;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Non-math text, run standard inline formatting
|
| 50 |
+
return <span key={ipIdx} dangerouslySetInnerHTML={{ __html: formatInline(ip) }} />;
|
| 51 |
+
})}
|
| 52 |
+
</React.Fragment>
|
| 53 |
+
);
|
| 54 |
+
})}
|
| 55 |
+
</>
|
| 56 |
+
);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Renders the scrollable thread of chat bubbles for a column
|
| 60 |
+
function ChatThread({ history, title }) {
|
| 61 |
+
const assistantMessages = history.filter(msg => msg.role === 'assistant');
|
| 62 |
+
return (
|
| 63 |
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.8rem', flex: 1, overflowY: 'auto', maxHeight: '420px', paddingRight: '0.25rem' }}>
|
| 64 |
+
{assistantMessages.map((msg, idx) => (
|
| 65 |
+
<div
|
| 66 |
+
key={idx}
|
| 67 |
+
style={{
|
| 68 |
+
alignSelf: 'flex-start',
|
| 69 |
+
background: 'rgba(255, 255, 255, 0.02)',
|
| 70 |
+
border: '1px solid var(--border-color)',
|
| 71 |
+
borderRadius: '12px',
|
| 72 |
+
padding: '0.75rem 1rem',
|
| 73 |
+
maxWidth: '100%',
|
| 74 |
+
}}
|
| 75 |
+
>
|
| 76 |
+
<span style={{
|
| 77 |
+
fontSize: '0.7rem',
|
| 78 |
+
fontWeight: 700,
|
| 79 |
+
textTransform: 'uppercase',
|
| 80 |
+
color: 'var(--text-secondary)',
|
| 81 |
+
display: 'block',
|
| 82 |
+
marginBottom: '0.3rem'
|
| 83 |
+
}}>
|
| 84 |
+
{title}
|
| 85 |
+
</span>
|
| 86 |
+
<SafeMarkdown content={msg.content} />
|
| 87 |
+
</div>
|
| 88 |
+
))}
|
| 89 |
+
</div>
|
| 90 |
+
);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// A simple local Markdown parser that converts basic markdown elements to safe HTML
|
| 94 |
+
function SafeMarkdown({ content }) {
|
| 95 |
+
if (!content) return null;
|
| 96 |
+
|
| 97 |
+
// Split by code blocks first
|
| 98 |
+
const parts = content.split(/(```[\s\S]*?```)/g);
|
| 99 |
+
|
| 100 |
+
return (
|
| 101 |
+
<div className="chat-response-content">
|
| 102 |
+
{parts.map((part, index) => {
|
| 103 |
+
if (part.startsWith('```') && part.endsWith('```')) {
|
| 104 |
+
// It's a code block
|
| 105 |
+
const code = part.slice(3, -3).replace(/^\w+\n/, ''); // remove language identifier if present
|
| 106 |
+
return (
|
| 107 |
+
<pre key={index}>
|
| 108 |
+
<code>{code}</code>
|
| 109 |
+
</pre>
|
| 110 |
+
);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// It's normal text, format paragraph breaks, bold, inline code, and lists
|
| 114 |
+
const formatted = part
|
| 115 |
+
.split('\n\n')
|
| 116 |
+
.map((para, paraIdx) => {
|
| 117 |
+
if (!para.trim()) return null;
|
| 118 |
+
|
| 119 |
+
// Handle bullet points
|
| 120 |
+
if (para.trim().startsWith('- ') || para.trim().startsWith('* ')) {
|
| 121 |
+
const items = para.split(/\n\s*[-*]\s+/);
|
| 122 |
+
return (
|
| 123 |
+
<ul key={paraIdx} style={{ marginBottom: '1rem', paddingLeft: '1.5rem' }}>
|
| 124 |
+
{items.map((item, itemIdx) => {
|
| 125 |
+
// strip initial bullet from first item if split didn't catch it
|
| 126 |
+
let cleanItem = item;
|
| 127 |
+
if (itemIdx === 0) {
|
| 128 |
+
cleanItem = item.replace(/^\s*[-*]\s+/, '');
|
| 129 |
+
}
|
| 130 |
+
if (!cleanItem.trim()) return null;
|
| 131 |
+
return (
|
| 132 |
+
<li key={itemIdx}>
|
| 133 |
+
<RenderLatex text={cleanItem} />
|
| 134 |
+
</li>
|
| 135 |
+
);
|
| 136 |
+
})}
|
| 137 |
+
</ul>
|
| 138 |
+
);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// Normal paragraph
|
| 142 |
+
return (
|
| 143 |
+
<p key={paraIdx}>
|
| 144 |
+
<RenderLatex text={para} />
|
| 145 |
+
</p>
|
| 146 |
+
);
|
| 147 |
+
});
|
| 148 |
+
|
| 149 |
+
return <React.Fragment key={index}>{formatted}</React.Fragment>;
|
| 150 |
+
})}
|
| 151 |
+
</div>
|
| 152 |
+
);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
// Format bold (**), italics (*), and inline code (`)
|
| 156 |
+
function formatInline(text) {
|
| 157 |
+
return text
|
| 158 |
+
// Bold
|
| 159 |
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
| 160 |
+
// Italics
|
| 161 |
+
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
| 162 |
+
// Inline code
|
| 163 |
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
| 164 |
+
// Replace newlines within a paragraph with breaks
|
| 165 |
+
.replace(/\n/g, '<br />');
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
export default function ChatWindow({
|
| 169 |
+
result,
|
| 170 |
+
loading,
|
| 171 |
+
error,
|
| 172 |
+
historyA = [],
|
| 173 |
+
historyB = [],
|
| 174 |
+
historyC = [],
|
| 175 |
+
historyD = [],
|
| 176 |
+
selectedOption = 'all',
|
| 177 |
+
onSubmitFollowUp
|
| 178 |
+
}) {
|
| 179 |
+
const [showPromptA, setShowPromptA] = useState(false);
|
| 180 |
+
const [showPromptB, setShowPromptB] = useState(false);
|
| 181 |
+
const [showPromptC, setShowPromptC] = useState(false);
|
| 182 |
+
const [showPromptD, setShowPromptD] = useState(false);
|
| 183 |
+
const [showDistC, setShowDistC] = useState(false);
|
| 184 |
+
const [showDistD, setShowDistD] = useState(false);
|
| 185 |
+
|
| 186 |
+
// Render loading skeleton
|
| 187 |
+
if (loading) {
|
| 188 |
+
const showA = selectedOption === 'all' || selectedOption === 'A';
|
| 189 |
+
const showB = selectedOption === 'all' || selectedOption === 'B';
|
| 190 |
+
const showC = selectedOption === 'all' || selectedOption === 'C';
|
| 191 |
+
const showD = selectedOption === 'all' || selectedOption === 'D';
|
| 192 |
+
|
| 193 |
+
return (
|
| 194 |
+
<div
|
| 195 |
+
className="comparison-grid"
|
| 196 |
+
style={{
|
| 197 |
+
gridTemplateColumns: selectedOption !== 'all' ? '1fr' : undefined,
|
| 198 |
+
maxWidth: selectedOption !== 'all' ? '700px' : undefined,
|
| 199 |
+
margin: selectedOption !== 'all' ? '0 auto' : undefined
|
| 200 |
+
}}
|
| 201 |
+
>
|
| 202 |
+
{/* Skeleton Column A */}
|
| 203 |
+
{showA && (
|
| 204 |
+
<div className="column-card">
|
| 205 |
+
<div className="column-header">
|
| 206 |
+
<div className="column-title-wrapper">
|
| 207 |
+
<h2><Sparkles size={18} color="var(--primary)" /> Output A</h2>
|
| 208 |
+
<p>Analyzing Sentiment...</p>
|
| 209 |
+
</div>
|
| 210 |
+
<div className="latency-badge">--s</div>
|
| 211 |
+
</div>
|
| 212 |
+
<div className="column-body">
|
| 213 |
+
<div className="skeleton-box" />
|
| 214 |
+
<div className="skeleton-wrapper">
|
| 215 |
+
<div className="skeleton-line long" />
|
| 216 |
+
<div className="skeleton-line medium" />
|
| 217 |
+
<div className="skeleton-line long" />
|
| 218 |
+
<div className="skeleton-line short" />
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
)}
|
| 223 |
+
|
| 224 |
+
{/* Skeleton Column B */}
|
| 225 |
+
{showB && (
|
| 226 |
+
<div className="column-card">
|
| 227 |
+
<div className="column-header">
|
| 228 |
+
<div className="column-title-wrapper">
|
| 229 |
+
<h2><Cpu size={18} color="var(--secondary)" /> Output B</h2>
|
| 230 |
+
<p>Analyzing Sentiment...</p>
|
| 231 |
+
</div>
|
| 232 |
+
<div className="latency-badge">--s</div>
|
| 233 |
+
</div>
|
| 234 |
+
<div className="column-body">
|
| 235 |
+
<div className="skeleton-box" />
|
| 236 |
+
<div className="skeleton-wrapper">
|
| 237 |
+
<div className="skeleton-line long" />
|
| 238 |
+
<div className="skeleton-line medium" />
|
| 239 |
+
<div className="skeleton-line long" />
|
| 240 |
+
<div className="skeleton-line short" />
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
)}
|
| 245 |
+
|
| 246 |
+
{/* Skeleton Column C */}
|
| 247 |
+
{showC && (
|
| 248 |
+
<div className="column-card">
|
| 249 |
+
<div className="column-header">
|
| 250 |
+
<div className="column-title-wrapper">
|
| 251 |
+
<h2><Brain size={18} color="var(--color-happy)" /> Output C</h2>
|
| 252 |
+
<p>Analyzing Sentiment...</p>
|
| 253 |
+
</div>
|
| 254 |
+
<div className="latency-badge">--s</div>
|
| 255 |
+
</div>
|
| 256 |
+
<div className="column-body">
|
| 257 |
+
<div className="skeleton-box" />
|
| 258 |
+
<div className="skeleton-wrapper">
|
| 259 |
+
<div className="skeleton-line long" />
|
| 260 |
+
<div className="skeleton-line medium" />
|
| 261 |
+
<div className="skeleton-line long" />
|
| 262 |
+
<div className="skeleton-line short" />
|
| 263 |
+
</div>
|
| 264 |
+
</div>
|
| 265 |
+
</div>
|
| 266 |
+
)}
|
| 267 |
+
|
| 268 |
+
{/* Skeleton Column D */}
|
| 269 |
+
{showD && (
|
| 270 |
+
<div className="column-card">
|
| 271 |
+
<div className="column-header">
|
| 272 |
+
<div className="column-title-wrapper">
|
| 273 |
+
<h2><Brain size={18} color="var(--secondary)" /> Output D</h2>
|
| 274 |
+
<p>Analyzing Sentiment...</p>
|
| 275 |
+
</div>
|
| 276 |
+
<div className="latency-badge">--s</div>
|
| 277 |
+
</div>
|
| 278 |
+
<div className="column-body">
|
| 279 |
+
<div className="skeleton-box" />
|
| 280 |
+
<div className="skeleton-wrapper">
|
| 281 |
+
<div className="skeleton-line long" />
|
| 282 |
+
<div className="skeleton-line medium" />
|
| 283 |
+
<div className="skeleton-line long" />
|
| 284 |
+
<div className="skeleton-line short" />
|
| 285 |
+
</div>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
)}
|
| 289 |
+
</div>
|
| 290 |
+
);
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
// Render error message
|
| 294 |
+
if (error) {
|
| 295 |
+
return (
|
| 296 |
+
<div style={{
|
| 297 |
+
background: 'rgba(244, 63, 94, 0.1)',
|
| 298 |
+
border: '1px solid var(--color-frustrated)',
|
| 299 |
+
borderRadius: '12px',
|
| 300 |
+
padding: '1.5rem',
|
| 301 |
+
display: 'flex',
|
| 302 |
+
alignItems: 'flex-start',
|
| 303 |
+
gap: '1rem',
|
| 304 |
+
color: 'var(--color-frustrated)'
|
| 305 |
+
}}>
|
| 306 |
+
<AlertCircle size={24} style={{ flexShrink: 0 }} />
|
| 307 |
+
<div>
|
| 308 |
+
<h3 style={{ fontWeight: 700, marginBottom: '0.3rem' }}>Analysis Failed</h3>
|
| 309 |
+
<p style={{ color: 'var(--text-primary)', fontSize: '0.95rem' }}>{error}</p>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
);
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
// Render empty state
|
| 316 |
+
if (!result) {
|
| 317 |
+
return (
|
| 318 |
+
<div className="empty-state">
|
| 319 |
+
<Terminal size={48} className="empty-state-icon" />
|
| 320 |
+
<h3>Awaiting your query</h3>
|
| 321 |
+
<p>Type an educational question above. The chatbot will process the query in two separate pipelines, show the classified sentiment, and respond using custom emotional contexts.</p>
|
| 322 |
+
</div>
|
| 323 |
+
);
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
return (
|
| 327 |
+
<>
|
| 328 |
+
{/* Student Question Card */}
|
| 329 |
+
<div
|
| 330 |
+
style={{
|
| 331 |
+
background: 'rgba(99, 102, 241, 0.04)',
|
| 332 |
+
border: '1px solid var(--border-color)',
|
| 333 |
+
borderRadius: '16px',
|
| 334 |
+
padding: '1.25rem 1.5rem',
|
| 335 |
+
marginBottom: '1.5rem',
|
| 336 |
+
display: 'flex',
|
| 337 |
+
flexDirection: 'column',
|
| 338 |
+
gap: '0.4rem',
|
| 339 |
+
boxShadow: '0 8px 32px 0 rgba(0, 0, 0, 0.15)',
|
| 340 |
+
position: 'relative',
|
| 341 |
+
overflow: 'hidden'
|
| 342 |
+
}}
|
| 343 |
+
>
|
| 344 |
+
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, var(--primary), var(--secondary))' }} />
|
| 345 |
+
<span style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: 'var(--primary)', letterSpacing: '0.5px' }}>
|
| 346 |
+
Student Question
|
| 347 |
+
</span>
|
| 348 |
+
<p style={{ fontSize: '1.05rem', fontWeight: 500, lineHeight: 1.5, margin: 0, color: 'var(--text-primary)' }}>
|
| 349 |
+
{historyA.length > 0 ? historyA[historyA.length - 2]?.content || historyA[0]?.content : '...'}
|
| 350 |
+
</p>
|
| 351 |
+
</div>
|
| 352 |
+
|
| 353 |
+
<div
|
| 354 |
+
className="comparison-grid"
|
| 355 |
+
style={{
|
| 356 |
+
gridTemplateColumns: selectedOption !== 'all' ? '1fr' : undefined,
|
| 357 |
+
maxWidth: selectedOption !== 'all' ? '700px' : undefined,
|
| 358 |
+
margin: selectedOption !== 'all' ? '0 auto' : undefined
|
| 359 |
+
}}
|
| 360 |
+
>
|
| 361 |
+
{/* Column A: Gemini Sentiment */}
|
| 362 |
+
{result.response_a && (
|
| 363 |
+
<div className="column-card">
|
| 364 |
+
<div className="column-header">
|
| 365 |
+
<div className="column-title-wrapper">
|
| 366 |
+
<h2><Sparkles size={18} color="var(--primary)" /> Output A</h2>
|
| 367 |
+
<p>Gemini 2.5 Flash Lite Sentiment</p>
|
| 368 |
+
</div>
|
| 369 |
+
<div className="column-meta">
|
| 370 |
+
<span className={`sentiment-badge ${result.sentiment_a.detected_sentiment}`}>
|
| 371 |
+
{result.sentiment_a.detected_sentiment}
|
| 372 |
+
</span>
|
| 373 |
+
<div className="latency-badge" title="Response Latency & Tokens">
|
| 374 |
+
<Clock size={12} style={{ display: 'inline', marginRight: '4px', verticalAlign: 'middle' }} />
|
| 375 |
+
{result.sentiment_a.latency_a || result.latency_a}s
|
| 376 |
+
{result.tokens_a !== undefined && result.tokens_a !== null && (
|
| 377 |
+
<>
|
| 378 |
+
<span style={{ margin: '0 6px', opacity: 0.4 }}>|</span>
|
| 379 |
+
<span>{result.tokens_a} tokens</span>
|
| 380 |
+
</>
|
| 381 |
+
)}
|
| 382 |
+
</div>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
|
| 386 |
+
<div className="column-body">
|
| 387 |
+
<div className="sentiment-analysis-box" style={{ marginBottom: '0.5rem' }}>
|
| 388 |
+
<h3>Gemini Sentiment Analysis</h3>
|
| 389 |
+
<p>{result.sentiment_a.explanation}</p>
|
| 390 |
+
</div>
|
| 391 |
+
|
| 392 |
+
<ChatThread history={historyA} title="Tutor A" />
|
| 393 |
+
</div>
|
| 394 |
+
|
| 395 |
+
{/* Prompt Context A Inspector */}
|
| 396 |
+
<div className="inspector-section">
|
| 397 |
+
<div className="inspector-header" onClick={() => setShowPromptA(!showPromptA)}>
|
| 398 |
+
<span>
|
| 399 |
+
<Terminal size={12} />
|
| 400 |
+
Prompt Context Context (Flow A)
|
| 401 |
+
</span>
|
| 402 |
+
{showPromptA ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 403 |
+
</div>
|
| 404 |
+
{showPromptA && (
|
| 405 |
+
<div className="inspector-body">
|
| 406 |
+
{result.prompt_context_a}
|
| 407 |
+
</div>
|
| 408 |
+
)}
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
)}
|
| 412 |
+
|
| 413 |
+
{/* Column B: Gemini Single-Pass Sentiment */}
|
| 414 |
+
{result.response_b && (
|
| 415 |
+
<div className="column-card">
|
| 416 |
+
<div className="column-header">
|
| 417 |
+
<div className="column-title-wrapper">
|
| 418 |
+
<h2><Cpu size={18} color="var(--secondary)" /> Output B</h2>
|
| 419 |
+
<p>Gemini Single-Pass</p>
|
| 420 |
+
</div>
|
| 421 |
+
<div className="column-meta">
|
| 422 |
+
<span className={`sentiment-badge ${result.sentiment_b.mapped_sentiment}`}>
|
| 423 |
+
{result.sentiment_b.mapped_sentiment}
|
| 424 |
+
</span>
|
| 425 |
+
<div className="latency-badge" title="Response Latency & Tokens">
|
| 426 |
+
<Clock size={12} style={{ display: 'inline', marginRight: '4px', verticalAlign: 'middle' }} />
|
| 427 |
+
{result.sentiment_b.latency_b || result.latency_b}s
|
| 428 |
+
{result.tokens_b !== undefined && result.tokens_b !== null && (
|
| 429 |
+
<>
|
| 430 |
+
<span style={{ margin: '0 6px', opacity: 0.4 }}>|</span>
|
| 431 |
+
<span>{result.tokens_b} tokens</span>
|
| 432 |
+
</>
|
| 433 |
+
)}
|
| 434 |
+
</div>
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
|
| 438 |
+
<div className="column-body">
|
| 439 |
+
<div className="sentiment-analysis-box" style={{ marginBottom: '0.5rem' }}>
|
| 440 |
+
<h3>Gemini Single-Pass Analysis</h3>
|
| 441 |
+
<p>Detected Sentiment and Socratic response generated in one pass: <strong>{result.sentiment_b.mapped_sentiment}</strong></p>
|
| 442 |
+
</div>
|
| 443 |
+
|
| 444 |
+
<ChatThread history={historyB} title="Tutor B" />
|
| 445 |
+
</div>
|
| 446 |
+
|
| 447 |
+
{/* Prompt Context B Inspector */}
|
| 448 |
+
<div className="inspector-section">
|
| 449 |
+
<div className="inspector-header" onClick={() => setShowPromptB(!showPromptB)}>
|
| 450 |
+
<span>
|
| 451 |
+
<Terminal size={12} />
|
| 452 |
+
Prompt Context Context (Flow B)
|
| 453 |
+
</span>
|
| 454 |
+
{showPromptB ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 455 |
+
</div>
|
| 456 |
+
{showPromptB && (
|
| 457 |
+
<div className="inspector-body">
|
| 458 |
+
{result.prompt_context_b}
|
| 459 |
+
</div>
|
| 460 |
+
)}
|
| 461 |
+
</div>
|
| 462 |
+
</div>
|
| 463 |
+
)}
|
| 464 |
+
|
| 465 |
+
{/* Column C: DistilRoBERTa Raw Scores -> Gemini */}
|
| 466 |
+
{result.response_c && (
|
| 467 |
+
<div className="column-card">
|
| 468 |
+
<div className="column-header">
|
| 469 |
+
<div className="column-title-wrapper">
|
| 470 |
+
<h2><Brain size={18} color="var(--color-happy)" /> Output C</h2>
|
| 471 |
+
<p>RoBERTa Raw Scores + Gemini</p>
|
| 472 |
+
</div>
|
| 473 |
+
<div className="column-meta">
|
| 474 |
+
<span className="sentiment-badge happy">
|
| 475 |
+
Distribution
|
| 476 |
+
</span>
|
| 477 |
+
<div className="latency-badge" title="Response Latency & Tokens">
|
| 478 |
+
<Clock size={12} style={{ display: 'inline', marginRight: '4px', verticalAlign: 'middle' }} />
|
| 479 |
+
{result.latency_c}s
|
| 480 |
+
{result.tokens_c !== undefined && result.tokens_c !== null && (
|
| 481 |
+
<>
|
| 482 |
+
<span style={{ margin: '0 6px', opacity: 0.4 }}>|</span>
|
| 483 |
+
<span>{result.tokens_c} tokens</span>
|
| 484 |
+
</>
|
| 485 |
+
)}
|
| 486 |
+
</div>
|
| 487 |
+
</div>
|
| 488 |
+
</div>
|
| 489 |
+
|
| 490 |
+
<div className="column-body">
|
| 491 |
+
<div className="sentiment-analysis-box" style={{ marginBottom: '0.5rem' }}>
|
| 492 |
+
<div
|
| 493 |
+
onClick={() => setShowDistC(!showDistC)}
|
| 494 |
+
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
| 495 |
+
>
|
| 496 |
+
<h3 style={{ margin: 0 }}>DistilRoBERTa Full Distribution</h3>
|
| 497 |
+
{showDistC ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 498 |
+
</div>
|
| 499 |
+
{showDistC && (
|
| 500 |
+
<div style={{ marginTop: '0.5rem' }}>
|
| 501 |
+
<p>All probability scores passed directly to Gemini for adaptive synthesis:</p>
|
| 502 |
+
{result.sentiment_d && result.sentiment_d.raw_emotions && (
|
| 503 |
+
<EmotionChart rawEmotions={result.sentiment_d.raw_emotions} modelName="DistilRoBERTa" />
|
| 504 |
+
)}
|
| 505 |
+
</div>
|
| 506 |
+
)}
|
| 507 |
+
</div>
|
| 508 |
+
|
| 509 |
+
<ChatThread history={historyC} title="Tutor C" />
|
| 510 |
+
</div>
|
| 511 |
+
|
| 512 |
+
{/* Prompt Context C Inspector */}
|
| 513 |
+
<div className="inspector-section">
|
| 514 |
+
<div className="inspector-header" onClick={() => setShowPromptC(!showPromptC)}>
|
| 515 |
+
<span>
|
| 516 |
+
<Terminal size={12} />
|
| 517 |
+
Prompt Context Context (Flow C)
|
| 518 |
+
</span>
|
| 519 |
+
{showPromptC ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 520 |
+
</div>
|
| 521 |
+
{showPromptC && (
|
| 522 |
+
<div className="inspector-body">
|
| 523 |
+
{result.prompt_context_c}
|
| 524 |
+
</div>
|
| 525 |
+
)}
|
| 526 |
+
</div>
|
| 527 |
+
</div>
|
| 528 |
+
)}
|
| 529 |
+
|
| 530 |
+
{/* Column D: DistilRoBERTa Mapped -> Gemini */}
|
| 531 |
+
{result.response_d && (
|
| 532 |
+
<div className="column-card">
|
| 533 |
+
<div className="column-header">
|
| 534 |
+
<div className="column-title-wrapper">
|
| 535 |
+
<h2><Brain size={18} color="var(--secondary)" /> Output D</h2>
|
| 536 |
+
<p>DistilRoBERTa Classifier + Gemini</p>
|
| 537 |
+
</div>
|
| 538 |
+
<div className="column-meta">
|
| 539 |
+
<span className={`sentiment-badge ${result.sentiment_d.mapped_sentiment}`}>
|
| 540 |
+
{result.sentiment_d.mapped_sentiment}
|
| 541 |
+
</span>
|
| 542 |
+
<div className="latency-badge" title="Response Latency & Tokens">
|
| 543 |
+
<Clock size={12} style={{ display: 'inline', marginRight: '4px', verticalAlign: 'middle' }} />
|
| 544 |
+
{result.sentiment_d.latency_d || result.latency_d}s
|
| 545 |
+
{result.tokens_d !== undefined && result.tokens_d !== null && (
|
| 546 |
+
<>
|
| 547 |
+
<span style={{ margin: '0 6px', opacity: 0.4 }}>|</span>
|
| 548 |
+
<span>{result.tokens_d} tokens</span>
|
| 549 |
+
</>
|
| 550 |
+
)}
|
| 551 |
+
</div>
|
| 552 |
+
</div>
|
| 553 |
+
</div>
|
| 554 |
+
|
| 555 |
+
<div className="column-body">
|
| 556 |
+
<div className="sentiment-analysis-box" style={{ marginBottom: '0.5rem' }}>
|
| 557 |
+
<div
|
| 558 |
+
onClick={() => setShowDistD(!showDistD)}
|
| 559 |
+
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
| 560 |
+
>
|
| 561 |
+
<h3 style={{ margin: 0 }}>DistilRoBERTa Mapped Category</h3>
|
| 562 |
+
{showDistD ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 563 |
+
</div>
|
| 564 |
+
{showDistD && (
|
| 565 |
+
<div style={{ marginTop: '0.5rem' }}>
|
| 566 |
+
<p>Mapped top-scoring emotions into high-level state: <strong>{result.sentiment_d.mapped_sentiment}</strong></p>
|
| 567 |
+
<EmotionChart rawEmotions={result.sentiment_d.raw_emotions} modelName="DistilRoBERTa" />
|
| 568 |
+
</div>
|
| 569 |
+
)}
|
| 570 |
+
</div>
|
| 571 |
+
|
| 572 |
+
<ChatThread history={historyD} title="Tutor D" />
|
| 573 |
+
</div>
|
| 574 |
+
|
| 575 |
+
{/* Prompt Context D Inspector */}
|
| 576 |
+
<div className="inspector-section">
|
| 577 |
+
<div className="inspector-header" onClick={() => setShowPromptD(!showPromptD)}>
|
| 578 |
+
<span>
|
| 579 |
+
<Terminal size={12} />
|
| 580 |
+
Prompt Context Context (Flow D)
|
| 581 |
+
</span>
|
| 582 |
+
{showPromptD ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
| 583 |
+
</div>
|
| 584 |
+
{showPromptD && (
|
| 585 |
+
<div className="inspector-body">
|
| 586 |
+
{result.prompt_context_d}
|
| 587 |
+
</div>
|
| 588 |
+
)}
|
| 589 |
+
</div>
|
| 590 |
+
</div>
|
| 591 |
+
)}
|
| 592 |
+
|
| 593 |
+
{/* Follow-up central conversation area */}
|
| 594 |
+
<div className="query-card" style={{ gridColumn: '1 / -1', marginTop: '1rem', background: 'rgba(99, 102, 241, 0.05)', borderColor: 'var(--primary-glow)' }}>
|
| 595 |
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem', marginBottom: '0.8rem' }}>
|
| 596 |
+
<h3 style={{ fontSize: '1.05rem', fontWeight: 800, display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
| 597 |
+
<Brain size={18} color="var(--primary)" />
|
| 598 |
+
Continue the Conversation on this Topic
|
| 599 |
+
</h3>
|
| 600 |
+
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
|
| 601 |
+
Reply to the tutors' questions. Active tutors will formulate their next Socratic nudges using their independent histories.
|
| 602 |
+
</p>
|
| 603 |
+
</div>
|
| 604 |
+
<form
|
| 605 |
+
onSubmit={(e) => {
|
| 606 |
+
e.preventDefault();
|
| 607 |
+
const inputEl = e.target.elements.followUpText;
|
| 608 |
+
const val = inputEl.value.trim();
|
| 609 |
+
if (val && !loading) {
|
| 610 |
+
onSubmitFollowUp(val);
|
| 611 |
+
inputEl.value = '';
|
| 612 |
+
}
|
| 613 |
+
}}
|
| 614 |
+
className="query-input-wrapper"
|
| 615 |
+
>
|
| 616 |
+
<textarea
|
| 617 |
+
name="followUpText"
|
| 618 |
+
placeholder="Type your response to the Socratic tutor..."
|
| 619 |
+
className="query-textarea"
|
| 620 |
+
disabled={loading}
|
| 621 |
+
onKeyDown={(e) => {
|
| 622 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 623 |
+
e.preventDefault();
|
| 624 |
+
e.target.form.requestSubmit();
|
| 625 |
+
}
|
| 626 |
+
}}
|
| 627 |
+
/>
|
| 628 |
+
<button
|
| 629 |
+
type="submit"
|
| 630 |
+
className="send-button"
|
| 631 |
+
disabled={loading}
|
| 632 |
+
>
|
| 633 |
+
Send Reply
|
| 634 |
+
</button>
|
| 635 |
+
</form>
|
| 636 |
+
</div>
|
| 637 |
+
</div>
|
| 638 |
+
</>
|
| 639 |
+
);
|
| 640 |
+
}
|
frontend/src/components/EmotionChart.jsx
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { ChevronDown, ChevronUp, BarChart2 } from 'lucide-react';
|
| 3 |
+
|
| 4 |
+
export default function EmotionChart({ rawEmotions, modelName = "RoBERTa" }) {
|
| 5 |
+
const [showAll, setShowAll] = useState(false);
|
| 6 |
+
|
| 7 |
+
if (!rawEmotions || rawEmotions.length === 0) {
|
| 8 |
+
return <p style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>No emotional details available.</p>;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Sort by score descending
|
| 12 |
+
const sorted = [...rawEmotions].sort((a, b) => b.score - a.score);
|
| 13 |
+
|
| 14 |
+
// Decide how many to display
|
| 15 |
+
const displayedEmotions = showAll ? sorted : sorted.slice(0, 5);
|
| 16 |
+
|
| 17 |
+
// Group emotions for coloring
|
| 18 |
+
const happySet = new Set(["joy", "amusement", "excitement", "pride", "optimism", "relief", "love", "admiration", "gratitude", "approval"]);
|
| 19 |
+
const sadSet = new Set(["sadness", "grief", "remorse", "disappointment", "embarrassment"]);
|
| 20 |
+
const frustratedSet = new Set(["anger", "annoyance", "disapproval", "disgust", "fear", "nervousness"]);
|
| 21 |
+
|
| 22 |
+
const getBarColor = (label) => {
|
| 23 |
+
if (happySet.has(label)) return 'var(--color-happy)';
|
| 24 |
+
if (sadSet.has(label)) return 'var(--color-sad)';
|
| 25 |
+
if (frustratedSet.has(label)) return 'var(--color-frustrated)';
|
| 26 |
+
return 'var(--color-neutral)';
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<div className="emotion-chart-container">
|
| 31 |
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.5rem' }}>
|
| 32 |
+
<h4 style={{
|
| 33 |
+
fontSize: '0.8rem',
|
| 34 |
+
color: 'var(--text-muted)',
|
| 35 |
+
textTransform: 'uppercase',
|
| 36 |
+
letterSpacing: '0.5px',
|
| 37 |
+
display: 'flex',
|
| 38 |
+
alignItems: 'center',
|
| 39 |
+
gap: '0.3rem'
|
| 40 |
+
}}>
|
| 41 |
+
<BarChart2 size={12} />
|
| 42 |
+
Local {modelName} Model Emotion Breakdown
|
| 43 |
+
</h4>
|
| 44 |
+
<button
|
| 45 |
+
onClick={() => setShowAll(!showAll)}
|
| 46 |
+
style={{
|
| 47 |
+
background: 'none',
|
| 48 |
+
border: 'none',
|
| 49 |
+
color: 'var(--primary)',
|
| 50 |
+
fontSize: '0.75rem',
|
| 51 |
+
cursor: 'pointer',
|
| 52 |
+
display: 'flex',
|
| 53 |
+
alignItems: 'center',
|
| 54 |
+
gap: '0.2rem',
|
| 55 |
+
fontWeight: 600
|
| 56 |
+
}}
|
| 57 |
+
>
|
| 58 |
+
{showAll ? (
|
| 59 |
+
<>
|
| 60 |
+
Show Top 5 <ChevronUp size={12} />
|
| 61 |
+
</>
|
| 62 |
+
) : (
|
| 63 |
+
<>
|
| 64 |
+
Show All 28 <ChevronDown size={12} />
|
| 65 |
+
</>
|
| 66 |
+
)}
|
| 67 |
+
</button>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
|
| 71 |
+
{displayedEmotions.map((item, idx) => {
|
| 72 |
+
const percentage = (item.score * 100).toFixed(1);
|
| 73 |
+
return (
|
| 74 |
+
<div key={idx} className="emotion-bar-row">
|
| 75 |
+
<div className="emotion-bar-label" title={item.label}>
|
| 76 |
+
{item.label}
|
| 77 |
+
</div>
|
| 78 |
+
<div className="emotion-bar-track">
|
| 79 |
+
<div
|
| 80 |
+
className="emotion-bar-fill"
|
| 81 |
+
style={{
|
| 82 |
+
width: `${percentage}%`,
|
| 83 |
+
backgroundColor: getBarColor(item.label),
|
| 84 |
+
boxShadow: `0 0 6px ${getBarColor(item.label)}33`
|
| 85 |
+
}}
|
| 86 |
+
/>
|
| 87 |
+
</div>
|
| 88 |
+
<div className="emotion-bar-value">
|
| 89 |
+
{percentage}%
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
);
|
| 93 |
+
})}
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
);
|
| 97 |
+
}
|
frontend/src/components/Settings.jsx
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { Eye, EyeOff, Key, Sparkles, RefreshCw } from 'lucide-react';
|
| 3 |
+
|
| 4 |
+
export default function Settings({
|
| 5 |
+
apiKey,
|
| 6 |
+
setApiKey,
|
| 7 |
+
systemPrompt,
|
| 8 |
+
setSystemPrompt,
|
| 9 |
+
backendStatus,
|
| 10 |
+
checkBackendStatus
|
| 11 |
+
}) {
|
| 12 |
+
const [showKey, setShowKey] = useState(false);
|
| 13 |
+
|
| 14 |
+
const getModelStatusText = (status, error) => {
|
| 15 |
+
if (status === 'loading') return 'Downloading/Loading...';
|
| 16 |
+
if (status === 'ready') return 'Ready';
|
| 17 |
+
if (status === 'failed') return `Error: ${error || 'Failed to load model'}`;
|
| 18 |
+
return 'Connecting...';
|
| 19 |
+
};
|
| 20 |
+
|
| 21 |
+
const getModelStatusClass = (status) => {
|
| 22 |
+
if (status === 'ready') return 'ready';
|
| 23 |
+
if (status === 'failed') return 'failed';
|
| 24 |
+
return 'loading';
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
return (
|
| 28 |
+
<div className="settings-drawer">
|
| 29 |
+
<div className="settings-group">
|
| 30 |
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
| 31 |
+
<label style={{ display: 'flex', alignStatus: 'center', gap: '0.5rem' }}>
|
| 32 |
+
<Key size={16} color="var(--primary)" />
|
| 33 |
+
Gemini API Key
|
| 34 |
+
</label>
|
| 35 |
+
<span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
|
| 36 |
+
Saved locally in your browser
|
| 37 |
+
</span>
|
| 38 |
+
</div>
|
| 39 |
+
<div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
|
| 40 |
+
<input
|
| 41 |
+
type={showKey ? 'text' : 'password'}
|
| 42 |
+
value={apiKey}
|
| 43 |
+
onChange={(e) => setApiKey(e.target.value)}
|
| 44 |
+
placeholder={backendStatus.gemini_api_key_configured ? "Using backend environment variable" : "Enter your GEMINI_API_KEY..."}
|
| 45 |
+
style={{ width: '100%', paddingRight: '2.5rem' }}
|
| 46 |
+
/>
|
| 47 |
+
<button
|
| 48 |
+
type="button"
|
| 49 |
+
onClick={() => setShowKey(!showKey)}
|
| 50 |
+
style={{
|
| 51 |
+
position: 'absolute',
|
| 52 |
+
right: '10px',
|
| 53 |
+
background: 'none',
|
| 54 |
+
border: 'none',
|
| 55 |
+
color: 'var(--text-secondary)',
|
| 56 |
+
cursor: 'pointer',
|
| 57 |
+
display: 'flex',
|
| 58 |
+
alignItems: 'center'
|
| 59 |
+
}}
|
| 60 |
+
>
|
| 61 |
+
{showKey ? <EyeOff size={18} /> : <Eye size={18} />}
|
| 62 |
+
</button>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<div className="settings-group">
|
| 67 |
+
<label style={{ display: 'flex', alignStatus: 'center', gap: '0.5rem' }}>
|
| 68 |
+
<Sparkles size={16} color="var(--secondary)" />
|
| 69 |
+
Chatbot System Instruction
|
| 70 |
+
</label>
|
| 71 |
+
<textarea
|
| 72 |
+
value={systemPrompt}
|
| 73 |
+
onChange={(e) => setSystemPrompt(e.target.value)}
|
| 74 |
+
placeholder="You are a helpful educational AI assistant..."
|
| 75 |
+
rows={3}
|
| 76 |
+
/>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div style={{
|
| 80 |
+
display: 'flex',
|
| 81 |
+
justifyContent: 'space-between',
|
| 82 |
+
alignItems: 'center',
|
| 83 |
+
borderTop: '1px solid var(--border-color)',
|
| 84 |
+
paddingTop: '1rem',
|
| 85 |
+
marginTop: '0.5rem'
|
| 86 |
+
}}>
|
| 87 |
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
| 88 |
+
<span style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>Backend DistilRoBERTa Status:</span>
|
| 89 |
+
<span className={`status-badge`} style={{ padding: '0.2rem 0.6rem' }}>
|
| 90 |
+
<span className={`status-dot ${getModelStatusClass(backendStatus.roberta_status)}`}></span>
|
| 91 |
+
<span style={{ marginLeft: '4px', fontSize: '0.8rem' }}>{getModelStatusText(backendStatus.roberta_status, backendStatus.roberta_error)}</span>
|
| 92 |
+
</span>
|
| 93 |
+
</div>
|
| 94 |
+
<button
|
| 95 |
+
onClick={checkBackendStatus}
|
| 96 |
+
className="settings-toggle-btn"
|
| 97 |
+
style={{ padding: '0.35rem 0.75rem' }}
|
| 98 |
+
title="Refresh Backend Status"
|
| 99 |
+
>
|
| 100 |
+
<RefreshCw size={14} />
|
| 101 |
+
Refresh
|
| 102 |
+
</button>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
);
|
| 106 |
+
}
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Reset styling, styling is managed in App.css */
|
| 2 |
+
body {
|
| 3 |
+
margin: 0;
|
| 4 |
+
padding: 0;
|
| 5 |
+
box-sizing: border-box;
|
| 6 |
+
}
|
frontend/src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.jsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/vite.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|