# LM Studio Web - Journal de Développement

**Date :** 2026-03-26 / 2026-03-27
**Statut :** Fonctionnel avec limitations identifiées
**Prochaine étape :** Détection intelligente des modèles vision vs texte

---

## 🎯 Objectif Principal

Interface web moderne pour chatter avec :
- LM Studio (local)
- Ollama (local)
- OpenRouter (cloud)
- + Génération d'images via ComfyUI (`/genimg`)

---

## ✅ Ce Qui Fonctionne (Acquis)

### Interface Utilisateur

- [x] Sélection de serveur avec presets (Ollama, LM Studio, OpenRouter, Custom)
- [x] Champ API key conditionnel (visible pour OpenRouter)
- [x] Dropdown de modèles (rempli automatiquement)
- [x] Chat avec streaming de réponses
- [x] Système de conversations (sidebar, New Chat, Delete, Switch)
- [x] Persistance localStorage (system prompt, sélection serveur, API key)
- [x] Markdown rendering (marked.js) + Highlight.js (code)
- [x] LaTeX support (MathJax)
- [x] Upload d'image locale (bouton "+" image)
- [x] Lightbox (click pour zoomer images)
- [x] Boutons "Copy" sur blocs de code
- [x] Spinner "Thinking..." pendant attente réponse
- [x] Responsive design (mobile-friendly)
- [x] Thème visuel purple moderne

### Backend & Intégration

- [x] Serveur web Python (port 8084) sert fichiers statiques
- [x] Serveur MCP FastAPI (port 8085) → ComfyUI
- [x] Endpoint `/generate-image` (POST) reçoit prompts, envoie à ComfyUI, polling, retourne image_url
- [x] Randomisation des seeds dans le workflow ComfyUI (évite cache)
- [x] Vérification ComfyUI au démarrage (`./start.sh`)
- [x] Logs serveurs dans fichiers (`server.log`, `mcp_server.log`)
- [x] Scripts d'arrêt/propre (`stop.sh`)
- [x] CORS configuré (frontend:8084 → backend:8085)

### Commandes

- [x] `/genimg <description>` → génère image via ComfyUI
- [x] Génération d'image fonctionne (prompt → ComfyUI → image dans chat)
- [x] Lightbox clique → zoom

---

## 🐛 Problèmes Rencontrés & Solutions

| Problème | Cause | Solution |
|----------|-------|----------|
| **CORS blocked** | Fichiers ouverts via `file://` | Serveur Python sur port 8084 + CORS middleware dans MCP |
| **`Identifier already declared`** | `main.js` chargé deux fois + code inline | Nettoyé HTML: un seul `<script src="main.js">` |
| **`Unexpected end of input`** | Corruption de `main.js` pendant édition | Rebuild complet du fichier |
| **Ollama/LM Studio 400 Bad Request** | `max_tokens: -1` (invalide) | Changé en `max_tokens: 4096` |
| **Image générée ajoutée 2×** | `addImageMessage()` + push manuel | Supprimé le push manuel dans `handleImageGeneration()` |
| **Image générée comme message `user`** | `addImageMessage(..., true)` | Changé en `false` → message `assistant` |
| **Model select vide après reconnexion** | `selectedIndex` non défini après `innerHTML = ""` | Forcé `modelSelect.selectedIndex = 0` |
| ** connexion perdue après génération image** | Doublon + format invalide → erreurs 400 → état inconsistent | Fixé doublon + format texte-only pour modèles texte |
| **Images toujours envoyées en texte-only** | `buildConversationHistory()` envoie seulement texte pour `msg.isImage` | **Non fixé** → voir section "À faire" |

---

## 🔧 Configuration Serveurs

### Presets

```javascript
SERVER_PRESETS = {
  ollama: 'http://localhost:11434',
  lmstudio: 'http://localhost:1234',
  openrouter: 'https://openrouter.ai/api/v1'
}
```

### Ports

- **Frontend web** : `http://localhost:8084`
- **MCP backend** : `http://localhost:8085`
- **ComfyUI** : `http://127.0.0.1:8188`
- **Ollama** : `http://localhost:11434`
- **LM Studio** : `http://localhost:1234`

### Variables d'environnement

Pas besoin. Les URLs sont en dur dans le code et le selecteur.

---

## 📦 Fichiers Créés/Modifiés

### Nouveaux fichiers

