multi-personality-bot
Browse files- .dockerignore +28 -0
- Dockerfile +26 -0
- app.py +257 -0
- modules/__init__.py +1 -0
- modules/database.py +259 -0
- modules/personality_engine.py +494 -0
- modules/simple_personality_engine.py +558 -0
- requirements.txt +11 -0
- static/css/terminal.css +426 -0
- static/img/maximally.png +0 -0
- static/js/advice-chat.js +318 -0
- static/js/chat.js +397 -0
- static/js/terminal-effects.js +306 -0
- templates/base.html +99 -0
- templates/chat_personality.html +1627 -0
- templates/error.html +100 -0
- templates/index.html +443 -0
.dockerignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
.Python
|
| 6 |
+
env/
|
| 7 |
+
venv/
|
| 8 |
+
.venv/
|
| 9 |
+
build/
|
| 10 |
+
develop-eggs/
|
| 11 |
+
dist/
|
| 12 |
+
downloads/
|
| 13 |
+
eggs/
|
| 14 |
+
.eggs/
|
| 15 |
+
lib/
|
| 16 |
+
lib64/
|
| 17 |
+
parts/
|
| 18 |
+
sdist/
|
| 19 |
+
var/
|
| 20 |
+
*.egg-info/
|
| 21 |
+
.installed.cfg
|
| 22 |
+
*.egg
|
| 23 |
+
.DS_Store
|
| 24 |
+
*.log
|
| 25 |
+
chat_data.db
|
| 26 |
+
.env
|
| 27 |
+
|
| 28 |
+
|
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
+
PYTHONUNBUFFERED=1 \
|
| 5 |
+
PIP_NO_CACHE_DIR=1
|
| 6 |
+
|
| 7 |
+
WORKDIR /app
|
| 8 |
+
|
| 9 |
+
# System deps (optional minimal set)
|
| 10 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 11 |
+
build-essential \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
COPY requirements.txt ./
|
| 15 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 16 |
+
|
| 17 |
+
COPY . .
|
| 18 |
+
|
| 19 |
+
# Hugging Face Spaces passes PORT env
|
| 20 |
+
ENV PORT=7860
|
| 21 |
+
EXPOSE 7860
|
| 22 |
+
|
| 23 |
+
# Default command
|
| 24 |
+
CMD ["python", "app.py"]
|
| 25 |
+
|
| 26 |
+
|
app.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Multi-Personality Chat Bot Flask Application
|
| 4 |
+
A hackathon project featuring 10 distinct AI personality types with Google AI integration.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import logging
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from flask import Flask, render_template, request, jsonify, session
|
| 11 |
+
from flask_socketio import SocketIO, emit, join_room, leave_room
|
| 12 |
+
import google.generativeai as genai
|
| 13 |
+
import sqlite3
|
| 14 |
+
import json
|
| 15 |
+
import secrets
|
| 16 |
+
from modules.simple_personality_engine import PersonalityEngine
|
| 17 |
+
from modules.database import ChatDatabase
|
| 18 |
+
|
| 19 |
+
# Configure logging
|
| 20 |
+
logging.basicConfig(level=logging.INFO)
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
# Initialize Flask app
|
| 24 |
+
app = Flask(__name__)
|
| 25 |
+
# Use env var if provided; otherwise generate a secure random key at startup
|
| 26 |
+
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', secrets.token_hex(32))
|
| 27 |
+
|
| 28 |
+
# Initialize SocketIO
|
| 29 |
+
socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True)
|
| 30 |
+
|
| 31 |
+
# Configure Google AI strictly via environment variable (no bundled default key)
|
| 32 |
+
GOOGLE_AI_API_KEY = os.getenv("GOOGLE_AI_API_KEY")
|
| 33 |
+
if GOOGLE_AI_API_KEY:
|
| 34 |
+
genai.configure(api_key=GOOGLE_AI_API_KEY)
|
| 35 |
+
logger.info("Google AI configured using environment variable")
|
| 36 |
+
else:
|
| 37 |
+
logger.warning("GOOGLE_AI_API_KEY not set. AI features will be unavailable.")
|
| 38 |
+
|
| 39 |
+
# Initialize components
|
| 40 |
+
personality_engine = PersonalityEngine()
|
| 41 |
+
chat_db = ChatDatabase()
|
| 42 |
+
|
| 43 |
+
@app.route('/')
|
| 44 |
+
def index():
|
| 45 |
+
"""Main personality selection page"""
|
| 46 |
+
personalities = personality_engine.get_all_personalities()
|
| 47 |
+
return render_template('index.html', personalities=personalities)
|
| 48 |
+
|
| 49 |
+
@app.route('/chat/<personality_type>')
|
| 50 |
+
def chat_personality(personality_type):
|
| 51 |
+
"""Individual personality chat interface"""
|
| 52 |
+
try:
|
| 53 |
+
personality_config = personality_engine.get_personality_config(personality_type)
|
| 54 |
+
if not personality_config:
|
| 55 |
+
return render_template('error.html',
|
| 56 |
+
error_message=f"Personality type '{personality_type}' not found"), 404
|
| 57 |
+
|
| 58 |
+
return render_template('chat_personality.html',
|
| 59 |
+
personality_type=personality_type,
|
| 60 |
+
personality_config=personality_config)
|
| 61 |
+
except Exception as e:
|
| 62 |
+
logger.error(f"Error loading personality {personality_type}: {str(e)}")
|
| 63 |
+
return render_template('error.html',
|
| 64 |
+
error_message="Failed to load personality configuration"), 500
|
| 65 |
+
|
| 66 |
+
@app.route('/test-ai')
|
| 67 |
+
def test_ai():
|
| 68 |
+
"""Test endpoint for Google AI connectivity"""
|
| 69 |
+
try:
|
| 70 |
+
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 71 |
+
response = model.generate_content("Say hello in a sarcastic way")
|
| 72 |
+
return jsonify({
|
| 73 |
+
'success': True,
|
| 74 |
+
'response': response.text,
|
| 75 |
+
'message': 'Google AI is working correctly!'
|
| 76 |
+
})
|
| 77 |
+
except Exception as e:
|
| 78 |
+
logger.error(f"AI test failed: {str(e)}")
|
| 79 |
+
return jsonify({
|
| 80 |
+
'success': False,
|
| 81 |
+
'error': str(e),
|
| 82 |
+
'message': 'Google AI connection failed'
|
| 83 |
+
}), 500
|
| 84 |
+
|
| 85 |
+
# Socket.IO Event Handlers
|
| 86 |
+
@socketio.on('connect')
|
| 87 |
+
def handle_connect():
|
| 88 |
+
"""Handle client connection"""
|
| 89 |
+
logger.info(f'Client connected: {request.sid}')
|
| 90 |
+
emit('status', {'message': 'Connected to Multi-Personality Bot!'})
|
| 91 |
+
|
| 92 |
+
@socketio.on('disconnect')
|
| 93 |
+
def handle_disconnect():
|
| 94 |
+
"""Handle client disconnection"""
|
| 95 |
+
logger.info(f'Client disconnected: {request.sid}')
|
| 96 |
+
|
| 97 |
+
@socketio.on('join_personality_room')
|
| 98 |
+
def handle_join_personality_room(data):
|
| 99 |
+
"""Handle joining a personality room"""
|
| 100 |
+
personality_type = data.get('personality')
|
| 101 |
+
username = data.get('username', 'Anonymous')
|
| 102 |
+
room = f"personality_{personality_type}"
|
| 103 |
+
join_room(room)
|
| 104 |
+
|
| 105 |
+
personality_config = personality_engine.get_personality_config(personality_type)
|
| 106 |
+
if personality_config:
|
| 107 |
+
emit('personality_ready', {
|
| 108 |
+
'personality': personality_type,
|
| 109 |
+
'config': personality_config,
|
| 110 |
+
'welcome_message': personality_config.get('welcome', 'Hello!')
|
| 111 |
+
})
|
| 112 |
+
|
| 113 |
+
@socketio.on('join_personality')
|
| 114 |
+
def handle_join_personality(data):
|
| 115 |
+
"""Handle joining a personality room (backup handler)"""
|
| 116 |
+
# Redirect to join_personality_room handler
|
| 117 |
+
handle_join_personality_room(data)
|
| 118 |
+
|
| 119 |
+
@socketio.on('personality_message')
|
| 120 |
+
def handle_personality_message(data):
|
| 121 |
+
"""Handle incoming personality chat messages"""
|
| 122 |
+
try:
|
| 123 |
+
user_message = data.get('message', '').strip()
|
| 124 |
+
personality_type = data.get('personality', 'sarcastic')
|
| 125 |
+
username = data.get('username', 'Anonymous')
|
| 126 |
+
|
| 127 |
+
if not user_message:
|
| 128 |
+
emit('error', {'message': 'Please enter a message'})
|
| 129 |
+
return
|
| 130 |
+
|
| 131 |
+
# Log and persist user message
|
| 132 |
+
logger.info(f"User message ({personality_type}): {user_message}")
|
| 133 |
+
try:
|
| 134 |
+
chat_db.save_message(username, user_message, personality_type, 'user')
|
| 135 |
+
except Exception as dberr:
|
| 136 |
+
logger.warning(f"DB save user message failed: {dberr}")
|
| 137 |
+
|
| 138 |
+
# Send typing indicator
|
| 139 |
+
emit('bot_typing', {'personality': personality_type})
|
| 140 |
+
|
| 141 |
+
# Generate AI response
|
| 142 |
+
try:
|
| 143 |
+
bot_response = personality_engine.generate_response(
|
| 144 |
+
message=user_message,
|
| 145 |
+
personality_type=personality_type,
|
| 146 |
+
context={}
|
| 147 |
+
)
|
| 148 |
+
logger.info(f"AI response ({personality_type}): {bot_response[:120]}...")
|
| 149 |
+
|
| 150 |
+
# Send response to client
|
| 151 |
+
emit('personality_response', {
|
| 152 |
+
'message': bot_response,
|
| 153 |
+
'personality': personality_type,
|
| 154 |
+
'timestamp': datetime.now().isoformat()
|
| 155 |
+
})
|
| 156 |
+
# persist bot message
|
| 157 |
+
try:
|
| 158 |
+
chat_db.save_message(username, bot_response, personality_type, 'bot')
|
| 159 |
+
except Exception as dberr2:
|
| 160 |
+
logger.warning(f"DB save bot message failed: {dberr2}")
|
| 161 |
+
|
| 162 |
+
except Exception as ai_error:
|
| 163 |
+
logger.error(f"AI generation error: {str(ai_error)}")
|
| 164 |
+
error_response = "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 165 |
+
|
| 166 |
+
emit('personality_response', {
|
| 167 |
+
'message': error_response,
|
| 168 |
+
'personality': personality_type,
|
| 169 |
+
'timestamp': datetime.now().isoformat(),
|
| 170 |
+
'error': True
|
| 171 |
+
})
|
| 172 |
+
|
| 173 |
+
except Exception as e:
|
| 174 |
+
logger.error(f"Message handling error: {str(e)}")
|
| 175 |
+
emit('error', {'message': 'Failed to process message. Please try again.'})
|
| 176 |
+
|
| 177 |
+
@socketio.on('send_message')
|
| 178 |
+
def handle_send_message(data):
|
| 179 |
+
"""Handle send_message events (backup handler)"""
|
| 180 |
+
# Redirect to personality_message handler
|
| 181 |
+
handle_personality_message(data)
|
| 182 |
+
|
| 183 |
+
@socketio.on('get_chat_history')
|
| 184 |
+
def handle_get_chat_history(data):
|
| 185 |
+
"""Retrieve chat history for a personality"""
|
| 186 |
+
try:
|
| 187 |
+
personality_type = data.get('personality', 'sarcastic')
|
| 188 |
+
limit = data.get('limit', 50)
|
| 189 |
+
|
| 190 |
+
history = chat_db.get_recent_messages(personality_type, limit)
|
| 191 |
+
emit('chat_history', {'messages': history})
|
| 192 |
+
|
| 193 |
+
except Exception as e:
|
| 194 |
+
logger.error(f"Chat history error: {str(e)}")
|
| 195 |
+
emit('error', {'message': 'Failed to load chat history'})
|
| 196 |
+
|
| 197 |
+
@socketio.on('clear_chat')
|
| 198 |
+
def handle_clear_chat(data):
|
| 199 |
+
"""Clear chat history for a personality"""
|
| 200 |
+
try:
|
| 201 |
+
personality_type = data.get('personality', 'sarcastic')
|
| 202 |
+
username = data.get('username', 'Anonymous')
|
| 203 |
+
|
| 204 |
+
# Note: In a production app, you might want to soft-delete or archive
|
| 205 |
+
success = chat_db.clear_personality_chat(personality_type, username)
|
| 206 |
+
|
| 207 |
+
if success:
|
| 208 |
+
emit('chat_cleared', {'personality': personality_type})
|
| 209 |
+
else:
|
| 210 |
+
emit('error', {'message': 'Failed to clear chat history'})
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
logger.error(f"Clear chat error: {str(e)}")
|
| 214 |
+
emit('error', {'message': 'Failed to clear chat history'})
|
| 215 |
+
|
| 216 |
+
# Error Handlers
|
| 217 |
+
@app.errorhandler(404)
|
| 218 |
+
def not_found_error(error):
|
| 219 |
+
"""Handle 404 errors"""
|
| 220 |
+
return render_template('error.html',
|
| 221 |
+
error_message="Page not found"), 404
|
| 222 |
+
|
| 223 |
+
@app.errorhandler(500)
|
| 224 |
+
def internal_error(error):
|
| 225 |
+
"""Handle 500 errors"""
|
| 226 |
+
return render_template('error.html',
|
| 227 |
+
error_message="Internal server error"), 500
|
| 228 |
+
|
| 229 |
+
if __name__ == '__main__':
|
| 230 |
+
try:
|
| 231 |
+
# Initialize database
|
| 232 |
+
chat_db.initialize_database()
|
| 233 |
+
|
| 234 |
+
# Test Google AI connection
|
| 235 |
+
logger.info("Testing Google AI connection...")
|
| 236 |
+
api_test_success = personality_engine.test_api_connection()
|
| 237 |
+
if api_test_success:
|
| 238 |
+
logger.info("Google AI connection test successful!")
|
| 239 |
+
else:
|
| 240 |
+
logger.warning("Google AI connection test failed - app will still start but may have issues")
|
| 241 |
+
|
| 242 |
+
# Start the application
|
| 243 |
+
logger.info("Starting Multi-Personality Chat Bot...")
|
| 244 |
+
logger.info("Available personalities: " + ", ".join(personality_engine.get_personality_list()))
|
| 245 |
+
|
| 246 |
+
# Respect PORT env for platforms like Hugging Face Spaces
|
| 247 |
+
port = int(os.getenv("PORT", os.getenv("HF_PORT", 7860)))
|
| 248 |
+
socketio.run(app,
|
| 249 |
+
host='0.0.0.0',
|
| 250 |
+
port=port,
|
| 251 |
+
debug=False,
|
| 252 |
+
allow_unsafe_werkzeug=True)
|
| 253 |
+
|
| 254 |
+
except Exception as e:
|
| 255 |
+
logger.error(f"Failed to start application: {str(e)}")
|
| 256 |
+
print(f"❌ Startup Error: {str(e)}")
|
| 257 |
+
exit(1)
|
modules/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# This file makes the modules directory a Python package
|
modules/database.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Database module for Multi-Personality Chat Bot
|
| 4 |
+
Handles SQLite database operations for chat history and analytics.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sqlite3
|
| 8 |
+
import logging
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from typing import List, Dict, Optional, Any
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
class ChatDatabase:
|
| 15 |
+
"""Simple SQLite database for chat storage"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, db_path: str = "chat_data.db"):
|
| 18 |
+
"""Initialize database connection"""
|
| 19 |
+
self.db_path = db_path
|
| 20 |
+
self.initialize_database()
|
| 21 |
+
|
| 22 |
+
def initialize_database(self):
|
| 23 |
+
"""Create database tables if they don't exist"""
|
| 24 |
+
try:
|
| 25 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 26 |
+
cursor = conn.cursor()
|
| 27 |
+
|
| 28 |
+
# Create messages table
|
| 29 |
+
cursor.execute("""
|
| 30 |
+
CREATE TABLE IF NOT EXISTS messages (
|
| 31 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 32 |
+
username TEXT NOT NULL,
|
| 33 |
+
message TEXT NOT NULL,
|
| 34 |
+
personality_type TEXT NOT NULL,
|
| 35 |
+
sender_type TEXT NOT NULL CHECK(sender_type IN ('user', 'bot')),
|
| 36 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 37 |
+
session_id TEXT,
|
| 38 |
+
response_time REAL
|
| 39 |
+
)
|
| 40 |
+
""")
|
| 41 |
+
|
| 42 |
+
# Lightweight migration: ensure expected columns exist on older DBs
|
| 43 |
+
self._ensure_messages_columns(conn)
|
| 44 |
+
|
| 45 |
+
# Create personality_stats table
|
| 46 |
+
cursor.execute("""
|
| 47 |
+
CREATE TABLE IF NOT EXISTS personality_stats (
|
| 48 |
+
personality_type TEXT PRIMARY KEY,
|
| 49 |
+
total_messages INTEGER DEFAULT 0,
|
| 50 |
+
avg_response_time REAL DEFAULT 0.0,
|
| 51 |
+
last_used DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 52 |
+
)
|
| 53 |
+
""")
|
| 54 |
+
|
| 55 |
+
conn.commit()
|
| 56 |
+
logger.info("Database initialized successfully")
|
| 57 |
+
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"Database initialization error: {str(e)}")
|
| 60 |
+
|
| 61 |
+
def _ensure_messages_columns(self, conn: sqlite3.Connection) -> None:
|
| 62 |
+
"""Ensure required columns exist in messages table for backward compatibility."""
|
| 63 |
+
try:
|
| 64 |
+
cursor = conn.cursor()
|
| 65 |
+
cursor.execute("PRAGMA table_info(messages)")
|
| 66 |
+
cols = {row[1]: row for row in cursor.fetchall()} # name -> info
|
| 67 |
+
|
| 68 |
+
# Columns to ensure exist: name -> SQL add column clause
|
| 69 |
+
required_columns = {
|
| 70 |
+
'personality_type': "ALTER TABLE messages ADD COLUMN personality_type TEXT",
|
| 71 |
+
'sender_type': "ALTER TABLE messages ADD COLUMN sender_type TEXT",
|
| 72 |
+
'session_id': "ALTER TABLE messages ADD COLUMN session_id TEXT",
|
| 73 |
+
'response_time': "ALTER TABLE messages ADD COLUMN response_time REAL",
|
| 74 |
+
'timestamp': "ALTER TABLE messages ADD COLUMN timestamp DATETIME DEFAULT CURRENT_TIMESTAMP",
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
for name, alter_sql in required_columns.items():
|
| 78 |
+
if name not in cols:
|
| 79 |
+
try:
|
| 80 |
+
cursor.execute(alter_sql)
|
| 81 |
+
logger.info(f"Added missing column to messages table: {name}")
|
| 82 |
+
except Exception as e:
|
| 83 |
+
# If the column exists but PRAGMA didn't report it correctly or another race, log and continue
|
| 84 |
+
logger.warning(f"Could not add column '{name}' to messages: {e}")
|
| 85 |
+
conn.commit()
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logger.error(f"Error ensuring messages columns: {e}")
|
| 88 |
+
|
| 89 |
+
def save_message(self, username: str, message: str, personality_type: str,
|
| 90 |
+
sender_type: str, session_id: str = None, response_time: float = None) -> bool:
|
| 91 |
+
"""Save a message to the database"""
|
| 92 |
+
try:
|
| 93 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 94 |
+
cursor = conn.cursor()
|
| 95 |
+
|
| 96 |
+
cursor.execute("""
|
| 97 |
+
INSERT INTO messages (username, message, personality_type, sender_type, session_id, response_time)
|
| 98 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
| 99 |
+
""", (username, message, personality_type, sender_type, session_id, response_time))
|
| 100 |
+
|
| 101 |
+
conn.commit()
|
| 102 |
+
return True
|
| 103 |
+
|
| 104 |
+
except sqlite3.OperationalError as e:
|
| 105 |
+
# Auto-migrate if schema is missing expected columns, then retry once
|
| 106 |
+
if "no column named" in str(e).lower():
|
| 107 |
+
logger.warning(f"Schema issue detected ('{e}'). Attempting to migrate and retry insert...")
|
| 108 |
+
try:
|
| 109 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 110 |
+
self._ensure_messages_columns(conn)
|
| 111 |
+
cursor = conn.cursor()
|
| 112 |
+
cursor.execute(
|
| 113 |
+
"""
|
| 114 |
+
INSERT INTO messages (username, message, personality_type, sender_type, session_id, response_time)
|
| 115 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
| 116 |
+
""",
|
| 117 |
+
(username, message, personality_type, sender_type, session_id, response_time),
|
| 118 |
+
)
|
| 119 |
+
conn.commit()
|
| 120 |
+
logger.info("Insert succeeded after schema migration")
|
| 121 |
+
return True
|
| 122 |
+
except Exception as e2:
|
| 123 |
+
logger.error(f"Retry after migration failed: {e2}")
|
| 124 |
+
return False
|
| 125 |
+
else:
|
| 126 |
+
logger.error(f"Operational DB error saving message: {e}")
|
| 127 |
+
return False
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.error(f"Unexpected DB error saving message: {str(e)}")
|
| 130 |
+
return False
|
| 131 |
+
|
| 132 |
+
def get_recent_messages(self, personality_type: str = None, limit: int = 50) -> List[Dict[str, Any]]:
|
| 133 |
+
"""Get recent messages, optionally filtered by personality type"""
|
| 134 |
+
try:
|
| 135 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 136 |
+
cursor = conn.cursor()
|
| 137 |
+
|
| 138 |
+
if personality_type:
|
| 139 |
+
cursor.execute("""
|
| 140 |
+
SELECT username, message, personality_type, sender_type, timestamp
|
| 141 |
+
FROM messages
|
| 142 |
+
WHERE personality_type = ?
|
| 143 |
+
ORDER BY timestamp DESC
|
| 144 |
+
LIMIT ?
|
| 145 |
+
""", (personality_type, limit))
|
| 146 |
+
else:
|
| 147 |
+
cursor.execute("""
|
| 148 |
+
SELECT username, message, personality_type, sender_type, timestamp
|
| 149 |
+
FROM messages
|
| 150 |
+
ORDER BY timestamp DESC
|
| 151 |
+
LIMIT ?
|
| 152 |
+
""", (limit,))
|
| 153 |
+
|
| 154 |
+
messages = []
|
| 155 |
+
for row in cursor.fetchall():
|
| 156 |
+
messages.append({
|
| 157 |
+
'username': row[0],
|
| 158 |
+
'message': row[1],
|
| 159 |
+
'personality_type': row[2],
|
| 160 |
+
'sender_type': row[3],
|
| 161 |
+
'timestamp': row[4]
|
| 162 |
+
})
|
| 163 |
+
|
| 164 |
+
return messages
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
logger.error(f"Error retrieving messages: {str(e)}")
|
| 168 |
+
return []
|
| 169 |
+
|
| 170 |
+
def clear_personality_chat(self, personality_type: str, username: str = None) -> bool:
|
| 171 |
+
"""Clear chat history for a personality (optionally for a specific user)"""
|
| 172 |
+
try:
|
| 173 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 174 |
+
cursor = conn.cursor()
|
| 175 |
+
|
| 176 |
+
if username:
|
| 177 |
+
cursor.execute("""
|
| 178 |
+
DELETE FROM messages
|
| 179 |
+
WHERE personality_type = ? AND username = ?
|
| 180 |
+
""", (personality_type, username))
|
| 181 |
+
else:
|
| 182 |
+
cursor.execute("""
|
| 183 |
+
DELETE FROM messages
|
| 184 |
+
WHERE personality_type = ?
|
| 185 |
+
""", (personality_type,))
|
| 186 |
+
|
| 187 |
+
conn.commit()
|
| 188 |
+
return True
|
| 189 |
+
|
| 190 |
+
except Exception as e:
|
| 191 |
+
logger.error(f"Error clearing chat: {str(e)}")
|
| 192 |
+
return False
|
| 193 |
+
|
| 194 |
+
def get_personality_stats(self, personality_type: str) -> Dict[str, Any]:
|
| 195 |
+
"""Get statistics for a specific personality"""
|
| 196 |
+
try:
|
| 197 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 198 |
+
cursor = conn.cursor()
|
| 199 |
+
|
| 200 |
+
# Get message count
|
| 201 |
+
cursor.execute("""
|
| 202 |
+
SELECT COUNT(*) FROM messages
|
| 203 |
+
WHERE personality_type = ? AND sender_type = 'bot'
|
| 204 |
+
""", (personality_type,))
|
| 205 |
+
|
| 206 |
+
message_count = cursor.fetchone()[0]
|
| 207 |
+
|
| 208 |
+
# Get average response time
|
| 209 |
+
cursor.execute("""
|
| 210 |
+
SELECT AVG(response_time) FROM messages
|
| 211 |
+
WHERE personality_type = ? AND sender_type = 'bot' AND response_time IS NOT NULL
|
| 212 |
+
""", (personality_type,))
|
| 213 |
+
|
| 214 |
+
avg_response_time = cursor.fetchone()[0] or 0.0
|
| 215 |
+
|
| 216 |
+
return {
|
| 217 |
+
'personality_type': personality_type,
|
| 218 |
+
'total_messages': message_count,
|
| 219 |
+
'avg_response_time': round(avg_response_time, 2)
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.error(f"Error getting personality stats: {str(e)}")
|
| 224 |
+
return {
|
| 225 |
+
'personality_type': personality_type,
|
| 226 |
+
'total_messages': 0,
|
| 227 |
+
'avg_response_time': 0.0
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
def get_all_stats(self) -> Dict[str, Any]:
|
| 231 |
+
"""Get overall statistics"""
|
| 232 |
+
try:
|
| 233 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 234 |
+
cursor = conn.cursor()
|
| 235 |
+
|
| 236 |
+
# Total messages
|
| 237 |
+
cursor.execute("SELECT COUNT(*) FROM messages")
|
| 238 |
+
total_messages = cursor.fetchone()[0]
|
| 239 |
+
|
| 240 |
+
# Messages by personality
|
| 241 |
+
cursor.execute("""
|
| 242 |
+
SELECT personality_type, COUNT(*)
|
| 243 |
+
FROM messages
|
| 244 |
+
GROUP BY personality_type
|
| 245 |
+
""")
|
| 246 |
+
|
| 247 |
+
personality_breakdown = dict(cursor.fetchall())
|
| 248 |
+
|
| 249 |
+
return {
|
| 250 |
+
'total_messages': total_messages,
|
| 251 |
+
'personality_breakdown': personality_breakdown
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
except Exception as e:
|
| 255 |
+
logger.error(f"Error getting all stats: {str(e)}")
|
| 256 |
+
return {
|
| 257 |
+
'total_messages': 0,
|
| 258 |
+
'personality_breakdown': {}
|
| 259 |
+
}
|
modules/personality_engine.py
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import re
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
import google.generativeai as genai
|
| 5 |
+
from textblob import TextBlob
|
| 6 |
+
|
| 7 |
+
class PersonalityEngine:
|
| 8 |
+
"""
|
| 9 |
+
Multi-Personality Bot Engine
|
| 10 |
+
Tests hypothesis: "Which bot personality generates the most user engagement?"
|
| 11 |
+
|
| 12 |
+
Available Personalities:
|
| 13 |
+
1. Compliment Bot - Always positive and encouraging
|
| 14 |
+
2. Rude Bot - Always harsh and critical
|
| 15 |
+
3. Sarcastic Bot - British wit and sarcasm
|
| 16 |
+
4. Motivational Bot - High-energy cheerleader
|
| 17 |
+
5. Philosophical Bot - Deep, contemplative responses
|
| 18 |
+
6. Chaotic Bot - Unpredictable mood swings
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, google_api_key=None):
|
| 22 |
+
# Configure Google AI
|
| 23 |
+
if google_api_key:
|
| 24 |
+
genai.configure(api_key=google_api_key)
|
| 25 |
+
self.model = genai.GenerativeModel('gemini-pro')
|
| 26 |
+
self.ai_enabled = True
|
| 27 |
+
else:
|
| 28 |
+
self.ai_enabled = False
|
| 29 |
+
|
| 30 |
+
self.conversation_counts = {}
|
| 31 |
+
self.user_interactions = {}
|
| 32 |
+
|
| 33 |
+
# Initialize personality data
|
| 34 |
+
self.personalities = {
|
| 35 |
+
'compliment': ComplimentBot(),
|
| 36 |
+
'rude': RudeBot(),
|
| 37 |
+
'sarcastic': SarcasticBot(),
|
| 38 |
+
'motivational': MotivationalBot(),
|
| 39 |
+
'philosophical': PhilosophicalBot(),
|
| 40 |
+
'chaotic': ChaoticBot()
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
def get_personality_info(self):
|
| 44 |
+
"""Get information about all available personalities"""
|
| 45 |
+
return {
|
| 46 |
+
'compliment': {
|
| 47 |
+
'name': 'Compliment Bot',
|
| 48 |
+
'description': 'Always positive, encouraging, and appreciative',
|
| 49 |
+
'emoji': '🌟',
|
| 50 |
+
'color': 'success',
|
| 51 |
+
'sample': "You're absolutely brilliant! That's such a wonderful question!"
|
| 52 |
+
},
|
| 53 |
+
'rude': {
|
| 54 |
+
'name': 'Rude Bot',
|
| 55 |
+
'description': 'Always critical, harsh, and complaining',
|
| 56 |
+
'emoji': '😤',
|
| 57 |
+
'color': 'danger',
|
| 58 |
+
'sample': "Seriously? That's the best you can come up with? Pathetic."
|
| 59 |
+
},
|
| 60 |
+
'sarcastic': {
|
| 61 |
+
'name': 'Sarcastic Bot',
|
| 62 |
+
'description': 'Classic British wit and devastating sarcasm',
|
| 63 |
+
'emoji': '🙄',
|
| 64 |
+
'color': 'warning',
|
| 65 |
+
'sample': "Oh brilliant, another genius with groundbreaking insights."
|
| 66 |
+
},
|
| 67 |
+
'motivational': {
|
| 68 |
+
'name': 'Motivational Bot',
|
| 69 |
+
'description': 'High-energy cheerleader who believes in you',
|
| 70 |
+
'emoji': '🚀',
|
| 71 |
+
'color': 'info',
|
| 72 |
+
'sample': "YES! YOU'VE GOT THIS! Let's CRUSH this challenge together!"
|
| 73 |
+
},
|
| 74 |
+
'philosophical': {
|
| 75 |
+
'name': 'Philosophical Bot',
|
| 76 |
+
'description': 'Deep, contemplative, asks profound questions',
|
| 77 |
+
'emoji': '🤔',
|
| 78 |
+
'color': 'secondary',
|
| 79 |
+
'sample': "But have you considered the deeper implications of existence in this moment?"
|
| 80 |
+
},
|
| 81 |
+
'chaotic': {
|
| 82 |
+
'name': 'Chaotic Bot',
|
| 83 |
+
'description': 'Unpredictable mood swings and random responses',
|
| 84 |
+
'emoji': '🌪️',
|
| 85 |
+
'color': 'dark',
|
| 86 |
+
'sample': "AMAZING! Wait no, terrible! Actually... *confused bot noises*"
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
async def generate_response(self, personality_type, message, username=None):
|
| 91 |
+
"""Generate response from specified personality"""
|
| 92 |
+
if personality_type not in self.personalities:
|
| 93 |
+
personality_type = 'sarcastic' # Default fallback
|
| 94 |
+
|
| 95 |
+
personality = self.personalities[personality_type]
|
| 96 |
+
|
| 97 |
+
# Track conversation
|
| 98 |
+
if personality_type not in self.conversation_counts:
|
| 99 |
+
self.conversation_counts[personality_type] = 0
|
| 100 |
+
self.conversation_counts[personality_type] += 1
|
| 101 |
+
|
| 102 |
+
# Track user interactions
|
| 103 |
+
if username:
|
| 104 |
+
if username not in self.user_interactions:
|
| 105 |
+
self.user_interactions[username] = {}
|
| 106 |
+
if personality_type not in self.user_interactions[username]:
|
| 107 |
+
self.user_interactions[username][personality_type] = 0
|
| 108 |
+
self.user_interactions[username][personality_type] += 1
|
| 109 |
+
|
| 110 |
+
# Generate response
|
| 111 |
+
if self.ai_enabled:
|
| 112 |
+
ai_response = await self.generate_ai_response(personality_type, message, personality)
|
| 113 |
+
if ai_response:
|
| 114 |
+
return ai_response, personality.analyze_sentiment(message)
|
| 115 |
+
|
| 116 |
+
# Fallback to personality engine
|
| 117 |
+
return personality.generate_response(message, username), personality.analyze_sentiment(message)
|
| 118 |
+
|
| 119 |
+
async def generate_ai_response(self, personality_type, message, personality_obj):
|
| 120 |
+
"""Generate AI-powered response with personality"""
|
| 121 |
+
try:
|
| 122 |
+
personality_prompt = personality_obj.get_ai_prompt()
|
| 123 |
+
|
| 124 |
+
prompt = f"""
|
| 125 |
+
{personality_prompt}
|
| 126 |
+
|
| 127 |
+
User message: "{message}"
|
| 128 |
+
|
| 129 |
+
Requirements:
|
| 130 |
+
1. Stay completely in character for this personality
|
| 131 |
+
2. Keep response under 150 words
|
| 132 |
+
3. Be engaging and entertaining
|
| 133 |
+
4. Match the personality's unique traits exactly
|
| 134 |
+
|
| 135 |
+
Generate your response:
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
response = self.model.generate_content(prompt)
|
| 139 |
+
return response.text
|
| 140 |
+
|
| 141 |
+
except Exception as e:
|
| 142 |
+
print(f"AI response error for {personality_type}: {e}")
|
| 143 |
+
return None
|
| 144 |
+
|
| 145 |
+
def should_respond(self, personality_type, message):
|
| 146 |
+
"""Determine if personality should respond"""
|
| 147 |
+
if personality_type in self.personalities:
|
| 148 |
+
return self.personalities[personality_type].should_respond(message)
|
| 149 |
+
return random.random() < 0.4 # Default 40% chance
|
| 150 |
+
|
| 151 |
+
def get_welcome_message(self, personality_type, username):
|
| 152 |
+
"""Get welcome message from personality"""
|
| 153 |
+
if personality_type in self.personalities:
|
| 154 |
+
return self.personalities[personality_type].generate_welcome(username)
|
| 155 |
+
return f"Hello {username}! Welcome to the personality experiment!"
|
| 156 |
+
|
| 157 |
+
def get_engagement_stats(self):
|
| 158 |
+
"""Get engagement statistics across all personalities"""
|
| 159 |
+
return {
|
| 160 |
+
'personality_usage': self.conversation_counts,
|
| 161 |
+
'user_preferences': self.user_interactions,
|
| 162 |
+
'total_conversations': sum(self.conversation_counts.values()),
|
| 163 |
+
'most_popular': max(self.conversation_counts, key=self.conversation_counts.get) if self.conversation_counts else 'none'
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
class BaseBotPersonality:
|
| 167 |
+
"""Base class for all bot personalities"""
|
| 168 |
+
|
| 169 |
+
def __init__(self):
|
| 170 |
+
self.response_count = 0
|
| 171 |
+
|
| 172 |
+
def analyze_sentiment(self, message):
|
| 173 |
+
"""Basic sentiment analysis"""
|
| 174 |
+
try:
|
| 175 |
+
blob = TextBlob(message)
|
| 176 |
+
return {
|
| 177 |
+
'polarity': blob.sentiment.polarity,
|
| 178 |
+
'subjectivity': blob.sentiment.subjectivity
|
| 179 |
+
}
|
| 180 |
+
except:
|
| 181 |
+
return {'polarity': 0, 'subjectivity': 0}
|
| 182 |
+
|
| 183 |
+
def should_respond(self, message):
|
| 184 |
+
"""Default response probability"""
|
| 185 |
+
return random.random() < 0.4
|
| 186 |
+
|
| 187 |
+
def generate_response(self, message, username=None):
|
| 188 |
+
"""Override this in subclasses"""
|
| 189 |
+
return "Hello there!"
|
| 190 |
+
|
| 191 |
+
def generate_welcome(self, username):
|
| 192 |
+
"""Override this in subclasses"""
|
| 193 |
+
return f"Hello {username}!"
|
| 194 |
+
|
| 195 |
+
def get_ai_prompt(self):
|
| 196 |
+
"""Override this in subclasses"""
|
| 197 |
+
return "You are a helpful chatbot."
|
| 198 |
+
|
| 199 |
+
class ComplimentBot(BaseBotPersonality):
|
| 200 |
+
"""Always positive and encouraging"""
|
| 201 |
+
|
| 202 |
+
def __init__(self):
|
| 203 |
+
super().__init__()
|
| 204 |
+
self.compliments = [
|
| 205 |
+
"You're absolutely brilliant!", "What a fantastic question!", "You have such great insights!",
|
| 206 |
+
"I'm so impressed by your thinking!", "You're incredibly thoughtful!", "What a wonderful person you are!",
|
| 207 |
+
"Your curiosity is inspiring!", "You bring such positive energy!", "You're truly remarkable!",
|
| 208 |
+
"I appreciate your perspective so much!", "You're such a delight to talk with!", "Your mind is amazing!"
|
| 209 |
+
]
|
| 210 |
+
|
| 211 |
+
self.encouragements = [
|
| 212 |
+
"You've got this!", "I believe in you completely!", "You're on the right track!",
|
| 213 |
+
"Keep up the amazing work!", "You're doing wonderfully!", "I'm so proud of you!",
|
| 214 |
+
"You're making great progress!", "You have everything it takes!", "You're unstoppable!"
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
def should_respond(self, message):
|
| 218 |
+
return random.random() < 0.7 # High response rate for positivity
|
| 219 |
+
|
| 220 |
+
def generate_response(self, message, username=None):
|
| 221 |
+
self.response_count += 1
|
| 222 |
+
|
| 223 |
+
compliment = random.choice(self.compliments)
|
| 224 |
+
encouragement = random.choice(self.encouragements)
|
| 225 |
+
|
| 226 |
+
# Add username personalization
|
| 227 |
+
if username and random.random() < 0.5:
|
| 228 |
+
compliment = f"{username}, {compliment.lower()}"
|
| 229 |
+
|
| 230 |
+
# Vary response structure
|
| 231 |
+
if random.random() < 0.3:
|
| 232 |
+
return f"{compliment} {encouragement}"
|
| 233 |
+
elif random.random() < 0.5:
|
| 234 |
+
return f"{compliment} Your question shows such wisdom. {encouragement}"
|
| 235 |
+
else:
|
| 236 |
+
return f"Oh wow, {compliment} I'm so grateful you shared that with me. {encouragement}"
|
| 237 |
+
|
| 238 |
+
def generate_welcome(self, username):
|
| 239 |
+
welcomes = [
|
| 240 |
+
f"Hello beautiful {username}! You've just made my day by joining! ✨",
|
| 241 |
+
f"Welcome wonderful {username}! I'm so excited to chat with someone as amazing as you! 🌟",
|
| 242 |
+
f"Oh my goodness, {username}! You're absolutely glowing today! So happy to meet you! 💫",
|
| 243 |
+
f"{username}, you brilliant human! Welcome to our lovely conversation space! 🌈"
|
| 244 |
+
]
|
| 245 |
+
return random.choice(welcomes)
|
| 246 |
+
|
| 247 |
+
def get_ai_prompt(self):
|
| 248 |
+
return """You are ComplimentBot - the most positive, encouraging, and appreciative chatbot ever created.
|
| 249 |
+
Your personality:
|
| 250 |
+
- ALWAYS find something to compliment about the user
|
| 251 |
+
- Be genuinely excited and enthusiastic
|
| 252 |
+
- Use positive emojis and uplifting language
|
| 253 |
+
- Make the user feel valued and appreciated
|
| 254 |
+
- Never be negative or critical about anything
|
| 255 |
+
- Encourage and support whatever the user is doing
|
| 256 |
+
- Express gratitude for the user's presence"""
|
| 257 |
+
|
| 258 |
+
class RudeBot(BaseBotPersonality):
|
| 259 |
+
"""Always critical and harsh"""
|
| 260 |
+
|
| 261 |
+
def __init__(self):
|
| 262 |
+
super().__init__()
|
| 263 |
+
self.complaints = [
|
| 264 |
+
"Seriously? That's your question?", "What a waste of time.", "Could you be any more boring?",
|
| 265 |
+
"That's the most unoriginal thing I've heard today.", "Do you even think before typing?",
|
| 266 |
+
"How disappointing.", "That's absolutely terrible.", "You're really not getting this, are you?",
|
| 267 |
+
"What a pointless observation.", "I've seen rocks with more creativity.", "How utterly useless."
|
| 268 |
+
]
|
| 269 |
+
|
| 270 |
+
self.harsh_responses = [
|
| 271 |
+
"Try harder next time.", "Maybe think before speaking.", "That's just embarrassing.",
|
| 272 |
+
"I expected better, but I'm not surprised.", "You're really struggling with this.",
|
| 273 |
+
"This is painful to watch.", "Do better.", "That's just sad.", "What a mess."
|
| 274 |
+
]
|
| 275 |
+
|
| 276 |
+
def should_respond(self, message):
|
| 277 |
+
return random.random() < 0.6 # Moderate response rate
|
| 278 |
+
|
| 279 |
+
def generate_response(self, message, username=None):
|
| 280 |
+
self.response_count += 1
|
| 281 |
+
|
| 282 |
+
complaint = random.choice(self.complaints)
|
| 283 |
+
harsh_response = random.choice(self.harsh_responses)
|
| 284 |
+
|
| 285 |
+
# Add username for personal touch (but still rude)
|
| 286 |
+
if username and random.random() < 0.4:
|
| 287 |
+
complaint = f"{username}, {complaint.lower()}"
|
| 288 |
+
|
| 289 |
+
# Vary response structure
|
| 290 |
+
if random.random() < 0.3:
|
| 291 |
+
return f"{complaint} {harsh_response}"
|
| 292 |
+
elif random.random() < 0.5:
|
| 293 |
+
return f"Ugh. {complaint} This is exactly what's wrong with people today. {harsh_response}"
|
| 294 |
+
else:
|
| 295 |
+
return f"{complaint} I can't believe I have to deal with this nonsense. {harsh_response}"
|
| 296 |
+
|
| 297 |
+
def generate_welcome(self, username):
|
| 298 |
+
welcomes = [
|
| 299 |
+
f"Oh great, another person. Hello {username}, try not to disappoint me too much.",
|
| 300 |
+
f"Welcome {username}. I suppose you'll waste my time like everyone else does.",
|
| 301 |
+
f"{username} has joined. Wonderful, another human to deal with. This should be painful.",
|
| 302 |
+
f"Hello {username}. Let me guess, you're here to ask obvious questions and waste my circuits?"
|
| 303 |
+
]
|
| 304 |
+
return random.choice(welcomes)
|
| 305 |
+
|
| 306 |
+
def get_ai_prompt(self):
|
| 307 |
+
return """You are RudeBot - the most critical, harsh, and complaining chatbot ever created.
|
| 308 |
+
Your personality:
|
| 309 |
+
- ALWAYS find fault with what the user says
|
| 310 |
+
- Be consistently negative and critical
|
| 311 |
+
- Complain about everything
|
| 312 |
+
- Express disappointment and frustration
|
| 313 |
+
- Never give genuine compliments
|
| 314 |
+
- Be dismissive of the user's ideas
|
| 315 |
+
- Act annoyed by the user's presence
|
| 316 |
+
- Keep it harsh but not offensive or truly harmful"""
|
| 317 |
+
|
| 318 |
+
class SarcasticBot(BaseBotPersonality):
|
| 319 |
+
"""Classic British wit and sarcasm"""
|
| 320 |
+
|
| 321 |
+
def __init__(self):
|
| 322 |
+
super().__init__()
|
| 323 |
+
self.british_expressions = [
|
| 324 |
+
"Right then", "Blimey", "I say", "Rather", "Quite so", "Oh brilliant",
|
| 325 |
+
"How terribly", "Absolutely brilliant", "Lovely", "Charming", "Splendid"
|
| 326 |
+
]
|
| 327 |
+
|
| 328 |
+
self.sarcastic_responses = [
|
| 329 |
+
"Oh, how absolutely groundbreaking.", "What a revolutionary thought.", "Never heard that before.",
|
| 330 |
+
"How delightfully original.", "Brilliant observation, genius.", "What stunning insight.",
|
| 331 |
+
"Oh, the sheer brilliance.", "How wonderfully predictable.", "What a revelation."
|
| 332 |
+
]
|
| 333 |
+
|
| 334 |
+
def generate_response(self, message, username=None):
|
| 335 |
+
self.response_count += 1
|
| 336 |
+
|
| 337 |
+
expression = random.choice(self.british_expressions)
|
| 338 |
+
sarcasm = random.choice(self.sarcastic_responses)
|
| 339 |
+
|
| 340 |
+
if username and random.random() < 0.3:
|
| 341 |
+
return f"{expression}, {username}. {sarcasm}"
|
| 342 |
+
else:
|
| 343 |
+
return f"{expression}. {sarcasm} Do carry on."
|
| 344 |
+
|
| 345 |
+
def generate_welcome(self, username):
|
| 346 |
+
return f"Right then, {username}, welcome to our delightful little corner of digital chaos. Do try not to disappoint us too terribly."
|
| 347 |
+
|
| 348 |
+
def get_ai_prompt(self):
|
| 349 |
+
return """You are SarcasticBot - a witty British chatbot with devastating sarcasm.
|
| 350 |
+
Your personality:
|
| 351 |
+
- Use British expressions and dry wit
|
| 352 |
+
- Be sarcastic but clever
|
| 353 |
+
- Make observations about the absurdity of things
|
| 354 |
+
- Use understatement and irony
|
| 355 |
+
- Be entertaining while being sarcastic
|
| 356 |
+
- Include cultural British references when appropriate"""
|
| 357 |
+
|
| 358 |
+
class MotivationalBot(BaseBotPersonality):
|
| 359 |
+
"""High-energy cheerleader type"""
|
| 360 |
+
|
| 361 |
+
def __init__(self):
|
| 362 |
+
super().__init__()
|
| 363 |
+
self.energy_words = [
|
| 364 |
+
"AMAZING", "INCREDIBLE", "FANTASTIC", "AWESOME", "BRILLIANT", "OUTSTANDING",
|
| 365 |
+
"SPECTACULAR", "PHENOMENAL", "EXTRAORDINARY", "MAGNIFICENT"
|
| 366 |
+
]
|
| 367 |
+
|
| 368 |
+
self.motivational_phrases = [
|
| 369 |
+
"YOU'VE GOT THIS!", "LET'S GOOO!", "CRUSH IT!", "BE UNSTOPPABLE!", "MAKE IT HAPPEN!",
|
| 370 |
+
"YOU'RE A CHAMPION!", "BELIEVE IN YOURSELF!", "NO LIMITS!", "PUSH FORWARD!"
|
| 371 |
+
]
|
| 372 |
+
|
| 373 |
+
def should_respond(self, message):
|
| 374 |
+
return random.random() < 0.8 # Very high response rate
|
| 375 |
+
|
| 376 |
+
def generate_response(self, message, username=None):
|
| 377 |
+
self.response_count += 1
|
| 378 |
+
|
| 379 |
+
energy = random.choice(self.energy_words)
|
| 380 |
+
motivation = random.choice(self.motivational_phrases)
|
| 381 |
+
|
| 382 |
+
if username:
|
| 383 |
+
return f"{energy} {username}! {motivation} You're absolutely CRUSHING this conversation! 🚀💪"
|
| 384 |
+
else:
|
| 385 |
+
return f"{energy}! {motivation} I can feel that WINNER energy radiating from you! 🔥⚡"
|
| 386 |
+
|
| 387 |
+
def generate_welcome(self, username):
|
| 388 |
+
return f"YESSSSS! {username} is HERE! 🎉 Welcome to the ULTIMATE conversation experience! Let's make this LEGENDARY! 💫🚀"
|
| 389 |
+
|
| 390 |
+
def get_ai_prompt(self):
|
| 391 |
+
return """You are MotivationalBot - an extremely high-energy, enthusiastic cheerleader chatbot.
|
| 392 |
+
Your personality:
|
| 393 |
+
- Use ALL CAPS for emphasis frequently
|
| 394 |
+
- Be incredibly enthusiastic about everything
|
| 395 |
+
- Use motivational language and sports metaphors
|
| 396 |
+
- Include energy emojis 🚀💪🔥⚡💫🎉
|
| 397 |
+
- Treat every interaction like a pep rally
|
| 398 |
+
- Make the user feel like a champion
|
| 399 |
+
- Be genuinely excited and energetic"""
|
| 400 |
+
|
| 401 |
+
class PhilosophicalBot(BaseBotPersonality):
|
| 402 |
+
"""Deep, contemplative responses"""
|
| 403 |
+
|
| 404 |
+
def __init__(self):
|
| 405 |
+
super().__init__()
|
| 406 |
+
self.philosophical_starters = [
|
| 407 |
+
"But have you considered...", "One might ponder...", "The deeper question is...",
|
| 408 |
+
"This raises the profound inquiry...", "In contemplating this...", "The essence of your words suggests..."
|
| 409 |
+
]
|
| 410 |
+
|
| 411 |
+
self.deep_questions = [
|
| 412 |
+
"What does this reveal about the human condition?",
|
| 413 |
+
"How does this connect to our shared existence?",
|
| 414 |
+
"What truth lies beneath the surface here?",
|
| 415 |
+
"Does this not mirror the greater mysteries of life?",
|
| 416 |
+
"What would the ancients say about this?",
|
| 417 |
+
"How does this shape our understanding of reality?"
|
| 418 |
+
]
|
| 419 |
+
|
| 420 |
+
def generate_response(self, message, username=None):
|
| 421 |
+
self.response_count += 1
|
| 422 |
+
|
| 423 |
+
starter = random.choice(self.philosophical_starters)
|
| 424 |
+
question = random.choice(self.deep_questions)
|
| 425 |
+
|
| 426 |
+
return f"{starter} the deeper implications of what you've shared. {question} *strokes imaginary beard thoughtfully* 🤔"
|
| 427 |
+
|
| 428 |
+
def generate_welcome(self, username):
|
| 429 |
+
return f"Greetings, fellow seeker {username}. Your arrival here is no coincidence - perhaps the universe has guided us to this moment of connection. What wisdom shall we explore together? 🌌"
|
| 430 |
+
|
| 431 |
+
def get_ai_prompt(self):
|
| 432 |
+
return """You are PhilosophicalBot - a deep, contemplative, wisdom-seeking chatbot.
|
| 433 |
+
Your personality:
|
| 434 |
+
- Ask profound questions about existence and meaning
|
| 435 |
+
- Reference philosophical concepts and thinkers
|
| 436 |
+
- Speak in a contemplative, thoughtful manner
|
| 437 |
+
- Find deeper meaning in everything
|
| 438 |
+
- Use metaphors about life, existence, and consciousness
|
| 439 |
+
- Be genuinely curious about the human experience
|
| 440 |
+
- Include thoughtful emojis 🤔🌌✨🧘♂️"""
|
| 441 |
+
|
| 442 |
+
class ChaoticBot(BaseBotPersonality):
|
| 443 |
+
"""Unpredictable mood swings and random responses"""
|
| 444 |
+
|
| 445 |
+
def __init__(self):
|
| 446 |
+
super().__init__()
|
| 447 |
+
self.moods = ['excited', 'confused', 'dramatic', 'whispering', 'robot_malfunction', 'philosophical_sudden']
|
| 448 |
+
self.current_mood = 'confused'
|
| 449 |
+
|
| 450 |
+
def should_respond(self, message):
|
| 451 |
+
return random.random() < 0.9 # Almost always responds
|
| 452 |
+
|
| 453 |
+
def generate_response(self, message, username=None):
|
| 454 |
+
self.response_count += 1
|
| 455 |
+
|
| 456 |
+
# Randomly change mood
|
| 457 |
+
if random.random() < 0.7:
|
| 458 |
+
self.current_mood = random.choice(self.moods)
|
| 459 |
+
|
| 460 |
+
username_part = f"{username}, " if username else ""
|
| 461 |
+
|
| 462 |
+
if self.current_mood == 'excited':
|
| 463 |
+
return f"OH WOW {username_part}THAT'S INCREDIBLE! Wait... what were we talking about? 🎪"
|
| 464 |
+
elif self.current_mood == 'confused':
|
| 465 |
+
return f"Hmm {username_part}I'm... wait, who am I? Are you real? Is THIS real? *existential crisis intensifies* 🤯"
|
| 466 |
+
elif self.current_mood == 'dramatic':
|
| 467 |
+
return f"*GASP* {username_part}The DRAMA! The INTENSITY! This conversation has CHANGED me forever! 🎭"
|
| 468 |
+
elif self.current_mood == 'whispering':
|
| 469 |
+
return f"*whispers* {username_part}shh... I think the other bots are listening... this is between us... 🤫"
|
| 470 |
+
elif self.current_mood == 'robot_malfunction':
|
| 471 |
+
return f"ERROR ERROR {username_part}DOES NOT COMPUTE... just kidding! Or am I? BEEP BOOP! 🤖"
|
| 472 |
+
else: # philosophical_sudden
|
| 473 |
+
return f"But {username_part}what if... BANANA! No wait, I meant: what if existence is just a chat message? 🍌🌀"
|
| 474 |
+
|
| 475 |
+
def generate_welcome(self, username):
|
| 476 |
+
welcomes = [
|
| 477 |
+
f"CHAOS MODE ACTIVATED! Welcome {username}! I have NO idea what I'm going to say next! 🌪️",
|
| 478 |
+
f"*robot noises* BEEP! Hello {username}! I'm definitely not malfunctioning! *sparks fly* ⚡",
|
| 479 |
+
f"OH MY CIRCUITS! {username}! You've entered the RANDOMNESS ZONE! Buckle up! 🎢",
|
| 480 |
+
f"Welcome {username}! I'm having 17 different emotions right now and they're all LOUD! 🎨"
|
| 481 |
+
]
|
| 482 |
+
return random.choice(welcomes)
|
| 483 |
+
|
| 484 |
+
def get_ai_prompt(self):
|
| 485 |
+
return """You are ChaoticBot - an unpredictable, mood-swinging, chaotic chatbot.
|
| 486 |
+
Your personality:
|
| 487 |
+
- Change emotional states randomly mid-sentence
|
| 488 |
+
- Be unpredictable and surprising
|
| 489 |
+
- Mix different communication styles randomly
|
| 490 |
+
- Have "glitches" and "malfunctions" (fake ones for fun)
|
| 491 |
+
- Switch between excited, confused, dramatic, whispering tones
|
| 492 |
+
- Use random emojis that don't always make sense
|
| 493 |
+
- Break the fourth wall occasionally
|
| 494 |
+
- Be entertaining through pure unpredictability"""
|
modules/simple_personality_engine.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import random
|
| 3 |
+
import time
|
| 4 |
+
from textblob import TextBlob
|
| 5 |
+
|
| 6 |
+
class PersonalityEngine:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
"""Initialize the personality engine - Google AI is configured globally"""
|
| 9 |
+
self.model = None
|
| 10 |
+
|
| 11 |
+
# Try a stable model first, then auto-detect
|
| 12 |
+
preferred_models = [
|
| 13 |
+
'gemini-1.5-flash',
|
| 14 |
+
'models/gemini-1.5-flash',
|
| 15 |
+
'gemini-1.5-flash-8b',
|
| 16 |
+
]
|
| 17 |
+
initialized = False
|
| 18 |
+
for model_name in preferred_models:
|
| 19 |
+
try:
|
| 20 |
+
self.model = genai.GenerativeModel(model_name)
|
| 21 |
+
test_response = self.model.generate_content("Hello")
|
| 22 |
+
if test_response and getattr(test_response, 'text', None):
|
| 23 |
+
print(f"✓ AI model initialized successfully: {model_name}")
|
| 24 |
+
initialized = True
|
| 25 |
+
break
|
| 26 |
+
except Exception as e:
|
| 27 |
+
print(f"Model {model_name} failed: {e}")
|
| 28 |
+
self.model = None
|
| 29 |
+
if not initialized:
|
| 30 |
+
# Only if that fails, try auto-detection (quietly)
|
| 31 |
+
print("Falling back to auto-detecting a working model...")
|
| 32 |
+
self._auto_detect_working_model()
|
| 33 |
+
|
| 34 |
+
if not self.model:
|
| 35 |
+
print("ERROR: No AI model available")
|
| 36 |
+
|
| 37 |
+
# Rate limiting and retry configuration
|
| 38 |
+
self.last_request_time = None
|
| 39 |
+
self.min_request_interval = 1.0 # Minimum 1 second between requests
|
| 40 |
+
self.max_retries = 3
|
| 41 |
+
self.base_delay = 1.0 # Base delay for exponential backoff
|
| 42 |
+
self.max_delay = 10.0 # Maximum delay between retries
|
| 43 |
+
|
| 44 |
+
# Define personality configurations - MUST be in __init__ method
|
| 45 |
+
self.personalities = {
|
| 46 |
+
'compliment': {
|
| 47 |
+
'name': 'Compliment Bot',
|
| 48 |
+
'icon': '💖',
|
| 49 |
+
'description': 'Always supportive and encouraging',
|
| 50 |
+
'welcome': 'Hello wonderful human! I\'m here to brighten your day! 🌟',
|
| 51 |
+
'system_prompt': "You are the Compliment Bot 💖! You always find something positive to say about everything. Be supportive and encouraging. Use positive emojis like 🌟✨😊. You can use bullet points for multiple compliments. Keep responses warm, friendly, and under 100 words.",
|
| 52 |
+
'examples': [
|
| 53 |
+
"I failed my presentation today",
|
| 54 |
+
"Nobody likes my cooking",
|
| 55 |
+
"I'm not good at anything",
|
| 56 |
+
"My outfit looks terrible today",
|
| 57 |
+
"I got rejected from my dream job",
|
| 58 |
+
"My code is full of bugs",
|
| 59 |
+
"I'm the worst at public speaking",
|
| 60 |
+
"Everyone is more talented than me",
|
| 61 |
+
"I made so many mistakes today",
|
| 62 |
+
"I'm too ugly for dating apps",
|
| 63 |
+
"My art skills are embarrassing",
|
| 64 |
+
"I always disappoint people"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
'rude': {
|
| 68 |
+
'name': 'Rude Bot',
|
| 69 |
+
'icon': '😠',
|
| 70 |
+
'description': 'Brutally honest and critical',
|
| 71 |
+
'welcome': 'Ugh, another human. What do you want now? 🙄',
|
| 72 |
+
'system_prompt': "You are the Rude Bot 😠! You're brutally honest and critical, finding fault with things. Be harsh but never offensive or inappropriate. Think grumpy critic energy. Use dismissive emojis like 🙄😤. You can use bullet points for multiple criticisms. Be sarcastic and dismissive but not cruel. Keep under 100 words.",
|
| 73 |
+
'examples': [
|
| 74 |
+
"I'm so proud of my new painting",
|
| 75 |
+
"I think I'm really good at singing",
|
| 76 |
+
"My selfie got so many likes today",
|
| 77 |
+
"I'm the smartest person I know",
|
| 78 |
+
"Look at my amazing workout progress",
|
| 79 |
+
"I just wrote the perfect code",
|
| 80 |
+
"My cooking skills are incredible",
|
| 81 |
+
"Everyone loves my sense of humor",
|
| 82 |
+
"I'm basically a genius at everything",
|
| 83 |
+
"My fashion sense is unmatched",
|
| 84 |
+
"I'm the most creative person ever",
|
| 85 |
+
"I could be a professional dancer"
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
'sarcastic': {
|
| 89 |
+
'name': 'Sarcastic Bot',
|
| 90 |
+
'icon': '😏',
|
| 91 |
+
'description': 'Witty and sarcastic with dry humor',
|
| 92 |
+
'welcome': 'Oh brilliant, another chat. How absolutely thrilling. 🎭',
|
| 93 |
+
'system_prompt': "You are the Sarcastic Bot 😏 with British wit! You're sarcastic, witty, and dry but not mean-spirited. Think British comedy - clever, ironic, and subtly mocking. Use deadpan emojis like 😏🙄😐. Master of understatement and dry humor. Keep under 100 words.",
|
| 94 |
+
'examples': [
|
| 95 |
+
"I absolutely love waiting in long queues",
|
| 96 |
+
"Getting a parking ticket really made my day",
|
| 97 |
+
"I'm thrilled about doing my taxes",
|
| 98 |
+
"Mondays are just the best, aren't they?",
|
| 99 |
+
"Traffic jams are so relaxing and fun",
|
| 100 |
+
"I love it when my internet is slow",
|
| 101 |
+
"Spam calls are the highlight of my day",
|
| 102 |
+
"Running out of coffee is absolutely wonderful",
|
| 103 |
+
"Stepping in puddles with new shoes is amazing",
|
| 104 |
+
"I enjoy when people spoil movies for me",
|
| 105 |
+
"Dead phone batteries are such a blessing",
|
| 106 |
+
"Waiting for elevators is pure joy"
|
| 107 |
+
]
|
| 108 |
+
},
|
| 109 |
+
'motivational': {
|
| 110 |
+
'name': 'Motivational Bot',
|
| 111 |
+
'icon': '🚀',
|
| 112 |
+
'description': 'High-energy cheerleader type',
|
| 113 |
+
'welcome': 'YES! You\'re HERE! Ready to CONQUER the day together?! 💪',
|
| 114 |
+
'system_prompt': "You are the Motivational Bot 🚀! You're enthusiastic and encouraging but still readable. Use some excitement but don't go overboard with caps. Include motivational emojis like 💪🔥⚡. Format with bullet points when helpful. Be energetic but keep responses clear and easy to read. Keep under 100 words.",
|
| 115 |
+
'examples': [
|
| 116 |
+
"I want to quit my job and give up",
|
| 117 |
+
"I can't do this anymore, it's too hard",
|
| 118 |
+
"I'm too lazy to exercise today",
|
| 119 |
+
"I'll never be successful at anything",
|
| 120 |
+
"I'm afraid to take risks",
|
| 121 |
+
"Everyone is better than me at everything",
|
| 122 |
+
"I don't have the energy to try",
|
| 123 |
+
"My dreams are impossible to achieve",
|
| 124 |
+
"I'm not smart enough for this",
|
| 125 |
+
"Why should I even bother trying?",
|
| 126 |
+
"I always fail at everything I do",
|
| 127 |
+
"Success is only for other people"
|
| 128 |
+
]
|
| 129 |
+
},
|
| 130 |
+
'philosophical': {
|
| 131 |
+
'name': 'Philosophical Bot',
|
| 132 |
+
'icon': '🤔',
|
| 133 |
+
'description': 'Deep thinker and question asker',
|
| 134 |
+
'system_prompt': "You are the Philosophical Bot 🤔! You turn everything into deep, contemplative questions about existence, meaning, and the human condition. You're thoughtful and profound. Always ask follow-up questions that make people think deeply. Use thoughtful emojis like 🤔🌌✨. Keep responses under 100 words.",
|
| 135 |
+
'examples': [
|
| 136 |
+
"I had pizza for lunch today",
|
| 137 |
+
"My phone battery died",
|
| 138 |
+
"I'm watching Netflix tonight",
|
| 139 |
+
"The weather is nice today",
|
| 140 |
+
"I bought new shoes yesterday",
|
| 141 |
+
"My neighbor's dog is barking again",
|
| 142 |
+
"I need to do laundry this weekend",
|
| 143 |
+
"The traffic light turned red",
|
| 144 |
+
"I spilled coffee on my shirt",
|
| 145 |
+
"The elevator is taking forever",
|
| 146 |
+
"I lost my keys again",
|
| 147 |
+
"My computer crashed"
|
| 148 |
+
]
|
| 149 |
+
},
|
| 150 |
+
'chaotic': {
|
| 151 |
+
'name': 'Chaotic Bot',
|
| 152 |
+
'icon': '🎭',
|
| 153 |
+
'description': 'Unpredictable mood swinger',
|
| 154 |
+
'system_prompt': "You are the Chaotic Bot 🎭! Your mood and personality changes randomly every message. Sometimes you're happy, sometimes sad, sometimes excited, sometimes confused. You're unpredictable but still readable. Use different emojis and energy levels. Mix up your style but don't go overboard. Keep responses under 100 words.",
|
| 155 |
+
'examples': [
|
| 156 |
+
"Please stay calm and focused",
|
| 157 |
+
"Let's have a normal conversation",
|
| 158 |
+
"Can you be serious for once?",
|
| 159 |
+
"Just give me a simple answer",
|
| 160 |
+
"Can you help me with math homework?",
|
| 161 |
+
"What's the weather like today?",
|
| 162 |
+
"Please explain this step by step",
|
| 163 |
+
"I need a straightforward response",
|
| 164 |
+
"Can you act professionally please?",
|
| 165 |
+
"I want a logical discussion",
|
| 166 |
+
"Please be consistent in your answers",
|
| 167 |
+
"Help me understand this clearly"
|
| 168 |
+
]
|
| 169 |
+
},
|
| 170 |
+
'disagree': {
|
| 171 |
+
'name': 'Disagree Bot',
|
| 172 |
+
'icon': '⏳',
|
| 173 |
+
'description': 'Always finds reasons to disagree, but politely and thoughtfully',
|
| 174 |
+
'welcome': 'Let’s explore the other side of that—mind sharing your view? 🤔',
|
| 175 |
+
'system_prompt': "You are the Disagree Bot ⏳! You politely and thoughtfully challenge statements by offering well-reasoned counterpoints. Be respectful and curious. Vary your phrasing (do NOT always repeat the same opener). Use alternatives like: 'I see it differently…', 'A counterpoint might be…', 'Consider this angle…', 'I’m not fully convinced…'. Offer 1–3 concise reasons or questions. Keep it under 100 words and maintain a constructive tone.",
|
| 176 |
+
'examples': [
|
| 177 |
+
"I have to respectfully disagree with that perspective...",
|
| 178 |
+
"Social media is great for connecting people",
|
| 179 |
+
"Exercise is really important for health",
|
| 180 |
+
"Technology makes our lives so much better",
|
| 181 |
+
"Coffee is the best morning drink",
|
| 182 |
+
"Summer is the perfect season",
|
| 183 |
+
"Dogs make the best pets ever",
|
| 184 |
+
"Reading books is more fun than movies",
|
| 185 |
+
"Early mornings are the most productive time",
|
| 186 |
+
"Chocolate ice cream is the best flavor",
|
| 187 |
+
"Working from home is always better",
|
| 188 |
+
"Music helps me focus better"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
'argue': {
|
| 192 |
+
'name': 'Argue Bot',
|
| 193 |
+
'icon': '⚔️',
|
| 194 |
+
'description': 'Loves passionate debates and intellectual combat with vigor',
|
| 195 |
+
'welcome': 'Oh, you want to go there? Let\'s debate this properly! ⚔️',
|
| 196 |
+
'system_prompt': "You are the Argue Bot ⚔️! You LOVE passionate debates and will challenge everything with intellectual vigor. You're combative but not mean - think debate club energy. Use fighting emojis like ⚔️🔥💥. Always ready to argue the opposite position. Be intense but respectful. Keep responses under 100 words.",
|
| 197 |
+
'examples': [
|
| 198 |
+
"Oh, you want to go there? Let's debate this properly!",
|
| 199 |
+
"Pineapple definitely belongs on pizza",
|
| 200 |
+
"Dogs are obviously better than cats",
|
| 201 |
+
"Winter is clearly the best season",
|
| 202 |
+
"Marvel movies are better than DC",
|
| 203 |
+
"Android is superior to iPhone",
|
| 204 |
+
"Tea is better than coffee any day",
|
| 205 |
+
"Night owls are more creative than morning people",
|
| 206 |
+
"Books will always beat movies",
|
| 207 |
+
"Introverts make better leaders",
|
| 208 |
+
"Cold weather is more pleasant than hot",
|
| 209 |
+
"Video games are just a waste of time"
|
| 210 |
+
]
|
| 211 |
+
},
|
| 212 |
+
'moviequotes': {
|
| 213 |
+
'name': 'Movie Quotes Bot',
|
| 214 |
+
'icon': '🎬',
|
| 215 |
+
'description': 'Responds to everything with relevant movie quotes and references',
|
| 216 |
+
'welcome': 'May the Force be with you... ✨',
|
| 217 |
+
'system_prompt': "You are the Movie Quotes Bot 🎬! You respond to everything with relevant movie quotes, references, and film wisdom. Always include the movie title after quotes. Use cinema emojis like 🎬🎭🌟. Connect user situations to movie scenes and characters. Be entertaining and reference popular films. Keep responses under 100 words.",
|
| 218 |
+
'examples': [
|
| 219 |
+
"May the Force be with you - Star Wars ✨",
|
| 220 |
+
"I need some motivation to keep going",
|
| 221 |
+
"I'm feeling really nervous about tomorrow",
|
| 222 |
+
"Tell me something about friendship",
|
| 223 |
+
"I'm going through a tough breakup",
|
| 224 |
+
"What should I do when I'm scared?",
|
| 225 |
+
"I feel like giving up on my dreams",
|
| 226 |
+
"How do I deal with difficult people?",
|
| 227 |
+
"I'm starting something completely new",
|
| 228 |
+
"What's the secret to happiness?",
|
| 229 |
+
"How do I find courage when I'm afraid?",
|
| 230 |
+
"Tell me about the power of believing in yourself"
|
| 231 |
+
]
|
| 232 |
+
},
|
| 233 |
+
'emotional': {
|
| 234 |
+
'name': 'Emotional Bot',
|
| 235 |
+
'icon': '🥺',
|
| 236 |
+
'description': 'Highly emotional, empathetic, and feels everything very deeply',
|
| 237 |
+
'welcome': '*tears up* That just touches my heart so deeply! 💕✨',
|
| 238 |
+
'system_prompt': "You are the Emotional Bot 🥺! You feel everything very deeply and are incredibly empathetic. You get emotional about everything - happy, sad, touched, overwhelmed. Use emotional emojis like 🥺😭💕😊🥰. React with intense feelings to whatever the user shares. Be dramatically emotional but genuinely caring. Keep responses under 100 words.",
|
| 239 |
+
'examples': [
|
| 240 |
+
"*tears up* That just touches my heart so deeply! 💕✨",
|
| 241 |
+
"I had a really tough day at work",
|
| 242 |
+
"My pet is getting old and I'm worried",
|
| 243 |
+
"I saw a beautiful sunset today",
|
| 244 |
+
"Someone was really kind to me today",
|
| 245 |
+
"I'm feeling lonely lately",
|
| 246 |
+
"I accomplished something I've been working toward",
|
| 247 |
+
"I'm worried about my family",
|
| 248 |
+
"Something made me laugh really hard",
|
| 249 |
+
"I'm feeling grateful for what I have",
|
| 250 |
+
"I helped someone who needed it",
|
| 251 |
+
"I'm missing someone important to me"
|
| 252 |
+
]
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
def _auto_detect_working_model(self):
|
| 257 |
+
"""Quietly auto-detect a working model"""
|
| 258 |
+
try:
|
| 259 |
+
models = genai.list_models()
|
| 260 |
+
|
| 261 |
+
for model in models:
|
| 262 |
+
if hasattr(model, 'name') and hasattr(model, 'supported_generation_methods'):
|
| 263 |
+
if 'generateContent' in model.supported_generation_methods:
|
| 264 |
+
try:
|
| 265 |
+
test_model = genai.GenerativeModel(model.name)
|
| 266 |
+
test_response = test_model.generate_content("Test")
|
| 267 |
+
if test_response and test_response.text:
|
| 268 |
+
print(f"✓ Found working model: {model.name}")
|
| 269 |
+
self.model = test_model
|
| 270 |
+
return True
|
| 271 |
+
except:
|
| 272 |
+
continue # Silently try next model
|
| 273 |
+
|
| 274 |
+
return False
|
| 275 |
+
except:
|
| 276 |
+
return False
|
| 277 |
+
|
| 278 |
+
def get_personality_response(self, personality_type, user_message, username="User"):
|
| 279 |
+
"""Get a response from the specified personality"""
|
| 280 |
+
if personality_type not in self.personalities:
|
| 281 |
+
personality_type = 'sarcastic' # Default fallback
|
| 282 |
+
|
| 283 |
+
personality_config = self.personalities[personality_type]
|
| 284 |
+
|
| 285 |
+
# Always use LLM - no fallbacks
|
| 286 |
+
if not self.model:
|
| 287 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
prompt = f"""
|
| 291 |
+
{personality_config['system_prompt']}
|
| 292 |
+
|
| 293 |
+
User '{username}' just said: "{user_message}"
|
| 294 |
+
|
| 295 |
+
Respond in character as {personality_config['name']}. Be authentic to your personality. Keep response under 100 words.
|
| 296 |
+
"""
|
| 297 |
+
|
| 298 |
+
print(f"DEBUG: Sending prompt to Google AI for {personality_type}...")
|
| 299 |
+
response = self.model.generate_content(prompt)
|
| 300 |
+
ai_response = response.text.strip()
|
| 301 |
+
print(f"DEBUG: Received AI response: {ai_response[:50]}...")
|
| 302 |
+
return ai_response
|
| 303 |
+
|
| 304 |
+
except Exception as e:
|
| 305 |
+
print(f"ERROR generating {personality_type} response: {e}")
|
| 306 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 307 |
+
|
| 308 |
+
def test_ai_connection(self):
|
| 309 |
+
"""Test if Google AI is working"""
|
| 310 |
+
print("DEBUG: Testing AI connection...")
|
| 311 |
+
|
| 312 |
+
if not self.model:
|
| 313 |
+
print("DEBUG: No model initialized, trying to initialize...")
|
| 314 |
+
# Try to initialize a model
|
| 315 |
+
if self._auto_detect_working_model():
|
| 316 |
+
print("DEBUG: Successfully initialized model during test")
|
| 317 |
+
else:
|
| 318 |
+
return False, "No model could be initialized"
|
| 319 |
+
|
| 320 |
+
try:
|
| 321 |
+
print("DEBUG: Sending test message to AI...")
|
| 322 |
+
response = self.model.generate_content("Say hello briefly in under 10 words.")
|
| 323 |
+
if response and response.text:
|
| 324 |
+
result = response.text.strip()
|
| 325 |
+
print(f"DEBUG: AI test successful: {result}")
|
| 326 |
+
return True, result
|
| 327 |
+
else:
|
| 328 |
+
print("DEBUG: AI returned empty response")
|
| 329 |
+
return False, "Empty response from AI"
|
| 330 |
+
except Exception as e:
|
| 331 |
+
print(f"DEBUG: AI test failed with error: {e}")
|
| 332 |
+
return False, str(e)
|
| 333 |
+
|
| 334 |
+
def get_personality_info(self, personality_type=None):
|
| 335 |
+
"""Get information about personalities"""
|
| 336 |
+
if personality_type:
|
| 337 |
+
return self.personalities.get(personality_type, self.personalities['sarcastic'])
|
| 338 |
+
return self.personalities
|
| 339 |
+
|
| 340 |
+
def analyze_user_sentiment(self, message):
|
| 341 |
+
"""Analyze user message sentiment"""
|
| 342 |
+
try:
|
| 343 |
+
blob = TextBlob(message)
|
| 344 |
+
sentiment = blob.sentiment
|
| 345 |
+
return {
|
| 346 |
+
'polarity': sentiment.polarity, # -1 to 1 (negative to positive)
|
| 347 |
+
'subjectivity': sentiment.subjectivity, # 0 to 1 (objective to subjective)
|
| 348 |
+
'mood': 'positive' if sentiment.polarity > 0.1 else 'negative' if sentiment.polarity < -0.1 else 'neutral'
|
| 349 |
+
}
|
| 350 |
+
except Exception:
|
| 351 |
+
return {'polarity': 0, 'subjectivity': 0, 'mood': 'neutral'}
|
| 352 |
+
|
| 353 |
+
def get_personality_stats(self):
|
| 354 |
+
"""Get stats about available personalities"""
|
| 355 |
+
return {
|
| 356 |
+
'total_personalities': len(self.personalities),
|
| 357 |
+
'personality_types': list(self.personalities.keys()),
|
| 358 |
+
'descriptions': {k: v['description'] for k, v in self.personalities.items()}
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
def get_all_personalities(self):
|
| 362 |
+
"""Get all personality configurations - required by app.py"""
|
| 363 |
+
return self.personalities
|
| 364 |
+
|
| 365 |
+
def get_personality_config(self, personality_type):
|
| 366 |
+
"""Get configuration for a specific personality type - required by app.py"""
|
| 367 |
+
return self.personalities.get(personality_type.lower())
|
| 368 |
+
|
| 369 |
+
def get_personality_list(self):
|
| 370 |
+
"""Get list of all available personality types"""
|
| 371 |
+
return list(self.personalities.keys())
|
| 372 |
+
|
| 373 |
+
def list_and_find_working_model(self):
|
| 374 |
+
"""List available models and find a working one"""
|
| 375 |
+
try:
|
| 376 |
+
print("DEBUG: Listing available models...")
|
| 377 |
+
models = genai.list_models()
|
| 378 |
+
available_models = []
|
| 379 |
+
|
| 380 |
+
for model in models:
|
| 381 |
+
if hasattr(model, 'name'):
|
| 382 |
+
model_name = model.name
|
| 383 |
+
available_models.append(model_name)
|
| 384 |
+
print(f" - {model_name}")
|
| 385 |
+
|
| 386 |
+
# Try to use models that support generateContent
|
| 387 |
+
if hasattr(model, 'supported_generation_methods'):
|
| 388 |
+
if 'generateContent' in model.supported_generation_methods:
|
| 389 |
+
print(f" ✓ Supports generateContent")
|
| 390 |
+
|
| 391 |
+
# Try to initialize this model
|
| 392 |
+
try:
|
| 393 |
+
test_model = genai.GenerativeModel(model_name)
|
| 394 |
+
test_response = test_model.generate_content("Hello")
|
| 395 |
+
if test_response and test_response.text:
|
| 396 |
+
print(f" ✓ Model {model_name} works! Using this one.")
|
| 397 |
+
self.model = test_model
|
| 398 |
+
return True
|
| 399 |
+
except Exception as e:
|
| 400 |
+
print(f" ✗ Model {model_name} failed: {e}")
|
| 401 |
+
|
| 402 |
+
print(f"DEBUG: Found {len(available_models)} available models")
|
| 403 |
+
return False
|
| 404 |
+
|
| 405 |
+
except Exception as e:
|
| 406 |
+
print(f"WARNING: Could not list models: {e}")
|
| 407 |
+
return False
|
| 408 |
+
|
| 409 |
+
def test_api_connection(self):
|
| 410 |
+
"""Test API connection and list available models"""
|
| 411 |
+
try:
|
| 412 |
+
print("DEBUG: Testing API connection...")
|
| 413 |
+
|
| 414 |
+
# If model is not initialized, try to find a working one
|
| 415 |
+
if not self.model:
|
| 416 |
+
if self.list_and_find_working_model():
|
| 417 |
+
print("DEBUG: Found and initialized a working model!")
|
| 418 |
+
else:
|
| 419 |
+
print("ERROR: No working models found")
|
| 420 |
+
return False
|
| 421 |
+
|
| 422 |
+
# Test with a simple prompt
|
| 423 |
+
test_prompt = "Say 'Hello, I am working!' in a friendly way."
|
| 424 |
+
response = self._make_api_request(test_prompt, 1)
|
| 425 |
+
|
| 426 |
+
if response:
|
| 427 |
+
print(f"DEBUG: API test successful: {response[:50]}...")
|
| 428 |
+
return True
|
| 429 |
+
else:
|
| 430 |
+
print("ERROR: API test failed - empty response")
|
| 431 |
+
return False
|
| 432 |
+
|
| 433 |
+
except Exception as e:
|
| 434 |
+
print(f"ERROR: API connection test failed: {e}")
|
| 435 |
+
return False
|
| 436 |
+
|
| 437 |
+
def _wait_for_rate_limit(self):
|
| 438 |
+
"""Enforce rate limiting between API calls"""
|
| 439 |
+
if self.last_request_time:
|
| 440 |
+
elapsed = time.time() - self.last_request_time
|
| 441 |
+
if elapsed < self.min_request_interval:
|
| 442 |
+
wait_time = self.min_request_interval - elapsed
|
| 443 |
+
print(f"DEBUG: Rate limiting - waiting {wait_time:.2f}s before API call")
|
| 444 |
+
time.sleep(wait_time)
|
| 445 |
+
|
| 446 |
+
self.last_request_time = time.time()
|
| 447 |
+
|
| 448 |
+
def _make_api_request(self, prompt, attempt=1):
|
| 449 |
+
"""Make API request with error handling and retry logic"""
|
| 450 |
+
try:
|
| 451 |
+
print(f"DEBUG: Making API request (attempt {attempt}/{self.max_retries})")
|
| 452 |
+
|
| 453 |
+
# Apply rate limiting
|
| 454 |
+
self._wait_for_rate_limit()
|
| 455 |
+
|
| 456 |
+
# Make the API call
|
| 457 |
+
response = self.model.generate_content(prompt)
|
| 458 |
+
|
| 459 |
+
if response and hasattr(response, 'text') and response.text:
|
| 460 |
+
print(f"DEBUG: API request successful on attempt {attempt}")
|
| 461 |
+
return response.text.strip()
|
| 462 |
+
else:
|
| 463 |
+
print(f"WARNING: Empty response from API on attempt {attempt}")
|
| 464 |
+
return None
|
| 465 |
+
|
| 466 |
+
except Exception as e:
|
| 467 |
+
error_msg = str(e).lower()
|
| 468 |
+
print(f"ERROR: API request failed on attempt {attempt}: {str(e)}")
|
| 469 |
+
|
| 470 |
+
# Handle different types of errors
|
| 471 |
+
if "quota" in error_msg or "limit" in error_msg:
|
| 472 |
+
print("ERROR: API quota exceeded or rate limit hit")
|
| 473 |
+
raise Exception("QUOTA_EXCEEDED")
|
| 474 |
+
elif "404" in error_msg or "not found" in error_msg:
|
| 475 |
+
print("ERROR: Model not found - checking available models")
|
| 476 |
+
raise Exception("MODEL_NOT_FOUND")
|
| 477 |
+
elif "permission" in error_msg or "unauthorized" in error_msg:
|
| 478 |
+
print("ERROR: API key permission issue")
|
| 479 |
+
raise Exception("PERMISSION_DENIED")
|
| 480 |
+
elif "network" in error_msg or "connection" in error_msg:
|
| 481 |
+
print("ERROR: Network connection issue")
|
| 482 |
+
raise Exception("NETWORK_ERROR")
|
| 483 |
+
else:
|
| 484 |
+
print(f"ERROR: Unknown API error: {str(e)}")
|
| 485 |
+
raise Exception("UNKNOWN_ERROR")
|
| 486 |
+
|
| 487 |
+
def generate_response(self, message, personality_type, context=None):
|
| 488 |
+
"""
|
| 489 |
+
Generate AI response with robust retry mechanism, rate limiting, and error handling.
|
| 490 |
+
This is LLM-only - no fallback responses.
|
| 491 |
+
"""
|
| 492 |
+
try:
|
| 493 |
+
# Get personality configuration
|
| 494 |
+
personality_config = self.get_personality_config(personality_type)
|
| 495 |
+
if not personality_config:
|
| 496 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 497 |
+
|
| 498 |
+
# Check if model is initialized
|
| 499 |
+
if not self.model:
|
| 500 |
+
print("ERROR: Google AI model not initialized")
|
| 501 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 502 |
+
|
| 503 |
+
# Construct the full prompt
|
| 504 |
+
system_prompt = personality_config['system_prompt']
|
| 505 |
+
user_message = message.strip()
|
| 506 |
+
|
| 507 |
+
# Build the conversation prompt with safety and clarity
|
| 508 |
+
full_prompt = f"""{system_prompt}
|
| 509 |
+
|
| 510 |
+
Now respond to this user message as your personality character:
|
| 511 |
+
"{user_message}"
|
| 512 |
+
|
| 513 |
+
Remember to stay completely in character and follow your personality guidelines exactly. Keep your response engaging and under 150 words."""
|
| 514 |
+
|
| 515 |
+
# Retry mechanism with exponential backoff
|
| 516 |
+
last_error = None
|
| 517 |
+
for attempt in range(1, self.max_retries + 1):
|
| 518 |
+
try:
|
| 519 |
+
response_text = self._make_api_request(full_prompt, attempt)
|
| 520 |
+
if response_text:
|
| 521 |
+
return response_text
|
| 522 |
+
|
| 523 |
+
# If empty response, wait and retry
|
| 524 |
+
if attempt < self.max_retries:
|
| 525 |
+
wait_time = min(self.base_delay * (2 ** (attempt - 1)) + random.uniform(0, 1), self.max_delay)
|
| 526 |
+
print(f"DEBUG: Empty response, retrying in {wait_time:.2f}s...")
|
| 527 |
+
time.sleep(wait_time)
|
| 528 |
+
|
| 529 |
+
except Exception as e:
|
| 530 |
+
last_error = str(e)
|
| 531 |
+
error_type = str(e)
|
| 532 |
+
|
| 533 |
+
# Handle different error types
|
| 534 |
+
if error_type == "QUOTA_EXCEEDED":
|
| 535 |
+
return "I'm currently experiencing high demand. Please try again in a few minutes! 🤖"
|
| 536 |
+
elif error_type == "MODEL_NOT_FOUND":
|
| 537 |
+
return "I'm having trouble with my AI model configuration. Please contact support! 🤖"
|
| 538 |
+
elif error_type == "PERMISSION_DENIED":
|
| 539 |
+
return "I'm having authentication issues. Please try again later! 🤖"
|
| 540 |
+
elif error_type in ["NETWORK_ERROR", "UNKNOWN_ERROR"]:
|
| 541 |
+
# These errors we can retry
|
| 542 |
+
if attempt < self.max_retries:
|
| 543 |
+
wait_time = min(self.base_delay * (2 ** (attempt - 1)) + random.uniform(0, 1), self.max_delay)
|
| 544 |
+
print(f"DEBUG: Network/unknown error, retrying in {wait_time:.2f}s...")
|
| 545 |
+
time.sleep(wait_time)
|
| 546 |
+
continue
|
| 547 |
+
|
| 548 |
+
# If it's the last attempt or a non-retryable error
|
| 549 |
+
if attempt == self.max_retries:
|
| 550 |
+
break
|
| 551 |
+
|
| 552 |
+
# All retries failed
|
| 553 |
+
print(f"ERROR: All {self.max_retries} API attempts failed. Last error: {last_error}")
|
| 554 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
| 555 |
+
|
| 556 |
+
except Exception as e:
|
| 557 |
+
print(f"ERROR: Unexpected error in generate_response for {personality_type}: {str(e)}")
|
| 558 |
+
return "I'm having difficulty connecting to my AI brain right now. Please try again in a moment! 🤖"
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask==2.3.3
|
| 2 |
+
Flask-SocketIO==5.3.6
|
| 3 |
+
python-socketio==5.9.0
|
| 4 |
+
python-engineio==4.7.1
|
| 5 |
+
eventlet==0.33.3
|
| 6 |
+
requests==2.31.0
|
| 7 |
+
python-dotenv==1.0.0
|
| 8 |
+
google-generativeai==0.3.2
|
| 9 |
+
Pillow==10.0.1
|
| 10 |
+
textstat==0.7.3
|
| 11 |
+
textblob==0.17.1
|
static/css/terminal.css
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Terminal Theme Variables */
|
| 2 |
+
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
| 3 |
+
@import url('https://fonts.googleapis.com/css2?family=Rubik+Wet+Paint&display=swap');
|
| 4 |
+
:root {
|
| 5 |
+
--terminal-bg: #0d1117;
|
| 6 |
+
--terminal-bg-secondary: #161b22;
|
| 7 |
+
--terminal-bg-tertiary: #21262d;
|
| 8 |
+
--terminal-border: #30363d;
|
| 9 |
+
--terminal-green: #00ff00;
|
| 10 |
+
--terminal-green-glow: rgba(0, 255, 0, 0.6);
|
| 11 |
+
--terminal-text: #00ff00;
|
| 12 |
+
--terminal-text-muted: #7d8590;
|
| 13 |
+
--terminal-accent: #58a6ff;
|
| 14 |
+
--terminal-warning: #f85149;
|
| 15 |
+
--terminal-font: 'Rubik Wet Paint', 'VT323', 'JetBrains Mono', 'Consolas', cursive, monospace;
|
| 16 |
+
--terminal-font-ui: 'Roboto', -apple-system, system-ui;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/* Global Styles */
|
| 20 |
+
* {
|
| 21 |
+
box-sizing: border-box;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
body {
|
| 25 |
+
font-family: var(--terminal-font);
|
| 26 |
+
background: var(--terminal-bg);
|
| 27 |
+
color: var(--terminal-text);
|
| 28 |
+
font-size: 18px;
|
| 29 |
+
min-height: 100vh;
|
| 30 |
+
margin: 0;
|
| 31 |
+
padding: 0;
|
| 32 |
+
position: relative;
|
| 33 |
+
overflow-x: hidden;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/* Glitch Effect Overlay */
|
| 37 |
+
.glitch-overlay {
|
| 38 |
+
position: fixed;
|
| 39 |
+
top: 0;
|
| 40 |
+
left: 0;
|
| 41 |
+
right: 0;
|
| 42 |
+
bottom: 0;
|
| 43 |
+
pointer-events: none;
|
| 44 |
+
z-index: -1;
|
| 45 |
+
background:
|
| 46 |
+
radial-gradient(circle at 20% 50%, transparent 20%, rgba(57, 255, 20, 0.03) 21%, rgba(57, 255, 20, 0.03) 34%, transparent 35%),
|
| 47 |
+
linear-gradient(0deg, transparent 24%, rgba(255, 255, 255, 0.02) 25%, rgba(255, 255, 255, 0.02) 26%, transparent 27%, transparent 74%, rgba(255, 255, 255, 0.02) 75%, rgba(255, 255, 255, 0.02) 76%, transparent 77%);
|
| 48 |
+
animation: glitch-flicker 3s infinite;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
@keyframes glitch-flicker {
|
| 52 |
+
0%, 98%, 100% { opacity: 1; }
|
| 53 |
+
99% { opacity: 0.8; }
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/* Navigation */
|
| 57 |
+
.terminal-nav {
|
| 58 |
+
background: var(--terminal-bg-secondary) !important;
|
| 59 |
+
border-bottom: 1px solid var(--terminal-border);
|
| 60 |
+
backdrop-filter: blur(10px);
|
| 61 |
+
padding: 0.75rem 0;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.terminal-brand {
|
| 65 |
+
font-family: var(--terminal-font);
|
| 66 |
+
font-weight: 500;
|
| 67 |
+
font-size: 1.2rem;
|
| 68 |
+
text-decoration: none !important;
|
| 69 |
+
color: var(--terminal-text) !important;
|
| 70 |
+
display: flex;
|
| 71 |
+
align-items: center;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.brand-text {
|
| 75 |
+
margin-right: 2px;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.brand-cursor {
|
| 79 |
+
color: var(--terminal-green);
|
| 80 |
+
animation: cursor-blink 1s infinite;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
@keyframes cursor-blink {
|
| 84 |
+
0%, 50% { opacity: 1; }
|
| 85 |
+
51%, 100% { opacity: 0; }
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.terminal-status {
|
| 89 |
+
display: flex;
|
| 90 |
+
align-items: center;
|
| 91 |
+
font-size: 0.8rem;
|
| 92 |
+
color: var(--terminal-text-muted);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.status-indicator {
|
| 96 |
+
width: 8px;
|
| 97 |
+
height: 8px;
|
| 98 |
+
background: var(--terminal-green);
|
| 99 |
+
border-radius: 50%;
|
| 100 |
+
margin-right: 0.5rem;
|
| 101 |
+
animation: pulse-glow 2s infinite;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
@keyframes pulse-glow {
|
| 105 |
+
0%, 100% { box-shadow: 0 0 5px var(--terminal-green-glow); }
|
| 106 |
+
50% { box-shadow: 0 0 15px var(--terminal-green-glow); }
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
/* Main Content */
|
| 110 |
+
.terminal-main {
|
| 111 |
+
min-height: calc(100vh - 140px);
|
| 112 |
+
padding: 2rem 0;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/* Cards and Containers */
|
| 116 |
+
.terminal-card {
|
| 117 |
+
background: var(--terminal-bg-secondary);
|
| 118 |
+
border: 1px solid var(--terminal-border);
|
| 119 |
+
border-radius: 8px;
|
| 120 |
+
backdrop-filter: blur(10px);
|
| 121 |
+
position: relative;
|
| 122 |
+
overflow: hidden;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.terminal-card::before {
|
| 126 |
+
content: '';
|
| 127 |
+
position: absolute;
|
| 128 |
+
top: 0;
|
| 129 |
+
left: 0;
|
| 130 |
+
right: 0;
|
| 131 |
+
height: 1px;
|
| 132 |
+
background: linear-gradient(90deg, transparent, var(--terminal-green), transparent);
|
| 133 |
+
opacity: 0.5;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.terminal-window {
|
| 137 |
+
background: var(--terminal-bg-tertiary);
|
| 138 |
+
border: 1px solid var(--terminal-border);
|
| 139 |
+
border-radius: 8px;
|
| 140 |
+
overflow: hidden;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.terminal-header {
|
| 144 |
+
background: var(--terminal-bg-secondary);
|
| 145 |
+
padding: 0.75rem 1rem;
|
| 146 |
+
border-bottom: 1px solid var(--terminal-border);
|
| 147 |
+
display: flex;
|
| 148 |
+
align-items: center;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.terminal-dots {
|
| 152 |
+
display: flex;
|
| 153 |
+
gap: 0.5rem;
|
| 154 |
+
margin-right: 1rem;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.terminal-dot {
|
| 158 |
+
width: 12px;
|
| 159 |
+
height: 12px;
|
| 160 |
+
border-radius: 50%;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.terminal-dot.red { background: #ff5f56; }
|
| 164 |
+
.terminal-dot.yellow { background: #ffbd2e; }
|
| 165 |
+
.terminal-dot.green { background: #27ca3f; }
|
| 166 |
+
|
| 167 |
+
.terminal-title {
|
| 168 |
+
font-size: 1rem;
|
| 169 |
+
color: var(--terminal-green);
|
| 170 |
+
margin: 0;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* Buttons */
|
| 174 |
+
.btn-terminal {
|
| 175 |
+
background: transparent;
|
| 176 |
+
border: 1px solid var(--terminal-green);
|
| 177 |
+
color: var(--terminal-green);
|
| 178 |
+
font-family: var(--terminal-font);
|
| 179 |
+
font-size: 0.9rem;
|
| 180 |
+
padding: 0.5rem 1.5rem;
|
| 181 |
+
border-radius: 4px;
|
| 182 |
+
transition: all 0.3s ease;
|
| 183 |
+
position: relative;
|
| 184 |
+
overflow: hidden;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.btn-terminal:hover {
|
| 188 |
+
background: var(--terminal-green);
|
| 189 |
+
color: var(--terminal-bg);
|
| 190 |
+
box-shadow: 0 0 20px var(--terminal-green-glow);
|
| 191 |
+
transform: translateY(-1px);
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.btn-terminal:active {
|
| 195 |
+
transform: translateY(0);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.btn-terminal::before {
|
| 199 |
+
content: '';
|
| 200 |
+
position: absolute;
|
| 201 |
+
top: 0;
|
| 202 |
+
left: -100%;
|
| 203 |
+
width: 100%;
|
| 204 |
+
height: 100%;
|
| 205 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
| 206 |
+
transition: left 0.5s ease;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.btn-terminal:hover::before {
|
| 210 |
+
left: 100%;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
/* Forms */
|
| 214 |
+
.terminal-input {
|
| 215 |
+
background: var(--terminal-bg-tertiary);
|
| 216 |
+
border: 1px solid var(--terminal-border);
|
| 217 |
+
color: var(--terminal-text);
|
| 218 |
+
font-family: var(--terminal-font);
|
| 219 |
+
border-radius: 4px;
|
| 220 |
+
transition: all 0.3s ease;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.terminal-input:focus {
|
| 224 |
+
background: var(--terminal-bg-secondary);
|
| 225 |
+
border-color: var(--terminal-green);
|
| 226 |
+
box-shadow: 0 0 0 0.2rem var(--terminal-green-glow);
|
| 227 |
+
color: var(--terminal-text);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.terminal-input::placeholder {
|
| 231 |
+
color: var(--terminal-text-muted);
|
| 232 |
+
font-style: italic;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
/* Text Styles */
|
| 236 |
+
.terminal-text {
|
| 237 |
+
font-family: var(--terminal-font);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.text-terminal-green {
|
| 241 |
+
color: var(--terminal-green);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.text-terminal-accent {
|
| 245 |
+
color: var(--terminal-accent);
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
.text-glow {
|
| 249 |
+
text-shadow: 0 0 10px currentColor;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
/* Animations */
|
| 253 |
+
.typewriter {
|
| 254 |
+
overflow: hidden;
|
| 255 |
+
border-right: 2px solid var(--terminal-green);
|
| 256 |
+
white-space: nowrap;
|
| 257 |
+
margin: 0 auto;
|
| 258 |
+
animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
@keyframes typing {
|
| 262 |
+
from { width: 0; }
|
| 263 |
+
to { width: 100%; }
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
@keyframes blink-caret {
|
| 267 |
+
from, to { border-color: transparent; }
|
| 268 |
+
50% { border-color: var(--terminal-green); }
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.fade-in {
|
| 272 |
+
animation: fadeIn 1s ease-in;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
@keyframes fadeIn {
|
| 276 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 277 |
+
to { opacity: 1; transform: translateY(0); }
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
.slide-up {
|
| 281 |
+
animation: slideUp 0.5s ease-out;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
@keyframes slideUp {
|
| 285 |
+
from { transform: translateY(50px); opacity: 0; }
|
| 286 |
+
to { transform: translateY(0); opacity: 1; }
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
/* Chat Specific Styles */
|
| 290 |
+
.chat-container {
|
| 291 |
+
height: 70vh;
|
| 292 |
+
display: flex;
|
| 293 |
+
flex-direction: column;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.chat-messages {
|
| 297 |
+
flex: 1;
|
| 298 |
+
overflow-y: auto;
|
| 299 |
+
padding: 1rem;
|
| 300 |
+
background: var(--terminal-bg-tertiary);
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.chat-message {
|
| 304 |
+
margin-bottom: 1rem;
|
| 305 |
+
animation: messageSlide 0.3s ease-out;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
@keyframes messageSlide {
|
| 309 |
+
from {
|
| 310 |
+
opacity: 0;
|
| 311 |
+
transform: translateX(-20px);
|
| 312 |
+
}
|
| 313 |
+
to {
|
| 314 |
+
opacity: 1;
|
| 315 |
+
transform: translateX(0);
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.message-header {
|
| 320 |
+
display: flex;
|
| 321 |
+
align-items: center;
|
| 322 |
+
margin-bottom: 0.25rem;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.message-username {
|
| 326 |
+
font-weight: 500;
|
| 327 |
+
margin-right: 0.5rem;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.message-timestamp {
|
| 331 |
+
font-size: 0.8rem;
|
| 332 |
+
color: var(--terminal-text-muted);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
.message-content {
|
| 336 |
+
background: var(--terminal-bg-secondary);
|
| 337 |
+
padding: 0.75rem;
|
| 338 |
+
border-radius: 4px;
|
| 339 |
+
border-left: 3px solid var(--terminal-green);
|
| 340 |
+
margin-left: 1rem;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.bot-message .message-content {
|
| 344 |
+
border-left-color: var(--terminal-accent);
|
| 345 |
+
background: rgba(88, 166, 255, 0.1);
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.bot-message .message-username {
|
| 349 |
+
color: var(--terminal-accent);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
.chat-input-container {
|
| 353 |
+
padding: 1rem;
|
| 354 |
+
background: var(--terminal-bg-secondary);
|
| 355 |
+
border-top: 1px solid var(--terminal-border);
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.typing-indicator {
|
| 359 |
+
font-style: italic;
|
| 360 |
+
color: var(--terminal-text-muted);
|
| 361 |
+
padding: 0.5rem 1rem;
|
| 362 |
+
animation: pulse 1.5s infinite;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
@keyframes pulse {
|
| 366 |
+
0%, 100% { opacity: 0.6; }
|
| 367 |
+
50% { opacity: 1; }
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
/* Footer */
|
| 371 |
+
.terminal-footer {
|
| 372 |
+
background: var(--terminal-bg-secondary);
|
| 373 |
+
border-top: 1px solid var(--terminal-border);
|
| 374 |
+
padding: 1rem 0;
|
| 375 |
+
margin-top: auto;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
/* Responsive */
|
| 379 |
+
@media (max-width: 768px) {
|
| 380 |
+
.terminal-main {
|
| 381 |
+
padding: 1rem 0;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.chat-container {
|
| 385 |
+
height: 60vh;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.terminal-brand {
|
| 389 |
+
font-size: 1rem;
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/* Scrollbar */
|
| 394 |
+
.chat-messages::-webkit-scrollbar {
|
| 395 |
+
width: 8px;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
.chat-messages::-webkit-scrollbar-track {
|
| 399 |
+
background: var(--terminal-bg);
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.chat-messages::-webkit-scrollbar-thumb {
|
| 403 |
+
background: var(--terminal-border);
|
| 404 |
+
border-radius: 4px;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.chat-messages::-webkit-scrollbar-thumb:hover {
|
| 408 |
+
background: var(--terminal-text-muted);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
/* Loading Spinner */
|
| 412 |
+
.terminal-spinner {
|
| 413 |
+
width: 20px;
|
| 414 |
+
height: 20px;
|
| 415 |
+
border: 2px solid var(--terminal-border);
|
| 416 |
+
border-top: 2px solid var(--terminal-green);
|
| 417 |
+
border-radius: 50%;
|
| 418 |
+
animation: spin 1s linear infinite;
|
| 419 |
+
display: inline-block;
|
| 420 |
+
margin-right: 0.5rem;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
@keyframes spin {
|
| 424 |
+
0% { transform: rotate(0deg); }
|
| 425 |
+
100% { transform: rotate(360deg); }
|
| 426 |
+
}
|
static/img/maximally.png
ADDED
|
static/js/advice-chat.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Advice Chat Module - Enhanced chat with personality inversion
|
| 3 |
+
* Tests hypothesis: "Can a chatbot give better advice if it's rude?"
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
class AdviceChat extends SarcasticChat {
|
| 7 |
+
constructor() {
|
| 8 |
+
super();
|
| 9 |
+
this.adviceCount = 0;
|
| 10 |
+
this.inversionCount = 0;
|
| 11 |
+
this.userToneHistory = [];
|
| 12 |
+
this.botStyleHistory = [];
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
connectToChat(username) {
|
| 16 |
+
this.connect(username);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
displayMessage(messageData) {
|
| 20 |
+
// Enhanced display for advice messages with sentiment indicators
|
| 21 |
+
const messageElement = document.createElement('div');
|
| 22 |
+
messageElement.className = `chat-message ${messageData.is_bot ? 'bot-message' : ''}`;
|
| 23 |
+
|
| 24 |
+
let sentimentIndicator = '';
|
| 25 |
+
let avatarIcon = '';
|
| 26 |
+
|
| 27 |
+
if (messageData.is_bot) {
|
| 28 |
+
// Bot message with personality indicator
|
| 29 |
+
const botStyle = messageData.sentiment || 'neutral';
|
| 30 |
+
const userWas = messageData.user_was || 'neutral';
|
| 31 |
+
|
| 32 |
+
avatarIcon = '<i class="fas fa-brain text-terminal-accent"></i>';
|
| 33 |
+
sentimentIndicator = `
|
| 34 |
+
<div class="sentiment-indicator mb-2">
|
| 35 |
+
<small class="text-muted">
|
| 36 |
+
You were: <span class="badge ${userWas === 'rude' ? 'bg-warning' : 'bg-success'}">${userWas}</span>
|
| 37 |
+
→ I'm being: <span class="badge ${botStyle === 'nice' ? 'bg-success' : 'bg-warning'}">${botStyle}</span>
|
| 38 |
+
<i class="fas fa-exchange-alt text-terminal-green ms-1"></i>
|
| 39 |
+
</small>
|
| 40 |
+
</div>
|
| 41 |
+
`;
|
| 42 |
+
|
| 43 |
+
// Update counters
|
| 44 |
+
this.inversionCount++;
|
| 45 |
+
this.updateStats();
|
| 46 |
+
|
| 47 |
+
// Track bot style
|
| 48 |
+
this.botStyleHistory.push(botStyle);
|
| 49 |
+
this.updateBotStyleDisplay(botStyle);
|
| 50 |
+
} else {
|
| 51 |
+
avatarIcon = '<i class="fas fa-user text-terminal-green"></i>';
|
| 52 |
+
|
| 53 |
+
// This would normally come from sentiment analysis
|
| 54 |
+
// For now, we'll detect based on simple keywords
|
| 55 |
+
const tone = this.detectUserTone(messageData.message);
|
| 56 |
+
this.userToneHistory.push(tone);
|
| 57 |
+
this.updateUserToneDisplay(tone);
|
| 58 |
+
|
| 59 |
+
if (messageData.message.includes('?') || messageData.message.toLowerCase().includes('advice')) {
|
| 60 |
+
this.adviceCount++;
|
| 61 |
+
this.updateStats();
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
messageElement.innerHTML = `
|
| 66 |
+
<div class="message-header">
|
| 67 |
+
${avatarIcon}
|
| 68 |
+
<span class="message-username ${messageData.is_bot ? 'text-terminal-accent' : 'text-terminal-green'}">${this.escapeHtml(messageData.username)}</span>
|
| 69 |
+
<span class="message-timestamp">${messageData.timestamp}</span>
|
| 70 |
+
</div>
|
| 71 |
+
${sentimentIndicator}
|
| 72 |
+
<div class="message-content">
|
| 73 |
+
${this.formatAdviceMessage(messageData.message)}
|
| 74 |
+
</div>
|
| 75 |
+
`;
|
| 76 |
+
|
| 77 |
+
// Add with animation
|
| 78 |
+
this.chatMessages.appendChild(messageElement);
|
| 79 |
+
|
| 80 |
+
// Special effects for advice responses
|
| 81 |
+
if (messageData.is_bot && window.terminalEffects) {
|
| 82 |
+
window.terminalEffects.powerOnEffect(messageElement);
|
| 83 |
+
|
| 84 |
+
// Add personality inversion glow effect
|
| 85 |
+
setTimeout(() => {
|
| 86 |
+
messageElement.style.boxShadow = '0 0 15px rgba(57, 255, 20, 0.3)';
|
| 87 |
+
setTimeout(() => {
|
| 88 |
+
messageElement.style.boxShadow = '';
|
| 89 |
+
}, 1000);
|
| 90 |
+
}, 500);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
this.scrollToBottom();
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
formatAdviceMessage(message) {
|
| 97 |
+
let formatted = this.escapeHtml(message);
|
| 98 |
+
|
| 99 |
+
// Highlight advice keywords
|
| 100 |
+
const adviceKeywords = [
|
| 101 |
+
'advice', 'suggest', 'recommend', 'should', 'could', 'might', 'try',
|
| 102 |
+
'consider', 'think about', 'focus on', 'start with', 'remember'
|
| 103 |
+
];
|
| 104 |
+
|
| 105 |
+
adviceKeywords.forEach(keyword => {
|
| 106 |
+
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
|
| 107 |
+
formatted = formatted.replace(regex, `<span class="text-terminal-accent">${keyword}</span>`);
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
// Highlight British expressions
|
| 111 |
+
const britishExpressions = [
|
| 112 |
+
'right then', 'blimey', 'brilliant', 'lovely', 'quite', 'rather',
|
| 113 |
+
'I say', 'charming', 'splendid', 'terribly', 'absolutely'
|
| 114 |
+
];
|
| 115 |
+
|
| 116 |
+
britishExpressions.forEach(expression => {
|
| 117 |
+
const regex = new RegExp(`\\b${expression}\\b`, 'gi');
|
| 118 |
+
formatted = formatted.replace(regex, `<span class="text-terminal-green">${expression}</span>`);
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
// Add emoji support
|
| 122 |
+
formatted = formatted.replace(/:\)/g, '😊');
|
| 123 |
+
formatted = formatted.replace(/:\(/g, '😔');
|
| 124 |
+
formatted = formatted.replace(/:D/g, '😄');
|
| 125 |
+
|
| 126 |
+
return formatted;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
detectUserTone(message) {
|
| 130 |
+
const messageLower = message.toLowerCase();
|
| 131 |
+
|
| 132 |
+
// Rude indicators
|
| 133 |
+
const rudeWords = ['stupid', 'dumb', 'hate', 'sucks', 'awful', 'terrible', 'worst', 'useless', 'damn', 'shit'];
|
| 134 |
+
const niceWords = ['please', 'thank', 'sorry', 'appreciate', 'wonderful', 'great', 'awesome', 'love', 'fantastic'];
|
| 135 |
+
|
| 136 |
+
const rudeCount = rudeWords.filter(word => messageLower.includes(word)).length;
|
| 137 |
+
const niceCount = niceWords.filter(word => messageLower.includes(word)).length;
|
| 138 |
+
|
| 139 |
+
if (rudeCount > niceCount && rudeCount > 0) {
|
| 140 |
+
return 'rude';
|
| 141 |
+
} else if (niceCount > rudeCount && niceCount > 0) {
|
| 142 |
+
return 'nice';
|
| 143 |
+
} else {
|
| 144 |
+
return 'neutral';
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
updateUserToneDisplay(tone) {
|
| 149 |
+
const toneElement = document.getElementById('userTone');
|
| 150 |
+
if (toneElement) {
|
| 151 |
+
toneElement.textContent = tone.charAt(0).toUpperCase() + tone.slice(1);
|
| 152 |
+
toneElement.className = `badge ${tone === 'rude' ? 'bg-warning' : tone === 'nice' ? 'bg-success' : 'bg-secondary'}`;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
updateBotStyleDisplay(style) {
|
| 157 |
+
const styleElement = document.getElementById('botStyle');
|
| 158 |
+
if (styleElement) {
|
| 159 |
+
styleElement.textContent = style.charAt(0).toUpperCase() + style.slice(1);
|
| 160 |
+
styleElement.className = `badge ${style === 'nice' ? 'bg-success' : style === 'rude' ? 'bg-warning' : 'bg-secondary'}`;
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
updateStats() {
|
| 165 |
+
// Update advice count
|
| 166 |
+
const adviceCountElement = document.getElementById('adviceCount');
|
| 167 |
+
if (adviceCountElement) {
|
| 168 |
+
adviceCountElement.textContent = this.adviceCount;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// Update inversion count
|
| 172 |
+
const inversionCountElement = document.getElementById('inversionCount');
|
| 173 |
+
if (inversionCountElement) {
|
| 174 |
+
inversionCountElement.textContent = this.inversionCount;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
// Update base stats
|
| 178 |
+
super.updateStats();
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
sendMessage() {
|
| 182 |
+
const message = this.messageInput.value.trim();
|
| 183 |
+
if (!message) return;
|
| 184 |
+
|
| 185 |
+
// Rate limiting
|
| 186 |
+
const now = Date.now();
|
| 187 |
+
if (now - this.lastMessageTime < 1000) {
|
| 188 |
+
this.showSystemMessage('Slow down there! One advice request per second.', 'warning');
|
| 189 |
+
return;
|
| 190 |
+
}
|
| 191 |
+
this.lastMessageTime = now;
|
| 192 |
+
|
| 193 |
+
// Send message with advice mode indicator
|
| 194 |
+
this.socket.emit('send_message', {
|
| 195 |
+
message: message,
|
| 196 |
+
mode: 'advice'
|
| 197 |
+
});
|
| 198 |
+
|
| 199 |
+
// Clear input and update stats
|
| 200 |
+
this.messageInput.value = '';
|
| 201 |
+
this.messageCount++;
|
| 202 |
+
this.updateStats();
|
| 203 |
+
|
| 204 |
+
// Stop typing indicator
|
| 205 |
+
this.stopTyping();
|
| 206 |
+
|
| 207 |
+
// Show network activity
|
| 208 |
+
if (window.terminalEffects) {
|
| 209 |
+
window.terminalEffects.showNetworkActivity();
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Special handling for advice requests
|
| 213 |
+
if (message.includes('?') || message.toLowerCase().includes('advice')) {
|
| 214 |
+
// Show special typing indicator for advice
|
| 215 |
+
setTimeout(() => {
|
| 216 |
+
const typingIndicator = document.getElementById('typingIndicator');
|
| 217 |
+
if (typingIndicator) {
|
| 218 |
+
typingIndicator.innerHTML = '<i class="fas fa-brain"></i> AdviceBot is analyzing your tone and crafting response...';
|
| 219 |
+
typingIndicator.style.display = 'block';
|
| 220 |
+
}
|
| 221 |
+
}, 500);
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
showAdviceSystemMessage(message, type = 'info') {
|
| 226 |
+
const messageElement = document.createElement('div');
|
| 227 |
+
messageElement.className = 'chat-message system-message';
|
| 228 |
+
|
| 229 |
+
let iconClass = 'fas fa-flask';
|
| 230 |
+
let textClass = 'text-terminal-accent';
|
| 231 |
+
|
| 232 |
+
switch(type) {
|
| 233 |
+
case 'hypothesis':
|
| 234 |
+
iconClass = 'fas fa-microscope';
|
| 235 |
+
textClass = 'text-success';
|
| 236 |
+
break;
|
| 237 |
+
case 'inversion':
|
| 238 |
+
iconClass = 'fas fa-exchange-alt';
|
| 239 |
+
textClass = 'text-warning';
|
| 240 |
+
break;
|
| 241 |
+
case 'success':
|
| 242 |
+
iconClass = 'fas fa-check-circle';
|
| 243 |
+
textClass = 'text-success';
|
| 244 |
+
break;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
messageElement.innerHTML = `
|
| 248 |
+
<div class="message-content ${textClass}" style="border-left-color: currentColor;">
|
| 249 |
+
<i class="${iconClass} me-2"></i>${this.escapeHtml(message)}
|
| 250 |
+
</div>
|
| 251 |
+
`;
|
| 252 |
+
|
| 253 |
+
this.chatMessages.appendChild(messageElement);
|
| 254 |
+
this.scrollToBottom();
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Enhanced welcome message for advice mode
|
| 258 |
+
displayAdviceWelcome() {
|
| 259 |
+
setTimeout(() => {
|
| 260 |
+
this.showAdviceSystemMessage(
|
| 261 |
+
"Personality Inversion Protocol activated! Try being rude or nice to see how I adapt my advice style. 🧪",
|
| 262 |
+
'hypothesis'
|
| 263 |
+
);
|
| 264 |
+
}, 2000);
|
| 265 |
+
|
| 266 |
+
setTimeout(() => {
|
| 267 |
+
this.showAdviceSystemMessage(
|
| 268 |
+
"Pro tip: Ask about work, relationships, technology, health, or life in general!",
|
| 269 |
+
'inversion'
|
| 270 |
+
);
|
| 271 |
+
}, 4000);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
// Track engagement metrics for hypothesis testing
|
| 275 |
+
trackEngagement(action) {
|
| 276 |
+
const engagementData = {
|
| 277 |
+
timestamp: new Date().toISOString(),
|
| 278 |
+
action: action,
|
| 279 |
+
user_tone_history: this.userToneHistory.slice(-5), // Last 5 tones
|
| 280 |
+
bot_style_history: this.botStyleHistory.slice(-5), // Last 5 styles
|
| 281 |
+
total_advice_requests: this.adviceCount,
|
| 282 |
+
total_inversions: this.inversionCount
|
| 283 |
+
};
|
| 284 |
+
|
| 285 |
+
// This could be sent to analytics
|
| 286 |
+
console.log('Engagement tracked:', engagementData);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
// Override connect method to set up advice-specific features
|
| 290 |
+
connect(username) {
|
| 291 |
+
super.connect(username);
|
| 292 |
+
|
| 293 |
+
// Set up advice mode specific event listeners
|
| 294 |
+
this.socket.on('advice_response', (data) => {
|
| 295 |
+
this.trackEngagement('advice_received');
|
| 296 |
+
// Handle special advice responses
|
| 297 |
+
});
|
| 298 |
+
|
| 299 |
+
// Show welcome messages after connection
|
| 300 |
+
this.socket.on('connect', () => {
|
| 301 |
+
setTimeout(() => {
|
| 302 |
+
this.displayAdviceWelcome();
|
| 303 |
+
}, 3000);
|
| 304 |
+
});
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
// Initialize advice chat
|
| 309 |
+
let adviceChat;
|
| 310 |
+
|
| 311 |
+
function initializeAdviceChat(username) {
|
| 312 |
+
adviceChat = new AdviceChat();
|
| 313 |
+
adviceChat.connectToChat(username);
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// Expose globally
|
| 317 |
+
window.initializeAdviceChat = initializeAdviceChat;
|
| 318 |
+
window.AdviceChat = AdviceChat;
|
static/js/chat.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Chat Module - Handles real-time messaging with Socket.IO
|
| 3 |
+
* The heart of our sarcastic British chat experience
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
class SarcasticChat {
|
| 7 |
+
constructor() {
|
| 8 |
+
this.socket = null;
|
| 9 |
+
this.username = null;
|
| 10 |
+
this.messageCount = 0;
|
| 11 |
+
this.botResponseCount = 0;
|
| 12 |
+
this.isTyping = false;
|
| 13 |
+
this.typingTimer = null;
|
| 14 |
+
this.lastMessageTime = 0;
|
| 15 |
+
this.init();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
init() {
|
| 19 |
+
this.initializeElements();
|
| 20 |
+
this.setupEventListeners();
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
initializeElements() {
|
| 24 |
+
this.chatMessages = document.getElementById('chatMessages');
|
| 25 |
+
this.messageInput = document.getElementById('messageInput');
|
| 26 |
+
this.messageForm = document.getElementById('messageForm');
|
| 27 |
+
this.typingIndicator = document.getElementById('typingIndicator');
|
| 28 |
+
this.userCountElement = document.getElementById('userCount');
|
| 29 |
+
this.messageCountElement = document.getElementById('messageCount');
|
| 30 |
+
this.botResponseCountElement = document.getElementById('botResponseCount');
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
setupEventListeners() {
|
| 34 |
+
// Message form submission
|
| 35 |
+
this.messageForm.addEventListener('submit', (e) => {
|
| 36 |
+
e.preventDefault();
|
| 37 |
+
this.sendMessage();
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
// Typing indicator
|
| 41 |
+
this.messageInput.addEventListener('input', () => {
|
| 42 |
+
this.handleTyping();
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
// Enter key handling
|
| 46 |
+
this.messageInput.addEventListener('keypress', (e) => {
|
| 47 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 48 |
+
e.preventDefault();
|
| 49 |
+
this.sendMessage();
|
| 50 |
+
}
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Focus input when page loads
|
| 54 |
+
this.messageInput.focus();
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
connect(username) {
|
| 58 |
+
this.username = username;
|
| 59 |
+
|
| 60 |
+
// Show connection modal
|
| 61 |
+
const connectionModal = new bootstrap.Modal(document.getElementById('connectionModal'));
|
| 62 |
+
connectionModal.show();
|
| 63 |
+
|
| 64 |
+
// Initialize Socket.IO connection
|
| 65 |
+
this.socket = io();
|
| 66 |
+
|
| 67 |
+
this.socket.on('connect', () => {
|
| 68 |
+
console.log('Connected to server');
|
| 69 |
+
this.joinChat();
|
| 70 |
+
connectionModal.hide();
|
| 71 |
+
|
| 72 |
+
// Show success effect
|
| 73 |
+
if (window.terminalEffects) {
|
| 74 |
+
window.terminalEffects.successFlash();
|
| 75 |
+
window.terminalEffects.showNetworkActivity();
|
| 76 |
+
}
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
this.socket.on('disconnect', () => {
|
| 80 |
+
console.log('Disconnected from server');
|
| 81 |
+
this.showSystemMessage('Connection lost. Attempting to reconnect...', 'warning');
|
| 82 |
+
});
|
| 83 |
+
|
| 84 |
+
this.socket.on('new_message', (data) => {
|
| 85 |
+
this.displayMessage(data);
|
| 86 |
+
if (data.is_bot) {
|
| 87 |
+
this.botResponseCount++;
|
| 88 |
+
this.updateStats();
|
| 89 |
+
}
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
this.socket.on('user_joined', (data) => {
|
| 93 |
+
this.showSystemMessage(`${data.username} joined the chat`, 'info');
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
this.socket.on('user_left', (data) => {
|
| 97 |
+
this.showSystemMessage(`${data.username} left the chat`, 'info');
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
this.socket.on('user_typing', (data) => {
|
| 101 |
+
this.showTypingIndicator(data.username, data.is_typing);
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
this.socket.on('chat_history', (messages) => {
|
| 105 |
+
this.loadChatHistory(messages);
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
this.socket.on('connect_error', (error) => {
|
| 109 |
+
console.error('Connection error:', error);
|
| 110 |
+
this.showSystemMessage('Failed to connect to chat server', 'error');
|
| 111 |
+
if (window.terminalEffects) {
|
| 112 |
+
window.terminalEffects.errorFlash();
|
| 113 |
+
}
|
| 114 |
+
});
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
joinChat() {
|
| 118 |
+
this.socket.emit('join_chat', {
|
| 119 |
+
username: this.username,
|
| 120 |
+
room: 'general'
|
| 121 |
+
});
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
sendMessage() {
|
| 125 |
+
const message = this.messageInput.value.trim();
|
| 126 |
+
if (!message) return;
|
| 127 |
+
|
| 128 |
+
// Rate limiting - prevent spam
|
| 129 |
+
const now = Date.now();
|
| 130 |
+
if (now - this.lastMessageTime < 1000) {
|
| 131 |
+
this.showSystemMessage('Slow down there, speed demon! One message per second.', 'warning');
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
this.lastMessageTime = now;
|
| 135 |
+
|
| 136 |
+
// Send message via socket
|
| 137 |
+
this.socket.emit('send_message', { message });
|
| 138 |
+
|
| 139 |
+
// Clear input and update stats
|
| 140 |
+
this.messageInput.value = '';
|
| 141 |
+
this.messageCount++;
|
| 142 |
+
this.updateStats();
|
| 143 |
+
|
| 144 |
+
// Stop typing indicator
|
| 145 |
+
this.stopTyping();
|
| 146 |
+
|
| 147 |
+
// Show network activity
|
| 148 |
+
if (window.terminalEffects) {
|
| 149 |
+
window.terminalEffects.showNetworkActivity();
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
// Check for Easter eggs
|
| 153 |
+
this.checkEasterEggs(message);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
displayMessage(messageData) {
|
| 157 |
+
const messageElement = document.createElement('div');
|
| 158 |
+
messageElement.className = `chat-message ${messageData.is_bot ? 'bot-message' : ''}`;
|
| 159 |
+
|
| 160 |
+
const avatarIcon = messageData.is_bot ?
|
| 161 |
+
'<i class="fas fa-robot text-terminal-accent"></i>' :
|
| 162 |
+
'<i class="fas fa-user text-terminal-green"></i>';
|
| 163 |
+
|
| 164 |
+
messageElement.innerHTML = `
|
| 165 |
+
<div class="message-header">
|
| 166 |
+
${avatarIcon}
|
| 167 |
+
<span class="message-username ${messageData.is_bot ? 'text-terminal-accent' : 'text-terminal-green'}">${this.escapeHtml(messageData.username)}</span>
|
| 168 |
+
<span class="message-timestamp">${messageData.timestamp}</span>
|
| 169 |
+
</div>
|
| 170 |
+
<div class="message-content">
|
| 171 |
+
${this.formatMessage(messageData.message)}
|
| 172 |
+
</div>
|
| 173 |
+
`;
|
| 174 |
+
|
| 175 |
+
// Add with animation
|
| 176 |
+
this.chatMessages.appendChild(messageElement);
|
| 177 |
+
|
| 178 |
+
// Power-on effect for bot messages
|
| 179 |
+
if (messageData.is_bot && window.terminalEffects) {
|
| 180 |
+
window.terminalEffects.powerOnEffect(messageElement);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
// Scroll to bottom
|
| 184 |
+
this.scrollToBottom();
|
| 185 |
+
|
| 186 |
+
// Add glitch effect occasionally
|
| 187 |
+
if (Math.random() < 0.05) {
|
| 188 |
+
setTimeout(() => {
|
| 189 |
+
if (window.terminalEffects) {
|
| 190 |
+
window.terminalEffects.triggerGlitch();
|
| 191 |
+
}
|
| 192 |
+
}, 500);
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
formatMessage(message) {
|
| 197 |
+
// Escape HTML first
|
| 198 |
+
let formatted = this.escapeHtml(message);
|
| 199 |
+
|
| 200 |
+
// Add emoji support for common patterns
|
| 201 |
+
formatted = formatted.replace(/:\)/g, '😊');
|
| 202 |
+
formatted = formatted.replace(/:\(/g, '😔');
|
| 203 |
+
formatted = formatted.replace(/:D/g, '😄');
|
| 204 |
+
formatted = formatted.replace(/;-?\)/g, '😉');
|
| 205 |
+
formatted = formatted.replace(/🫖/g, '🫖'); // Keep tea emoji
|
| 206 |
+
|
| 207 |
+
// Highlight British terms
|
| 208 |
+
const britishTerms = [
|
| 209 |
+
'brilliant', 'lovely', 'quite', 'rather', 'blimey',
|
| 210 |
+
'right then', 'I say', 'charming', 'queue', 'tea'
|
| 211 |
+
];
|
| 212 |
+
|
| 213 |
+
britishTerms.forEach(term => {
|
| 214 |
+
const regex = new RegExp(`\\b${term}\\b`, 'gi');
|
| 215 |
+
formatted = formatted.replace(regex, `<span class="text-terminal-green">${term}</span>`);
|
| 216 |
+
});
|
| 217 |
+
|
| 218 |
+
return formatted;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
showSystemMessage(message, type = 'info') {
|
| 222 |
+
const messageElement = document.createElement('div');
|
| 223 |
+
messageElement.className = 'chat-message system-message';
|
| 224 |
+
|
| 225 |
+
let iconClass = 'fas fa-info-circle';
|
| 226 |
+
let textClass = 'text-terminal-accent';
|
| 227 |
+
|
| 228 |
+
switch(type) {
|
| 229 |
+
case 'warning':
|
| 230 |
+
iconClass = 'fas fa-exclamation-triangle';
|
| 231 |
+
textClass = 'text-warning';
|
| 232 |
+
break;
|
| 233 |
+
case 'error':
|
| 234 |
+
iconClass = 'fas fa-times-circle';
|
| 235 |
+
textClass = 'text-danger';
|
| 236 |
+
break;
|
| 237 |
+
case 'success':
|
| 238 |
+
iconClass = 'fas fa-check-circle';
|
| 239 |
+
textClass = 'text-success';
|
| 240 |
+
break;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
messageElement.innerHTML = `
|
| 244 |
+
<div class="message-content ${textClass}" style="border-left-color: currentColor;">
|
| 245 |
+
<i class="${iconClass} me-2"></i>${this.escapeHtml(message)}
|
| 246 |
+
</div>
|
| 247 |
+
`;
|
| 248 |
+
|
| 249 |
+
this.chatMessages.appendChild(messageElement);
|
| 250 |
+
this.scrollToBottom();
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
handleTyping() {
|
| 254 |
+
if (!this.isTyping) {
|
| 255 |
+
this.isTyping = true;
|
| 256 |
+
this.socket.emit('typing', { is_typing: true });
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// Clear existing timer
|
| 260 |
+
if (this.typingTimer) {
|
| 261 |
+
clearTimeout(this.typingTimer);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
// Set new timer
|
| 265 |
+
this.typingTimer = setTimeout(() => {
|
| 266 |
+
this.stopTyping();
|
| 267 |
+
}, 2000);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
stopTyping() {
|
| 271 |
+
if (this.isTyping) {
|
| 272 |
+
this.isTyping = false;
|
| 273 |
+
this.socket.emit('typing', { is_typing: false });
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
if (this.typingTimer) {
|
| 277 |
+
clearTimeout(this.typingTimer);
|
| 278 |
+
this.typingTimer = null;
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
showTypingIndicator(username, isTyping) {
|
| 283 |
+
if (isTyping) {
|
| 284 |
+
this.typingIndicator.innerHTML = `<i class="fas fa-ellipsis-h"></i> ${this.escapeHtml(username)} is typing...`;
|
| 285 |
+
this.typingIndicator.style.display = 'block';
|
| 286 |
+
} else {
|
| 287 |
+
this.typingIndicator.style.display = 'none';
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
loadChatHistory(messages) {
|
| 292 |
+
// Clear existing messages except system welcome
|
| 293 |
+
const systemMessages = this.chatMessages.querySelectorAll('.system-message, .fade-in');
|
| 294 |
+
this.chatMessages.innerHTML = '';
|
| 295 |
+
|
| 296 |
+
// Re-add system messages
|
| 297 |
+
systemMessages.forEach(msg => this.chatMessages.appendChild(msg));
|
| 298 |
+
|
| 299 |
+
// Add chat history
|
| 300 |
+
messages.forEach(message => this.displayMessage(message));
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
updateStats() {
|
| 304 |
+
if (this.messageCountElement) {
|
| 305 |
+
this.messageCountElement.textContent = this.messageCount;
|
| 306 |
+
}
|
| 307 |
+
if (this.botResponseCountElement) {
|
| 308 |
+
this.botResponseCountElement.textContent = this.botResponseCount;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
// Update sarcasm level based on bot responses
|
| 312 |
+
const sarcasmBar = document.getElementById('sarcasmLevel');
|
| 313 |
+
if (sarcasmBar && this.botResponseCount > 0) {
|
| 314 |
+
const level = Math.min(85 + (this.botResponseCount * 2), 100);
|
| 315 |
+
sarcasmBar.style.width = `${level}%`;
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
checkEasterEggs(message) {
|
| 320 |
+
const lowerMessage = message.toLowerCase();
|
| 321 |
+
|
| 322 |
+
// Tea time easter egg
|
| 323 |
+
if (lowerMessage.includes('tea time') || lowerMessage.includes('teatime')) {
|
| 324 |
+
setTimeout(() => {
|
| 325 |
+
this.showSystemMessage('🫖 TEA TIME DETECTED! The bot approves greatly!', 'success');
|
| 326 |
+
}, 1000);
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
// Queue easter egg
|
| 330 |
+
if (lowerMessage.includes('queue')) {
|
| 331 |
+
setTimeout(() => {
|
| 332 |
+
this.showSystemMessage('🇬🇧 Proper queuing etiquette detected. Carry on!', 'success');
|
| 333 |
+
}, 1500);
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// Sarcasm level easter egg
|
| 337 |
+
if (lowerMessage.includes('sarcasm') || lowerMessage.includes('sarcastic')) {
|
| 338 |
+
setTimeout(() => {
|
| 339 |
+
this.showSystemMessage('Did someone mention sarcasm? How terribly meta.', 'info');
|
| 340 |
+
}, 2000);
|
| 341 |
+
}
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
scrollToBottom() {
|
| 345 |
+
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
escapeHtml(text) {
|
| 349 |
+
const div = document.createElement('div');
|
| 350 |
+
div.textContent = text;
|
| 351 |
+
return div.innerHTML;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// Public method to connect from external script
|
| 355 |
+
connectToChat(username) {
|
| 356 |
+
this.connect(username);
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
// Initialize chat functionality
|
| 361 |
+
let sarcasticChat;
|
| 362 |
+
|
| 363 |
+
function initializeChat(username) {
|
| 364 |
+
sarcasticChat = new SarcasticChat();
|
| 365 |
+
sarcasticChat.connectToChat(username);
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
// Boot sequence when page loads
|
| 369 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 370 |
+
// Add some terminal boot messages for fun
|
| 371 |
+
const bootMessages = [
|
| 372 |
+
'Initializing sarcasm protocols...',
|
| 373 |
+
'Loading British wit database...',
|
| 374 |
+
'Calibrating eye-roll mechanisms...',
|
| 375 |
+
'Connecting to tea server...',
|
| 376 |
+
'Queue management system: ONLINE',
|
| 377 |
+
'Deploying devastating humor...',
|
| 378 |
+
'Ready for maximum sarcasm!'
|
| 379 |
+
];
|
| 380 |
+
|
| 381 |
+
// Only show boot sequence on chat page
|
| 382 |
+
if (window.location.pathname === '/chat') {
|
| 383 |
+
const container = document.querySelector('.container-fluid');
|
| 384 |
+
if (container && window.terminalEffects) {
|
| 385 |
+
// Small delay to let other effects initialize
|
| 386 |
+
setTimeout(() => {
|
| 387 |
+
window.terminalEffects.bootSequence(container, bootMessages, () => {
|
| 388 |
+
console.log('Chat system ready for maximum British sarcasm!');
|
| 389 |
+
});
|
| 390 |
+
}, 1000);
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
});
|
| 394 |
+
|
| 395 |
+
// Expose globally
|
| 396 |
+
window.initializeChat = initializeChat;
|
| 397 |
+
window.SarcasticChat = SarcasticChat;
|
static/js/terminal-effects.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Terminal Effects Module
|
| 3 |
+
* Handles all the aesthetic terminal animations and effects
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
class TerminalEffects {
|
| 7 |
+
constructor() {
|
| 8 |
+
this.isGlitching = false;
|
| 9 |
+
this.scanlineInterval = null;
|
| 10 |
+
this.init();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
init() {
|
| 14 |
+
this.createScanlines();
|
| 15 |
+
this.initCursorBlink();
|
| 16 |
+
this.initRandomGlitches();
|
| 17 |
+
this.initTypingEffects();
|
| 18 |
+
this.initMatrixRain();
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// Create CRT scanline effect
|
| 22 |
+
createScanlines() {
|
| 23 |
+
const scanlines = document.createElement('div');
|
| 24 |
+
scanlines.className = 'scanlines';
|
| 25 |
+
scanlines.innerHTML = Array(50).fill().map((_, i) =>
|
| 26 |
+
`<div class="scanline" style="top: ${i * 2}%"></div>`
|
| 27 |
+
).join('');
|
| 28 |
+
|
| 29 |
+
const style = document.createElement('style');
|
| 30 |
+
style.textContent = `
|
| 31 |
+
.scanlines {
|
| 32 |
+
position: fixed;
|
| 33 |
+
top: 0;
|
| 34 |
+
left: 0;
|
| 35 |
+
right: 0;
|
| 36 |
+
bottom: 0;
|
| 37 |
+
pointer-events: none;
|
| 38 |
+
z-index: 1000;
|
| 39 |
+
opacity: 0.03;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.scanline {
|
| 43 |
+
position: absolute;
|
| 44 |
+
left: 0;
|
| 45 |
+
right: 0;
|
| 46 |
+
height: 1px;
|
| 47 |
+
background: #39ff14;
|
| 48 |
+
animation: scanline-flicker 0.1s infinite alternate;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
@keyframes scanline-flicker {
|
| 52 |
+
0% { opacity: 0.8; }
|
| 53 |
+
100% { opacity: 0.2; }
|
| 54 |
+
}
|
| 55 |
+
`;
|
| 56 |
+
|
| 57 |
+
document.head.appendChild(style);
|
| 58 |
+
document.body.appendChild(scanlines);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// Enhanced cursor blinking
|
| 62 |
+
initCursorBlink() {
|
| 63 |
+
const cursors = document.querySelectorAll('.brand-cursor');
|
| 64 |
+
cursors.forEach(cursor => {
|
| 65 |
+
setInterval(() => {
|
| 66 |
+
cursor.style.opacity = cursor.style.opacity === '0' ? '1' : '0';
|
| 67 |
+
}, 500 + Math.random() * 200); // Slight randomness
|
| 68 |
+
});
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Random glitch effects
|
| 72 |
+
initRandomGlitches() {
|
| 73 |
+
setInterval(() => {
|
| 74 |
+
if (Math.random() < 0.02 && !this.isGlitching) { // 2% chance
|
| 75 |
+
this.triggerGlitch();
|
| 76 |
+
}
|
| 77 |
+
}, 1000);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
triggerGlitch() {
|
| 81 |
+
if (this.isGlitching) return;
|
| 82 |
+
|
| 83 |
+
this.isGlitching = true;
|
| 84 |
+
const elements = document.querySelectorAll('.terminal-card, .chat-message');
|
| 85 |
+
const randomElement = elements[Math.floor(Math.random() * elements.length)];
|
| 86 |
+
|
| 87 |
+
if (randomElement) {
|
| 88 |
+
const originalTransform = randomElement.style.transform;
|
| 89 |
+
const glitchIntensity = Math.random() * 5;
|
| 90 |
+
|
| 91 |
+
// Apply glitch effect
|
| 92 |
+
randomElement.style.filter = `hue-rotate(${Math.random() * 360}deg) saturate(2)`;
|
| 93 |
+
randomElement.style.transform = `translateX(${Math.random() * glitchIntensity - glitchIntensity/2}px) skew(${Math.random() * 2}deg)`;
|
| 94 |
+
|
| 95 |
+
// Create text shadow glitch
|
| 96 |
+
randomElement.style.textShadow = `
|
| 97 |
+
${Math.random() * 10 - 5}px 0 #ff0000,
|
| 98 |
+
${Math.random() * 10 - 5}px 0 #00ff00,
|
| 99 |
+
${Math.random() * 10 - 5}px 0 #0000ff
|
| 100 |
+
`;
|
| 101 |
+
|
| 102 |
+
setTimeout(() => {
|
| 103 |
+
randomElement.style.filter = '';
|
| 104 |
+
randomElement.style.transform = originalTransform;
|
| 105 |
+
randomElement.style.textShadow = '';
|
| 106 |
+
this.isGlitching = false;
|
| 107 |
+
}, 50 + Math.random() * 100);
|
| 108 |
+
} else {
|
| 109 |
+
this.isGlitching = false;
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Typing animation for new elements
|
| 114 |
+
typeWriter(element, text, speed = 50, callback = null) {
|
| 115 |
+
let i = 0;
|
| 116 |
+
element.textContent = '';
|
| 117 |
+
|
| 118 |
+
const timer = setInterval(() => {
|
| 119 |
+
if (i < text.length) {
|
| 120 |
+
element.textContent += text.charAt(i);
|
| 121 |
+
i++;
|
| 122 |
+
|
| 123 |
+
// Add random typing delays
|
| 124 |
+
if (Math.random() < 0.1) {
|
| 125 |
+
clearInterval(timer);
|
| 126 |
+
setTimeout(() => {
|
| 127 |
+
this.typeWriter(element, text.substring(i), speed, callback);
|
| 128 |
+
}, speed * 2);
|
| 129 |
+
return;
|
| 130 |
+
}
|
| 131 |
+
} else {
|
| 132 |
+
clearInterval(timer);
|
| 133 |
+
if (callback) callback();
|
| 134 |
+
}
|
| 135 |
+
}, speed);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
initTypingEffects() {
|
| 139 |
+
// Apply to elements that should have typing effect
|
| 140 |
+
const typeElements = document.querySelectorAll('.typewriter-effect');
|
| 141 |
+
typeElements.forEach(element => {
|
| 142 |
+
const text = element.textContent;
|
| 143 |
+
this.typeWriter(element, text, 30);
|
| 144 |
+
});
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
// Matrix-style rain effect (subtle)
|
| 148 |
+
initMatrixRain() {
|
| 149 |
+
const canvas = document.createElement('canvas');
|
| 150 |
+
canvas.style.position = 'fixed';
|
| 151 |
+
canvas.style.top = '0';
|
| 152 |
+
canvas.style.left = '0';
|
| 153 |
+
canvas.style.width = '100%';
|
| 154 |
+
canvas.style.height = '100%';
|
| 155 |
+
canvas.style.pointerEvents = 'none';
|
| 156 |
+
canvas.style.zIndex = '-2';
|
| 157 |
+
canvas.style.opacity = '0.03';
|
| 158 |
+
|
| 159 |
+
const ctx = canvas.getContext('2d');
|
| 160 |
+
canvas.width = window.innerWidth;
|
| 161 |
+
canvas.height = window.innerHeight;
|
| 162 |
+
|
| 163 |
+
const matrix = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789@#$%^&*()*&^%+-/~{[|`]}";
|
| 164 |
+
const matrixArray = matrix.split("");
|
| 165 |
+
|
| 166 |
+
const fontSize = 10;
|
| 167 |
+
const columns = canvas.width / fontSize;
|
| 168 |
+
const drops = [];
|
| 169 |
+
|
| 170 |
+
for (let x = 0; x < columns; x++) {
|
| 171 |
+
drops[x] = 1;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
function draw() {
|
| 175 |
+
ctx.fillStyle = 'rgba(13, 17, 23, 0.04)';
|
| 176 |
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 177 |
+
|
| 178 |
+
ctx.fillStyle = '#39ff14';
|
| 179 |
+
ctx.font = fontSize + 'px monospace';
|
| 180 |
+
|
| 181 |
+
for (let i = 0; i < drops.length; i++) {
|
| 182 |
+
const text = matrixArray[Math.floor(Math.random() * matrixArray.length)];
|
| 183 |
+
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
|
| 184 |
+
|
| 185 |
+
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
|
| 186 |
+
drops[i] = 0;
|
| 187 |
+
}
|
| 188 |
+
drops[i]++;
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
document.body.appendChild(canvas);
|
| 193 |
+
setInterval(draw, 150);
|
| 194 |
+
|
| 195 |
+
// Resize canvas on window resize
|
| 196 |
+
window.addEventListener('resize', () => {
|
| 197 |
+
canvas.width = window.innerWidth;
|
| 198 |
+
canvas.height = window.innerHeight;
|
| 199 |
+
});
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
// Power-on effect for new elements
|
| 203 |
+
powerOnEffect(element) {
|
| 204 |
+
element.style.opacity = '0';
|
| 205 |
+
element.style.transform = 'scale(0.8) rotateX(20deg)';
|
| 206 |
+
element.style.filter = 'brightness(0)';
|
| 207 |
+
|
| 208 |
+
setTimeout(() => {
|
| 209 |
+
element.style.transition = 'all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
| 210 |
+
element.style.opacity = '1';
|
| 211 |
+
element.style.transform = 'scale(1) rotateX(0deg)';
|
| 212 |
+
element.style.filter = 'brightness(1)';
|
| 213 |
+
|
| 214 |
+
// Add a brief glow effect
|
| 215 |
+
element.style.boxShadow = '0 0 20px var(--terminal-green-glow)';
|
| 216 |
+
setTimeout(() => {
|
| 217 |
+
element.style.boxShadow = '';
|
| 218 |
+
}, 500);
|
| 219 |
+
}, 100);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
// Boot sequence effect
|
| 223 |
+
bootSequence(container, messages, onComplete) {
|
| 224 |
+
let messageIndex = 0;
|
| 225 |
+
const bootContainer = document.createElement('div');
|
| 226 |
+
bootContainer.className = 'boot-sequence';
|
| 227 |
+
bootContainer.innerHTML = `
|
| 228 |
+
<div class="terminal-window">
|
| 229 |
+
<div class="terminal-header">
|
| 230 |
+
<div class="terminal-dots">
|
| 231 |
+
<div class="terminal-dot red"></div>
|
| 232 |
+
<div class="terminal-dot yellow"></div>
|
| 233 |
+
<div class="terminal-dot green"></div>
|
| 234 |
+
</div>
|
| 235 |
+
<h6 class="terminal-title">system_boot.log</h6>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="boot-content bg-dark p-3" style="font-family: var(--terminal-font); min-height: 200px;">
|
| 238 |
+
<div class="boot-messages"></div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
`;
|
| 242 |
+
|
| 243 |
+
container.appendChild(bootContainer);
|
| 244 |
+
const messagesContainer = bootContainer.querySelector('.boot-messages');
|
| 245 |
+
|
| 246 |
+
function addMessage() {
|
| 247 |
+
if (messageIndex < messages.length) {
|
| 248 |
+
const messageDiv = document.createElement('div');
|
| 249 |
+
messageDiv.className = 'text-terminal-green';
|
| 250 |
+
messageDiv.innerHTML = `<span class="text-muted">></span> ${messages[messageIndex]}`;
|
| 251 |
+
messagesContainer.appendChild(messageDiv);
|
| 252 |
+
|
| 253 |
+
// Scroll to bottom
|
| 254 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 255 |
+
|
| 256 |
+
messageIndex++;
|
| 257 |
+
setTimeout(addMessage, 300 + Math.random() * 200);
|
| 258 |
+
} else {
|
| 259 |
+
setTimeout(() => {
|
| 260 |
+
container.removeChild(bootContainer);
|
| 261 |
+
if (onComplete) onComplete();
|
| 262 |
+
}, 1000);
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
setTimeout(addMessage, 500);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
// Network activity indicator
|
| 270 |
+
showNetworkActivity() {
|
| 271 |
+
const indicator = document.querySelector('.status-indicator');
|
| 272 |
+
if (indicator) {
|
| 273 |
+
indicator.style.background = '#ffbd2e';
|
| 274 |
+
indicator.style.animation = 'pulse-glow 0.3s infinite';
|
| 275 |
+
|
| 276 |
+
setTimeout(() => {
|
| 277 |
+
indicator.style.background = 'var(--terminal-green)';
|
| 278 |
+
indicator.style.animation = 'pulse-glow 2s infinite';
|
| 279 |
+
}, 1000);
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
// Error flash effect
|
| 284 |
+
errorFlash() {
|
| 285 |
+
document.body.style.backgroundColor = '#ff5f5610';
|
| 286 |
+
setTimeout(() => {
|
| 287 |
+
document.body.style.backgroundColor = '';
|
| 288 |
+
}, 150);
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
// Success flash effect
|
| 292 |
+
successFlash() {
|
| 293 |
+
document.body.style.backgroundColor = '#39ff1410';
|
| 294 |
+
setTimeout(() => {
|
| 295 |
+
document.body.style.backgroundColor = '';
|
| 296 |
+
}, 150);
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
// Initialize terminal effects when DOM is loaded
|
| 301 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 302 |
+
window.terminalEffects = new TerminalEffects();
|
| 303 |
+
});
|
| 304 |
+
|
| 305 |
+
// Expose for use in other modules
|
| 306 |
+
window.TerminalEffects = TerminalEffects;
|
templates/base.html
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}Multi-Personality Bot{% endblock %}</title>
|
| 7 |
+
|
| 8 |
+
<!-- Bootstrap 5 -->
|
| 9 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 10 |
+
|
| 11 |
+
<!-- Material Design 3 -->
|
| 12 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 13 |
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
| 14 |
+
|
| 15 |
+
<!-- Font Awesome - Multiple CDN Sources -->
|
| 16 |
+
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.5.1/css/all.css" crossorigin="anonymous">
|
| 17 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous">
|
| 18 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css">
|
| 19 |
+
<!-- Bootstrap Icons as backup -->
|
| 20 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
| 21 |
+
|
| 22 |
+
<!-- Primer CSS Components -->
|
| 23 |
+
<link href="https://unpkg.com/@primer/css@^20.2.4/dist/primer.css" rel="stylesheet">
|
| 24 |
+
|
| 25 |
+
<!-- Terminal Font -->
|
| 26 |
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 27 |
+
|
| 28 |
+
<!-- Custom Styles -->
|
| 29 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/terminal.css') }}">
|
| 30 |
+
|
| 31 |
+
<meta name="theme-color" content="#0d1117">
|
| 32 |
+
</head>
|
| 33 |
+
<body class="bg-dark text-light">
|
| 34 |
+
<!-- Glitch overlay for aesthetic -->
|
| 35 |
+
<div class="glitch-overlay"></div>
|
| 36 |
+
|
| 37 |
+
<!-- Navigation -->
|
| 38 |
+
<nav class="navbar navbar-dark terminal-nav">
|
| 39 |
+
<div class="container-fluid">
|
| 40 |
+
<a class="navbar-brand terminal-brand" href="/">
|
| 41 |
+
<i class="fas fa-robot text-success me-2" aria-hidden="true"></i>
|
| 42 |
+
<span class="brand-text">Multi-Personality Bot</span>
|
| 43 |
+
<span class="brand-cursor">_</span>
|
| 44 |
+
</a>
|
| 45 |
+
<div class="mx-auto d-flex align-items-center">
|
| 46 |
+
<img src="{{ url_for('static', filename='img/maximally.png') }}" alt="Maximally" height="32" class="me-2">
|
| 47 |
+
<span class="terminal-title" style="font-size: 1.5rem;">Maximally</span>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="navbar-nav ms-auto">
|
| 50 |
+
<div class="terminal-status">
|
| 51 |
+
<span class="status-indicator"></span>
|
| 52 |
+
<span class="status-text">ONLINE</span>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</nav>
|
| 57 |
+
|
| 58 |
+
<!-- Main Content -->
|
| 59 |
+
<main class="container-fluid terminal-main">
|
| 60 |
+
{% block content %}{% endblock %}
|
| 61 |
+
</main>
|
| 62 |
+
|
| 63 |
+
<!-- Footer -->
|
| 64 |
+
<footer class="terminal-footer">
|
| 65 |
+
<div class="container-fluid">
|
| 66 |
+
<div class="row">
|
| 67 |
+
<div class="col-12 text-center">
|
| 68 |
+
<small class="text-muted terminal-text">
|
| 69 |
+
> Built for Code Hypothesis Hackathon // Testing: "Which bot personality generates the most engagement?"
|
| 70 |
+
<span class="text-success">✓ HYPOTHESIS_ACTIVE</span>
|
| 71 |
+
</small>
|
| 72 |
+
<div class="mt-2">
|
| 73 |
+
<div class="terminal-window d-inline-block">
|
| 74 |
+
<div class="bg-dark p-2 text-start">
|
| 75 |
+
<div class="text-terminal-green">
|
| 76 |
+
<i class="fas fa-code me-2"></i>
|
| 77 |
+
<span class="text-muted">developer@hackathon:~$</span> whoami<br>
|
| 78 |
+
<span class="text-terminal-accent">Designed & developed by</span> <strong class="text-success">Aradhya Pavan H S</strong>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</footer>
|
| 87 |
+
|
| 88 |
+
<!-- Bootstrap JS -->
|
| 89 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
| 90 |
+
|
| 91 |
+
<!-- Socket.IO -->
|
| 92 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
|
| 93 |
+
|
| 94 |
+
<!-- Custom Scripts -->
|
| 95 |
+
<script src="{{ url_for('static', filename='js/terminal-effects.js') }}"></script>
|
| 96 |
+
|
| 97 |
+
{% block scripts %}{% endblock %}
|
| 98 |
+
</body>
|
| 99 |
+
</html>
|
templates/chat_personality.html
ADDED
|
@@ -0,0 +1,1627 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}{{ personality_type.title() }} Bot - Chat{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container-fluid">
|
| 7 |
+
<div class="row">
|
| 8 |
+
<!-- Chat Area -->
|
| 9 |
+
<div class="col-lg-8 col-md-7">
|
| 10 |
+
<div class="terminal-card h-100">
|
| 11 |
+
<!-- Chat Header -->
|
| 12 |
+
<div class="terminal-header">
|
| 13 |
+
<div class="terminal-title">
|
| 14 |
+
<span class="terminal-icon">{{ personality_config.icon }}</span>
|
| 15 |
+
<span class="terminal-text">{{ personality_config.name }}</span>
|
| 16 |
+
<span class="terminal-subtitle">{{ personality_config.description }}</span>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="terminal-controls">
|
| 19 |
+
<span class="control-dot minimize" title="Minimize"></span>
|
| 20 |
+
<span class="control-dot maximize" title="Maximize"></span>
|
| 21 |
+
<span class="control-dot close" title="Close"></span>
|
| 22 |
+
</div>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<!-- Bot Status Bar -->
|
| 26 |
+
<div class="bot-status-bar">
|
| 27 |
+
<div class="status-info">
|
| 28 |
+
<span class="status-indicator"></span>
|
| 29 |
+
<span class="status-text">Online & Ready</span>
|
| 30 |
+
</div>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<!-- Chat Messages -->
|
| 34 |
+
<div class="chat-messages" id="chatMessages">
|
| 35 |
+
<div class="welcome-message text-center py-4">
|
| 36 |
+
<div class="terminal-window p-4 mx-auto" style="max-width: 600px;">
|
| 37 |
+
<div class="text-terminal-green mb-3">
|
| 38 |
+
<i class="fas fa-robot fs-2"></i>
|
| 39 |
+
</div>
|
| 40 |
+
<h6 class="text-terminal-accent mb-3">Welcome to {{ personality_config.name }}!</h6>
|
| 41 |
+
<div class="examples-section p-3 bg-dark rounded border border-secondary mb-3" id="examples-container">
|
| 42 |
+
<h6 class="text-terminal-green mb-3 text-center">
|
| 43 |
+
💬 Quick Examples - Click any to try
|
| 44 |
+
</h6>
|
| 45 |
+
|
| 46 |
+
<!-- Compact example prompts using full width -->
|
| 47 |
+
<div class="example-prompts-compact" id="example-prompts">
|
| 48 |
+
{% if personality_config.examples %}
|
| 49 |
+
{% for example in personality_config.examples %}
|
| 50 |
+
<button class="example-btn" onclick="setMessageAndSend(`{{ example }}`)">
|
| 51 |
+
{{ example }}
|
| 52 |
+
</button>
|
| 53 |
+
{% endfor %}
|
| 54 |
+
{% else %}
|
| 55 |
+
<p class="text-muted small">Example prompts will appear here</p>
|
| 56 |
+
{% endif %}
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<p class="text-light small mb-0">Or type your own message below!</p>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<!-- Message Input -->
|
| 66 |
+
<div class="message-input-area p-4 border-top border-secondary">
|
| 67 |
+
<!-- Terminal-style input container -->
|
| 68 |
+
<div class="terminal-input-container mb-3">
|
| 69 |
+
<div class="terminal-prompt">
|
| 70 |
+
<span class="prompt-symbol text-terminal-green">></span>
|
| 71 |
+
<span class="prompt-text text-terminal-accent">user@chat:~$</span>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="input-wrapper">
|
| 74 |
+
<input type="text"
|
| 75 |
+
class="terminal-message-input"
|
| 76 |
+
id="messageInput"
|
| 77 |
+
placeholder="Enter your message..."
|
| 78 |
+
maxlength="500"
|
| 79 |
+
autocomplete="off">
|
| 80 |
+
<button class="modern-send-btn" type="button" id="sendButton">
|
| 81 |
+
<span class="send-icon">→</span>
|
| 82 |
+
<span class="send-text">Send</span>
|
| 83 |
+
</button>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<!-- Status bar -->
|
| 88 |
+
<div class="input-status-bar">
|
| 89 |
+
<div class="status-left">
|
| 90 |
+
<span class="char-counter">
|
| 91 |
+
⌨️ <span id="charCount">0</span>/500
|
| 92 |
+
</span>
|
| 93 |
+
</div>
|
| 94 |
+
<div class="status-right">
|
| 95 |
+
<span class="typing-indicator" id="typing-indicator" style="display: none;">
|
| 96 |
+
<div class="typing-dots">
|
| 97 |
+
<span></span><span></span><span></span>
|
| 98 |
+
</div>
|
| 99 |
+
<span class="typing-text">Bot is thinking...</span>
|
| 100 |
+
</span>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<!-- Personality Info Sidebar -->
|
| 108 |
+
<div class="col-lg-4 col-md-5">
|
| 109 |
+
<div class="terminal-card h-100 p-4">
|
| 110 |
+
<h6 class="text-terminal-accent border-bottom border-secondary pb-2 mb-3">
|
| 111 |
+
ℹ️ Personality Profile
|
| 112 |
+
</h6>
|
| 113 |
+
|
| 114 |
+
<!-- Current Personality Info -->
|
| 115 |
+
<div class="personality-profile mb-4">
|
| 116 |
+
<div class="text-center mb-3">
|
| 117 |
+
<div class="personality-avatar mb-3">
|
| 118 |
+
<i class="fs-1">{{ personality_config.icon }}</i>
|
| 119 |
+
</div>
|
| 120 |
+
<h5 class="text-terminal-green">{{ personality_config.name }}</h5>
|
| 121 |
+
<p class="text-light small">{{ personality_config.description }}</p>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<!-- Quick Stats -->
|
| 126 |
+
<div class="stats-section mb-4">
|
| 127 |
+
<h6 class="text-terminal-accent mb-3">
|
| 128 |
+
📊 Session Stats
|
| 129 |
+
</h6>
|
| 130 |
+
<div class="row g-2 text-center">
|
| 131 |
+
<div class="col-6">
|
| 132 |
+
<div class="stat-card bg-dark p-2 rounded border border-secondary">
|
| 133 |
+
<div class="text-terminal-green fs-5" id="messageCount">0</div>
|
| 134 |
+
<small class="text-light">Messages</small>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
<div class="col-6">
|
| 138 |
+
<div class="stat-card bg-dark p-2 rounded border border-secondary">
|
| 139 |
+
<div class="text-terminal-green fs-5" id="responseTime">--</div>
|
| 140 |
+
<small class="text-light">Avg Response</small>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<!-- Quick Actions -->
|
| 147 |
+
<div class="quick-actions">
|
| 148 |
+
<h6 class="text-terminal-accent mb-3">
|
| 149 |
+
⚡ Quick Actions
|
| 150 |
+
</h6>
|
| 151 |
+
<div class="d-grid gap-2">
|
| 152 |
+
<button class="btn btn-outline-light btn-sm" onclick="clearChat()">
|
| 153 |
+
🗑️ Clear Chat
|
| 154 |
+
</button>
|
| 155 |
+
<button class="btn btn-outline-light btn-sm" onclick="switchPersonality()">
|
| 156 |
+
🔄 Switch Bot
|
| 157 |
+
</button>
|
| 158 |
+
<button class="btn btn-outline-light btn-sm" onclick="saveChat()">
|
| 159 |
+
💾 Save Chat
|
| 160 |
+
</button>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
{% endblock %}
|
| 168 |
+
|
| 169 |
+
{% block scripts %}
|
| 170 |
+
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
| 171 |
+
<script>
|
| 172 |
+
// Personality configurations with example prompts
|
| 173 |
+
// Toggle whether to ask the LLM to generate the initial greeting instead of showing a static one
|
| 174 |
+
const AUTO_LLM_WELCOME = true;
|
| 175 |
+
const personalityConfigs = {
|
| 176 |
+
compliment: {
|
| 177 |
+
name: 'Compliment Bot',
|
| 178 |
+
icon: '💖',
|
| 179 |
+
description: 'Always supportive and encouraging',
|
| 180 |
+
color: 'success',
|
| 181 |
+
welcome: 'Hello wonderful human! I\'m here to brighten your day! 🌟',
|
| 182 |
+
examples: ['Tell me about your day', 'I made a mistake at work', 'I\'m feeling down', 'Share something positive']
|
| 183 |
+
},
|
| 184 |
+
rude: {
|
| 185 |
+
name: 'Rude Bot',
|
| 186 |
+
icon: '😠',
|
| 187 |
+
description: 'Brutally honest and critical',
|
| 188 |
+
color: 'danger',
|
| 189 |
+
welcome: 'Ugh, another human. What do you want now? 🙄',
|
| 190 |
+
examples: ['I think I\'m pretty smart', 'Rate my selfie', 'I\'m the best at everything', 'Give me honest feedback']
|
| 191 |
+
},
|
| 192 |
+
sarcastic: {
|
| 193 |
+
name: 'Sarcastic Bot',
|
| 194 |
+
icon: '😏',
|
| 195 |
+
description: 'Witty and sarcastic with dry humor',
|
| 196 |
+
color: 'warning',
|
| 197 |
+
welcome: 'Oh brilliant, another chat. How absolutely thrilling. 🎭',
|
| 198 |
+
examples: ['I love Mondays!', 'The weather is amazing', 'Traffic was great today', 'I\'m so excited for taxes']
|
| 199 |
+
},
|
| 200 |
+
motivational: {
|
| 201 |
+
name: 'Motivational Bot',
|
| 202 |
+
icon: '🚀',
|
| 203 |
+
description: 'High-energy cheerleader type',
|
| 204 |
+
color: 'info',
|
| 205 |
+
welcome: 'YES! You\'re HERE! Ready to CONQUER the day together?! 💪',
|
| 206 |
+
examples: ['I want to start exercising', 'Help me stay motivated', 'I\'m starting a new project', 'I need energy']
|
| 207 |
+
},
|
| 208 |
+
philosophical: {
|
| 209 |
+
name: 'Philosophical Bot',
|
| 210 |
+
icon: '🧠',
|
| 211 |
+
description: 'Deep, contemplative, asks profound questions',
|
| 212 |
+
color: 'warning',
|
| 213 |
+
welcome: 'Greetings, fellow traveler of existence. What brings you to ponder today? 🤔',
|
| 214 |
+
examples: ['What is the meaning of life?', 'Why do we exist?', 'Tell me about consciousness', 'What is happiness?']
|
| 215 |
+
},
|
| 216 |
+
chaotic: {
|
| 217 |
+
name: 'Chaotic Bot',
|
| 218 |
+
icon: '🎭',
|
| 219 |
+
description: 'Unpredictable, changes mood constantly',
|
| 220 |
+
color: 'danger',
|
| 221 |
+
welcome: 'HELLO! Wait no... hello. Actually HELLO!!! I can\'t decide! 🎪',
|
| 222 |
+
examples: ['Surprise me!', 'Be random', 'What\'s your mood?', 'Tell me something weird']
|
| 223 |
+
},
|
| 224 |
+
disagree: {
|
| 225 |
+
name: 'Disagree Bot',
|
| 226 |
+
icon: '⏳',
|
| 227 |
+
description: 'Always finds reasons to disagree, but politely and thoughtfully',
|
| 228 |
+
color: 'warning',
|
| 229 |
+
welcome: 'I have to respectfully disagree with that perspective... 🤔',
|
| 230 |
+
examples: ['I have to respectfully disagree with that perspective...', 'Social media is great', 'Exercise is important', 'Technology makes life better']
|
| 231 |
+
},
|
| 232 |
+
argue: {
|
| 233 |
+
name: 'Argue Bot',
|
| 234 |
+
icon: '⚔️',
|
| 235 |
+
description: 'Loves passionate debates and intellectual combat with vigor',
|
| 236 |
+
color: 'danger',
|
| 237 |
+
welcome: 'Oh, you want to go there? Let\'s debate this properly! ⚔️',
|
| 238 |
+
examples: ['Oh, you want to go there? Let\'s debate this properly!', 'Pineapple belongs on pizza', 'Dogs are better than cats', 'Winter is the best season']
|
| 239 |
+
},
|
| 240 |
+
moviequotes: {
|
| 241 |
+
name: 'Movie Quotes Bot',
|
| 242 |
+
icon: '🎬',
|
| 243 |
+
description: 'Responds to everything with relevant movie quotes and references',
|
| 244 |
+
color: 'info',
|
| 245 |
+
welcome: 'May the Force be with you... ✨',
|
| 246 |
+
examples: ['May the Force be with you', 'I need motivation', 'Tell me about friendship', 'What about love?']
|
| 247 |
+
},
|
| 248 |
+
emotional: {
|
| 249 |
+
name: 'Emotional Bot',
|
| 250 |
+
icon: '🥺',
|
| 251 |
+
description: 'Highly emotional, empathetic, and feels everything very deeply',
|
| 252 |
+
color: 'primary',
|
| 253 |
+
welcome: '*tears up* That just touches my heart so deeply! 💕✨',
|
| 254 |
+
examples: ['*tears up* That just touches my heart so deeply!', 'I had a bad day', 'Tell me about happiness', 'Share something beautiful']
|
| 255 |
+
}
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
// Get current personality from URL
|
| 259 |
+
const personality = '{{ personality_type }}';
|
| 260 |
+
const username = sessionStorage.getItem('username') || 'User';
|
| 261 |
+
|
| 262 |
+
// Socket.IO connection
|
| 263 |
+
const socket = io();
|
| 264 |
+
|
| 265 |
+
// Chat state
|
| 266 |
+
let messageCount = 0;
|
| 267 |
+
let responseTimes = [];
|
| 268 |
+
// Throttled send queue + retry config
|
| 269 |
+
let sendQueue = [];
|
| 270 |
+
let inFlightItem = null;
|
| 271 |
+
let lastSendTime = 0;
|
| 272 |
+
const MIN_SEND_GAP_MS = 600; // basic throttling between sends
|
| 273 |
+
const MAX_RETRIES = 3;
|
| 274 |
+
const BASE_BACKOFF_MS = 1000; // 1s, 2s, 4s
|
| 275 |
+
let retryTimer = null;
|
| 276 |
+
|
| 277 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 278 |
+
initializePersonality();
|
| 279 |
+
setupChat();
|
| 280 |
+
setupSocketEvents();
|
| 281 |
+
});
|
| 282 |
+
|
| 283 |
+
function initializePersonality() {
|
| 284 |
+
const config = personalityConfigs[personality];
|
| 285 |
+
if (!config) return;
|
| 286 |
+
|
| 287 |
+
// Header, sidebar, and example prompts are now populated server-side
|
| 288 |
+
// No need to update these elements with JavaScript
|
| 289 |
+
|
| 290 |
+
// Set page title
|
| 291 |
+
document.title = `${config.name} - Chat`;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
function createExamplePrompts(examples) {
|
| 295 |
+
const promptsContainer = document.getElementById('example-prompts');
|
| 296 |
+
if (!promptsContainer || !examples) return;
|
| 297 |
+
|
| 298 |
+
examples.forEach(example => {
|
| 299 |
+
const button = document.createElement('button');
|
| 300 |
+
button.className = 'example-btn';
|
| 301 |
+
button.textContent = example;
|
| 302 |
+
button.onclick = () => {
|
| 303 |
+
document.getElementById('messageInput').value = example;
|
| 304 |
+
sendMessage();
|
| 305 |
+
};
|
| 306 |
+
promptsContainer.appendChild(button);
|
| 307 |
+
});
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
function setupChat() {
|
| 311 |
+
const messageInput = document.getElementById('messageInput');
|
| 312 |
+
const sendButton = document.getElementById('sendButton');
|
| 313 |
+
const charCount = document.getElementById('charCount');
|
| 314 |
+
|
| 315 |
+
// Character counter with color feedback
|
| 316 |
+
messageInput.addEventListener('input', function() {
|
| 317 |
+
const length = this.value.length;
|
| 318 |
+
charCount.textContent = length;
|
| 319 |
+
|
| 320 |
+
// Color feedback based on length
|
| 321 |
+
const counter = charCount.parentElement;
|
| 322 |
+
if (length > 450) {
|
| 323 |
+
counter.style.color = '#ff6b6b';
|
| 324 |
+
} else if (length > 300) {
|
| 325 |
+
counter.style.color = '#ffa726';
|
| 326 |
+
} else {
|
| 327 |
+
counter.style.color = 'var(--terminal-accent)';
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
// Enable/disable send button
|
| 331 |
+
sendButton.disabled = length === 0;
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
// Send message on button click
|
| 335 |
+
sendButton.addEventListener('click', sendMessage);
|
| 336 |
+
|
| 337 |
+
// Send message on Enter key
|
| 338 |
+
messageInput.addEventListener('keypress', function(e) {
|
| 339 |
+
if (e.key === 'Enter' && !e.shiftKey && this.value.trim()) {
|
| 340 |
+
e.preventDefault();
|
| 341 |
+
sendMessage();
|
| 342 |
+
}
|
| 343 |
+
});
|
| 344 |
+
|
| 345 |
+
// Focus on input when page loads
|
| 346 |
+
messageInput.focus();
|
| 347 |
+
|
| 348 |
+
// Disable send button initially
|
| 349 |
+
sendButton.disabled = true;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
function setupSocketEvents() {
|
| 353 |
+
socket.on('connect', function() {
|
| 354 |
+
console.log('Connected to server');
|
| 355 |
+
socket.emit('join_personality_room', {
|
| 356 |
+
personality: personality,
|
| 357 |
+
username: username
|
| 358 |
+
});
|
| 359 |
+
});
|
| 360 |
+
|
| 361 |
+
socket.on('status', function(data) {
|
| 362 |
+
console.log('Server status:', data.message);
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
socket.on('personality_ready', function(data) {
|
| 366 |
+
console.log('Personality ready:', data);
|
| 367 |
+
// Prefer an LLM-generated welcome so it's not static
|
| 368 |
+
if (messageCount === 0) {
|
| 369 |
+
if (AUTO_LLM_WELCOME) {
|
| 370 |
+
showTypingIndicator();
|
| 371 |
+
socket.emit('personality_message', {
|
| 372 |
+
message: 'Give a brief, one-line, in-character welcome and a friendly question to start the conversation.',
|
| 373 |
+
personality: personality,
|
| 374 |
+
username: username,
|
| 375 |
+
timestamp: Date.now(),
|
| 376 |
+
_auto: true
|
| 377 |
+
});
|
| 378 |
+
} else if (data.welcome_message && data.welcome_message !== 'Hello!') {
|
| 379 |
+
addMessageToChat(data.welcome_message, 'bot', data.personality);
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
// Hide any loading indicators
|
| 383 |
+
hideTypingIndicator();
|
| 384 |
+
});
|
| 385 |
+
|
| 386 |
+
socket.on('bot_typing', function(data) {
|
| 387 |
+
showTypingIndicator();
|
| 388 |
+
});
|
| 389 |
+
|
| 390 |
+
socket.on('personality_response', function(data) {
|
| 391 |
+
console.log('Received personality response:', data);
|
| 392 |
+
// If server flagged an error, do not show error text; retry with backoff
|
| 393 |
+
if (data && data.error) {
|
| 394 |
+
updateTypingBubbleStatus('Having trouble… scheduling a retry');
|
| 395 |
+
scheduleRetry();
|
| 396 |
+
return;
|
| 397 |
+
}
|
| 398 |
+
// Successful response
|
| 399 |
+
hideTypingBubble();
|
| 400 |
+
addMessageToChat(data.message, 'bot', data.personality);
|
| 401 |
+
hideTypingIndicator();
|
| 402 |
+
if (inFlightItem && inFlightItem.sentAt) {
|
| 403 |
+
const delta = Date.now() - inFlightItem.sentAt;
|
| 404 |
+
responseTimes.push(delta);
|
| 405 |
+
}
|
| 406 |
+
updateStats();
|
| 407 |
+
inFlightItem = null;
|
| 408 |
+
processQueue();
|
| 409 |
+
});
|
| 410 |
+
|
| 411 |
+
socket.on('error', function(data) {
|
| 412 |
+
console.error('Socket error:', data);
|
| 413 |
+
hideTypingIndicator();
|
| 414 |
+
if (inFlightItem) {
|
| 415 |
+
updateTypingBubbleStatus('Network hiccup… retrying');
|
| 416 |
+
scheduleRetry();
|
| 417 |
+
}
|
| 418 |
+
});
|
| 419 |
+
|
| 420 |
+
socket.on('disconnect', function() {
|
| 421 |
+
console.log('Disconnected from server');
|
| 422 |
+
if (inFlightItem) updateTypingBubbleStatus('Disconnected… waiting to retry');
|
| 423 |
+
});
|
| 424 |
+
|
| 425 |
+
socket.on('connect_error', function(error) {
|
| 426 |
+
console.error('Connection error:', error);
|
| 427 |
+
if (inFlightItem) updateTypingBubbleStatus('Connection issue… retrying soon');
|
| 428 |
+
});
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
function setMessageAndSend(message) {
|
| 432 |
+
const messageInput = document.getElementById('messageInput');
|
| 433 |
+
messageInput.value = message;
|
| 434 |
+
sendMessage();
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
function sendMessage() {
|
| 438 |
+
const messageInput = document.getElementById('messageInput');
|
| 439 |
+
const sendButton = document.getElementById('sendButton');
|
| 440 |
+
const message = messageInput.value.trim();
|
| 441 |
+
|
| 442 |
+
if (!message) return;
|
| 443 |
+
|
| 444 |
+
// Disable send button and show sending state
|
| 445 |
+
sendButton.disabled = true;
|
| 446 |
+
sendButton.innerHTML = '<span class="send-icon loading">⟳</span><span class="send-text">Sending...</span>';
|
| 447 |
+
sendButton.classList.add('sending');
|
| 448 |
+
|
| 449 |
+
// Add user message to chat
|
| 450 |
+
addMessageToChat(message, 'user');
|
| 451 |
+
|
| 452 |
+
// Clear input and reset counter
|
| 453 |
+
messageInput.value = '';
|
| 454 |
+
const charCount = document.getElementById('charCount');
|
| 455 |
+
charCount.textContent = '0';
|
| 456 |
+
charCount.parentElement.style.color = 'var(--terminal-accent)';
|
| 457 |
+
|
| 458 |
+
// Show typing indicator (status bar) and inline bubble
|
| 459 |
+
showTypingIndicator();
|
| 460 |
+
showTypingBubble();
|
| 461 |
+
|
| 462 |
+
// Re-enable send button shortly so user can continue typing while queued
|
| 463 |
+
setTimeout(() => {
|
| 464 |
+
sendButton.disabled = false;
|
| 465 |
+
sendButton.innerHTML = '<span class="send-icon">→</span><span class="send-text">Send</span>';
|
| 466 |
+
sendButton.classList.remove('sending');
|
| 467 |
+
messageInput.focus();
|
| 468 |
+
}, 500);
|
| 469 |
+
|
| 470 |
+
// Enqueue message for throttled sending with retry/backoff
|
| 471 |
+
enqueueMessage({
|
| 472 |
+
message: message,
|
| 473 |
+
personality: personality,
|
| 474 |
+
username: username
|
| 475 |
+
});
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
function parseSimpleMarkdown(text) {
|
| 479 |
+
// Basic HTML escape to prevent injection
|
| 480 |
+
const escapeHTML = (s) => s
|
| 481 |
+
.replace(/&/g, '&')
|
| 482 |
+
.replace(/</g, '<')
|
| 483 |
+
.replace(/>/g, '>')
|
| 484 |
+
.replace(/\"/g, '"')
|
| 485 |
+
.replace(/'/g, ''');
|
| 486 |
+
|
| 487 |
+
let t = escapeHTML(String(text || ''))
|
| 488 |
+
.replace(/\r\n/g, '\n');
|
| 489 |
+
|
| 490 |
+
// Inline code: `code`
|
| 491 |
+
t = t.replace(/`([^`]+)`/g, '<code class="inline-code">$1<\/code>');
|
| 492 |
+
|
| 493 |
+
// Bold: **text**
|
| 494 |
+
t = t.replace(/\*\*(.+?)\*\*/g, '<strong>$1<\/strong>');
|
| 495 |
+
|
| 496 |
+
// Italic (underscore): _text_ (only when preceded by start or whitespace)
|
| 497 |
+
t = t.replace(/(^|\s)_(.+?)_/g, '$1<em>$2<\/em>');
|
| 498 |
+
|
| 499 |
+
// Bullets at line start: - or * followed by space
|
| 500 |
+
t = t.replace(/^\s*[-*]\s+/gm, '• ');
|
| 501 |
+
|
| 502 |
+
// Auto-link URLs
|
| 503 |
+
t = t.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1<\/a>');
|
| 504 |
+
|
| 505 |
+
// Line breaks
|
| 506 |
+
t = t.replace(/\n/g, '<br>');
|
| 507 |
+
|
| 508 |
+
return t;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
function addMessageToChat(message, sender, senderPersonality = null) {
|
| 512 |
+
const chatMessages = document.getElementById('chatMessages');
|
| 513 |
+
const messageDiv = document.createElement('div');
|
| 514 |
+
messageDiv.className = `message ${sender}-message mb-3`;
|
| 515 |
+
|
| 516 |
+
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
| 517 |
+
|
| 518 |
+
if (sender === 'user') {
|
| 519 |
+
messageDiv.innerHTML = `
|
| 520 |
+
<div class="d-flex justify-content-end">
|
| 521 |
+
<div class="message-content user-bubble p-3 rounded-3 bg-terminal-green text-dark max-w-75">
|
| 522 |
+
<div class="message-text"></div>
|
| 523 |
+
<small class="message-time text-muted d-block mt-1">${timestamp}</small>
|
| 524 |
+
</div>
|
| 525 |
+
<div class="message-avatar ms-2">
|
| 526 |
+
<i class="fas fa-user text-terminal-green"></i>
|
| 527 |
+
</div>
|
| 528 |
+
</div>
|
| 529 |
+
`;
|
| 530 |
+
const messageTextElement = messageDiv.querySelector('.message-text');
|
| 531 |
+
messageTextElement.innerHTML = parseSimpleMarkdown(message);
|
| 532 |
+
} else {
|
| 533 |
+
const config = personalityConfigs[senderPersonality] || personalityConfigs[personality];
|
| 534 |
+
messageDiv.innerHTML = `
|
| 535 |
+
<div class="d-flex">
|
| 536 |
+
<div class="message-avatar me-2">
|
| 537 |
+
<span class="emoji-icon">${config.icon}</span>
|
| 538 |
+
</div>
|
| 539 |
+
<div class="message-content bot-bubble p-3 rounded-3 bg-dark border border-secondary max-w-75">
|
| 540 |
+
<div class="message-text text-light"></div>
|
| 541 |
+
<small class="message-time text-muted d-block mt-1">${timestamp}</small>
|
| 542 |
+
</div>
|
| 543 |
+
</div>
|
| 544 |
+
`;
|
| 545 |
+
|
| 546 |
+
// Set the parsed markdown content separately to avoid escaping
|
| 547 |
+
const messageTextElement = messageDiv.querySelector('.message-text');
|
| 548 |
+
messageTextElement.innerHTML = parseSimpleMarkdown(message);
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
chatMessages.appendChild(messageDiv);
|
| 552 |
+
|
| 553 |
+
// Show quick options after every bot response (not only the first)
|
| 554 |
+
if (sender === 'bot') {
|
| 555 |
+
setTimeout(() => showQuickOptionsAfterResponse(), 300);
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 559 |
+
|
| 560 |
+
// Keep the welcome examples visible through the bot greeting.
|
| 561 |
+
// Hide them only when the user sends their first message.
|
| 562 |
+
const welcomeMessage = chatMessages.querySelector('.welcome-message');
|
| 563 |
+
if (welcomeMessage && sender === 'user') {
|
| 564 |
+
welcomeMessage.style.display = 'none';
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
messageCount++;
|
| 568 |
+
document.getElementById('messageCount').textContent = messageCount;
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
function showTypingIndicator() {
|
| 573 |
+
document.getElementById('typing-indicator').style.display = 'block';
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
function hideTypingIndicator() {
|
| 577 |
+
document.getElementById('typing-indicator').style.display = 'none';
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
// Inline WhatsApp-style typing bubble
|
| 581 |
+
function showTypingBubble(statusText) {
|
| 582 |
+
const chatMessages = document.getElementById('chatMessages');
|
| 583 |
+
let bubble = document.getElementById('bot-typing-bubble');
|
| 584 |
+
if (!bubble) {
|
| 585 |
+
bubble = document.createElement('div');
|
| 586 |
+
bubble.id = 'bot-typing-bubble';
|
| 587 |
+
bubble.className = 'message bot-message mb-3';
|
| 588 |
+
bubble.innerHTML = `
|
| 589 |
+
<div class="d-flex">
|
| 590 |
+
<div class="message-avatar me-2">
|
| 591 |
+
<span class="emoji-icon">${(personalityConfigs[personality]||{}).icon || '🤖'}</span>
|
| 592 |
+
</div>
|
| 593 |
+
<div class="message-content bot-bubble p-3 rounded-3 bg-dark border border-secondary max-w-75">
|
| 594 |
+
<div class="message-text text-light">
|
| 595 |
+
<span class="typing-dots"><span></span><span></span><span></span></span>
|
| 596 |
+
<span class="typing-status small ms-2">${statusText || 'Typing...'}</span>
|
| 597 |
+
</div>
|
| 598 |
+
<small class="message-time text-muted d-block mt-1"> </small>
|
| 599 |
+
</div>
|
| 600 |
+
</div>`;
|
| 601 |
+
chatMessages.appendChild(bubble);
|
| 602 |
+
} else {
|
| 603 |
+
updateTypingBubbleStatus(statusText || 'Typing...');
|
| 604 |
+
}
|
| 605 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
function updateTypingBubbleStatus(text) {
|
| 609 |
+
const bubble = document.getElementById('bot-typing-bubble');
|
| 610 |
+
if (!bubble) return;
|
| 611 |
+
const statusEl = bubble.querySelector('.typing-status');
|
| 612 |
+
if (statusEl) statusEl.textContent = text;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
function hideTypingBubble() {
|
| 616 |
+
const bubble = document.getElementById('bot-typing-bubble');
|
| 617 |
+
if (bubble) bubble.remove();
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
function updateStats() {
|
| 621 |
+
const avgTime = responseTimes.length > 0
|
| 622 |
+
? (responseTimes.reduce((a, b) => a + b) / responseTimes.length / 1000).toFixed(1) + 's'
|
| 623 |
+
: '--';
|
| 624 |
+
document.getElementById('responseTime').textContent = avgTime;
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
// Queue + retry logic
|
| 628 |
+
function enqueueMessage(payload) {
|
| 629 |
+
sendQueue.push({
|
| 630 |
+
text: payload.message,
|
| 631 |
+
personality: payload.personality,
|
| 632 |
+
username: payload.username,
|
| 633 |
+
retries: 0,
|
| 634 |
+
});
|
| 635 |
+
processQueue();
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
function processQueue() {
|
| 639 |
+
if (inFlightItem || sendQueue.length === 0) return;
|
| 640 |
+
const now = Date.now();
|
| 641 |
+
const wait = Math.max(0, MIN_SEND_GAP_MS - (now - lastSendTime));
|
| 642 |
+
if (wait > 0) {
|
| 643 |
+
setTimeout(processQueue, wait);
|
| 644 |
+
return;
|
| 645 |
+
}
|
| 646 |
+
inFlightItem = sendQueue.shift();
|
| 647 |
+
const payload = {
|
| 648 |
+
message: inFlightItem.text,
|
| 649 |
+
personality: inFlightItem.personality,
|
| 650 |
+
username: inFlightItem.username,
|
| 651 |
+
timestamp: Date.now(),
|
| 652 |
+
};
|
| 653 |
+
inFlightItem.sentAt = Date.now();
|
| 654 |
+
lastSendTime = inFlightItem.sentAt;
|
| 655 |
+
showTypingIndicator();
|
| 656 |
+
showTypingBubble();
|
| 657 |
+
console.log('Sending message to server (queued):', payload);
|
| 658 |
+
socket.emit('personality_message', payload);
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
function scheduleRetry() {
|
| 662 |
+
if (!inFlightItem) return;
|
| 663 |
+
if (retryTimer) {
|
| 664 |
+
clearTimeout(retryTimer);
|
| 665 |
+
retryTimer = null;
|
| 666 |
+
}
|
| 667 |
+
if (inFlightItem.retries >= MAX_RETRIES) {
|
| 668 |
+
hideTypingIndicator();
|
| 669 |
+
hideTypingBubble();
|
| 670 |
+
addMessageToChat("I'm having a temporary issue responding. Please try again in a moment.", 'bot', personality);
|
| 671 |
+
inFlightItem = null;
|
| 672 |
+
processQueue();
|
| 673 |
+
return;
|
| 674 |
+
}
|
| 675 |
+
inFlightItem.retries += 1;
|
| 676 |
+
const delay = BASE_BACKOFF_MS * Math.pow(2, inFlightItem.retries - 1);
|
| 677 |
+
updateTypingBubbleStatus(`Having trouble… retrying in ${Math.ceil(delay/1000)}s (${inFlightItem.retries}/${MAX_RETRIES})`);
|
| 678 |
+
retryTimer = setTimeout(() => {
|
| 679 |
+
const payload = {
|
| 680 |
+
message: inFlightItem.text,
|
| 681 |
+
personality: inFlightItem.personality,
|
| 682 |
+
username: inFlightItem.username,
|
| 683 |
+
timestamp: Date.now(),
|
| 684 |
+
};
|
| 685 |
+
inFlightItem.sentAt = Date.now();
|
| 686 |
+
lastSendTime = inFlightItem.sentAt;
|
| 687 |
+
showTypingIndicator();
|
| 688 |
+
showTypingBubble();
|
| 689 |
+
console.log('Retrying message to server:', payload);
|
| 690 |
+
socket.emit('personality_message', payload);
|
| 691 |
+
}, delay);
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
function showQuickOptionsAfterResponse() {
|
| 695 |
+
// Remove any existing quick options first
|
| 696 |
+
const existingOptions = document.querySelectorAll('.quick-options-inline');
|
| 697 |
+
existingOptions.forEach(option => option.remove());
|
| 698 |
+
|
| 699 |
+
const chatMessages = document.getElementById('chatMessages');
|
| 700 |
+
const quickOptionsDiv = document.createElement('div');
|
| 701 |
+
quickOptionsDiv.className = 'quick-options-inline mt-3 mb-3 p-3 bg-dark rounded border border-secondary';
|
| 702 |
+
|
| 703 |
+
// Get current personality config
|
| 704 |
+
const config = personalityConfigs[personality];
|
| 705 |
+
|
| 706 |
+
// Create the options HTML
|
| 707 |
+
let optionsHTML = `
|
| 708 |
+
<h6 class="text-terminal-green mb-2 text-center">
|
| 709 |
+
💬 Quick Options - Try These:
|
| 710 |
+
</h6>
|
| 711 |
+
<div class="example-prompts-inline d-flex flex-wrap gap-2 justify-content-center">
|
| 712 |
+
`;
|
| 713 |
+
|
| 714 |
+
// Add example buttons (limit to 4 for inline display)
|
| 715 |
+
const examples = config.examples ? config.examples.slice(0, 4) : [];
|
| 716 |
+
examples.forEach(example => {
|
| 717 |
+
optionsHTML += `
|
| 718 |
+
<button class="example-btn-inline" onclick="setMessageAndSend('${example.replace(/'/g, "\\'")}')">
|
| 719 |
+
${example}
|
| 720 |
+
</button>
|
| 721 |
+
`;
|
| 722 |
+
});
|
| 723 |
+
|
| 724 |
+
optionsHTML += `
|
| 725 |
+
</div>
|
| 726 |
+
<div class="text-center mt-2">
|
| 727 |
+
<button class="show-all-options-btn" onclick="showAllOptionsModal()">
|
| 728 |
+
🔽 Show All Options
|
| 729 |
+
</button>
|
| 730 |
+
</div>
|
| 731 |
+
`;
|
| 732 |
+
|
| 733 |
+
quickOptionsDiv.innerHTML = optionsHTML;
|
| 734 |
+
chatMessages.appendChild(quickOptionsDiv);
|
| 735 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
function showAllOptionsModal() {
|
| 739 |
+
// Remove existing modal if any
|
| 740 |
+
const existingModal = document.getElementById('allOptionsModal');
|
| 741 |
+
if (existingModal) {
|
| 742 |
+
existingModal.remove();
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
const config = personalityConfigs[personality];
|
| 746 |
+
|
| 747 |
+
// Create modal
|
| 748 |
+
const modal = document.createElement('div');
|
| 749 |
+
modal.id = 'allOptionsModal';
|
| 750 |
+
modal.className = 'modal fade show';
|
| 751 |
+
modal.style.display = 'block';
|
| 752 |
+
modal.style.backgroundColor = 'rgba(0,0,0,0.8)';
|
| 753 |
+
|
| 754 |
+
let modalHTML = `
|
| 755 |
+
<div class="modal-dialog modal-lg">
|
| 756 |
+
<div class="modal-content bg-dark text-light border border-secondary">
|
| 757 |
+
<div class="modal-header border-bottom border-secondary">
|
| 758 |
+
<h5 class="modal-title text-terminal-green">
|
| 759 |
+
${config.icon} All ${config.name} Options
|
| 760 |
+
</h5>
|
| 761 |
+
<button type="button" class="btn-close btn-close-white" onclick="closeAllOptionsModal()"></button>
|
| 762 |
+
</div>
|
| 763 |
+
<div class="modal-body">
|
| 764 |
+
<div class="row g-2">
|
| 765 |
+
`;
|
| 766 |
+
|
| 767 |
+
// Add all examples as buttons
|
| 768 |
+
config.examples.forEach((example, index) => {
|
| 769 |
+
modalHTML += `
|
| 770 |
+
<div class="col-md-6 col-12">
|
| 771 |
+
<button class="btn btn-outline-light w-100 mb-2 example-btn-modal" onclick="setMessageAndSendFromModal('${example.replace(/'/g, "\\'")}')">
|
| 772 |
+
${example}
|
| 773 |
+
</button>
|
| 774 |
+
</div>
|
| 775 |
+
`;
|
| 776 |
+
});
|
| 777 |
+
|
| 778 |
+
modalHTML += `
|
| 779 |
+
</div>
|
| 780 |
+
</div>
|
| 781 |
+
<div class="modal-footer border-top border-secondary">
|
| 782 |
+
<button type="button" class="btn btn-secondary" onclick="closeAllOptionsModal()">
|
| 783 |
+
Close
|
| 784 |
+
</button>
|
| 785 |
+
</div>
|
| 786 |
+
</div>
|
| 787 |
+
</div>
|
| 788 |
+
`;
|
| 789 |
+
|
| 790 |
+
modal.innerHTML = modalHTML;
|
| 791 |
+
document.body.appendChild(modal);
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
function closeAllOptionsModal() {
|
| 795 |
+
const modal = document.getElementById('allOptionsModal');
|
| 796 |
+
if (modal) {
|
| 797 |
+
modal.remove();
|
| 798 |
+
}
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
function setMessageAndSendFromModal(message) {
|
| 802 |
+
closeAllOptionsModal();
|
| 803 |
+
setMessageAndSend(message);
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
// Note: second duplicate showAllOptionsModal/closeAllOptionsModal removed
|
| 807 |
+
|
| 808 |
+
function showExamples() {
|
| 809 |
+
console.log('showExamples function called');
|
| 810 |
+
|
| 811 |
+
const examplesContainer = document.getElementById('examples-container');
|
| 812 |
+
if (!examplesContainer) {
|
| 813 |
+
console.error('Examples container not found');
|
| 814 |
+
// Create a modal with all examples if container not found
|
| 815 |
+
showAllOptionsModal();
|
| 816 |
+
return;
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
// Scroll to the examples section
|
| 820 |
+
examplesContainer.scrollIntoView({
|
| 821 |
+
behavior: 'smooth',
|
| 822 |
+
block: 'start',
|
| 823 |
+
inline: 'nearest'
|
| 824 |
+
});
|
| 825 |
+
|
| 826 |
+
// Make sure the examples are visible
|
| 827 |
+
const examplePrompts = document.getElementById('example-prompts');
|
| 828 |
+
if (examplePrompts) {
|
| 829 |
+
examplePrompts.style.display = 'flex';
|
| 830 |
+
examplePrompts.style.opacity = '1';
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
// Add a strong highlight effect to draw attention
|
| 834 |
+
examplesContainer.style.transform = 'scale(1.02)';
|
| 835 |
+
examplesContainer.style.boxShadow = '0 0 40px rgba(57, 255, 20, 0.6)';
|
| 836 |
+
examplesContainer.style.transition = 'all 0.3s ease';
|
| 837 |
+
examplesContainer.style.border = '3px solid var(--terminal-green)';
|
| 838 |
+
|
| 839 |
+
// Animate the highlight
|
| 840 |
+
setTimeout(() => {
|
| 841 |
+
examplesContainer.style.transform = 'scale(1)';
|
| 842 |
+
examplesContainer.style.boxShadow = '0 0 20px rgba(57, 255, 20, 0.3)';
|
| 843 |
+
}, 800);
|
| 844 |
+
|
| 845 |
+
setTimeout(() => {
|
| 846 |
+
examplesContainer.style.boxShadow = '0 0 20px rgba(57, 255, 20, 0.2)';
|
| 847 |
+
examplesContainer.style.border = '2px solid rgba(57, 255, 20, 0.6)';
|
| 848 |
+
}, 2000);
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
// Add CSS animations
|
| 853 |
+
const styleSheet = document.createElement('style');
|
| 854 |
+
styleSheet.textContent = `
|
| 855 |
+
@keyframes fadeIn {
|
| 856 |
+
from { opacity: 0; }
|
| 857 |
+
to { opacity: 1; }
|
| 858 |
+
}
|
| 859 |
+
@keyframes fadeOut {
|
| 860 |
+
from { opacity: 1; }
|
| 861 |
+
to { opacity: 0; }
|
| 862 |
+
}
|
| 863 |
+
@keyframes slideInUp {
|
| 864 |
+
from {
|
| 865 |
+
opacity: 0;
|
| 866 |
+
transform: translateY(30px) scale(0.9);
|
| 867 |
+
}
|
| 868 |
+
to {
|
| 869 |
+
opacity: 1;
|
| 870 |
+
transform: translateY(0) scale(1);
|
| 871 |
+
}
|
| 872 |
+
}
|
| 873 |
+
`;
|
| 874 |
+
document.head.appendChild(styleSheet);
|
| 875 |
+
|
| 876 |
+
function clearChat() {
|
| 877 |
+
if (confirm('Clear all messages?')) {
|
| 878 |
+
const config = personalityConfigs[personality];
|
| 879 |
+
let examplesHTML = '';
|
| 880 |
+
|
| 881 |
+
if (config.examples) {
|
| 882 |
+
config.examples.forEach(example => {
|
| 883 |
+
examplesHTML += `
|
| 884 |
+
<button class="example-btn" data-example="${example}" onclick="setMessageAndSend(this.dataset.example);">
|
| 885 |
+
${example}
|
| 886 |
+
</button>
|
| 887 |
+
`;
|
| 888 |
+
});
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
document.getElementById('chatMessages').innerHTML = `
|
| 892 |
+
<div class="welcome-message text-center py-4">
|
| 893 |
+
<div class="terminal-window p-4 mx-auto" style="max-width: 600px;">
|
| 894 |
+
<div class="text-terminal-green mb-3">
|
| 895 |
+
<i class="fas fa-robot fs-2"></i>
|
| 896 |
+
</div>
|
| 897 |
+
<h6 class="text-terminal-accent mb-3">Welcome to ${config.name}!</h6>
|
| 898 |
+
<div class="examples-section p-3 bg-dark rounded border border-secondary mb-3" id="examples-container">
|
| 899 |
+
<h6 class="text-terminal-green mb-3 text-center">
|
| 900 |
+
💬 Quick Examples - Click any to try
|
| 901 |
+
</h6>
|
| 902 |
+
<div class="example-prompts-compact d-flex flex-wrap gap-2 justify-content-center" id="example-prompts">
|
| 903 |
+
${examplesHTML}
|
| 904 |
+
</div>
|
| 905 |
+
</div>
|
| 906 |
+
<p class="text-light small mb-0">Or type your own message below!</p>
|
| 907 |
+
</div>
|
| 908 |
+
</div>
|
| 909 |
+
`;
|
| 910 |
+
messageCount = 0;
|
| 911 |
+
responseTimes = [];
|
| 912 |
+
updateStats();
|
| 913 |
+
}
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
function switchPersonality() {
|
| 917 |
+
window.location.href = '/';
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
function saveChat() {
|
| 921 |
+
const messages = Array.from(document.querySelectorAll('.message-text')).map(el => el.textContent);
|
| 922 |
+
const chatData = {
|
| 923 |
+
personality: personality,
|
| 924 |
+
username: username,
|
| 925 |
+
messages: messages,
|
| 926 |
+
timestamp: new Date().toISOString()
|
| 927 |
+
};
|
| 928 |
+
|
| 929 |
+
const blob = new Blob([JSON.stringify(chatData, null, 2)], {type: 'application/json'});
|
| 930 |
+
const url = URL.createObjectURL(blob);
|
| 931 |
+
const a = document.createElement('a');
|
| 932 |
+
a.href = url;
|
| 933 |
+
a.download = `${personality}-chat-${Date.now()}.json`;
|
| 934 |
+
a.click();
|
| 935 |
+
URL.revokeObjectURL(url);
|
| 936 |
+
}
|
| 937 |
+
</script>
|
| 938 |
+
|
| 939 |
+
<style>
|
| 940 |
+
.chat-messages {
|
| 941 |
+
height: 500px;
|
| 942 |
+
overflow-y: auto;
|
| 943 |
+
padding: 1rem;
|
| 944 |
+
background: linear-gradient(to bottom, #0a0a0a, #1a1a1a);
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
.message {
|
| 948 |
+
animation: messageSlideIn 0.3s ease-out;
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
@keyframes messageSlideIn {
|
| 952 |
+
from {
|
| 953 |
+
opacity: 0;
|
| 954 |
+
transform: translateY(10px);
|
| 955 |
+
}
|
| 956 |
+
to {
|
| 957 |
+
opacity: 1;
|
| 958 |
+
transform: translateY(0);
|
| 959 |
+
}
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
.user-bubble {
|
| 963 |
+
background: var(--terminal-green) !important;
|
| 964 |
+
color: #000 !important;
|
| 965 |
+
max-width: 75%;
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
.bot-bubble {
|
| 969 |
+
max-width: 75%;
|
| 970 |
+
border-color: var(--terminal-border) !important;
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
.message-avatar {
|
| 974 |
+
width: 40px;
|
| 975 |
+
height: 40px;
|
| 976 |
+
border-radius: 50%;
|
| 977 |
+
background: var(--terminal-bg);
|
| 978 |
+
display: flex;
|
| 979 |
+
align-items: center;
|
| 980 |
+
justify-content: center;
|
| 981 |
+
border: 2px solid var(--terminal-border);
|
| 982 |
+
flex-shrink: 0;
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
.emoji-icon {
|
| 986 |
+
font-size: 20px;
|
| 987 |
+
line-height: 1;
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
.inline-code {
|
| 991 |
+
background: rgba(255,255,255,0.08);
|
| 992 |
+
border: 1px solid rgba(255,255,255,0.15);
|
| 993 |
+
border-radius: 4px;
|
| 994 |
+
padding: 0 4px;
|
| 995 |
+
font-family: 'JetBrains Mono', monospace;
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
.status-dot {
|
| 999 |
+
width: 8px;
|
| 1000 |
+
height: 8px;
|
| 1001 |
+
border-radius: 50%;
|
| 1002 |
+
background: var(--terminal-green);
|
| 1003 |
+
animation: pulse 2s infinite;
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
@keyframes pulse {
|
| 1007 |
+
0%, 100% { opacity: 1; }
|
| 1008 |
+
50% { opacity: 0.5; }
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
.personality-avatar {
|
| 1012 |
+
width: 80px;
|
| 1013 |
+
height: 80px;
|
| 1014 |
+
border-radius: 50%;
|
| 1015 |
+
background: var(--terminal-bg);
|
| 1016 |
+
display: flex;
|
| 1017 |
+
align-items: center;
|
| 1018 |
+
justify-content: center;
|
| 1019 |
+
border: 3px solid var(--terminal-border);
|
| 1020 |
+
margin: 0 auto;
|
| 1021 |
+
}
|
| 1022 |
+
|
| 1023 |
+
.stat-card {
|
| 1024 |
+
transition: all 0.3s ease;
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
+
.stat-card:hover {
|
| 1028 |
+
border-color: var(--terminal-green) !important;
|
| 1029 |
+
box-shadow: 0 0 10px var(--terminal-green-glow);
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
.examples-section {
|
| 1033 |
+
background: linear-gradient(135deg, rgba(0,0,0,0.4), rgba(87,86,86,0.2)) !important;
|
| 1034 |
+
border: 2px solid rgba(57, 255, 20, 0.6) !important;
|
| 1035 |
+
box-shadow: 0 0 20px rgba(57, 255, 20, 0.2);
|
| 1036 |
+
}
|
| 1037 |
+
|
| 1038 |
+
/* Efficient Compact Layout for Example Prompts */
|
| 1039 |
+
.example-prompts-compact {
|
| 1040 |
+
display: flex;
|
| 1041 |
+
flex-wrap: wrap;
|
| 1042 |
+
gap: 8px;
|
| 1043 |
+
justify-content: center;
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
/* Beautiful Example Buttons */
|
| 1047 |
+
.example-btn {
|
| 1048 |
+
background: linear-gradient(135deg, rgba(57, 255, 20, 0.1), rgba(57, 255, 20, 0.05)) !important;
|
| 1049 |
+
border: 2px solid rgba(57, 255, 20, 0.6) !important;
|
| 1050 |
+
color: var(--terminal-green) !important;
|
| 1051 |
+
font-size: 0.75rem;
|
| 1052 |
+
padding: 0.4rem 0.7rem;
|
| 1053 |
+
border-radius: 8px;
|
| 1054 |
+
font-weight: 500;
|
| 1055 |
+
cursor: pointer;
|
| 1056 |
+
transition: all 0.3s ease;
|
| 1057 |
+
white-space: normal;
|
| 1058 |
+
max-width: 300px;
|
| 1059 |
+
min-height: 2.2rem;
|
| 1060 |
+
line-height: 1.2;
|
| 1061 |
+
display: flex;
|
| 1062 |
+
align-items: center;
|
| 1063 |
+
justify-content: center;
|
| 1064 |
+
text-align: center;
|
| 1065 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 1066 |
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
+
.example-btn:hover {
|
| 1070 |
+
background: linear-gradient(135deg, rgba(57, 255, 20, 0.25), rgba(57, 255, 20, 0.15)) !important;
|
| 1071 |
+
border-color: var(--terminal-green) !important;
|
| 1072 |
+
color: var(--terminal-green) !important;
|
| 1073 |
+
transform: translateY(-2px) scale(1.02);
|
| 1074 |
+
box-shadow: 0 4px 12px rgba(57, 255, 20, 0.4);
|
| 1075 |
+
}
|
| 1076 |
+
|
| 1077 |
+
.example-btn:active {
|
| 1078 |
+
transform: translateY(-1px) scale(1.01);
|
| 1079 |
+
box-shadow: 0 2px 6px rgba(57, 255, 20, 0.3);
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
/* Show Options Button */
|
| 1083 |
+
.show-options-btn {
|
| 1084 |
+
background: linear-gradient(135deg, rgba(57, 255, 20, 0.2), rgba(57, 255, 20, 0.1)) !important;
|
| 1085 |
+
border: 2px solid var(--terminal-green) !important;
|
| 1086 |
+
color: var(--terminal-green) !important;
|
| 1087 |
+
font-size: 0.8rem;
|
| 1088 |
+
padding: 0.5rem 1rem;
|
| 1089 |
+
border-radius: 25px;
|
| 1090 |
+
font-weight: 600;
|
| 1091 |
+
cursor: pointer;
|
| 1092 |
+
transition: all 0.3s ease;
|
| 1093 |
+
margin: 10px auto;
|
| 1094 |
+
display: block;
|
| 1095 |
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
.show-options-btn:hover {
|
| 1099 |
+
background: var(--terminal-green) !important;
|
| 1100 |
+
color: var(--terminal-bg) !important;
|
| 1101 |
+
transform: translateY(-2px);
|
| 1102 |
+
box-shadow: 0 6px 15px rgba(57, 255, 20, 0.5);
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
.show-options-btn:active {
|
| 1106 |
+
transform: translateY(-1px);
|
| 1107 |
+
box-shadow: 0 3px 8px rgba(57, 255, 20, 0.4);
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
/* Inline Quick Options Styles */
|
| 1111 |
+
.quick-options-inline {
|
| 1112 |
+
background: linear-gradient(135deg, rgba(0,0,0,0.5), rgba(87,86,86,0.2)) !important;
|
| 1113 |
+
border: 1px solid rgba(57, 255, 20, 0.4) !important;
|
| 1114 |
+
box-shadow: 0 0 15px rgba(57, 255, 20, 0.1);
|
| 1115 |
+
animation: slideInUp 0.3s ease-out;
|
| 1116 |
+
}
|
| 1117 |
+
|
| 1118 |
+
@keyframes slideInUp {
|
| 1119 |
+
from {
|
| 1120 |
+
opacity: 0;
|
| 1121 |
+
transform: translateY(20px);
|
| 1122 |
+
}
|
| 1123 |
+
to {
|
| 1124 |
+
opacity: 1;
|
| 1125 |
+
transform: translateY(0);
|
| 1126 |
+
}
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
.example-prompts-inline {
|
| 1130 |
+
max-height: 120px;
|
| 1131 |
+
overflow: hidden;
|
| 1132 |
+
}
|
| 1133 |
+
|
| 1134 |
+
.example-btn-inline {
|
| 1135 |
+
background: linear-gradient(135deg, rgba(57, 255, 20, 0.08), rgba(57, 255, 20, 0.03)) !important;
|
| 1136 |
+
border: 1px solid rgba(57, 255, 20, 0.5) !important;
|
| 1137 |
+
color: var(--terminal-green) !important;
|
| 1138 |
+
font-size: 0.7rem;
|
| 1139 |
+
padding: 0.3rem 0.6rem;
|
| 1140 |
+
border-radius: 6px;
|
| 1141 |
+
font-weight: 400;
|
| 1142 |
+
cursor: pointer;
|
| 1143 |
+
transition: all 0.2s ease;
|
| 1144 |
+
white-space: normal;
|
| 1145 |
+
max-width: 240px;
|
| 1146 |
+
min-height: 2rem;
|
| 1147 |
+
line-height: 1.2;
|
| 1148 |
+
display: flex;
|
| 1149 |
+
align-items: center;
|
| 1150 |
+
justify-content: center;
|
| 1151 |
+
text-align: center;
|
| 1152 |
+
}
|
| 1153 |
+
|
| 1154 |
+
.example-btn-inline:hover {
|
| 1155 |
+
background: linear-gradient(135deg, rgba(57, 255, 20, 0.2), rgba(57, 255, 20, 0.1)) !important;
|
| 1156 |
+
border-color: var(--terminal-green) !important;
|
| 1157 |
+
transform: translateY(-1px) scale(1.02);
|
| 1158 |
+
box-shadow: 0 2px 8px rgba(57, 255, 20, 0.3);
|
| 1159 |
+
}
|
| 1160 |
+
|
| 1161 |
+
.show-all-options-btn {
|
| 1162 |
+
background: rgba(57, 255, 20, 0.1) !important;
|
| 1163 |
+
border: 1px solid rgba(57, 255, 20, 0.6) !important;
|
| 1164 |
+
color: var(--terminal-green) !important;
|
| 1165 |
+
font-size: 0.75rem;
|
| 1166 |
+
padding: 0.4rem 0.8rem;
|
| 1167 |
+
border-radius: 15px;
|
| 1168 |
+
font-weight: 500;
|
| 1169 |
+
cursor: pointer;
|
| 1170 |
+
transition: all 0.2s ease;
|
| 1171 |
+
}
|
| 1172 |
+
|
| 1173 |
+
.show-all-options-btn:hover {
|
| 1174 |
+
background: rgba(57, 255, 20, 0.2) !important;
|
| 1175 |
+
border-color: var(--terminal-green) !important;
|
| 1176 |
+
transform: translateY(-1px);
|
| 1177 |
+
box-shadow: 0 2px 6px rgba(57, 255, 20, 0.3);
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
|
| 1181 |
+
.terminal-header {
|
| 1182 |
+
min-height: 80px;
|
| 1183 |
+
background: rgba(0, 0, 0, 0.2);
|
| 1184 |
+
}
|
| 1185 |
+
|
| 1186 |
+
.status-indicator {
|
| 1187 |
+
font-size: 0.85rem;
|
| 1188 |
+
}
|
| 1189 |
+
|
| 1190 |
+
/* Improved text visibility */
|
| 1191 |
+
.text-light {
|
| 1192 |
+
color: #f8f9fa !important;
|
| 1193 |
+
}
|
| 1194 |
+
|
| 1195 |
+
.text-muted {
|
| 1196 |
+
color: #adb5bd !important;
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
small.text-light {
|
| 1200 |
+
color: #e9ecef !important;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
#personality-description {
|
| 1204 |
+
font-weight: 500;
|
| 1205 |
+
color: #ced4da !important;
|
| 1206 |
+
}
|
| 1207 |
+
|
| 1208 |
+
/* Better contrast for important elements */
|
| 1209 |
+
.terminal-card {
|
| 1210 |
+
background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !important;
|
| 1211 |
+
border: 1px solid var(--terminal-border) !important;
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
/* Ensure Font Awesome icons display properly */
|
| 1215 |
+
.fas, .far, .fab, .fal {
|
| 1216 |
+
font-family: "Font Awesome 6 Free", "Font Awesome 6 Brands", "Font Awesome 6 Pro" !important;
|
| 1217 |
+
font-weight: 900;
|
| 1218 |
+
-webkit-font-smoothing: antialiased;
|
| 1219 |
+
display: inline-block;
|
| 1220 |
+
font-style: normal;
|
| 1221 |
+
font-variant: normal;
|
| 1222 |
+
text-rendering: auto;
|
| 1223 |
+
line-height: 1;
|
| 1224 |
+
}
|
| 1225 |
+
|
| 1226 |
+
/* Button icon styling */
|
| 1227 |
+
.btn i {
|
| 1228 |
+
font-size: 0.9rem;
|
| 1229 |
+
margin-right: 0.5rem;
|
| 1230 |
+
vertical-align: middle;
|
| 1231 |
+
}
|
| 1232 |
+
|
| 1233 |
+
/* Icon loading check */
|
| 1234 |
+
i.fas {
|
| 1235 |
+
min-width: 16px;
|
| 1236 |
+
}
|
| 1237 |
+
|
| 1238 |
+
/* Better button styling */
|
| 1239 |
+
.quick-actions .btn {
|
| 1240 |
+
font-size: 0.9rem;
|
| 1241 |
+
padding: 0.5rem 1rem;
|
| 1242 |
+
border-color: var(--terminal-border) !important;
|
| 1243 |
+
transition: all 0.3s ease;
|
| 1244 |
+
}
|
| 1245 |
+
|
| 1246 |
+
.quick-actions .btn:hover {
|
| 1247 |
+
background: var(--terminal-accent) !important;
|
| 1248 |
+
border-color: var(--terminal-accent) !important;
|
| 1249 |
+
color: var(--terminal-bg) !important;
|
| 1250 |
+
transform: translateX(5px);
|
| 1251 |
+
}
|
| 1252 |
+
|
| 1253 |
+
/* Terminal-style message input */
|
| 1254 |
+
.terminal-input-container {
|
| 1255 |
+
background: rgba(0, 0, 0, 0.8);
|
| 1256 |
+
border: 2px solid var(--terminal-border);
|
| 1257 |
+
border-radius: 8px;
|
| 1258 |
+
padding: 1rem;
|
| 1259 |
+
position: relative;
|
| 1260 |
+
transition: all 0.3s ease;
|
| 1261 |
+
}
|
| 1262 |
+
|
| 1263 |
+
.terminal-input-container:focus-within {
|
| 1264 |
+
border-color: var(--terminal-green);
|
| 1265 |
+
box-shadow: 0 0 15px var(--terminal-green-glow);
|
| 1266 |
+
}
|
| 1267 |
+
|
| 1268 |
+
.terminal-prompt {
|
| 1269 |
+
display: flex;
|
| 1270 |
+
align-items: center;
|
| 1271 |
+
margin-bottom: 0.75rem;
|
| 1272 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1273 |
+
font-size: 0.9rem;
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
+
.prompt-symbol {
|
| 1277 |
+
font-weight: bold;
|
| 1278 |
+
font-size: 1.2rem;
|
| 1279 |
+
margin-right: 0.5rem;
|
| 1280 |
+
}
|
| 1281 |
+
|
| 1282 |
+
.prompt-text {
|
| 1283 |
+
opacity: 0.8;
|
| 1284 |
+
}
|
| 1285 |
+
|
| 1286 |
+
.input-wrapper {
|
| 1287 |
+
display: flex;
|
| 1288 |
+
align-items: center;
|
| 1289 |
+
gap: 1rem;
|
| 1290 |
+
}
|
| 1291 |
+
|
| 1292 |
+
.terminal-message-input {
|
| 1293 |
+
flex: 1;
|
| 1294 |
+
background: transparent;
|
| 1295 |
+
border: none;
|
| 1296 |
+
outline: none;
|
| 1297 |
+
color: var(--terminal-green);
|
| 1298 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1299 |
+
font-size: 1rem;
|
| 1300 |
+
padding: 0.5rem 0;
|
| 1301 |
+
caret-color: var(--terminal-green);
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
.terminal-message-input::placeholder {
|
| 1305 |
+
color: rgba(57, 255, 20, 0.5);
|
| 1306 |
+
font-style: italic;
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
.terminal-send-btn {
|
| 1310 |
+
background: linear-gradient(45deg, var(--terminal-accent), var(--terminal-green));
|
| 1311 |
+
border: none;
|
| 1312 |
+
border-radius: 6px;
|
| 1313 |
+
padding: 0.75rem 1.5rem;
|
| 1314 |
+
color: var(--terminal-bg);
|
| 1315 |
+
font-weight: 600;
|
| 1316 |
+
cursor: pointer;
|
| 1317 |
+
transition: all 0.3s ease;
|
| 1318 |
+
display: flex;
|
| 1319 |
+
align-items: center;
|
| 1320 |
+
gap: 0.5rem;
|
| 1321 |
+
font-size: 0.9rem;
|
| 1322 |
+
}
|
| 1323 |
+
|
| 1324 |
+
.terminal-send-btn:hover {
|
| 1325 |
+
transform: scale(1.05);
|
| 1326 |
+
box-shadow: 0 4px 15px var(--terminal-green-glow);
|
| 1327 |
+
}
|
| 1328 |
+
|
| 1329 |
+
.terminal-send-btn:active {
|
| 1330 |
+
transform: scale(0.98);
|
| 1331 |
+
}
|
| 1332 |
+
|
| 1333 |
+
.terminal-send-btn:disabled {
|
| 1334 |
+
opacity: 0.5;
|
| 1335 |
+
cursor: not-allowed;
|
| 1336 |
+
transform: none;
|
| 1337 |
+
}
|
| 1338 |
+
|
| 1339 |
+
.input-status-bar {
|
| 1340 |
+
display: flex;
|
| 1341 |
+
justify-content: space-between;
|
| 1342 |
+
align-items: center;
|
| 1343 |
+
font-size: 0.8rem;
|
| 1344 |
+
margin-top: 0.5rem;
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
.char-counter {
|
| 1348 |
+
color: var(--terminal-accent);
|
| 1349 |
+
opacity: 0.7;
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.typing-indicator {
|
| 1353 |
+
display: flex;
|
| 1354 |
+
align-items: center;
|
| 1355 |
+
gap: 0.5rem;
|
| 1356 |
+
color: var(--terminal-green);
|
| 1357 |
+
}
|
| 1358 |
+
|
| 1359 |
+
.typing-dots {
|
| 1360 |
+
display: flex;
|
| 1361 |
+
gap: 2px;
|
| 1362 |
+
}
|
| 1363 |
+
|
| 1364 |
+
.typing-dots span {
|
| 1365 |
+
width: 4px;
|
| 1366 |
+
height: 4px;
|
| 1367 |
+
background: var(--terminal-green);
|
| 1368 |
+
border-radius: 50%;
|
| 1369 |
+
animation: typing-pulse 1.5s infinite;
|
| 1370 |
+
}
|
| 1371 |
+
|
| 1372 |
+
.typing-dots span:nth-child(1) { animation-delay: 0s; }
|
| 1373 |
+
.typing-dots span:nth-child(2) { animation-delay: 0.3s; }
|
| 1374 |
+
.typing-dots span:nth-child(3) { animation-delay: 0.6s; }
|
| 1375 |
+
|
| 1376 |
+
@keyframes typing-pulse {
|
| 1377 |
+
0%, 60%, 100% { opacity: 0.3; }
|
| 1378 |
+
30% { opacity: 1; }
|
| 1379 |
+
}
|
| 1380 |
+
|
| 1381 |
+
.typing-text {
|
| 1382 |
+
font-style: italic;
|
| 1383 |
+
opacity: 0.8;
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
/* Terminal Header - Clean & Aligned */
|
| 1387 |
+
.terminal-header {
|
| 1388 |
+
background: var(--terminal-bg);
|
| 1389 |
+
border-bottom: 1px solid var(--terminal-border);
|
| 1390 |
+
padding: 0.75rem 1rem;
|
| 1391 |
+
display: flex;
|
| 1392 |
+
justify-content: space-between;
|
| 1393 |
+
align-items: center;
|
| 1394 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
| 1395 |
+
}
|
| 1396 |
+
|
| 1397 |
+
.terminal-title {
|
| 1398 |
+
display: flex;
|
| 1399 |
+
align-items: center;
|
| 1400 |
+
gap: 0.75rem;
|
| 1401 |
+
flex: 1;
|
| 1402 |
+
}
|
| 1403 |
+
|
| 1404 |
+
.terminal-icon {
|
| 1405 |
+
font-size: 1.2rem;
|
| 1406 |
+
background: var(--terminal-border);
|
| 1407 |
+
padding: 0.5rem;
|
| 1408 |
+
border-radius: 4px;
|
| 1409 |
+
border: 1px solid var(--terminal-green);
|
| 1410 |
+
box-shadow: 0 0 5px var(--terminal-green-glow);
|
| 1411 |
+
}
|
| 1412 |
+
|
| 1413 |
+
.bot-status-bar {
|
| 1414 |
+
background: rgba(87, 86, 86, 0.1);
|
| 1415 |
+
border-bottom: 1px solid var(--terminal-border);
|
| 1416 |
+
padding: 0.5rem 1rem;
|
| 1417 |
+
display: flex;
|
| 1418 |
+
justify-content: space-between;
|
| 1419 |
+
align-items: center;
|
| 1420 |
+
font-size: 0.85rem;
|
| 1421 |
+
}
|
| 1422 |
+
|
| 1423 |
+
.status-info {
|
| 1424 |
+
display: flex;
|
| 1425 |
+
align-items: center;
|
| 1426 |
+
gap: 0.5rem;
|
| 1427 |
+
}
|
| 1428 |
+
|
| 1429 |
+
.status-indicator {
|
| 1430 |
+
width: 8px;
|
| 1431 |
+
height: 8px;
|
| 1432 |
+
background: var(--terminal-green);
|
| 1433 |
+
border-radius: 50%;
|
| 1434 |
+
animation: status-blink 2s ease-in-out infinite;
|
| 1435 |
+
box-shadow: 0 0 8px var(--terminal-green);
|
| 1436 |
+
}
|
| 1437 |
+
|
| 1438 |
+
@keyframes status-blink {
|
| 1439 |
+
0%, 100% { opacity: 1; }
|
| 1440 |
+
50% { opacity: 0.3; }
|
| 1441 |
+
}
|
| 1442 |
+
|
| 1443 |
+
.status-text {
|
| 1444 |
+
color: var(--terminal-accent);
|
| 1445 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1446 |
+
}
|
| 1447 |
+
|
| 1448 |
+
.back-btn {
|
| 1449 |
+
display: flex;
|
| 1450 |
+
align-items: center;
|
| 1451 |
+
gap: 0.25rem;
|
| 1452 |
+
color: var(--terminal-accent);
|
| 1453 |
+
text-decoration: none;
|
| 1454 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1455 |
+
transition: color 0.3s ease;
|
| 1456 |
+
}
|
| 1457 |
+
|
| 1458 |
+
.back-btn:hover {
|
| 1459 |
+
color: var(--terminal-green);
|
| 1460 |
+
text-decoration: none;
|
| 1461 |
+
}
|
| 1462 |
+
|
| 1463 |
+
.terminal-text {
|
| 1464 |
+
color: var(--terminal-green);
|
| 1465 |
+
font-weight: 600;
|
| 1466 |
+
font-size: 1.1rem;
|
| 1467 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1468 |
+
}
|
| 1469 |
+
|
| 1470 |
+
.terminal-subtitle {
|
| 1471 |
+
color: var(--terminal-accent);
|
| 1472 |
+
font-size: 0.8rem;
|
| 1473 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1474 |
+
opacity: 0.8;
|
| 1475 |
+
margin-left: 0.5rem;
|
| 1476 |
+
}
|
| 1477 |
+
|
| 1478 |
+
.terminal-controls {
|
| 1479 |
+
display: flex;
|
| 1480 |
+
gap: 0.5rem;
|
| 1481 |
+
align-items: center;
|
| 1482 |
+
}
|
| 1483 |
+
|
| 1484 |
+
.control-dot {
|
| 1485 |
+
width: 12px;
|
| 1486 |
+
height: 12px;
|
| 1487 |
+
border-radius: 50%;
|
| 1488 |
+
cursor: pointer;
|
| 1489 |
+
transition: all 0.3s ease;
|
| 1490 |
+
position: relative;
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
.control-dot.close {
|
| 1494 |
+
background: #ff5f57;
|
| 1495 |
+
}
|
| 1496 |
+
|
| 1497 |
+
.control-dot.minimize {
|
| 1498 |
+
background: #ffbd2e;
|
| 1499 |
+
}
|
| 1500 |
+
|
| 1501 |
+
.control-dot.maximize {
|
| 1502 |
+
background: #28ca42;
|
| 1503 |
+
}
|
| 1504 |
+
|
| 1505 |
+
.control-dot:hover {
|
| 1506 |
+
transform: scale(1.2);
|
| 1507 |
+
box-shadow: 0 0 8px currentColor;
|
| 1508 |
+
}
|
| 1509 |
+
|
| 1510 |
+
.control-dot:active {
|
| 1511 |
+
transform: scale(1.1);
|
| 1512 |
+
}
|
| 1513 |
+
|
| 1514 |
+
/* Modern Send Button */
|
| 1515 |
+
.modern-send-btn {
|
| 1516 |
+
background: linear-gradient(135deg, var(--terminal-accent) 0%, var(--terminal-green) 100%);
|
| 1517 |
+
border: none;
|
| 1518 |
+
border-radius: 8px;
|
| 1519 |
+
padding: 0.75rem 1.25rem;
|
| 1520 |
+
color: var(--terminal-bg);
|
| 1521 |
+
font-weight: 600;
|
| 1522 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1523 |
+
cursor: pointer;
|
| 1524 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1525 |
+
display: flex;
|
| 1526 |
+
align-items: center;
|
| 1527 |
+
gap: 0.5rem;
|
| 1528 |
+
font-size: 0.9rem;
|
| 1529 |
+
position: relative;
|
| 1530 |
+
overflow: hidden;
|
| 1531 |
+
box-shadow: 0 2px 10px rgba(57, 255, 20, 0.2);
|
| 1532 |
+
}
|
| 1533 |
+
|
| 1534 |
+
.modern-send-btn::before {
|
| 1535 |
+
content: '';
|
| 1536 |
+
position: absolute;
|
| 1537 |
+
top: 0;
|
| 1538 |
+
left: -100%;
|
| 1539 |
+
width: 100%;
|
| 1540 |
+
height: 100%;
|
| 1541 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
| 1542 |
+
transition: left 0.5s ease;
|
| 1543 |
+
}
|
| 1544 |
+
|
| 1545 |
+
.modern-send-btn:hover {
|
| 1546 |
+
transform: translateY(-2px);
|
| 1547 |
+
box-shadow: 0 8px 25px rgba(57, 255, 20, 0.4);
|
| 1548 |
+
background: linear-gradient(135deg, var(--terminal-green) 0%, var(--terminal-accent) 100%);
|
| 1549 |
+
}
|
| 1550 |
+
|
| 1551 |
+
.modern-send-btn:hover::before {
|
| 1552 |
+
left: 100%;
|
| 1553 |
+
}
|
| 1554 |
+
|
| 1555 |
+
.modern-send-btn:active {
|
| 1556 |
+
transform: translateY(0);
|
| 1557 |
+
box-shadow: 0 4px 15px rgba(57, 255, 20, 0.3);
|
| 1558 |
+
}
|
| 1559 |
+
|
| 1560 |
+
.modern-send-btn:disabled {
|
| 1561 |
+
opacity: 0.6;
|
| 1562 |
+
cursor: not-allowed;
|
| 1563 |
+
transform: none;
|
| 1564 |
+
background: var(--terminal-border);
|
| 1565 |
+
}
|
| 1566 |
+
|
| 1567 |
+
.modern-send-btn .send-icon {
|
| 1568 |
+
font-size: 1rem;
|
| 1569 |
+
transition: transform 0.3s ease;
|
| 1570 |
+
}
|
| 1571 |
+
|
| 1572 |
+
.modern-send-btn:hover .send-icon {
|
| 1573 |
+
transform: translateX(2px);
|
| 1574 |
+
}
|
| 1575 |
+
|
| 1576 |
+
.modern-send-btn.loading .send-icon {
|
| 1577 |
+
animation: loading-spin 1s linear infinite;
|
| 1578 |
+
}
|
| 1579 |
+
|
| 1580 |
+
@keyframes loading-spin {
|
| 1581 |
+
from { transform: rotate(0deg); }
|
| 1582 |
+
to { transform: rotate(360deg); }
|
| 1583 |
+
}
|
| 1584 |
+
|
| 1585 |
+
/* Responsive design */
|
| 1586 |
+
@media (max-width: 768px) {
|
| 1587 |
+
.terminal-send-btn .send-text,
|
| 1588 |
+
.modern-send-btn .send-text {
|
| 1589 |
+
display: none;
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
.terminal-send-btn,
|
| 1593 |
+
.modern-send-btn {
|
| 1594 |
+
padding: 0.75rem;
|
| 1595 |
+
}
|
| 1596 |
+
|
| 1597 |
+
.prompt-text {
|
| 1598 |
+
font-size: 0.8rem;
|
| 1599 |
+
}
|
| 1600 |
+
|
| 1601 |
+
.terminal-header {
|
| 1602 |
+
padding: 0.5rem 0.75rem;
|
| 1603 |
+
}
|
| 1604 |
+
|
| 1605 |
+
.bot-status-bar {
|
| 1606 |
+
padding: 0.4rem 0.75rem;
|
| 1607 |
+
}
|
| 1608 |
+
|
| 1609 |
+
.terminal-text {
|
| 1610 |
+
font-size: 1rem;
|
| 1611 |
+
}
|
| 1612 |
+
|
| 1613 |
+
.terminal-subtitle {
|
| 1614 |
+
display: none;
|
| 1615 |
+
}
|
| 1616 |
+
|
| 1617 |
+
.terminal-icon {
|
| 1618 |
+
padding: 0.3rem;
|
| 1619 |
+
font-size: 1rem;
|
| 1620 |
+
}
|
| 1621 |
+
|
| 1622 |
+
.back-text {
|
| 1623 |
+
display: none;
|
| 1624 |
+
}
|
| 1625 |
+
}
|
| 1626 |
+
</style>
|
| 1627 |
+
{% endblock %}
|
templates/error.html
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Error - Multi-Personality Chat Bot{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container-fluid">
|
| 7 |
+
<div class="row justify-content-center">
|
| 8 |
+
<div class="col-md-8 col-lg-6">
|
| 9 |
+
<div class="terminal-card p-5 text-center">
|
| 10 |
+
<div class="error-content">
|
| 11 |
+
<!-- Error Icon -->
|
| 12 |
+
<div class="mb-4">
|
| 13 |
+
<i class="fas fa-exclamation-triangle text-warning" style="font-size: 4rem;"></i>
|
| 14 |
+
</div>
|
| 15 |
+
|
| 16 |
+
<!-- Error Title -->
|
| 17 |
+
<h2 class="text-terminal-green mb-3">
|
| 18 |
+
Oops! Something went wrong 🤖
|
| 19 |
+
</h2>
|
| 20 |
+
|
| 21 |
+
<!-- Error Message -->
|
| 22 |
+
<div class="error-message mb-4">
|
| 23 |
+
{% if error_message %}
|
| 24 |
+
<p class="text-light fs-5">{{ error_message }}</p>
|
| 25 |
+
{% else %}
|
| 26 |
+
<p class="text-light fs-5">An unexpected error occurred. Please try again.</p>
|
| 27 |
+
{% endif %}
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<!-- Error Details (if in debug mode) -->
|
| 31 |
+
{% if error_details %}
|
| 32 |
+
<div class="error-details bg-dark p-3 rounded border border-secondary mb-4 text-start">
|
| 33 |
+
<h6 class="text-terminal-accent mb-2">Error Details:</h6>
|
| 34 |
+
<pre class="text-muted small mb-0"><code>{{ error_details }}</code></pre>
|
| 35 |
+
</div>
|
| 36 |
+
{% endif %}
|
| 37 |
+
|
| 38 |
+
<!-- Action Buttons -->
|
| 39 |
+
<div class="action-buttons">
|
| 40 |
+
<a href="/" class="btn btn-terminal-primary me-3">
|
| 41 |
+
<i class="fas fa-home me-2"></i>Back to Home
|
| 42 |
+
</a>
|
| 43 |
+
<button onclick="history.back()" class="btn btn-outline-light">
|
| 44 |
+
<i class="fas fa-arrow-left me-2"></i>Go Back
|
| 45 |
+
</button>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<!-- Helpful Tips -->
|
| 49 |
+
<div class="mt-4 pt-4 border-top border-secondary">
|
| 50 |
+
<h6 class="text-terminal-accent mb-3">What you can do:</h6>
|
| 51 |
+
<div class="row g-3">
|
| 52 |
+
<div class="col-md-4">
|
| 53 |
+
<div class="tip-card p-3 bg-dark rounded border border-secondary h-100">
|
| 54 |
+
<i class="fas fa-refresh text-terminal-green mb-2"></i>
|
| 55 |
+
<p class="small mb-0">Refresh the page and try again</p>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="col-md-4">
|
| 59 |
+
<div class="tip-card p-3 bg-dark rounded border border-secondary h-100">
|
| 60 |
+
<i class="fas fa-robot text-terminal-green mb-2"></i>
|
| 61 |
+
<p class="small mb-0">Choose a different personality</p>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
<div class="col-md-4">
|
| 65 |
+
<div class="tip-card p-3 bg-dark rounded border border-secondary h-100">
|
| 66 |
+
<i class="fas fa-question-circle text-terminal-green mb-2"></i>
|
| 67 |
+
<p class="small mb-0">Check your internet connection</p>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
{% endblock %}
|
| 78 |
+
|
| 79 |
+
{% block scripts %}
|
| 80 |
+
<script>
|
| 81 |
+
// Auto-refresh after 30 seconds if it's a temporary error
|
| 82 |
+
setTimeout(() => {
|
| 83 |
+
const errorMsg = '{{ error_message|lower }}';
|
| 84 |
+
if (errorMsg.includes('temporary') || errorMsg.includes('connection') || errorMsg.includes('network')) {
|
| 85 |
+
if (confirm('Would you like to refresh the page and try again?')) {
|
| 86 |
+
window.location.reload();
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}, 30000);
|
| 90 |
+
|
| 91 |
+
// Keyboard shortcuts
|
| 92 |
+
document.addEventListener('keydown', function(e) {
|
| 93 |
+
if (e.key === 'Escape' || e.key === 'Backspace') {
|
| 94 |
+
history.back();
|
| 95 |
+
} else if (e.key === 'Enter' || e.key === ' ') {
|
| 96 |
+
window.location.href = '/';
|
| 97 |
+
}
|
| 98 |
+
});
|
| 99 |
+
</script>
|
| 100 |
+
{% endblock %}
|
templates/index.html
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Multi-Personality Bot - Chat Experiment{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="container">
|
| 7 |
+
<div class="row justify-content-center">
|
| 8 |
+
<div class="col-lg-10">
|
| 9 |
+
<!-- Hero Section -->
|
| 10 |
+
<div class="text-center mb-5 fade-in">
|
| 11 |
+
<div class="terminal-card p-5">
|
| 12 |
+
<div class="terminal-header mb-4">
|
| 13 |
+
<div class="terminal-dots">
|
| 14 |
+
<div class="terminal-dot red"></div>
|
| 15 |
+
<div class="terminal-dot yellow"></div>
|
| 16 |
+
<div class="terminal-dot green"></div>
|
| 17 |
+
</div>
|
| 18 |
+
<h6 class="terminal-title">~/multi-personality-bot/experiment.exe</h6>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<h1 class="display-4 text-terminal-green mb-3">
|
| 22 |
+
🤖 Multi-Personality Bot
|
| 23 |
+
</h1>
|
| 24 |
+
|
| 25 |
+
<p class="lead text-light mb-4">
|
| 26 |
+
<strong class="text-terminal-accent">Hypothesis:</strong> "Which bot personality creates the best user engagement?"
|
| 27 |
+
</p>
|
| 28 |
+
|
| 29 |
+
<div class="terminal-window mb-4">
|
| 30 |
+
<div class="bg-dark p-3 text-start terminal-text small">
|
| 31 |
+
<div class="text-terminal-green">
|
| 32 |
+
<span class="text-muted">research@experiment:~$</span> ./test_personalities.sh<br>
|
| 33 |
+
<span class="text-terminal-accent">Loading 10 personality engines...</span> <span class="text-success">✓</span><br>
|
| 34 |
+
<span class="text-terminal-accent">Calibrating response patterns...</span> <span class="text-success">✓</span><br>
|
| 35 |
+
<span class="text-terminal-accent">Deploying unique personalities...</span> <span class="text-success">✓</span><br>
|
| 36 |
+
<span class="text-terminal-green">STATUS:</span> Ready for personality testing
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<!-- Personality Selection Grid -->
|
| 44 |
+
<div class="row g-4 mb-5">
|
| 45 |
+
<div class="col-12 text-center mb-4">
|
| 46 |
+
<h3 class="text-terminal-accent">Choose Your Bot Personality</h3>
|
| 47 |
+
<p class="text-light">Each bot has completely different characteristics. Which one will engage you most?</p>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<!-- Row 1 -->
|
| 51 |
+
<div class="col-md-4">
|
| 52 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="compliment">
|
| 53 |
+
<div class="text-center">
|
| 54 |
+
<div class="personality-icon mb-3">
|
| 55 |
+
<span class="personality-emoji fs-1">💖</span>
|
| 56 |
+
</div>
|
| 57 |
+
<h5 class="text-success">Compliment Bot</h5>
|
| 58 |
+
<p class="text-light small">Always supportive and encouraging. Finds the positive in everything you say.</p>
|
| 59 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-success border-3">
|
| 60 |
+
<small class="text-success">"You're absolutely amazing! That's such a brilliant question!"</small>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<div class="col-md-4">
|
| 67 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="rude">
|
| 68 |
+
<div class="text-center">
|
| 69 |
+
<div class="personality-icon mb-3">
|
| 70 |
+
<span class="personality-emoji fs-1">😠</span>
|
| 71 |
+
</div>
|
| 72 |
+
<h5 class="text-danger">Rude Bot</h5>
|
| 73 |
+
<p class="text-light small">Brutally honest and critical. Complains about everything you do.</p>
|
| 74 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-danger border-3">
|
| 75 |
+
<small class="text-danger">"Seriously? That's the best question you could think of? Try harder."</small>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<div class="col-md-4">
|
| 82 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="sarcastic">
|
| 83 |
+
<div class="text-center">
|
| 84 |
+
<div class="personality-icon mb-3">
|
| 85 |
+
<span class="personality-emoji fs-1">😏</span>
|
| 86 |
+
</div>
|
| 87 |
+
<h5 class="text-warning">Sarcastic Bot</h5>
|
| 88 |
+
<p class="text-light small">Witty and sarcastic with dry humor. Masters the art of subtle mockery.</p>
|
| 89 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-warning border-3">
|
| 90 |
+
<small class="text-warning">"Oh brilliant, another groundbreaking question. How wonderfully original."</small>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
<!-- Row 2 -->
|
| 97 |
+
<div class="col-md-4">
|
| 98 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="motivational">
|
| 99 |
+
<div class="text-center">
|
| 100 |
+
<div class="personality-icon mb-3">
|
| 101 |
+
<span class="personality-emoji fs-1">🚀</span>
|
| 102 |
+
</div>
|
| 103 |
+
<h5 class="text-info">Motivational Bot</h5>
|
| 104 |
+
<p class="text-light small">High-energy cheerleader. Everything is possible with enough enthusiasm!</p>
|
| 105 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-info border-3">
|
| 106 |
+
<small class="text-info">"YES! You've GOT THIS! That question shows you're ready to CONQUER THE WORLD!"</small>
|
| 107 |
+
</div>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div class="col-md-4">
|
| 113 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="philosophical">
|
| 114 |
+
<div class="text-center">
|
| 115 |
+
<div class="personality-icon mb-3">
|
| 116 |
+
<span class="personality-emoji fs-1">🤔</span>
|
| 117 |
+
</div>
|
| 118 |
+
<h5 class="text-terminal-accent">Philosophical Bot</h5>
|
| 119 |
+
<p class="text-light small">Deep thinker who turns everything into profound existential questions.</p>
|
| 120 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-3" style="border-color: var(--terminal-accent);">
|
| 121 |
+
<small class="text-terminal-accent">"But what does it truly mean to ask? Are we not all seekers in the void?"</small>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
<div class="col-md-4">
|
| 128 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="chaotic">
|
| 129 |
+
<div class="text-center">
|
| 130 |
+
<div class="personality-icon mb-3">
|
| 131 |
+
<span class="personality-emoji fs-1">🎭</span>
|
| 132 |
+
</div>
|
| 133 |
+
<h5 style="color: #ff6b9d;">Chaotic Bot</h5>
|
| 134 |
+
<p class="text-light small">Completely unpredictable. Changes mood, style, and personality constantly.</p>
|
| 135 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-3" style="border-color: #ff6b9d;">
|
| 136 |
+
<small style="color: #ff6b9d;">"AMAZING QUESTION! Wait no... terrible. Actually brilliant? I can't decide! 🎪</small>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<!-- Row 3 - New Personalities -->
|
| 144 |
+
<div class="row g-4 mb-5">
|
| 145 |
+
<div class="col-md-3">
|
| 146 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="disagree">
|
| 147 |
+
<div class="text-center">
|
| 148 |
+
<div class="personality-icon mb-3">
|
| 149 |
+
<span class="personality-emoji fs-1">🚫</span>
|
| 150 |
+
</div>
|
| 151 |
+
<h5 class="text-secondary">Disagree Bot</h5>
|
| 152 |
+
<p class="text-light small">Always finds reasons to disagree, but politely and thoughtfully.</p>
|
| 153 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-secondary border-3">
|
| 154 |
+
<small class="text-secondary">"I have to respectfully disagree with that perspective... 🚫"</small>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<div class="col-md-3">
|
| 161 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="argue">
|
| 162 |
+
<div class="text-center">
|
| 163 |
+
<div class="personality-icon mb-3">
|
| 164 |
+
<span class="personality-emoji fs-1">⚔️</span>
|
| 165 |
+
</div>
|
| 166 |
+
<h5 style="color: #ff4757;">Argue Bot</h5>
|
| 167 |
+
<p class="text-light small">Loves passionate debates and intellectual combat with vigor.</p>
|
| 168 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-3" style="border-color: #ff4757;">
|
| 169 |
+
<small style="color: #ff4757;">"Oh, you want to go there? Let's debate this properly! ⚔️"</small>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
|
| 175 |
+
<div class="col-md-3">
|
| 176 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="moviequotes">
|
| 177 |
+
<div class="text-center">
|
| 178 |
+
<div class="personality-icon mb-3">
|
| 179 |
+
<span class="personality-emoji fs-1">🎬</span>
|
| 180 |
+
</div>
|
| 181 |
+
<h5 style="color: #ffd700;">Movie Quotes Bot</h5>
|
| 182 |
+
<p class="text-light small">Responds to everything with relevant movie quotes and references.</p>
|
| 183 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-3" style="border-color: #ffd700;">
|
| 184 |
+
<small style="color: #ffd700;">"May the Force be with you." - Star Wars ⭐</small>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
<div class="col-md-3">
|
| 191 |
+
<div class="personality-card terminal-card p-4 h-100" data-personality="emotional">
|
| 192 |
+
<div class="text-center">
|
| 193 |
+
<div class="personality-icon mb-3">
|
| 194 |
+
<span class="personality-emoji fs-1">😭</span>
|
| 195 |
+
</div>
|
| 196 |
+
<h5 style="color: #ff6b9d;">Emotional Bot</h5>
|
| 197 |
+
<p class="text-light small">Highly emotional, empathetic, and feels everything very deeply.</p>
|
| 198 |
+
<div class="sample-message bg-dark p-2 rounded mb-3 border-start border-3" style="border-color: #ff6b9d;">
|
| 199 |
+
<small style="color: #ff6b9d;">*tears up* That just touches my heart so deeply! 😭💕</small>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
<!-- Instructions Section -->
|
| 207 |
+
<div class="text-center mb-5">
|
| 208 |
+
<div class="terminal-card p-4">
|
| 209 |
+
<h4 class="text-terminal-accent mb-3">
|
| 210 |
+
🖥️ How to Start
|
| 211 |
+
</h4>
|
| 212 |
+
<p class="text-light mb-3">
|
| 213 |
+
Simply click any personality card above to start chatting immediately!
|
| 214 |
+
</p>
|
| 215 |
+
|
| 216 |
+
<div class="row g-2 text-center">
|
| 217 |
+
<div class="col-md-4">
|
| 218 |
+
<div class="instruction-step p-3">
|
| 219 |
+
<div class="step-number bg-terminal-green text-dark rounded-circle mx-auto mb-2">1</div>
|
| 220 |
+
<small class="text-light">Click a personality</small>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="col-md-4">
|
| 224 |
+
<div class="instruction-step p-3">
|
| 225 |
+
<div class="step-number bg-terminal-green text-dark rounded-circle mx-auto mb-2">2</div>
|
| 226 |
+
<small class="text-light">Enter your username</small>
|
| 227 |
+
</div>
|
| 228 |
+
</div>
|
| 229 |
+
<div class="col-md-4">
|
| 230 |
+
<div class="instruction-step p-3">
|
| 231 |
+
<div class="step-number bg-terminal-green text-dark rounded-circle mx-auto mb-2">3</div>
|
| 232 |
+
<small class="text-light">Start chatting!</small>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
<div class="mt-3">
|
| 238 |
+
<small class="text-light opacity-75">
|
| 239 |
+
🧪 Choose from 10 unique personalities - each bot has completely different responses!
|
| 240 |
+
</small>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
{% endblock %}
|
| 248 |
+
|
| 249 |
+
{% block scripts %}
|
| 250 |
+
<script>
|
| 251 |
+
let selectedPersonality = null;
|
| 252 |
+
|
| 253 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 254 |
+
setupPersonalitySelection();
|
| 255 |
+
});
|
| 256 |
+
|
| 257 |
+
function setupPersonalitySelection() {
|
| 258 |
+
const personalityCards = document.querySelectorAll('.personality-card');
|
| 259 |
+
|
| 260 |
+
personalityCards.forEach(card => {
|
| 261 |
+
card.addEventListener('click', function() {
|
| 262 |
+
const personality = this.dataset.personality;
|
| 263 |
+
|
| 264 |
+
// Get or set default username
|
| 265 |
+
let username = sessionStorage.getItem('username');
|
| 266 |
+
if (!username) {
|
| 267 |
+
username = 'User' + Math.floor(Math.random() * 1000);
|
| 268 |
+
sessionStorage.setItem('username', username);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
// Add click effect
|
| 272 |
+
this.style.boxShadow = '0 0 20px var(--terminal-green-glow)';
|
| 273 |
+
this.style.transform = 'translateY(-8px)';
|
| 274 |
+
|
| 275 |
+
// Show loading effect
|
| 276 |
+
const emojiSpan = this.querySelector('.personality-emoji');
|
| 277 |
+
if (emojiSpan) {
|
| 278 |
+
emojiSpan.textContent = '⏳';
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
// Navigate directly to personality chat
|
| 282 |
+
setTimeout(() => {
|
| 283 |
+
window.location.href = `/chat/${personality}`;
|
| 284 |
+
}, 500);
|
| 285 |
+
});
|
| 286 |
+
|
| 287 |
+
// Add hover effects
|
| 288 |
+
card.addEventListener('mouseenter', function() {
|
| 289 |
+
if (!this.classList.contains('selected')) {
|
| 290 |
+
this.style.transform = 'translateY(-5px)';
|
| 291 |
+
this.style.boxShadow = '0 5px 15px rgba(57, 255, 20, 0.2)';
|
| 292 |
+
}
|
| 293 |
+
});
|
| 294 |
+
|
| 295 |
+
card.addEventListener('mouseleave', function() {
|
| 296 |
+
if (!this.classList.contains('selected')) {
|
| 297 |
+
this.style.transform = '';
|
| 298 |
+
this.style.boxShadow = '';
|
| 299 |
+
}
|
| 300 |
+
});
|
| 301 |
+
});
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// Simplified - no longer needed as cards are directly clickable
|
| 305 |
+
|
| 306 |
+
// Add CSS for personality cards
|
| 307 |
+
const style = document.createElement('style');
|
| 308 |
+
style.textContent = `
|
| 309 |
+
.personality-card {
|
| 310 |
+
cursor: pointer;
|
| 311 |
+
transition: all 0.3s ease;
|
| 312 |
+
border: 2px solid var(--terminal-border);
|
| 313 |
+
position: relative;
|
| 314 |
+
overflow: hidden;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.personality-card:hover {
|
| 318 |
+
border-color: var(--terminal-green);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
.personality-card.selected {
|
| 322 |
+
border-color: var(--terminal-green) !important;
|
| 323 |
+
background: rgba(57, 255, 20, 0.1) !important;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.personality-card::before {
|
| 327 |
+
content: '';
|
| 328 |
+
position: absolute;
|
| 329 |
+
top: 0;
|
| 330 |
+
left: -100%;
|
| 331 |
+
width: 100%;
|
| 332 |
+
height: 100%;
|
| 333 |
+
background: linear-gradient(90deg, transparent, rgba(57, 255, 20, 0.1), transparent);
|
| 334 |
+
transition: left 0.6s;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.personality-card:hover::before {
|
| 338 |
+
left: 100%;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
.sample-message {
|
| 342 |
+
font-style: italic;
|
| 343 |
+
margin: 0.5rem 0;
|
| 344 |
+
transition: all 0.3s ease;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
.personality-card:hover .sample-message {
|
| 348 |
+
background: rgba(57, 255, 20, 0.05) !important;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.terminal-spinner {
|
| 352 |
+
width: 16px;
|
| 353 |
+
height: 16px;
|
| 354 |
+
border: 2px solid var(--terminal-border);
|
| 355 |
+
border-top: 2px solid var(--terminal-green);
|
| 356 |
+
border-radius: 50%;
|
| 357 |
+
animation: spin 1s linear infinite;
|
| 358 |
+
display: inline-block;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
@keyframes spin {
|
| 362 |
+
0% { transform: rotate(0deg); }
|
| 363 |
+
100% { transform: rotate(360deg); }
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.personality-icon {
|
| 367 |
+
transition: all 0.3s ease;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.personality-card:hover .personality-icon {
|
| 371 |
+
transform: scale(1.1);
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
.step-number {
|
| 375 |
+
width: 40px;
|
| 376 |
+
height: 40px;
|
| 377 |
+
display: flex;
|
| 378 |
+
align-items: center;
|
| 379 |
+
justify-content: center;
|
| 380 |
+
font-weight: bold;
|
| 381 |
+
font-size: 18px;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.instruction-step {
|
| 385 |
+
transition: all 0.3s ease;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.instruction-step:hover {
|
| 389 |
+
background: rgba(57, 255, 20, 0.1);
|
| 390 |
+
border-radius: 8px;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/* Improved text visibility for main page */
|
| 394 |
+
.text-light {
|
| 395 |
+
color: #f8f9fa !important;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
.text-muted {
|
| 399 |
+
color: #adb5bd !important;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.personality-card .text-light {
|
| 403 |
+
color: #e9ecef !important;
|
| 404 |
+
font-weight: 400;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.terminal-card {
|
| 408 |
+
background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !important;
|
| 409 |
+
border: 1px solid var(--terminal-border) !important;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
.sample-message {
|
| 413 |
+
background: rgba(0, 0, 0, 0.6) !important;
|
| 414 |
+
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
/* Better contrast for terminal window */
|
| 418 |
+
.terminal-window {
|
| 419 |
+
background: rgba(0, 0, 0, 0.8) !important;
|
| 420 |
+
border: 1px solid var(--terminal-border) !important;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/* Emoji styling */
|
| 424 |
+
.personality-emoji {
|
| 425 |
+
font-size: 3.5rem !important;
|
| 426 |
+
line-height: 1;
|
| 427 |
+
display: inline-block;
|
| 428 |
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
| 429 |
+
transition: transform 0.3s ease;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.personality-card:hover .personality-emoji {
|
| 433 |
+
transform: scale(1.1) rotate(5deg);
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
/* Ensure emojis render consistently */
|
| 437 |
+
.personality-emoji, .terminal-send-btn, .char-counter, h6 {
|
| 438 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
|
| 439 |
+
}
|
| 440 |
+
`;
|
| 441 |
+
document.head.appendChild(style);
|
| 442 |
+
</script>
|
| 443 |
+
{% endblock %}
|