# šŸ’‹ SexyChat (Aphrodite) A real-time encrypted chat platform with an AI companion, role-based moderation, and a glassmorphism UI. Built with Flask-SocketIO, AES-GCM encryption, and a local Ollama-powered AI named **Violet**. --- ## Table of Contents - [Features](#features) - [Architecture](#architecture) - [Project Structure](#project-structure) - [Setup & Installation](#setup--installation) - [Configuration](#configuration) - [Running the Server](#running-the-server) - [Database Models](#database-models) - [Socket Events Reference](#socket-events-reference) - [REST API Endpoints](#rest-api-endpoints) - [Role System & Admin Panel](#role-system--admin-panel) - [Encryption & Security](#encryption--security) - [Theme System](#theme-system) - [Settings Panel](#settings-panel) - [Violet AI Companion](#violet-ai-companion) - [Premium / Paywall](#premium--paywall) --- ## Features ### Core Chat - **Public Lobby** — Real-time ephemeral group chat. Messages are never persisted; they live only in connected clients' browsers. - **Private Messaging** — AES-GCM-256 encrypted PMs with persistent history (up to 500 messages per conversation). - **Guest Mode** — Join and chat anonymously without an account. - **Registration & Login** — Bcrypt password hashing, JWT token auth (7-day expiry), automatic session restore on page refresh. - **Rate Limiting** — 6 messages per 5-second window per session. ### Violet AI Companion - Local Ollama inference (`sadiq-bd/llama3.2-3b-uncensored:latest` by default). - Transit-encrypted messages — decrypted server-side only for inference, never stored in plaintext. - Per-user conversation memory (last 20 turns), persisted across sessions. `/reset` command to clear. - Free trial (configurable, default: 3 messages), then paywall — admins can grant unlimited access. - Real-time typing indicator while Violet is generating a response. - Friendly signup prompt for unregistered guests who try to PM Violet. ### Moderation & Roles - **4-tier role hierarchy:** `root` (šŸ‘‘) > `admin` (āš”ļø) > `mod` (šŸ›”ļø) > `user` - Kick, ban (username + IP), kickban, mute/unmute, verify/unverify. - Persistent bans and mutes survive server restarts (stored in DB, loaded on startup). - Admin panel with three tabs: Users, Bans, Mutes. - Privilege escalation prevention — you can't modify users at or above your own power level. ### User Features - Persistent ignore list (stealth blocking — ignored users receive no indication). - Context menu (right-click a username) for PM, ignore, and mod actions. - Settings panel with four tabs: Account, Chat, Violet, Premium. - Password change from settings (requires current password). - Unread PM indicators on conversation tabs. ### UI/UX - Glassmorphism dark theme with CSS custom property theming. - **10 color themes** (dark, light, neon, cyberpunk, and more). - Responsive mobile layout with collapsible sidebar. - Auto-expanding message textarea. - Enter-to-send toggle (configurable). - Google Fonts (Inter + Outfit). --- ## Architecture ``` ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” WebSocket / HTTP ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ Browser │ ◄────────────────────► │ Gunicorn + Eventlet │ │ (chat.js │ │ Flask-SocketIO │ │ crypto.js) │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │ REST Blueprint │ │ │ │ (routes.py) │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │ AI Worker (greenlet) │ │ │ │ Serialized Ollama queue │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ Ollama (localhost:11434) │ │ LLaMA 3.2 3B Uncensored │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ SQLite (instance/sexchat.db)│ │ Users, Messages, Bans, │ │ Mutes, VioletHistory │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ``` ### Key Design Decisions - **Single-process, single-worker** — Eventlet async mode with one Gunicorn worker. All in-memory state (connected users, rooms, rate limits) lives in-process. - **AI inference queue** — A dedicated eventlet greenlet serialises Ollama requests one at a time, broadcasting `violet_typing` indicators while busy. Runs within Flask app context for DB access. - **Lobby is ephemeral** — No lobby messages are ever written to the database. - **PMs are encrypted at rest** — The server stores only AES-GCM ciphertext and nonces for registered-user PMs. - **Shared config module** — `config.py` centralises all constants, config loading, AES-GCM helpers, and JWT helpers so `app.py` and `routes.py` share a single source of truth. ### Tech Stack | Layer | Technology | |-------|-----------| | Web server | Gunicorn 21.x with eventlet worker | | Framework | Flask 3.x + Flask-SocketIO 5.x | | Concurrency | Eventlet (monkey-patched greenlets) | | Database | SQLite (default) or PostgreSQL via `DATABASE_URL` | | ORM | SQLAlchemy 3.x + Flask-Migrate 4.x (Alembic) | | AI | Ollama HTTP API (local inference) | | Auth | JWT (PyJWT 2.x) + bcrypt 4.x | | Encryption | AES-GCM-256 (server: `cryptography` 42.x, client: Web Crypto API) | | Frontend | Vanilla JS, Socket.IO 4 client, custom CSS | | Scaling | Optional Redis message queue for multi-worker deployments | --- ## Project Structure ``` aprhodite/ ā”œā”€ā”€ app.py # Flask-SocketIO app: socket events, AI worker, admin panel, moderation ā”œā”€ā”€ config.py # Shared config loader, constants, AES-GCM helpers, JWT helpers, Violet prompt ā”œā”€ā”€ database.py # SQLAlchemy + Flask-Migrate init, Violet bot user seeding ā”œā”€ā”€ models.py # ORM models: User, Message, Ban, Mute, UserIgnore, VioletHistory ā”œā”€ā”€ routes.py # REST API blueprint: auth, PM history, AI message, payment webhook ā”œā”€ā”€ start.py # Process manager: start / stop / restart / status / debug ā”œā”€ā”€ gunicorn.conf.py # Gunicorn configuration (workers, bind, worker class) ā”œā”€ā”€ config.json # Runtime secrets & overrides (gitignored) ā”œā”€ā”€ requirements.txt # Python dependencies ā”œā”€ā”€ index.html # Single-page app: join screen, chat, settings modal, admin panel modal ā”œā”€ā”€ static/ │ ā”œā”€ā”€ chat.js # Frontend logic: socket events, PMs, settings, admin panel, themes │ ā”œā”€ā”€ crypto.js # Client-side AES-GCM encryption (PBKDF2 key derivation, SubtleCrypto) │ ā”œā”€ā”€ style.css # All styles: glassmorphism, 10 themes, admin panel, responsive layout │ └── socket.io.min.js# Socket.IO v4 client library └── instance/ └── sexchat.db # SQLite database (auto-created on first run) ``` --- ## Setup & Installation ### Prerequisites - **Python 3.11+** - **[Ollama](https://ollama.ai)** (required for Violet AI) ### Install ```bash git clone https://git.computertech.dev/lord3nd3r/aprhodite.git cd aprhodite python -m venv venv source venv/bin/activate pip install -r requirements.txt ``` ### Pull the AI Model ```bash ollama pull sadiq-bd/llama3.2-3b-uncensored:latest ``` ### Create `config.json` ```json { "SECRET_KEY": "your-random-secret-key", "JWT_SECRET": "your-random-jwt-secret", "ADMIN_PASSWORD": "a-strong-moderator-password", "AI_FREE_LIMIT": 3, "HOST": "0.0.0.0", "PORT": 5000 } ``` > **Tip:** Generate secrets with `python -c "import uuid; print(uuid.uuid4().hex)"`. ### Database On first run, `database.py` calls `db.create_all()` and seeds the **Violet** bot user automatically. No manual migration is needed for a fresh install. For schema changes after the initial setup: ```bash flask db migrate -m "description of changes" flask db upgrade ``` --- ## Configuration Configuration is resolved with the following precedence: **Environment Variable → `config.json` → Default Value**. All config loading is centralised in `config.py`. | Key | Default | Description | |-----|---------|-------------| | `SECRET_KEY` | Random UUID hex | Flask session secret | | `JWT_SECRET` | Random UUID hex | JWT HMAC signing key | | `ADMIN_PASSWORD` | `"admin1234"` | Shared mod password for guest elevation | | `DATABASE_URL` | `"sqlite:///sexchat.db"` | SQLAlchemy database URI | | `PAYMENT_SECRET` | `"change-me-payment-secret"` | Payment webhook validation secret | | `CORS_ORIGINS` | `None` | Socket.IO CORS allowlist | | `HOST` | `"0.0.0.0"` | Server bind address | | `PORT` | `5000` | Server bind port | | `OLLAMA_URL` | `"http://localhost:11434"` | Ollama API endpoint | | `VIOLET_MODEL` | `"sadiq-bd/llama3.2-3b-uncensored:latest"` | Ollama model tag for Violet | | `AI_FREE_LIMIT` | `3` | Free Violet messages before paywall | | `SOCKETIO_MESSAGE_QUEUE` / `REDIS_URL` | `None` | Redis URL for multi-worker Socket.IO | ### Hardcoded Constants (in `config.py`) | Constant | Value | Description | |----------|-------|-------------| | `MAX_MSG_LEN` | 500 chars | Maximum message length | | `MAX_HISTORY` | 500 messages | PM history cap per conversation pair | | `JWT_EXPIRY_DAYS` | 7 days | JWT token lifespan | | `AI_BOT_NAME` | `"Violet"` | AI bot display name | | `PBKDF2_ITERATIONS` | 100,000 | Client-side key derivation iterations (`crypto.js`) | | Rate limit | 6 msgs / 5 sec | Per-session lobby rate limit (`app.py`) | | `MAX_HISTORY_PER_USER` | 20 turns | Violet conversation memory depth (`app.py`) | --- ## Running the Server ### Debug Mode (foreground, full logging) ```bash python start.py debug ``` ### Daemon Mode ```bash python start.py start # Start in background (PID written to .pid file) python start.py stop # Graceful shutdown python start.py restart # Stop + start python start.py status # Check if running ``` ### Direct Gunicorn ```bash gunicorn --worker-class eventlet -w 1 --bind 0.0.0.0:5000 start:application ``` The server will be available at `http://localhost:5000`. --- ## Database Models Six models defined in `models.py`. All timestamps use `datetime.now(timezone.utc)`. ### `users` | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | Auto-increment | | `username` | String(20), unique, indexed | Alphanumeric + `-` + `_` | | `password_hash` | String(128) | Bcrypt hash | | `email` | String(255), unique, nullable | Optional email | | `role` | String(10), default `"user"` | `root`, `admin`, `mod`, or `user` | | `has_ai_access` | Boolean, default `False` | Unlimited Violet access flag | | `ai_messages_used` | Integer, default `0` | Free trial counter | | `is_verified` | Boolean, default `False` | Manual verification status | | `created_at` | DateTime | UTC timestamp | ### `messages` (Encrypted PM History) | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | | | `sender_id` | FK → `users.id` | | | `recipient_id` | FK → `users.id` | | | `encrypted_content` | Text | Base64-encoded AES-GCM ciphertext | | `nonce` | String(64) | Base64-encoded 12-byte IV | | `timestamp` | DateTime | Indexed with sender/recipient for query performance | ### `bans` | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | | | `username` | String(20), indexed | Banned username (lowercase) | | `ip` | String(45), nullable, indexed | Banned IP address | | `reason` | String(255), nullable | Ban reason | | `created_at` | DateTime | | ### `mutes` | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | | | `username` | String(20), unique, indexed | Muted username | | `created_at` | DateTime | | ### `user_ignores` | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | | | `ignorer_id` | FK → `users.id` | User doing the ignoring | | `ignored_id` | FK → `users.id` | User being ignored | | `created_at` | DateTime | | Unique composite index on `(ignorer_id, ignored_id)`. ### `violet_history` | Column | Type | Description | |--------|------|-------------| | `id` | Integer, PK | | | `user_id` | FK → `users.id` | Owning user | | `role` | String(10) | `"user"` or `"assistant"` | | `text` | Text | Plaintext conversation turn | | `timestamp` | DateTime | | --- ## Socket Events Reference ### Connection & Auth | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `join` | `{ mode, username, password?, email?, mod_password? }` | Authenticate and enter lobby. `mode`: `guest`, `login`, `register`, or `restore`. | | S→C | `joined` | `{ username, is_admin, role, is_registered, has_ai_access, ai_messages_used, email?, token?, ignored_list? }` | Auth success — includes JWT token for session restore. | | S→C | `error` | `{ msg }` | Error message. | | S→C | `kicked` | `{ msg }` | User was kicked/banned. | ### Lobby Chat | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `message` | `{ text }` | Send lobby message (rate-limited). | | S→C | `message` | `{ username, text, is_admin, is_registered, ts }` | Broadcast lobby message. | | S→C | `system` | `{ msg, ts }` | System notification (joins, parts, mod actions). | | S→C | `nicklist` | `{ users: [{ username, is_admin, is_registered, is_verified, role }] }` | Full online user list (includes Violet bot). | ### Private Messaging | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `pm_open` | `{ target }` | Initiate PM with target user. | | C→S | `pm_accept` | `{ room }` | Accept an incoming PM invitation (validated against pending invites). | | C→S | `pm_message` | `{ room, text? }` or `{ room, ciphertext, nonce, transit_key? }` | Send PM — plaintext (guests) or encrypted (registered). Include `transit_key` for Violet. | | S→C | `pm_invite` | `{ from, room, room_key }` | Incoming PM invitation with server-derived room key. | | S→C | `pm_ready` | `{ with, room, room_key }` | PM room opened — sent to the initiator. | | S→C | `pm_message` | `{ from, text?, ciphertext?, nonce?, room, ts }` | Receive a PM. | ### Violet AI | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `pm_message` (to Violet room) | `{ room, ciphertext, nonce, transit_key }` | Encrypted message routed to Violet via AI worker. | | C→S | `violet_reset` | _(none)_ | Clear your conversation memory with Violet. | | S→C | `violet_typing` | `{ busy, room? }` | AI processing indicator (shown while Ollama generates). | | S→C | `ai_response` | `{ error: "ai_limit_reached", room }` | Free trial exhausted. | | S→C | `ai_unlock` | `{ has_ai_access, msg? }` | Premium access granted or revoked (pushed live). | ### Moderation | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `mod_kick` | `{ target }` | Kick user (mod+). | | C→S | `mod_ban` | `{ target }` | Ban username + IP (mod+). | | C→S | `mod_kickban` | `{ target }` | Kick + ban simultaneously (mod+). | | C→S | `mod_mute` | `{ target }` | Toggle mute on user (mod+). | | C→S | `mod_verify` | `{ target }` | Toggle verification on user (mod+). | ### User Actions | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `user_ignore` | `{ target }` | Ignore a user (registered only). | | C→S | `user_unignore` | `{ target }` | Unignore a user. | | C→S | `change_password` | `{ old_password, new_password }` | Change password (6-char minimum). | | S→C | `ignore_status` | `{ target, ignored }` | Ignore list update confirmation. | | S→C | `password_changed` | `{ success, msg? }` | Password change result. | | S→C | `role_updated` | `{ role }` | Live notification when your role is changed by an admin. | ### Admin Panel | Direction | Event | Data | Description | |-----------|-------|------|-------------| | C→S | `admin_get_users` | _(none)_ | Request full user list (mod+). | | C→S | `admin_get_bans` | _(none)_ | Request ban list (mod+). | | C→S | `admin_get_mutes` | _(none)_ | Request mute list (mod+). | | C→S | `admin_set_role` | `{ user_id, role }` | Change a user's role (admin+ only, can't set ≄ own role). | | C→S | `admin_verify_user` | `{ user_id }` | Toggle verification (mod+). | | C→S | `admin_toggle_ai` | `{ user_id }` | Toggle unlimited AI access (admin+). Notifies user in real-time. | | C→S | `admin_unban` | `{ ban_id }` | Remove a ban (mod+). | | C→S | `admin_unmute` | `{ mute_id }` | Remove a mute (mod+). | | S→C | `admin_users` | `{ users: [{ id, username, role, is_verified, has_ai_access, email, created_at, online }] }` | User list with online status. | | S→C | `admin_bans` | `{ bans: [{ id, username, ip?, reason?, created_at }] }` | Ban list. | | S→C | `admin_mutes` | `{ mutes: [{ id, username, created_at }] }` | Mute list. | | S→C | `admin_action_ok` | `{ msg }` | Action success toast (panel auto-refreshes). | --- ## REST API Endpoints All endpoints are under the `/api` prefix. ### `POST /api/auth/register` Create a new account. ```json // Request { "username": "alice", "password": "hunter2", "email": "alice@example.com" } // Response 201 { "token": "eyJ...", "user": { "id": 3, "username": "alice", "has_ai_access": false, "ai_messages_used": 0 } } ``` ### `POST /api/auth/login` Authenticate and receive a JWT. ```json // Request { "username": "alice", "password": "hunter2" } // Response 200 { "token": "eyJ...", "user": { "id": 3, "username": "alice", ... } } ``` ### `GET /api/pm/history?with={username}` Fetch encrypted PM history with another user. Requires `Authorization: Bearer `. ```json // Response 200 { "room_key": "base64...", "messages": [ { "from_me": true, "ciphertext": "...", "nonce": "...", "ts": "2025-04-12T..." }, { "from_me": false, "ciphertext": "...", "nonce": "...", "ts": "2025-04-12T..." } ] } ``` ### `POST /api/ai/message` Send an encrypted message to Violet via REST (alternative to the socket path). ```json // Request (Authorization: Bearer ) { "ciphertext": "...", "nonce": "...", "transit_key": "..." } // Response 200 { "ciphertext": "...", "nonce": "...", "ai_messages_used": 2, "has_ai_access": false } ``` ### `POST /api/payment/success` Payment webhook (server-to-server). Validates `secret` with constant-time HMAC comparison. ```json // Request { "secret": "PAYMENT_SECRET", "user_id": 3 } // Response 200 { "status": "ok", "has_ai_access": true } ``` --- ## Role System & Admin Panel ### Hierarchy | Role | Power Level | Badge | Capabilities | |------|-------------|-------|-------------| | `root` | 3 | šŸ‘‘ | Everything. Can promote to admin. Cannot be demoted. | | `admin` | 2 | āš”ļø | Set roles (mod/user), toggle AI access, all mod actions. | | `mod` | 1 | šŸ›”ļø | Kick, ban, mute, verify, view admin panel. | | `user` | 0 | — | Standard chat, PMs, Violet AI (free trial). | **Rules:** - You cannot modify a user whose power level is ≄ your own. - You cannot assign a role whose power level is ≄ your own. - The first registered human user should be set to `root` in the database. - **Legacy:** Guests can enter the shared mod password at login to gain temporary `mod` session access. ### Admin Panel Accessible via the shield icon (šŸ›”ļø) in the header (visible to `mod` and above). Three tabs: | Tab | Features | |-----|----------| | **Users** | Searchable user list. Change roles (dropdown), toggle verification (checkmark), grant/revoke AI access (brain icon). Shows online status. | | **Bans** | All active bans with username, IP, and date. One-click unban button. | | **Mutes** | All active mutes. One-click unmute button. | All actions trigger a system message in the lobby, auto-refresh the panel, and (where applicable) push live updates to the affected user's session. --- ## Encryption & Security ### PM Encryption (AES-GCM-256) ``` 1. Server generates a deterministic room key: HMAC-SHA256(JWT_SECRET, room_name) 2. Room key is delivered to both clients via pm_invite / pm_ready events 3. Client encrypts each message with AES-GCM-256 using the room key + random 12-byte nonce 4. Server stores only base64(ciphertext) + base64(nonce) — never sees plaintext 5. Recipient decrypts client-side with the same room key ``` ### Violet AI Transit Encryption ``` 1. Client generates a random AES-256 transit key 2. Client encrypts message with transit key → sends { ciphertext, nonce, transit_key } 3. Server decrypts with transit key → sends plaintext to Ollama for inference 4. Server encrypts Violet's response with the same transit key → delivers to client 5. Transit key is ephemeral — used only in memory, never stored ``` ### Security Measures | Category | Implementation | |----------|---------------| | **Password hashing** | Bcrypt with adaptive cost factor | | **Authentication** | JWT tokens (HMAC-SHA256), 7-day expiry | | **Session restore** | JWT stored in `localStorage`, validated on reconnect | | **Input validation** | Regex-enforced usernames (`[a-zA-Z0-9_-]`, 1–20 chars), message length limits, SQLAlchemy parameterized queries | | **Rate limiting** | 6 messages per 5-second window per session | | **RBAC** | `@_require_role()` decorator with power-level hierarchy checks | | **Webhook auth** | Constant-time HMAC comparison for payment secret | | **Ban persistence** | Username + IP stored in DB `bans` table, loaded into memory on startup | | **Mute persistence** | Stored in DB `mutes` table, loaded on startup | | **PM invite validation** | Server-side pending invite mapping — `pm_accept` only works for legitimately invited rooms | --- ## Theme System 10 themes available in **Settings → Chat → Theme**. Each theme is a set of CSS custom properties applied via the `data-theme` attribute on the document root. | Theme | Style | Palette | |-------|-------|---------| | **Midnight Purple** | Dark | Deep purple + neon magenta *(default)* | | **Crimson Noir** | Dark | Black + crimson red | | **Ocean Deep** | Dark | Navy + cyan / teal | | **Ember** | Dark | Dark brown + orange / amber | | **Neon Green** | Dark | Black + bright lime | | **Cyberpunk** | Dark | Purple + electric yellow | | **Rose Gold** | Dark | Dark rose + blush pink | | **Arctic** | Light | Cool blue-white + indigo accents | | **Daylight** | Light | White + pink / purple accents | | **Midnight Blue** | Dark | Deep navy + blue glow | Theme preference is saved to `localStorage` (`sc_theme`) and applied immediately on page load — no flash of default theme. --- ## Settings Panel Accessed via the gear icon (āš™ļø) in the header. Four tabs: ### Account - View your username and email. - Change password (requires current password, 6-character minimum for the new one). ### Chat - **Theme** — 10-option grid with gradient color swatches. - **Font Size** — 12–20px slider. - **Timestamp Format** — 12-hour / 24-hour toggle. - **Enter to Send** — On/Off. When off, Enter inserts a newline and Shift+Enter sends. - **Notification Sounds** — On/Off. ### Violet - AI access status display (Unlimited āœ“ or X remaining free messages). - Messages used counter. - Reset conversation memory button. ### Premium - Feature showcase (Unlimited Violet, Encrypted PMs, Priority Access, Custom Themes). - Pricing display ($10/month). - Coming soon placeholder. All preferences are persisted in `localStorage` with the `sc_` prefix (e.g., `sc_theme`, `sc_fontSize`, `sc_enterSend`). --- ## Violet AI Companion Violet is an AI chat companion powered by a local Ollama model. She's configured as a *"flirtatious and sophisticated nightclub hostess at an exclusive, dimly-lit members-only club"* via her system prompt in `config.py`. ### How It Works 1. User clicks **Violet** in the nicklist → PM room opens automatically (no invite needed for the bot). 2. User's message is transit-encrypted (AES-GCM) and queued to a single-threaded inference greenlet. 3. The AI worker decrypts the message, builds a prompt from the last 20 conversation turns (`violet_history` table), and calls Ollama's `/api/generate` endpoint. 4. The response is transit-encrypted with the same key and delivered as a PM back to the user. 5. Both the user's message and Violet's reply are saved to `violet_history` for context continuity across sessions. ### Commands | Command | Description | |---------|-------------| | `/reset` | Clear Violet's memory of your conversation (deletes all `violet_history` rows for your user). | ### Guest Behavior Unregistered guests who PM Violet receive a friendly plaintext reply: > *"Hey hun šŸ’œ You'll need to register an account before we can chat privately. Go back to the join screen and sign up — I'll be waiting for you! 😘"* ### Free Trial & Paywall Users get `AI_FREE_LIMIT` (default: 3) free messages. After that, a paywall modal is shown. Admins can grant unlimited access via the admin panel's "Grant AI" button — the user is notified in real-time via `ai_unlock`. ### Fallback If Ollama is unreachable or returns an error, Violet sends a graceful fallback message instead of crashing. --- ## Premium / Paywall When a user exhausts their free Violet messages, a paywall modal appears with pricing and feature highlights. Premium access (`has_ai_access = True`) can be granted two ways: 1. **Admin Panel** — Any `admin`+ can click the brain icon (🧠) on a user in the Users tab to toggle unlimited AI access. The user receives a real-time notification. 2. **Payment Webhook** — `POST /api/payment/success` with the payment secret and user ID. Validated with constant-time comparison. Premium unlocks unlimited Violet messages. The `ai_unlock` event is pushed to the user's active socket session immediately. > **Note:** The payment flow is currently a stub. For production, replace the secret-comparison logic with a proper payment provider webhook (e.g., Stripe's `stripe.Webhook.construct_event()`). --- ## Dependencies From `requirements.txt`: | Package | Version | Purpose | |---------|---------|---------| | `flask` | ≄3.0 | Web framework | | `flask-socketio` | ≄5.3 | WebSocket support | | `eventlet` | ≄0.35 | Async concurrency | | `gunicorn` | ≄21.0 | Production WSGI server | | `flask-sqlalchemy` | ≄3.1 | ORM | | `flask-migrate` | ≄4.0 | Database migrations (Alembic) | | `psycopg2-binary` | ≄2.9 | PostgreSQL driver (optional) | | `bcrypt` | ≄4.0 | Password hashing | | `PyJWT` | ≄2.8 | JWT auth tokens | | `cryptography` | ≄42.0 | Server-side AES-GCM | | `redis` | ≄5.0 | Socket.IO multi-worker adapter (optional) | | `requests` | ≄2.31 | Ollama HTTP client | --- ## License Private project. All rights reserved. --- ## Credits Built by **End3r** — [git.computertech.dev/lord3nd3r/aprhodite](https://git.computertech.dev/lord3nd3r/aprhodite)