aradhyapavan commited on
Commit
540412a
·
verified ·
1 Parent(s): e22c871

multi-personality-bot

Browse files
.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, '&amp;')
482
+ .replace(/</g, '&lt;')
483
+ .replace(/>/g, '&gt;')
484
+ .replace(/\"/g, '&quot;')
485
+ .replace(/'/g, '&#39;');
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">&nbsp;</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 %}