aprhodite/README.md

682 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 💋 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 <jwt>`.
```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 <jwt>)
{ "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_-]`, 120 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** — 1220px 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)