# Claude PHP — Interface de chat type Claude (Laravel + API Anthropic)

Application web **PHP / Laravel 13** reproduisant l'expérience de claude.ai : interface de
chat, réponses **en streaming**, **upload** de fichiers analysés par Claude, **téléchargement**
(pièces jointes + export de conversation en Markdown), le tout multi-utilisateurs avec
**comptes, rôles et quotas de tokens**.

Elle s'appuie sur le SDK officiel **`anthropic-ai/sdk`** et l'API Claude professionnelle
(modèle par défaut : `claude-opus-4-8`).

> 🔒 La clé API Claude reste **strictement côté serveur** (dans `.env`, jamais commitée). Tous
> les appels passent par le backend Laravel.

## Fonctionnalités

- **Authentification** (Laravel Breeze) : inscription, connexion, profil.
- **Rôles** : `user`, `premium`, `admin`.
- **Quotas** : plafond mensuel de tokens par rôle (middleware `EnforceQuota`), `admin` illimité.
- **Chat multi-conversations** : barre latérale, création / renommage / suppression, sélecteur
  de modèle (`claude-opus-4-8`, `claude-sonnet-5`).
- **Streaming SSE** : les réponses s'affichent token par token, rendu Markdown.
- **Upload de fichiers « zéro stockage »** : images, PDF, texte/CSV/JSON transmis **directement
  à la Files API d'Anthropic** — rien n'est écrit sur le serveur, seule la référence
  `file_id` est conservée en base ; le fichier reste utilisable dans toute la conversation.
- **Rétention 30 jours** : purge automatique des fichiers (suppression chez Anthropic + en
  base) via `php artisan attachments:purge` (planifiée quotidiennement).
- **Téléchargement** : **export de conversation** en `.md` (les fichiers uploadés ne sont pas
  re-téléchargeables : ils ne sont pas conservés sur le serveur).
- **Page d'administration** : suivi de l'usage de tokens par utilisateur.
- **Feature flags** (`config/features.php`) : Redis désactivé pour le MVP (drivers forcés sur
  `database`), rétention fichiers configurable, jeton des endpoints d'exploitation.

## Prérequis

- PHP ≥ 8.2 (testé sur 8.4) avec `pdo_sqlite`, `mbstring`, `curl`, `openssl`.
- Composer, Node.js ≥ 18 + npm.
- Une clé API Claude : https://console.anthropic.com/

## Installation

```bash
composer install
npm install && npm run build        # compile Tailwind/Breeze (public/build)

cp .env.example .env                # si .env absent
php artisan key:generate            # si APP_KEY absente
touch database/database.sqlite      # base SQLite (dev)

php artisan migrate                 # crée les tables

# Renseigner la clé API dans .env :
#   ANTHROPIC_API_KEY=sk-ant-...
#   ANTHROPIC_MODEL=claude-opus-4-8   (optionnel)

php artisan serve                   # http://127.0.0.1:8000
```

Créez un compte via `/register`, puis commencez à discuter sur `/chat`.

### Rendre un utilisateur administrateur

```bash
php artisan tinker --execute="App\Models\User::where('email','vous@exemple.com')->update(['role'=>'admin']);"
```

L'admin voit le lien **Admin** (usage & quotas) dans la barre de navigation.

## Configuration des quotas

Dans `config/quotas.php` (ou via `.env`) :

```php
'limits' => [
    'user'    => (int) env('QUOTA_USER', 200000),     // tokens / mois
    'premium' => (int) env('QUOTA_PREMIUM', 2000000),
    'admin'   => null,                                 // illimité
],
```

Au dépassement, l'API de chat renvoie **HTTP 429** avec un message clair.

## Architecture

```
Navigateur (Blade + Tailwind + Alpine + public/js/chat.js)
   │  fetch() / SSE
   ▼
Laravel
   ├─ Auth (Breeze) + middleware auth / quota / admin
   ├─ ConversationController · ChatController (stream SSE) · FileController · Admin\UsageController
   ├─ App\Services\AnthropicService  ──►  anthropic-ai/sdk  ──►  API Claude
   └─ SQLite/MySQL (conversations, messages, attachments, usage_records)
```

Fichiers clés :

| Rôle | Fichier |
|------|---------|
| Intégration API (streaming, blocs de contenu) | `app/Services/AnthropicService.php` |
| Envoi de message + streaming SSE | `app/Http/Controllers/ChatController.php` |
| Conversations (CRUD + export Markdown) | `app/Http/Controllers/ConversationController.php` |
| Upload / download de fichiers | `app/Http/Controllers/FileController.php` |
| Quotas / accès admin | `app/Http/Middleware/{EnforceQuota,EnsureUserIsAdmin}.php` |
| Interface de chat | `resources/views/chat/*`, `public/js/chat.js` |
| Schéma BDD | `database/migrations/2026_01_01_*` |

