Spaces:
Sleeping
Sleeping
findEthics commited on
Commit ·
6ec566b
1
Parent(s): dcffe87
Add anonymous chat access feature
Browse files- Allow users to chat without creating an account
- Add choice page for selecting access mode (anonymous vs authenticated)
- Implement session-based chat storage for anonymous users
- Add rate limiting (20 messages/hour) for anonymous users
- Add message length limits (2000 chars) for anonymous users
- Update UI to show anonymous user indicators and session warnings
- Maintain backward compatibility with existing authenticated flow
- .gitignore +67 -0
- CLAUDE.md +193 -0
- app.py +163 -36
- config.py +8 -0
- static/css/style.css +223 -0
- static/js/chat.js +44 -8
- task_list.md +140 -1
- templates/base.html +4 -0
- templates/choose.html +75 -0
- templates/index.html +17 -0
.gitignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Claude
|
| 2 |
+
.claude
|
| 3 |
+
.swarm
|
| 4 |
+
.claude-flow/metrics
|
| 5 |
+
.kiro
|
| 6 |
+
|
| 7 |
+
# Environment variables
|
| 8 |
+
.env
|
| 9 |
+
.env.local
|
| 10 |
+
.env.production
|
| 11 |
+
|
| 12 |
+
# Python
|
| 13 |
+
__pycache__/
|
| 14 |
+
*.py[cod]
|
| 15 |
+
*$py.class
|
| 16 |
+
*.so
|
| 17 |
+
.Python
|
| 18 |
+
build/
|
| 19 |
+
develop-eggs/
|
| 20 |
+
dist/
|
| 21 |
+
downloads/
|
| 22 |
+
eggs/
|
| 23 |
+
.eggs/
|
| 24 |
+
lib/
|
| 25 |
+
lib64/
|
| 26 |
+
parts/
|
| 27 |
+
sdist/
|
| 28 |
+
var/
|
| 29 |
+
wheels/
|
| 30 |
+
*.egg-info/
|
| 31 |
+
.installed.cfg
|
| 32 |
+
*.egg
|
| 33 |
+
MANIFEST
|
| 34 |
+
|
| 35 |
+
# Virtual environments
|
| 36 |
+
atlas_env/
|
| 37 |
+
venv/
|
| 38 |
+
env/
|
| 39 |
+
ENV/
|
| 40 |
+
|
| 41 |
+
# IDE
|
| 42 |
+
.vscode/
|
| 43 |
+
.idea/
|
| 44 |
+
*.swp
|
| 45 |
+
*.swo
|
| 46 |
+
|
| 47 |
+
# OS
|
| 48 |
+
.DS_Store
|
| 49 |
+
Thumbs.db
|
| 50 |
+
|
| 51 |
+
# Logs
|
| 52 |
+
*.log
|
| 53 |
+
|
| 54 |
+
# Test files (exclude from root, but allow in tests/ folder)
|
| 55 |
+
/test_*.py
|
| 56 |
+
/debug_*.py
|
| 57 |
+
/validate_*.py
|
| 58 |
+
/deployment_check.py
|
| 59 |
+
|
| 60 |
+
# MongoDB local data
|
| 61 |
+
*.json
|
| 62 |
+
*.bson
|
| 63 |
+
|
| 64 |
+
# API Keys (backup protection)
|
| 65 |
+
*.key
|
| 66 |
+
*.pem
|
| 67 |
+
api_keys.txt
|
CLAUDE.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Chatty - Ethics Chat Application Context
|
| 2 |
+
|
| 3 |
+
## Project Overview
|
| 4 |
+
Chatty is a secure web-based chat interface that connects to the findEthics-Atlas API for ethical discussions and guidance. Originally a Gradio chatbot template, it has been transformed into a full Flask web application with user authentication, chat history persistence, and deployment on Hugging Face Spaces.
|
| 5 |
+
|
| 6 |
+
## Architecture
|
| 7 |
+
|
| 8 |
+
### Tech Stack
|
| 9 |
+
- **Backend**: Flask 3.0.0 (Python web framework)
|
| 10 |
+
- **Database**: MongoDB Atlas (user accounts & chat history)
|
| 11 |
+
- **Authentication**: Flask-Login with JWT tokens for stateless auth
|
| 12 |
+
- **Security**: CSRF protection (disabled for HF Spaces), rate limiting, secure password hashing (Werkzeug PBKDF2)
|
| 13 |
+
- **Frontend**: HTML, CSS (minimal design), JavaScript (vanilla ES6)
|
| 14 |
+
- **Deployment**: Hugging Face Spaces (primary), supports Docker and traditional servers
|
| 15 |
+
- **API Integration**: findEthics-Atlas API at https://findEthics-Atlas.hf.space/chat
|
| 16 |
+
|
| 17 |
+
### Project Structure
|
| 18 |
+
```
|
| 19 |
+
Chatty/
|
| 20 |
+
├── app.py # Main Flask application with routes and JWT auth
|
| 21 |
+
├── config.py # Environment-based configuration management
|
| 22 |
+
├── auth.py # Authentication utilities & Flask-Login setup
|
| 23 |
+
├── models.py # MongoDB models (User, ChatSession)
|
| 24 |
+
├── database.py # MongoDB connection and collection utilities
|
| 25 |
+
├── deploy.py # Deployment tools and configuration helpers
|
| 26 |
+
├── requirements.txt # Python dependencies
|
| 27 |
+
├── start.sh # Application startup script
|
| 28 |
+
├── templates/ # HTML templates
|
| 29 |
+
│ ├── base.html # Base template with navigation
|
| 30 |
+
│ ├── login.html # Login form
|
| 31 |
+
│ ├── register.html # Registration form
|
| 32 |
+
│ ├── index.html # Main chat interface
|
| 33 |
+
│ └── errors/ # Error page templates (400, 401, 429, 500)
|
| 34 |
+
├── static/ # Static assets
|
| 35 |
+
│ ├── css/
|
| 36 |
+
│ │ ├── style.css # Main stylesheet
|
| 37 |
+
│ │ └── auth.css # Authentication form styles
|
| 38 |
+
│ └── js/
|
| 39 |
+
│ └── chat.js # Frontend chat functionality
|
| 40 |
+
├── Test/ # Test suite
|
| 41 |
+
└── Documentation/
|
| 42 |
+
├── README.md # Main documentation with HF Spaces YAML header
|
| 43 |
+
├── HUGGINGFACE_DEPLOYMENT_CHECKLIST.md
|
| 44 |
+
├── PRODUCTION_DEPLOYMENT.md
|
| 45 |
+
└── instructions.md # Original requirements
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## Key Features
|
| 49 |
+
1. **User Authentication**: Secure registration/login with email validation
|
| 50 |
+
2. **Personalized Chat History**: Each user's conversations saved in MongoDB
|
| 51 |
+
3. **Stateless JWT Auth**: For Hugging Face Spaces compatibility
|
| 52 |
+
4. **Real-time Chat**: Instant messaging with ethics AI
|
| 53 |
+
5. **Security Features**: Rate limiting, secure sessions, password hashing
|
| 54 |
+
6. **Responsive Design**: Mobile-friendly minimal UI
|
| 55 |
+
|
| 56 |
+
## API Integration
|
| 57 |
+
|
| 58 |
+
### findEthics-Atlas API
|
| 59 |
+
- **Endpoint**: https://findEthics-Atlas.hf.space/chat
|
| 60 |
+
- **Method**: POST
|
| 61 |
+
- **Request Format**:
|
| 62 |
+
```json
|
| 63 |
+
{
|
| 64 |
+
"prompt": "user message",
|
| 65 |
+
"history": []
|
| 66 |
+
}
|
| 67 |
+
```
|
| 68 |
+
- **Response Format**:
|
| 69 |
+
```json
|
| 70 |
+
{
|
| 71 |
+
"response": "AI response text",
|
| 72 |
+
"search_results": []
|
| 73 |
+
}
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
## Database Schema
|
| 77 |
+
|
| 78 |
+
### Users Collection
|
| 79 |
+
```javascript
|
| 80 |
+
{
|
| 81 |
+
_id: ObjectId,
|
| 82 |
+
email: String (lowercase, unique),
|
| 83 |
+
password_hash: String (Werkzeug PBKDF2),
|
| 84 |
+
created_at: DateTime,
|
| 85 |
+
is_active: Boolean
|
| 86 |
+
}
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
### Chat Sessions Collection
|
| 90 |
+
```javascript
|
| 91 |
+
{
|
| 92 |
+
_id: ObjectId,
|
| 93 |
+
user_id: ObjectId (reference to Users),
|
| 94 |
+
message: String,
|
| 95 |
+
response: String,
|
| 96 |
+
timestamp: DateTime,
|
| 97 |
+
session_data: Object (metadata)
|
| 98 |
+
}
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Recent Updates
|
| 102 |
+
- **Stateless JWT Authentication**: Implemented for Hugging Face Spaces compatibility
|
| 103 |
+
- **Session Persistence Fixes**: Resolved load balancing issues on HF Spaces
|
| 104 |
+
- **CSRF Protection**: Disabled for HF Spaces deployment (iframe/proxy limitations)
|
| 105 |
+
- **Cookie Configuration**: Adjusted for HF Spaces environment
|
| 106 |
+
|
| 107 |
+
## Deployment Configuration
|
| 108 |
+
|
| 109 |
+
### Hugging Face Spaces
|
| 110 |
+
- **FLASK_ENV**: huggingface
|
| 111 |
+
- **Port**: 7860
|
| 112 |
+
- **CSRF**: Disabled (WTF_CSRF_ENABLED=false)
|
| 113 |
+
- **Session Cookies**: Secure=false (for HF proxy)
|
| 114 |
+
- **JWT Tokens**: 48-hour expiry for stateless auth
|
| 115 |
+
|
| 116 |
+
### Environment Variables
|
| 117 |
+
Required:
|
| 118 |
+
- `SECRET_KEY`: 64-character secure key
|
| 119 |
+
- `MONGODB_URL`: MongoDB Atlas connection string
|
| 120 |
+
- `MONGODB_DATABASE`: Atlas
|
| 121 |
+
|
| 122 |
+
Security (HF Spaces optimized):
|
| 123 |
+
- `SESSION_COOKIE_SECURE`: false
|
| 124 |
+
- `WTF_CSRF_ENABLED`: false
|
| 125 |
+
- `MAX_LOGIN_ATTEMPTS`: 3
|
| 126 |
+
- `RATE_LIMIT_WINDOW`: 1800
|
| 127 |
+
|
| 128 |
+
## Security Considerations
|
| 129 |
+
1. **Password Security**: PBKDF2 hashing via Werkzeug
|
| 130 |
+
2. **Rate Limiting**: Login attempt limiting (5 attempts default)
|
| 131 |
+
3. **JWT Tokens**: Stateless authentication for distributed deployment
|
| 132 |
+
4. **Input Validation**: Server and client-side validation
|
| 133 |
+
5. **HTTPS**: Provided by Hugging Face Spaces
|
| 134 |
+
|
| 135 |
+
## Testing
|
| 136 |
+
- **Test Suite**: Located in Test/ directory
|
| 137 |
+
- **Coverage**: Unit tests, integration tests, auth flow tests
|
| 138 |
+
- **Run Tests**: `python Test/run_tests.py`
|
| 139 |
+
|
| 140 |
+
## Development Workflow
|
| 141 |
+
|
| 142 |
+
### Local Development
|
| 143 |
+
```bash
|
| 144 |
+
# Setup virtual environment
|
| 145 |
+
python -m venv atlas_env
|
| 146 |
+
source atlas_env/bin/activate
|
| 147 |
+
|
| 148 |
+
# Install dependencies
|
| 149 |
+
pip install -r requirements.txt
|
| 150 |
+
|
| 151 |
+
# Configure environment
|
| 152 |
+
python deploy.py setup-dev
|
| 153 |
+
|
| 154 |
+
# Run application
|
| 155 |
+
./start.sh
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
### Production Deployment
|
| 159 |
+
```bash
|
| 160 |
+
# Check production readiness
|
| 161 |
+
python deploy.py check-prod
|
| 162 |
+
|
| 163 |
+
# Generate secure key
|
| 164 |
+
python deploy.py gen-secret
|
| 165 |
+
|
| 166 |
+
# Deploy to HF Spaces
|
| 167 |
+
git push origin main
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
## Important Notes
|
| 171 |
+
1. **CSRF Protection**: Disabled for HF Spaces due to iframe limitations
|
| 172 |
+
2. **Session Management**: Uses JWT tokens for stateless operation
|
| 173 |
+
3. **Database**: Requires MongoDB Atlas with proper network access
|
| 174 |
+
4. **API Dependency**: Relies on findEthics-Atlas API availability
|
| 175 |
+
5. **Minimal UI**: Intentionally basic design per requirements
|
| 176 |
+
|
| 177 |
+
## Common Issues & Solutions
|
| 178 |
+
1. **Session Persistence**: Resolved with JWT tokens
|
| 179 |
+
2. **CSRF Errors on HF**: Disabled CSRF for HF environment
|
| 180 |
+
3. **Database Connection**: Ensure MongoDB Atlas allows HF IP ranges
|
| 181 |
+
4. **Cookie Issues**: Configured for HF Spaces proxy environment
|
| 182 |
+
|
| 183 |
+
## Maintenance Tasks
|
| 184 |
+
- Monitor MongoDB Atlas usage and performance
|
| 185 |
+
- Check API endpoint availability
|
| 186 |
+
- Review authentication logs for security
|
| 187 |
+
- Update dependencies regularly
|
| 188 |
+
- Backup database periodically
|
| 189 |
+
|
| 190 |
+
## Contact & Support
|
| 191 |
+
- Deployment issues: Check HUGGINGFACE_DEPLOYMENT_CHECKLIST.md
|
| 192 |
+
- Production setup: See PRODUCTION_DEPLOYMENT.md
|
| 193 |
+
- Test failures: Review Test/TEST_SUMMARY.md
|
app.py
CHANGED
|
@@ -20,6 +20,8 @@ from config import get_config, validate_environment
|
|
| 20 |
from auth import init_login_manager, authenticate_user, create_user_account
|
| 21 |
from models import User, ChatSession
|
| 22 |
from functools import wraps
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# Validate configuration before starting
|
| 25 |
if not validate_environment():
|
|
@@ -175,6 +177,66 @@ def record_login_attempt(ip_address):
|
|
| 175 |
API_URL = app.config['API_URL']
|
| 176 |
TIMEOUT = app.config['API_TIMEOUT']
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
def make_api_request(message, history=None):
|
| 179 |
"""
|
| 180 |
Make POST request to the findEthics-Atlas API
|
|
@@ -235,11 +297,16 @@ def make_api_request(message, history=None):
|
|
| 235 |
@app.route('/')
|
| 236 |
def index():
|
| 237 |
"""
|
| 238 |
-
Main route -
|
| 239 |
"""
|
| 240 |
if current_user.is_authenticated:
|
| 241 |
return redirect(url_for('chat_interface'))
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
@app.route('/register', methods=['GET', 'POST'])
|
| 245 |
def register():
|
|
@@ -396,6 +463,42 @@ def logout():
|
|
| 396 |
|
| 397 |
return response
|
| 398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
@app.route('/chat')
|
| 400 |
@hf_login_required
|
| 401 |
def chat_interface():
|
|
@@ -411,14 +514,16 @@ def chat_interface():
|
|
| 411 |
return render_template('index.html')
|
| 412 |
|
| 413 |
@app.route('/api/chat', methods=['POST'])
|
| 414 |
-
@hf_login_required
|
| 415 |
def chat():
|
| 416 |
"""
|
| 417 |
-
|
| 418 |
"""
|
| 419 |
try:
|
| 420 |
-
# Check if user
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
| 422 |
return jsonify({
|
| 423 |
"success": False,
|
| 424 |
"error": "Your session has expired. Please log in again.",
|
|
@@ -440,47 +545,68 @@ def chat():
|
|
| 440 |
"error": "Your message cannot be empty. Please type something."
|
| 441 |
}), 400
|
| 442 |
|
| 443 |
-
# Check message length (
|
| 444 |
-
if
|
|
|
|
| 445 |
return jsonify({
|
| 446 |
"success": False,
|
| 447 |
-
"error": "Your message is too long. Please keep it under
|
| 448 |
}), 400
|
| 449 |
|
| 450 |
-
#
|
| 451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
|
| 453 |
# Make request to the external API
|
| 454 |
result = make_api_request(message, user_history)
|
| 455 |
|
| 456 |
-
# If successful, save the message and response
|
| 457 |
if result.get('success'):
|
| 458 |
response_text = result.get('response', '')
|
| 459 |
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
'
|
| 464 |
-
'
|
| 465 |
-
'
|
| 466 |
-
}
|
| 467 |
-
|
| 468 |
-
save_success = ChatSession.save_message(
|
| 469 |
-
user_id=current_user.id,
|
| 470 |
-
message=message,
|
| 471 |
-
response=response_text,
|
| 472 |
-
session_metadata=session_metadata
|
| 473 |
-
)
|
| 474 |
-
|
| 475 |
-
if not save_success:
|
| 476 |
-
# Log the error but don't fail the request
|
| 477 |
-
app.logger.warning(f"Failed to save chat message for user {current_user.id}")
|
| 478 |
-
# Optionally notify user that history wasn't saved
|
| 479 |
-
result['history_saved'] = False
|
| 480 |
else:
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
else:
|
| 485 |
# Enhance error messages from API
|
| 486 |
error_msg = result.get('error', 'Unknown error occurred')
|
|
@@ -494,7 +620,8 @@ def chat():
|
|
| 494 |
return jsonify(result)
|
| 495 |
|
| 496 |
except Exception as e:
|
| 497 |
-
|
|
|
|
| 498 |
return jsonify({
|
| 499 |
"success": False,
|
| 500 |
"error": "We're experiencing technical difficulties. Please try again in a few moments."
|
|
|
|
| 20 |
from auth import init_login_manager, authenticate_user, create_user_account
|
| 21 |
from models import User, ChatSession
|
| 22 |
from functools import wraps
|
| 23 |
+
import secrets
|
| 24 |
+
import string
|
| 25 |
|
| 26 |
# Validate configuration before starting
|
| 27 |
if not validate_environment():
|
|
|
|
| 177 |
API_URL = app.config['API_URL']
|
| 178 |
TIMEOUT = app.config['API_TIMEOUT']
|
| 179 |
|
| 180 |
+
# Anonymous user session management
|
| 181 |
+
def generate_anonymous_id():
|
| 182 |
+
"""Generate a unique anonymous user ID"""
|
| 183 |
+
random_string = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(8))
|
| 184 |
+
return f"anon_{random_string}"
|
| 185 |
+
|
| 186 |
+
def init_anonymous_session():
|
| 187 |
+
"""Initialize session for anonymous user"""
|
| 188 |
+
session.permanent = True
|
| 189 |
+
session['is_anonymous'] = True
|
| 190 |
+
session['anonymous_id'] = generate_anonymous_id()
|
| 191 |
+
session['anonymous_chat_history'] = []
|
| 192 |
+
session['anonymous_message_count'] = 0
|
| 193 |
+
session['anonymous_first_message_time'] = None
|
| 194 |
+
app.logger.info(f"Initialized anonymous session: {session['anonymous_id']}")
|
| 195 |
+
return session['anonymous_id']
|
| 196 |
+
|
| 197 |
+
def get_anonymous_chat_history():
|
| 198 |
+
"""Get chat history from session for anonymous user"""
|
| 199 |
+
return session.get('anonymous_chat_history', [])
|
| 200 |
+
|
| 201 |
+
def add_anonymous_message(message, response):
|
| 202 |
+
"""Add message and response to anonymous session history"""
|
| 203 |
+
if 'anonymous_chat_history' not in session:
|
| 204 |
+
session['anonymous_chat_history'] = []
|
| 205 |
+
|
| 206 |
+
session['anonymous_chat_history'].append({
|
| 207 |
+
'user': message,
|
| 208 |
+
'assistant': response
|
| 209 |
+
})
|
| 210 |
+
|
| 211 |
+
# Keep only last 20 messages for anonymous users
|
| 212 |
+
if len(session['anonymous_chat_history']) > 20:
|
| 213 |
+
session['anonymous_chat_history'] = session['anonymous_chat_history'][-20:]
|
| 214 |
+
|
| 215 |
+
def check_anonymous_rate_limit():
|
| 216 |
+
"""Check if anonymous user has exceeded rate limit"""
|
| 217 |
+
current_time = time.time()
|
| 218 |
+
|
| 219 |
+
# Initialize rate limit tracking
|
| 220 |
+
if 'anonymous_first_message_time' not in session or session.get('anonymous_first_message_time') is None:
|
| 221 |
+
session['anonymous_first_message_time'] = current_time
|
| 222 |
+
session['anonymous_message_count'] = 0
|
| 223 |
+
|
| 224 |
+
# Check if rate limit window has passed
|
| 225 |
+
time_since_first = current_time - session['anonymous_first_message_time']
|
| 226 |
+
if time_since_first > app.config['ANONYMOUS_RATE_LIMIT_WINDOW']:
|
| 227 |
+
# Reset rate limit window
|
| 228 |
+
session['anonymous_first_message_time'] = current_time
|
| 229 |
+
session['anonymous_message_count'] = 0
|
| 230 |
+
|
| 231 |
+
# Check if limit exceeded
|
| 232 |
+
if session.get('anonymous_message_count', 0) >= app.config['ANONYMOUS_RATE_LIMIT']:
|
| 233 |
+
time_remaining = app.config['ANONYMOUS_RATE_LIMIT_WINDOW'] - time_since_first
|
| 234 |
+
return False, f"Rate limit exceeded. Please wait {int(time_remaining/60)} minutes."
|
| 235 |
+
|
| 236 |
+
# Increment message count
|
| 237 |
+
session['anonymous_message_count'] = session.get('anonymous_message_count', 0) + 1
|
| 238 |
+
return True, None
|
| 239 |
+
|
| 240 |
def make_api_request(message, history=None):
|
| 241 |
"""
|
| 242 |
Make POST request to the findEthics-Atlas API
|
|
|
|
| 297 |
@app.route('/')
|
| 298 |
def index():
|
| 299 |
"""
|
| 300 |
+
Main route - show choice page for non-authenticated users
|
| 301 |
"""
|
| 302 |
if current_user.is_authenticated:
|
| 303 |
return redirect(url_for('chat_interface'))
|
| 304 |
+
|
| 305 |
+
# Check if anonymous access is enabled
|
| 306 |
+
if app.config.get('ANONYMOUS_ENABLED', True):
|
| 307 |
+
return redirect(url_for('choose'))
|
| 308 |
+
else:
|
| 309 |
+
return redirect(url_for('login'))
|
| 310 |
|
| 311 |
@app.route('/register', methods=['GET', 'POST'])
|
| 312 |
def register():
|
|
|
|
| 463 |
|
| 464 |
return response
|
| 465 |
|
| 466 |
+
@app.route('/choose')
|
| 467 |
+
def choose():
|
| 468 |
+
"""
|
| 469 |
+
Access mode selection page - choose between anonymous or authenticated access
|
| 470 |
+
"""
|
| 471 |
+
# If user is already logged in, redirect to chat
|
| 472 |
+
if current_user.is_authenticated:
|
| 473 |
+
return redirect(url_for('chat_interface'))
|
| 474 |
+
|
| 475 |
+
# Check if anonymous access is enabled
|
| 476 |
+
if not app.config.get('ANONYMOUS_ENABLED', True):
|
| 477 |
+
return redirect(url_for('login'))
|
| 478 |
+
|
| 479 |
+
return render_template('choose.html')
|
| 480 |
+
|
| 481 |
+
@app.route('/anonymous')
|
| 482 |
+
def anonymous():
|
| 483 |
+
"""
|
| 484 |
+
Anonymous chat interface - no authentication required
|
| 485 |
+
"""
|
| 486 |
+
# If user is authenticated, redirect to authenticated chat
|
| 487 |
+
if current_user.is_authenticated:
|
| 488 |
+
return redirect(url_for('chat_interface'))
|
| 489 |
+
|
| 490 |
+
# Check if anonymous access is enabled
|
| 491 |
+
if not app.config.get('ANONYMOUS_ENABLED', True):
|
| 492 |
+
flash('Anonymous access is not available. Please login or register.', 'info')
|
| 493 |
+
return redirect(url_for('login'))
|
| 494 |
+
|
| 495 |
+
# Initialize anonymous session if not already done
|
| 496 |
+
if not session.get('is_anonymous'):
|
| 497 |
+
init_anonymous_session()
|
| 498 |
+
|
| 499 |
+
# Render chat interface in anonymous mode
|
| 500 |
+
return render_template('index.html', is_anonymous=True)
|
| 501 |
+
|
| 502 |
@app.route('/chat')
|
| 503 |
@hf_login_required
|
| 504 |
def chat_interface():
|
|
|
|
| 514 |
return render_template('index.html')
|
| 515 |
|
| 516 |
@app.route('/api/chat', methods=['POST'])
|
|
|
|
| 517 |
def chat():
|
| 518 |
"""
|
| 519 |
+
API route to handle chat requests - supports both authenticated and anonymous users
|
| 520 |
"""
|
| 521 |
try:
|
| 522 |
+
# Check if user is anonymous
|
| 523 |
+
is_anonymous = session.get('is_anonymous', False)
|
| 524 |
+
|
| 525 |
+
# If not anonymous and not authenticated, require login
|
| 526 |
+
if not is_anonymous and not current_user.is_authenticated:
|
| 527 |
return jsonify({
|
| 528 |
"success": False,
|
| 529 |
"error": "Your session has expired. Please log in again.",
|
|
|
|
| 545 |
"error": "Your message cannot be empty. Please type something."
|
| 546 |
}), 400
|
| 547 |
|
| 548 |
+
# Check message length (different limits for anonymous vs authenticated)
|
| 549 |
+
max_length = app.config['ANONYMOUS_MAX_MESSAGE_LENGTH'] if is_anonymous else 5000
|
| 550 |
+
if len(message) > max_length:
|
| 551 |
return jsonify({
|
| 552 |
"success": False,
|
| 553 |
+
"error": f"Your message is too long. Please keep it under {max_length} characters."
|
| 554 |
}), 400
|
| 555 |
|
| 556 |
+
# Check rate limit for anonymous users
|
| 557 |
+
if is_anonymous:
|
| 558 |
+
allowed, error_msg = check_anonymous_rate_limit()
|
| 559 |
+
if not allowed:
|
| 560 |
+
return jsonify({
|
| 561 |
+
"success": False,
|
| 562 |
+
"error": error_msg
|
| 563 |
+
}), 429
|
| 564 |
+
|
| 565 |
+
# Get chat history for context
|
| 566 |
+
if is_anonymous:
|
| 567 |
+
user_history = get_anonymous_chat_history()
|
| 568 |
+
else:
|
| 569 |
+
user_history = ChatSession.get_session_context(current_user.id, limit=10)
|
| 570 |
|
| 571 |
# Make request to the external API
|
| 572 |
result = make_api_request(message, user_history)
|
| 573 |
|
| 574 |
+
# If successful, save the message and response
|
| 575 |
if result.get('success'):
|
| 576 |
response_text = result.get('response', '')
|
| 577 |
|
| 578 |
+
if is_anonymous:
|
| 579 |
+
# Save to session for anonymous users
|
| 580 |
+
add_anonymous_message(message, response_text)
|
| 581 |
+
result['history_saved'] = False # Indicate temporary storage
|
| 582 |
+
result['user_id'] = session.get('anonymous_id', 'anonymous')
|
| 583 |
+
result['is_anonymous'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
else:
|
| 585 |
+
# Save to database for authenticated users
|
| 586 |
+
session_metadata = {
|
| 587 |
+
'timestamp': datetime.utcnow().isoformat(),
|
| 588 |
+
'api_url': API_URL,
|
| 589 |
+
'user_agent': request.headers.get('User-Agent', ''),
|
| 590 |
+
'ip_address': request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
save_success = ChatSession.save_message(
|
| 594 |
+
user_id=current_user.id,
|
| 595 |
+
message=message,
|
| 596 |
+
response=response_text,
|
| 597 |
+
session_metadata=session_metadata
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
if not save_success:
|
| 601 |
+
# Log the error but don't fail the request
|
| 602 |
+
app.logger.warning(f"Failed to save chat message for user {current_user.id}")
|
| 603 |
+
# Optionally notify user that history wasn't saved
|
| 604 |
+
result['history_saved'] = False
|
| 605 |
+
else:
|
| 606 |
+
result['history_saved'] = True
|
| 607 |
+
|
| 608 |
+
result['user_id'] = current_user.id
|
| 609 |
+
result['is_anonymous'] = False
|
| 610 |
else:
|
| 611 |
# Enhance error messages from API
|
| 612 |
error_msg = result.get('error', 'Unknown error occurred')
|
|
|
|
| 620 |
return jsonify(result)
|
| 621 |
|
| 622 |
except Exception as e:
|
| 623 |
+
user_identifier = session.get('anonymous_id', 'unknown') if is_anonymous else (current_user.id if current_user.is_authenticated else 'unknown')
|
| 624 |
+
app.logger.error(f"Chat API error for user {user_identifier}: {str(e)}")
|
| 625 |
return jsonify({
|
| 626 |
"success": False,
|
| 627 |
"error": "We're experiencing technical difficulties. Please try again in a few moments."
|
config.py
CHANGED
|
@@ -39,6 +39,13 @@ class Config:
|
|
| 39 |
MAX_LOGIN_ATTEMPTS = int(os.environ.get('MAX_LOGIN_ATTEMPTS', 5))
|
| 40 |
RATE_LIMIT_WINDOW = int(os.environ.get('RATE_LIMIT_WINDOW', 900)) # 15 minutes
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
# API Configuration
|
| 43 |
API_URL = os.environ.get('API_URL', 'https://findEthics-Atlas.hf.space/chat')
|
| 44 |
API_TIMEOUT = int(os.environ.get('API_TIMEOUT', 30))
|
|
@@ -72,6 +79,7 @@ class DevelopmentConfig(Config):
|
|
| 72 |
DEBUG = True
|
| 73 |
SESSION_COOKIE_SECURE = False
|
| 74 |
WTF_CSRF_SSL_STRICT = False
|
|
|
|
| 75 |
LOG_LEVEL = 'DEBUG'
|
| 76 |
|
| 77 |
class ProductionConfig(Config):
|
|
|
|
| 39 |
MAX_LOGIN_ATTEMPTS = int(os.environ.get('MAX_LOGIN_ATTEMPTS', 5))
|
| 40 |
RATE_LIMIT_WINDOW = int(os.environ.get('RATE_LIMIT_WINDOW', 900)) # 15 minutes
|
| 41 |
|
| 42 |
+
# Anonymous User Configuration
|
| 43 |
+
ANONYMOUS_ENABLED = os.environ.get('ANONYMOUS_ENABLED', 'True').lower() == 'true'
|
| 44 |
+
ANONYMOUS_RATE_LIMIT = int(os.environ.get('ANONYMOUS_RATE_LIMIT', 20)) # messages per hour
|
| 45 |
+
ANONYMOUS_RATE_LIMIT_WINDOW = int(os.environ.get('ANONYMOUS_RATE_LIMIT_WINDOW', 3600)) # 1 hour
|
| 46 |
+
ANONYMOUS_SESSION_TIMEOUT = int(os.environ.get('ANONYMOUS_SESSION_TIMEOUT', 3600)) # 1 hour
|
| 47 |
+
ANONYMOUS_MAX_MESSAGE_LENGTH = int(os.environ.get('ANONYMOUS_MAX_MESSAGE_LENGTH', 2000)) # characters
|
| 48 |
+
|
| 49 |
# API Configuration
|
| 50 |
API_URL = os.environ.get('API_URL', 'https://findEthics-Atlas.hf.space/chat')
|
| 51 |
API_TIMEOUT = int(os.environ.get('API_TIMEOUT', 30))
|
|
|
|
| 79 |
DEBUG = True
|
| 80 |
SESSION_COOKIE_SECURE = False
|
| 81 |
WTF_CSRF_SSL_STRICT = False
|
| 82 |
+
WTF_CSRF_ENABLED = False # Disable CSRF for testing
|
| 83 |
LOG_LEVEL = 'DEBUG'
|
| 84 |
|
| 85 |
class ProductionConfig(Config):
|
static/css/style.css
CHANGED
|
@@ -542,4 +542,227 @@ uch and interaction improvements */
|
|
| 542 |
border: 1px solid #ccc;
|
| 543 |
page-break-inside: avoid;
|
| 544 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
}
|
|
|
|
| 542 |
border: 1px solid #ccc;
|
| 543 |
page-break-inside: avoid;
|
| 544 |
}
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/* Choice Page Styles */
|
| 548 |
+
.choose-container {
|
| 549 |
+
width: 100%;
|
| 550 |
+
min-height: calc(100vh - 60px);
|
| 551 |
+
display: flex;
|
| 552 |
+
align-items: center;
|
| 553 |
+
justify-content: center;
|
| 554 |
+
padding: 2rem;
|
| 555 |
+
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.choose-wrapper {
|
| 559 |
+
max-width: 900px;
|
| 560 |
+
width: 100%;
|
| 561 |
+
text-align: center;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
.choose-title {
|
| 565 |
+
font-size: 2.5rem;
|
| 566 |
+
margin-bottom: 0.5rem;
|
| 567 |
+
color: black;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.choose-subtitle {
|
| 571 |
+
font-size: 1.2rem;
|
| 572 |
+
color: #666;
|
| 573 |
+
margin-bottom: 3rem;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.choose-options {
|
| 577 |
+
display: grid;
|
| 578 |
+
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
| 579 |
+
gap: 2rem;
|
| 580 |
+
margin-bottom: 2rem;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.choose-option {
|
| 584 |
+
background: white;
|
| 585 |
+
border: 2px solid #e5e5e5;
|
| 586 |
+
border-radius: 12px;
|
| 587 |
+
padding: 2rem;
|
| 588 |
+
transition: all 0.3s ease;
|
| 589 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
.choose-option:hover {
|
| 593 |
+
border-color: #000;
|
| 594 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
.choose-option h2 {
|
| 598 |
+
margin-bottom: 1.5rem;
|
| 599 |
+
font-size: 1.5rem;
|
| 600 |
+
color: black;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
.option-features ul {
|
| 604 |
+
list-style: none;
|
| 605 |
+
padding: 0;
|
| 606 |
+
margin-bottom: 1.5rem;
|
| 607 |
+
text-align: left;
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
.option-features li {
|
| 611 |
+
padding: 0.5rem 0;
|
| 612 |
+
padding-left: 1.5rem;
|
| 613 |
+
position: relative;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
.option-features li:before {
|
| 617 |
+
content: "✓";
|
| 618 |
+
position: absolute;
|
| 619 |
+
left: 0;
|
| 620 |
+
color: #10b981;
|
| 621 |
+
font-weight: bold;
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
.privacy-notice {
|
| 625 |
+
background: #fef3c7;
|
| 626 |
+
border: 1px solid #fbbf24;
|
| 627 |
+
border-radius: 8px;
|
| 628 |
+
padding: 1rem;
|
| 629 |
+
margin-bottom: 1.5rem;
|
| 630 |
+
font-size: 0.9rem;
|
| 631 |
+
text-align: left;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
.privacy-notice p {
|
| 635 |
+
margin: 0;
|
| 636 |
+
color: #92400e;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
.choose-button {
|
| 640 |
+
display: inline-block;
|
| 641 |
+
padding: 0.75rem 2rem;
|
| 642 |
+
background: black;
|
| 643 |
+
color: white;
|
| 644 |
+
text-decoration: none;
|
| 645 |
+
border-radius: 8px;
|
| 646 |
+
font-weight: 500;
|
| 647 |
+
transition: all 0.3s ease;
|
| 648 |
+
border: 2px solid black;
|
| 649 |
+
margin: 0.5rem;
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
.choose-button:hover {
|
| 653 |
+
background: white;
|
| 654 |
+
color: black;
|
| 655 |
+
transform: translateY(-2px);
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
.anonymous-button {
|
| 659 |
+
background: #6b7280;
|
| 660 |
+
border-color: #6b7280;
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
.anonymous-button:hover {
|
| 664 |
+
background: white;
|
| 665 |
+
color: #6b7280;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
.register-button {
|
| 669 |
+
background: #10b981;
|
| 670 |
+
border-color: #10b981;
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
.register-button:hover {
|
| 674 |
+
background: white;
|
| 675 |
+
color: #10b981;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
.auth-buttons {
|
| 679 |
+
display: flex;
|
| 680 |
+
justify-content: center;
|
| 681 |
+
gap: 1rem;
|
| 682 |
+
flex-wrap: wrap;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
.choose-footer {
|
| 686 |
+
margin-top: 3rem;
|
| 687 |
+
color: #666;
|
| 688 |
+
font-size: 0.9rem;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
/* Anonymous User Styles */
|
| 692 |
+
.anonymous-user {
|
| 693 |
+
color: #6b7280;
|
| 694 |
+
font-style: italic;
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
.session-warning {
|
| 698 |
+
background: #fef3c7;
|
| 699 |
+
border-bottom: 2px solid #fbbf24;
|
| 700 |
+
padding: 0.75rem;
|
| 701 |
+
text-align: center;
|
| 702 |
+
color: #92400e;
|
| 703 |
+
}
|
| 704 |
+
|
| 705 |
+
.warning-content {
|
| 706 |
+
max-width: 800px;
|
| 707 |
+
margin: 0 auto;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
.warning-content a {
|
| 711 |
+
color: #92400e;
|
| 712 |
+
font-weight: bold;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
.session-expiry-warning {
|
| 716 |
+
position: fixed;
|
| 717 |
+
top: 0;
|
| 718 |
+
left: 0;
|
| 719 |
+
right: 0;
|
| 720 |
+
background: #ef4444;
|
| 721 |
+
color: white;
|
| 722 |
+
padding: 1rem;
|
| 723 |
+
text-align: center;
|
| 724 |
+
z-index: 10000;
|
| 725 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
.session-expiry-warning button {
|
| 729 |
+
background: white;
|
| 730 |
+
color: #ef4444;
|
| 731 |
+
border: none;
|
| 732 |
+
padding: 0.5rem 1rem;
|
| 733 |
+
margin: 0 0.5rem;
|
| 734 |
+
border-radius: 4px;
|
| 735 |
+
cursor: pointer;
|
| 736 |
+
font-weight: bold;
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
.session-expiry-warning button:hover {
|
| 740 |
+
background: #fee2e2;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
/* System message styles */
|
| 744 |
+
.message.system .message-content {
|
| 745 |
+
background: #f3f4f6;
|
| 746 |
+
color: #4b5563;
|
| 747 |
+
border: 1px solid #d1d5db;
|
| 748 |
+
font-style: italic;
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
/* Mobile responsive for choice page */
|
| 752 |
+
@media (max-width: 768px) {
|
| 753 |
+
.choose-options {
|
| 754 |
+
grid-template-columns: 1fr;
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
.choose-title {
|
| 758 |
+
font-size: 2rem;
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
.auth-buttons {
|
| 762 |
+
flex-direction: column;
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
.choose-button {
|
| 766 |
+
width: 100%;
|
| 767 |
+
}
|
| 768 |
}
|
static/js/chat.js
CHANGED
|
@@ -23,6 +23,11 @@ class ChatApp {
|
|
| 23 |
this.historyLoaded = false;
|
| 24 |
this.sessionWarningShown = false;
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
this.init();
|
| 27 |
}
|
| 28 |
|
|
@@ -151,7 +156,11 @@ class ChatApp {
|
|
| 151 |
this.handleSessionExpiry();
|
| 152 |
this.showError('Your session has expired. Please log in again to continue.');
|
| 153 |
} else if (error.message.includes('429')) {
|
| 154 |
-
this.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
} else if (error.message.includes('400')) {
|
| 156 |
this.showError('There was a problem with your message. Please check it and try again.');
|
| 157 |
} else if (error.message.includes('500')) {
|
|
@@ -251,6 +260,14 @@ class ChatApp {
|
|
| 251 |
}
|
| 252 |
|
| 253 |
async loadChatHistory() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
try {
|
| 255 |
// Show loading state
|
| 256 |
this.showHistoryLoading(true);
|
|
@@ -407,12 +424,25 @@ class ChatApp {
|
|
| 407 |
// Create session expiry warning banner
|
| 408 |
const warningDiv = document.createElement('div');
|
| 409 |
warningDiv.className = 'session-expiry-warning';
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
|
| 417 |
// Insert at top of page
|
| 418 |
document.body.insertBefore(warningDiv, document.body.firstChild);
|
|
@@ -420,7 +450,7 @@ class ChatApp {
|
|
| 420 |
// Disable chat interface
|
| 421 |
this.messageInput.disabled = true;
|
| 422 |
this.sendButton.disabled = true;
|
| 423 |
-
this.messageInput.placeholder = 'Session expired - please log in again';
|
| 424 |
}
|
| 425 |
|
| 426 |
showSessionWarning(message) {
|
|
@@ -450,6 +480,12 @@ class ChatApp {
|
|
| 450 |
}
|
| 451 |
}, 5000);
|
| 452 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
}
|
| 454 |
|
| 455 |
// Initialize the chat application when the page loads
|
|
|
|
| 23 |
this.historyLoaded = false;
|
| 24 |
this.sessionWarningShown = false;
|
| 25 |
|
| 26 |
+
// Check if anonymous mode
|
| 27 |
+
this.isAnonymous = window.chatConfig && window.chatConfig.isAnonymous;
|
| 28 |
+
this.anonymousRateLimit = window.chatConfig ? window.chatConfig.anonymousRateLimit : 0;
|
| 29 |
+
this.anonymousId = window.chatConfig ? window.chatConfig.anonymousId : '';
|
| 30 |
+
|
| 31 |
this.init();
|
| 32 |
}
|
| 33 |
|
|
|
|
| 156 |
this.handleSessionExpiry();
|
| 157 |
this.showError('Your session has expired. Please log in again to continue.');
|
| 158 |
} else if (error.message.includes('429')) {
|
| 159 |
+
if (this.isAnonymous) {
|
| 160 |
+
this.showError(`Rate limit exceeded. Anonymous users are limited to ${this.anonymousRateLimit} messages per hour.`);
|
| 161 |
+
} else {
|
| 162 |
+
this.showError('You are sending messages too quickly. Please wait a moment and try again.');
|
| 163 |
+
}
|
| 164 |
} else if (error.message.includes('400')) {
|
| 165 |
this.showError('There was a problem with your message. Please check it and try again.');
|
| 166 |
} else if (error.message.includes('500')) {
|
|
|
|
| 260 |
}
|
| 261 |
|
| 262 |
async loadChatHistory() {
|
| 263 |
+
// Skip loading history for anonymous users (they start fresh)
|
| 264 |
+
if (this.isAnonymous) {
|
| 265 |
+
this.historyLoaded = true;
|
| 266 |
+
// Show welcome message for anonymous users
|
| 267 |
+
this.showAnonymousWelcome();
|
| 268 |
+
return;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
try {
|
| 272 |
// Show loading state
|
| 273 |
this.showHistoryLoading(true);
|
|
|
|
| 424 |
// Create session expiry warning banner
|
| 425 |
const warningDiv = document.createElement('div');
|
| 426 |
warningDiv.className = 'session-expiry-warning';
|
| 427 |
+
|
| 428 |
+
if (this.isAnonymous) {
|
| 429 |
+
warningDiv.innerHTML = `
|
| 430 |
+
Your anonymous session has expired.
|
| 431 |
+
<button onclick="window.location.href='/anonymous'">
|
| 432 |
+
Start New Session
|
| 433 |
+
</button>
|
| 434 |
+
<button onclick="window.location.href='/register'">
|
| 435 |
+
Create Account
|
| 436 |
+
</button>
|
| 437 |
+
`;
|
| 438 |
+
} else {
|
| 439 |
+
warningDiv.innerHTML = `
|
| 440 |
+
Your session has expired. Please log in again to continue chatting.
|
| 441 |
+
<button onclick="window.location.href='/login?next=' + encodeURIComponent(window.location.pathname)">
|
| 442 |
+
Login Again
|
| 443 |
+
</button>
|
| 444 |
+
`;
|
| 445 |
+
}
|
| 446 |
|
| 447 |
// Insert at top of page
|
| 448 |
document.body.insertBefore(warningDiv, document.body.firstChild);
|
|
|
|
| 450 |
// Disable chat interface
|
| 451 |
this.messageInput.disabled = true;
|
| 452 |
this.sendButton.disabled = true;
|
| 453 |
+
this.messageInput.placeholder = this.isAnonymous ? 'Session expired - start a new anonymous session' : 'Session expired - please log in again';
|
| 454 |
}
|
| 455 |
|
| 456 |
showSessionWarning(message) {
|
|
|
|
| 480 |
}
|
| 481 |
}, 5000);
|
| 482 |
}
|
| 483 |
+
|
| 484 |
+
showAnonymousWelcome() {
|
| 485 |
+
// Show welcome message for anonymous users
|
| 486 |
+
const welcomeMessage = `Welcome to Anonymous Chat! You can send up to ${this.anonymousRateLimit} messages per hour. Your chat history is temporary and will be lost when you close your browser.`;
|
| 487 |
+
this.addMessage(welcomeMessage, 'system', false);
|
| 488 |
+
}
|
| 489 |
}
|
| 490 |
|
| 491 |
// Initialize the chat application when the page loads
|
task_list.md
CHANGED
|
@@ -122,4 +122,143 @@ Transform the current Gradio chatbot template into a web app that uses the API "
|
|
| 122 |
- [x] UI is minimal and basic as requested
|
| 123 |
- [x] Chat functionality works with conversation history
|
| 124 |
- [x] App can be deployed as a Hugging Face space
|
| 125 |
-
- [x] Error handling is implemented for API failures
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
- [x] UI is minimal and basic as requested
|
| 123 |
- [x] Chat functionality works with conversation history
|
| 124 |
- [x] App can be deployed as a Hugging Face space
|
| 125 |
+
- [x] Error handling is implemented for API failures
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
# Anonymous Chat Access Implementation Tasks
|
| 130 |
+
|
| 131 |
+
## Overview
|
| 132 |
+
Add anonymous chat functionality alongside existing authenticated access, allowing users to chat without creating an account while maintaining the option for registered users.
|
| 133 |
+
|
| 134 |
+
## Task List
|
| 135 |
+
|
| 136 |
+
### Phase 1: Backend Infrastructure
|
| 137 |
+
|
| 138 |
+
- [x] **Task 1: Update config.py with anonymous user settings**
|
| 139 |
+
- Add ANONYMOUS_ENABLED flag
|
| 140 |
+
- Add ANONYMOUS_RATE_LIMIT (messages per hour)
|
| 141 |
+
- Add ANONYMOUS_SESSION_TIMEOUT
|
| 142 |
+
- Add ANONYMOUS_MAX_MESSAGE_LENGTH
|
| 143 |
+
- **Status: COMPLETED**
|
| 144 |
+
- **Assignee: Backend Coder Agent**
|
| 145 |
+
|
| 146 |
+
- [x] **Task 2: Create anonymous session management functions in app.py**
|
| 147 |
+
- Generate anonymous user IDs (e.g., anon_<hash>)
|
| 148 |
+
- Implement session-based chat history storage
|
| 149 |
+
- Add session validation functions
|
| 150 |
+
- **Status: COMPLETED**
|
| 151 |
+
- **Assignee: Backend Coder Agent**
|
| 152 |
+
|
| 153 |
+
- [x] **Task 3: Update app.py index route to show choice page**
|
| 154 |
+
- Modify `/` route to show choice instead of redirect
|
| 155 |
+
- Handle authenticated user redirect
|
| 156 |
+
- **Status: COMPLETED**
|
| 157 |
+
- **Assignee: Backend Coder Agent**
|
| 158 |
+
|
| 159 |
+
- [x] **Task 4: Add /choose route for access mode selection**
|
| 160 |
+
- Create route handler
|
| 161 |
+
- Implement GET method for displaying choice page
|
| 162 |
+
- **Status: COMPLETED**
|
| 163 |
+
- **Assignee: Backend Coder Agent**
|
| 164 |
+
|
| 165 |
+
- [x] **Task 5: Add /anonymous route for anonymous chat interface**
|
| 166 |
+
- Create route for anonymous chat
|
| 167 |
+
- Set up anonymous session
|
| 168 |
+
- Render chat template with anonymous mode
|
| 169 |
+
- **Status: COMPLETED**
|
| 170 |
+
- **Assignee: Backend Coder Agent**
|
| 171 |
+
|
| 172 |
+
- [x] **Task 6: Update /api/chat endpoint to handle anonymous users**
|
| 173 |
+
- Detect anonymous vs authenticated users
|
| 174 |
+
- Handle session-based chat history for anonymous
|
| 175 |
+
- Skip MongoDB saves for anonymous users
|
| 176 |
+
- Implement different rate limiting
|
| 177 |
+
- **Status: COMPLETED**
|
| 178 |
+
- **Assignee: Backend Coder Agent**
|
| 179 |
+
|
| 180 |
+
- [x] **Task 7: Implement rate limiting for anonymous users**
|
| 181 |
+
- Track message count per session
|
| 182 |
+
- Use IP + session combo for tracking
|
| 183 |
+
- Return 429 errors when exceeded
|
| 184 |
+
- **Status: COMPLETED**
|
| 185 |
+
- **Assignee: Backend Coder Agent**
|
| 186 |
+
|
| 187 |
+
### Phase 2: Frontend Updates
|
| 188 |
+
|
| 189 |
+
- [x] **Task 8: Create choose.html template**
|
| 190 |
+
- Design landing page with two options
|
| 191 |
+
- "Chat Anonymously" button
|
| 192 |
+
- "Login/Register" section
|
| 193 |
+
- Include privacy notice
|
| 194 |
+
- **Status: COMPLETED**
|
| 195 |
+
- **Assignee: Frontend Coder Agent**
|
| 196 |
+
|
| 197 |
+
- [x] **Task 9: Update base.html to handle anonymous users**
|
| 198 |
+
- Conditional navigation for anonymous users
|
| 199 |
+
- Show "Anonymous User" instead of email
|
| 200 |
+
- Different logout/exit behavior
|
| 201 |
+
- **Status: COMPLETED**
|
| 202 |
+
- **Assignee: Frontend Coder Agent**
|
| 203 |
+
|
| 204 |
+
- [x] **Task 10: Update index.html for anonymous mode**
|
| 205 |
+
- Add session warning banner
|
| 206 |
+
- Include "Create Account" prompt
|
| 207 |
+
- Display anonymous user indicator
|
| 208 |
+
- **Status: COMPLETED**
|
| 209 |
+
- **Assignee: Frontend Coder Agent**
|
| 210 |
+
|
| 211 |
+
- [x] **Task 11: Update chat.js for anonymous mode support**
|
| 212 |
+
- Detect anonymous mode from page data
|
| 213 |
+
- Handle session expiry warnings
|
| 214 |
+
- Modify API calls for anonymous users
|
| 215 |
+
- Add upgrade prompts
|
| 216 |
+
- **Status: COMPLETED**
|
| 217 |
+
- **Assignee: Frontend Coder Agent**
|
| 218 |
+
|
| 219 |
+
- [x] **Task 12: Add CSS styles for choice page**
|
| 220 |
+
- Style choice buttons
|
| 221 |
+
- Add anonymous mode indicators
|
| 222 |
+
- Style session warnings
|
| 223 |
+
- **Status: COMPLETED**
|
| 224 |
+
- **Assignee: Frontend Coder Agent**
|
| 225 |
+
|
| 226 |
+
### Phase 3: Testing & Documentation
|
| 227 |
+
|
| 228 |
+
- [ ] **Task 13: Test anonymous chat functionality**
|
| 229 |
+
- Test anonymous chat flow
|
| 230 |
+
- Verify rate limiting
|
| 231 |
+
- Test session expiry
|
| 232 |
+
- Test switching between modes
|
| 233 |
+
- **Status: PENDING**
|
| 234 |
+
- **Assignee: Testing Agent**
|
| 235 |
+
|
| 236 |
+
- [ ] **Task 14: Update CLAUDE.md with anonymous feature**
|
| 237 |
+
- Document new architecture
|
| 238 |
+
- Add anonymous mode details
|
| 239 |
+
- Update configuration section
|
| 240 |
+
- **Status: PENDING**
|
| 241 |
+
- **Assignee: Documentation Agent**
|
| 242 |
+
|
| 243 |
+
- [ ] **Task 15: Update README.md**
|
| 244 |
+
- Add anonymous mode to features
|
| 245 |
+
- Document usage instructions
|
| 246 |
+
- Add configuration details
|
| 247 |
+
- **Status: PENDING**
|
| 248 |
+
- **Assignee: Documentation Agent**
|
| 249 |
+
|
| 250 |
+
## Implementation Order
|
| 251 |
+
1. Backend configuration (Tasks 1-2)
|
| 252 |
+
2. Core routes (Tasks 3-5)
|
| 253 |
+
3. API updates (Tasks 6-7)
|
| 254 |
+
4. Frontend templates (Tasks 8-10)
|
| 255 |
+
5. JavaScript updates (Task 11)
|
| 256 |
+
6. Styling (Task 12)
|
| 257 |
+
7. Testing (Task 13)
|
| 258 |
+
8. Documentation (Tasks 14-15)
|
| 259 |
+
|
| 260 |
+
## Notes
|
| 261 |
+
- Maintain backward compatibility with existing authenticated flow
|
| 262 |
+
- Anonymous sessions are temporary and not persisted to database
|
| 263 |
+
- Stricter rate limits for anonymous users to prevent abuse
|
| 264 |
+
- Clear privacy notices about session-only storage
|
templates/base.html
CHANGED
|
@@ -27,6 +27,10 @@
|
|
| 27 |
{% endif %}
|
| 28 |
<button type="submit" class="nav-button logout-button">Logout</button>
|
| 29 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
{% else %}
|
| 31 |
<a href="{{ url_for('login') }}" class="nav-button">Login</a>
|
| 32 |
<a href="{{ url_for('register') }}" class="nav-button">Register</a>
|
|
|
|
| 27 |
{% endif %}
|
| 28 |
<button type="submit" class="nav-button logout-button">Logout</button>
|
| 29 |
</form>
|
| 30 |
+
{% elif is_anonymous %}
|
| 31 |
+
<span class="user-info anonymous-user">Anonymous User</span>
|
| 32 |
+
<a href="{{ url_for('login') }}" class="nav-button">Login</a>
|
| 33 |
+
<a href="{{ url_for('register') }}" class="nav-button">Create Account</a>
|
| 34 |
{% else %}
|
| 35 |
<a href="{{ url_for('login') }}" class="nav-button">Login</a>
|
| 36 |
<a href="{{ url_for('register') }}" class="nav-button">Register</a>
|
templates/choose.html
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Choose Access Mode - Chatty{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="choose-container">
|
| 7 |
+
<div class="choose-wrapper">
|
| 8 |
+
<h1 class="choose-title">Welcome to Chatty</h1>
|
| 9 |
+
<p class="choose-subtitle">Ethics Chat Application</p>
|
| 10 |
+
|
| 11 |
+
<div class="choose-options">
|
| 12 |
+
<!-- Anonymous Access Option -->
|
| 13 |
+
<div class="choose-option anonymous-option">
|
| 14 |
+
<h2>Chat Anonymously</h2>
|
| 15 |
+
<div class="option-features">
|
| 16 |
+
<ul>
|
| 17 |
+
<li>No registration required</li>
|
| 18 |
+
<li>Start chatting immediately</li>
|
| 19 |
+
<li>Session-based conversation</li>
|
| 20 |
+
<li>Limited to {{ config.ANONYMOUS_RATE_LIMIT }} messages per hour</li>
|
| 21 |
+
</ul>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="privacy-notice">
|
| 24 |
+
<p><strong>Privacy Notice:</strong> Your chat history will be stored temporarily in your browser session and will be lost when you close the browser or after {{ config.ANONYMOUS_SESSION_TIMEOUT // 3600 }} hour(s) of inactivity.</p>
|
| 25 |
+
</div>
|
| 26 |
+
<a href="{{ url_for('anonymous') }}" class="choose-button anonymous-button">
|
| 27 |
+
Start Anonymous Chat
|
| 28 |
+
</a>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<!-- Authenticated Access Option -->
|
| 32 |
+
<div class="choose-option auth-option">
|
| 33 |
+
<h2>Login or Register</h2>
|
| 34 |
+
<div class="option-features">
|
| 35 |
+
<ul>
|
| 36 |
+
<li>Save your chat history permanently</li>
|
| 37 |
+
<li>Access conversations from any device</li>
|
| 38 |
+
<li>No message limits</li>
|
| 39 |
+
<li>Personalized experience</li>
|
| 40 |
+
</ul>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="auth-buttons">
|
| 43 |
+
<a href="{{ url_for('login') }}" class="choose-button login-button">
|
| 44 |
+
Login
|
| 45 |
+
</a>
|
| 46 |
+
<a href="{{ url_for('register') }}" class="choose-button register-button">
|
| 47 |
+
Create Account
|
| 48 |
+
</a>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<div class="choose-footer">
|
| 54 |
+
<p>By using this service, you agree to our ethical guidelines and responsible AI usage.</p>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
{% endblock %}
|
| 59 |
+
|
| 60 |
+
{% block scripts %}
|
| 61 |
+
<script>
|
| 62 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 63 |
+
// Add hover effects
|
| 64 |
+
const options = document.querySelectorAll('.choose-option');
|
| 65 |
+
options.forEach(option => {
|
| 66 |
+
option.addEventListener('mouseenter', function() {
|
| 67 |
+
this.style.transform = 'translateY(-5px)';
|
| 68 |
+
});
|
| 69 |
+
option.addEventListener('mouseleave', function() {
|
| 70 |
+
this.style.transform = 'translateY(0)';
|
| 71 |
+
});
|
| 72 |
+
});
|
| 73 |
+
});
|
| 74 |
+
</script>
|
| 75 |
+
{% endblock %}
|
templates/index.html
CHANGED
|
@@ -4,6 +4,15 @@
|
|
| 4 |
|
| 5 |
{% block content %}
|
| 6 |
<div class="chat-container">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
<div class="chat-messages" id="chatMessages">
|
| 8 |
<!-- Messages will be dynamically added here -->
|
| 9 |
</div>
|
|
@@ -33,5 +42,13 @@
|
|
| 33 |
{% endblock %}
|
| 34 |
|
| 35 |
{% block scripts %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
<script src="{{ url_for('static', filename='js/chat.js') }}"></script>
|
| 37 |
{% endblock %}
|
|
|
|
| 4 |
|
| 5 |
{% block content %}
|
| 6 |
<div class="chat-container">
|
| 7 |
+
{% if is_anonymous %}
|
| 8 |
+
<div class="session-warning">
|
| 9 |
+
<div class="warning-content">
|
| 10 |
+
<strong>Anonymous Session:</strong> Your chat history is temporary and will be lost when you close your browser.
|
| 11 |
+
<a href="{{ url_for('register') }}">Create an account</a> to save your conversations permanently.
|
| 12 |
+
</div>
|
| 13 |
+
</div>
|
| 14 |
+
{% endif %}
|
| 15 |
+
|
| 16 |
<div class="chat-messages" id="chatMessages">
|
| 17 |
<!-- Messages will be dynamically added here -->
|
| 18 |
</div>
|
|
|
|
| 42 |
{% endblock %}
|
| 43 |
|
| 44 |
{% block scripts %}
|
| 45 |
+
<script>
|
| 46 |
+
// Pass server-side variables to JavaScript
|
| 47 |
+
window.chatConfig = {
|
| 48 |
+
isAnonymous: {{ 'true' if is_anonymous else 'false' }},
|
| 49 |
+
anonymousRateLimit: {{ config.ANONYMOUS_RATE_LIMIT if is_anonymous else 0 }},
|
| 50 |
+
anonymousId: "{{ session.get('anonymous_id', '') if is_anonymous else '' }}"
|
| 51 |
+
};
|
| 52 |
+
</script>
|
| 53 |
<script src="{{ url_for('static', filename='js/chat.js') }}"></script>
|
| 54 |
{% endblock %}
|