- `start.sh` — Démarre ComfyUI check → web server → MCP server
- `stop.sh` — Arrête proprement les serveurs
- `styles.css` — CSS externalisé (tiré de l'ancien `index.html`)
- `PROGRESS.md` — Ce fichier

### Fichiers modifiés

- `index.html` — Ajouté `server-url-input` (hidden), removed inline script
- `main.js` — Rebuild complet + nombreuses corrections
- `backend/mcp_server.py` — Ajouté logs, seed randomization, sleep
- `README.md` — Mis à jour avec nouvelles fonctionnalités et instructions

### Fichiers temporaires (ignorer)

- `main.js.corrupted` — Sauvegarde corruption
- `index.html.bak`, `index.html.bak_jsfix` — Backups
- `server.log`, `mcp_server.log` — Logs serveurs
- `.web_server.pid`, `.mcp_server.pid` — PIDs serveurs

---

## 🔄 Workflow de Développement

1. **Modifier le code** (`main.js` ou `backend/mcp_server.py`)
2. **Arrêter serveurs** : `./stop.sh`
3. **Redémarrer** : `./start.sh`
4. **Rafraîchir navigateur** : **Ctrl+Shift+R** (hard refresh, désactive cache)
5. **Tester** dans F12 → Console + Network

**Note :** Le cache navigateur est activé par défaut. Utiliser DevTools → Network → "Disable cache" (quand DevTools ouvert) pour éviter Ctrl+Shift+R.

---

## ⚠️ Limitation Actuelle (Bloquant)

### Images toujours en texte-only

`buildConversationHistory()` dans `main.js` convertit **tous** les messages avec images (`msg.isImage`) en simple texte :

```javascript
if (msg.isImage) {
  const textContent = msg.text || "Image generated.";
  history.push({
    role: msg.isUser ? 'user' : 'assistant',
    content: textContent  // ← SEULEMENT TEXTE, PAS L'IMAGE
  });
}
```

**Conséquence :**
- Modèles **texte-only** (llama3.2, mistral, etc.) → OK (ne comprennent pas les images de toute façon)
- Modèles **vision** (GPT-4o, Claude 3.5 Sonnet, llava, etc.) → ❌ Ne reçoivent **pas l'image**, seulement le texte

**Résultat :** L'upload d'image et les images générées ne sont jamais "vues" par l'IA, même si le modèle le supporte.

---

## 🎯 Plan Pour Demain (Suite)

### 1. Détection automatique modèles vision

Créer une fonction `modelSupportsVision(modelName)` :

```javascript
function modelSupportsVision(modelName) {
  const visionModels = [
    'gpt-4o', 'gpt-4-turbo', 'gpt-4-vision',
    'claude-3', 'claude-3-5', 'claude-3-opus',
    'llava', 'bakllava', 'moondream'
  ];
  return visionModels.some(v => modelName.toLowerCase().includes(v));
}
```

### 2. Adapter `buildConversationHistory()`

```javascript
currentChat.messages.forEach(msg => {
  if (msg.isImage) {
    const textContent = msg.text || "Image generated.";
    if (modelSupportsVision(currentModel)) {
      // Format multimodal (texte + image)
      history.push({
        role: msg.isUser ? 'user' : 'assistant',
        content: [
          { type: "text", text: textContent },
          { type: "image_url", image_url: { url: msg.imageData } }
        ]
      });
    } else {
      // Texte seulement (modèle texte)
      history.push({
        role: msg.isUser ? 'user' : 'assistant',
        content: textContent
      });
    }
  } else {
    history.push({ role: msg.isUser ? 'user' : 'assistant', content: msg.content });
  }
});
```

### 3. (Optionnel) Override manuel

Ajouter un checkbox "Enable vision" dans l'UI (sidebar) qui force le mode vision indépendamment du nom du modèle.

### 4. Tests à effectuer

- [ ] **Ollama + `llama3.2`** (texte-only) :
  - Upload image → doit envoyer texte seulement, pas d'erreur
  - Générer image → doit continuer chat sans 400

- [ ] **Ollama + `llava:7b`** (vision) :
  - Upload image → l'IA doit "voir" l'image (payload contient `image_url`)
  - Générer image → l'IA discute de l'image générée

- [ ] **OpenRouter + GPT-4o** (vision) :
  - Mêmes tests

---

## 📝 Notes Techniques

### Format OpenAI pour messages multimodaux

```json
{
  "role": "user",
  "content": [
    { "type": "text", "text": "What's in this image?" },
    { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,..." } }
  ]
}
```

**Important :** `image_url` peut être:
- URL externe (ComfyUI retourne `http://127.0.0.1:8188/view?...`)
- Base64 data URL (pour uploads locaux)

Dans notre code, upload → base64, généré ComfyUI → URL HTTP. Les deux fonctionnent si le modèle peut fetch l'URL externe (Ollama local peut, OpenAI/OpenRouter ne peuvent pas fetch `127.0.0.1`). Pour OpenRouter, il faudrait converter en base64.

**Non abordé ici** : conversion URL → base64 pour cloud APIs. À considérer plus tard si besoin.

### Seed randomization

Dans `mcp_server.py`, avant d'envoyer le workflow à ComfyUI, on parcourt tous les nœuds avec `inputs.seed` et on y met une valeur aléatoire. Cela évite que ComfyUI ne retourne le même résultat (cache interne).

```python
for node in workflow.values():
    if isinstance(node, dict) and "inputs" in node:
        if "seed" in node["inputs"]:
            node["inputs"]["seed"] = random.randint(0, 2**32 - 1)
```

---

## 🧪 Commandes de Test

```bash
# Vérifier ComfyUI
curl http://127.0.0.1:8188/system_stats

# Démarrer l'application
./start.sh

# Voir logs MCP en temps réel
tail -f backend/mcp_server.log

# Arrêter
./stop.sh
```

---

## 📌 Points d'Attention

1. **Cache navigateur** : Toujours Ctrl+Shift+R après modif JS/CSS
2. **CORS** : Le serveur Python sur 8084 et MCP sur 8085 doivent être démarrés
3. **ComfyUI** : Doit être démarré à la main avant `./start.sh`
4. **Ollama models** : `llama3.2` est texte-only. Pour images, installer `llava` (`ollama pull llava`)
5. **LM Studio** : Assure-toi que le mode "Server" est activé dans l'app
6. **OpenRouter** : Besoin d'une clé API valide

---

## 🎉 What's Next?

1. ✅ Sauvegarde de l'état actuel dans `PROGRESS.md` (ce fichier)
2. [ ] Implémenter détection vision + format adaptatif
3. [ ] Tester avec modèles vision et texte
4. [ ] (Optionnel) Override manuel "Enable vision"
5. [ ] (Optionnel) Gérer data URLs pour OpenRouter (base64 conversion)
6. [ ] Nettoyer code, ajouter commentaires

---

**Signature :** Claude (Assistant IA) — Dernière mise à jour : 2026-03-26 22:00 UTC