## Déploiement OVH mutualisé (sans SSH) — pipeline Jenkins

Sur un hébergement mutualisé OVH sans SSH (SFTP uniquement, MySQL distant `*.mysql.db`
injoignable de l'extérieur), on ne peut pas lancer `php artisan migrate` à distance. L'app
expose donc des **endpoints d'exploitation protégés par jeton** :

| Endpoint | Usage |
|----------|-------|
| `POST /ops/migrate` | Migrations + vidage des caches — appelé par Jenkins après l'upload |
| `POST /ops/extract` | Déploiement rapide : extrait `deploy.zip` téléversé à la racine (garde anti zip-slip, `.env`/`storage/` jamais écrasés) |
| `GET/POST /ops/cron` | Tâches planifiées (purge des fichiers 30 j) — appelé par le cron OVH « appel URL » |

**Performance des déploiements** : le mode par défaut du pipeline (`OVH_DEPLOY_MODE=zip`)
téléverse **une seule archive** puis appelle `/ops/extract` — 10 à 50× plus rapide que le
transfert FTP des ~10 000 fichiers de `vendor/`. Le mode `mirror` (fichier par fichier,
incrémental via `--only-newer`) reste **obligatoire pour le tout premier déploiement**,
tant que `/ops/extract` n'existe pas encore sur le serveur.

Sécurité : en-tête `X-Deploy-Token: <valeur>` (ou `?token=` pour le cron), comparé en temps
constant à `DEPLOY_TOKEN` du `.env`. **Jeton vide = endpoints désactivés (404).**
Générer un jeton : `php -r "echo bin2hex(random_bytes(32));"`

Mise en place :

1. **Docroot** : dans l'espace client OVH, pointer le multisite sur le dossier `public/`.
2. **`.env` de production** : créé une fois à la main via SFTP (jamais déployé par le
   pipeline) — MySQL OVH (`DB_HOST=xxxx.mysql.db`), `APP_ENV=production`, `APP_DEBUG=false`,
   `ANTHROPIC_API_KEY`, `DEPLOY_TOKEN`.
3. **Jenkins** : le `Jenkinsfile` fourni fait : `composer install --no-dev` → `npm run build` →
   upload SFTP (`lftp mirror -R`, exclusions `.env`/`storage/`) → `curl -X POST
   -H "X-Deploy-Token: …" https://site/ops/migrate` → smoke test `/up`.
   Rien n'est codé en dur : la configuration vient de **variables d'environnement Jenkins**
   (Manage Jenkins → System → Global properties, ou au niveau du dossier/job) —
   `OVH_SFTP_HOST`, `OVH_REMOTE_DIR`, `SITE_URL` (requises), `OVH_SFTP_PORT`,
   `OVH_SFTP_CREDENTIALS_ID`, `DEPLOY_TOKEN_CREDENTIALS_ID` (optionnelles) — le pipeline
   échoue immédiatement avec un message clair si une variable requise manque.
   Credentials à créer : `ovh-sftp` (user/password) et `deploy-token` (secret text).

   **Jenkins « as code »** (optionnel) : le dossier `jenkins/` fournit
   - `jenkins/jenkins.yaml` — bootstrap JCasC (credentials staging/production injectés par
     variables d'environnement du serveur Jenkins, seed job) ; pointer
     `CASC_JENKINS_CONFIG` dessus ;
   - `jenkins/seed.groovy` — Job DSL générant un job de déploiement par environnement
     (staging auto-déclenché, production manuelle), les valeurs (hôte SFTP, dossier, URL,
     IDs de credentials) étant passées en paramètres de job consommés par le `Jenkinsfile`.
   Adapter les hôtes/chemins/branches marqués `⚠️` dans `seed.groovy`.
4. **Cron OVH** : tâche « appel d'URL » quotidienne vers
   `https://site/ops/cron?token=<DEPLOY_TOKEN>` (purge de rétention), ou cron PHP CLI
   `php artisan schedule:run`.

## Notes

- Le streaming et les messages passent par l'endpoint **bêta** des Messages avec l'en-tête
  `files-api-2025-04-14` (nécessaire pour référencer les fichiers de la Files API).
- **Zéro stockage** : les uploads partent directement chez Anthropic ; l'upload nécessite donc
  une clé API valide. Après la purge de rétention, les anciens messages conservent leur texte
  mais perdent la pièce jointe brute.
- `public/build/` (assets compilés) est ignoré par git : lancez `npm run build` après clone.
- En production, préférez MySQL/PostgreSQL (voir `.env`) et un serveur qui **ne bufferise pas**
  les réponses `text/event-stream` (l'app envoie déjà `X-Accel-Buffering: no`).
