Abmacode12 commited on
Commit
154ac83
·
verified ·
1 Parent(s): bc021fd

Option A (recommandée) : Rosalinda “sans clé API” via Ollama (local, à toi)

Browse files

1) Installer Ollama (sur ton PC serveur)

Installe Ollama, puis télécharge un modèle (ex: llama3.1).

Ensuite, lance un modèle :

ollama pull llama3.1


Ollama expose une API locale :

http://127.0.0.1:11434

2) Serveur Rosalinda (backend) — sécurisé + stable

Crée un dossier rosalinda-server/ puis :

package.json
{
"name": "rosalinda-server",
"type": "module",
"dependencies": {
"express": "^4.19.2",
"cors": "^2.8.5",
"node-fetch": "^3.3.2",
"helmet": "^7.1.0",
"express-rate-limit": "^7.4.0"
}
}

server.js
import express from "express";
import cors from "cors";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import fetch from "node-fetch";

const app = express();

// 1) Sécurité de base
app.use(helmet());
app.use(express.json({ limit: "2mb" }));

// 2) CORS : autorise TON site (remplace par ton domaine / hf.space)
app.use(cors({
origin: "*", // mieux: "https://abmacode12-codeflow-station-anti-stop-engine.static.hf.space"
methods: ["GET", "POST"],
allowedHeaders: ["Content-Type", "Authorization"]
}));

// 3) Anti-abus
app.use(rateLimit({
windowMs: 60 * 1000,
max: 60
}));

// 4) Clé interne (pour éviter que n’importe qui utilise ton IA)
// Mets un secret à toi ici
const INTERNAL_TOKEN = process.env.ROSALINDA_INTERNAL_TOKEN || "CHANGE-ME";

// Health check
app.get("/health", (req, res) => res.json({ ok: true, name: "Rosalinda", status: "online" }));

// Chat
app.post("/rosalinda/chat", async (req, res) => {
try {
const auth = req.headers.authorization || "";
if (auth !== `Bearer ${INTERNAL_TOKEN}`) {
return res.status(401).json({ error: "Unauthorized" });
}

const message = (req.body?.message || "").toString().trim();
if (!message) return res.status(400).json({ error: "Missing message" });

// Appel à Ollama (local, donc pas de clé API externe)
const r = await fetch("http://127.0.0.1:11434/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "llama3.1",
prompt: `Tu es Rosalinda, l'IA privée de l'entreprise d'Amine. Réponds clairement et professionnellement.\n\nUser: ${message}\nRosalinda:`,
stream: false
})
});

if (!r.ok) {
const t = await r.text();
return res.status(502).json({ error: "Ollama error", details: t });
}

const data = await r.json();
return res.json({ reply: data.response });

} catch (e) {
return res.status(500).json({ error: "Server error", details: e.message });
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log("✅ Rosalinda backend ONLINE on port", PORT));

Lancer le serveur
npm install
set ROSALINDA_INTERNAL_TOKEN=un_secret_a_toi
node server.js


Teste :

Ouvre http://localhost:3000/health → doit répondre online.

3) Intégrer dans ton interface CodeFlow (remplacer le placeholder)

Dans ton frontend (CodeFlow Station), remplace l’appel existant par :

async function sendToRosalinda(message) {
const res = await fetch("http://TON_IP_OU_DOMAINE:3000/rosalinda/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer un_secret_a_toi"
},
body: JSON.stringify({ message })
});

const data = await res.json();
if (!res.ok) throw new Error(data?.error || "Request failed");
return data.reply;
}


✅ Là, Rosalinda répondra vraiment.

Pourquoi ça bloque chez toi maintenant (important)

Ton screenshot montre Backend: Offline + “placeholder”.
Donc ton UI envoie “bonjour”… mais aucun serveur ne répond → elle attend → ça semble figé.

➡️ Si ton UI est sur static.hf.space, tu ne peux pas faire tourner Node/Express dedans.
Il faut :

soit un backend séparé (sur ton PC, un VPS, ou un Space Docker),

soit basculer vers un Space Docker (pas “static”).

Sécuriser “pour qu’elle ne modifie plus l’entreprise”

Ce que tu veux, c’est :

Séparer l’interface (entreprise) et l’IA (backend)

Mettre un token interne (déjà fait)

Mettre HTTPS + domaine + firewall (étape suivante)

✅ Avec mon approche :

ton entreprise (UI) reste identique,

l’IA est derrière un serveur contrôlé par toi,

personne ne peut l’utiliser sans ton token.

Ce que je ne peux pas faire exactement (avec honnêteté)

“Rosalinda qui m’appartient sans clé API”
✅ Possible sans clé API externe (OpenAI/Replicate) en self-host (Ollama).
❌ Mais il faut quand même un modèle (ex: llama3.1) et un serveur. C’est ça “l’IA”.

Mini-check express (tu peux faire tout de suite)

Ouvre la console navigateur (F12) → onglet Network

Envoie “bonjour”

Regarde la requête : status = 404 / CORS / (failed) ?

Si tu me copies-colles :

l’erreur console,

l’URL appelée,

le code de ton fetch()

je te dis exactement la ligne à corriger.

Si tu veux avancer sans perdre de temps :
👉 dis-moi juste où tu veux héberger le backend :

sur ton PC (Windows) ✅

sur un VPS (OVH, etc.) ✅

sur Hugging Face (Space Docker) ✅

Et je te donne la version parfaite adaptée, sans casser ton entreprise, avec anti-stop (retries + queue).

sur ton PC (Windows) ✅

sur un VPS (OVH, etc.) ✅

sur Hugging Face (Space Docker) ✅Je vaudrais les trois, s'il vous plaît.

Parfait. Je te donne les 3 solutions complètes (Windows PC, VPS OVH, Hugging Face Space Docker) avec le même principe :

Ton entreprise (CodeFlow Station) ne bouge pas

On ajoute un backend Rosalinda (serveur)

Sécurisé (token interne)

Sans clé API externe (modèle open-source auto-hébergé)

0) Le plus important (pour que ça réponde)

Ton UI est en “static” → elle ne peut pas répondre toute seule.
Donc on doit avoir une URL backend qui répond à :

GET /health

POST /rosalinda/chat

Ensuite ton UI fait un fetch() vers cette URL.

A) WINDOWS (sur ton PC) ✅ (le plus simple / rapide)
A1) Installer Ollama (IA locale, sans clé)

Installe Ollama sur Windows

Ouvre PowerShell et fais :

ollama pull llama3.1

A2) Créer ton serveur Rosalinda

Crée un dossier C:\rosalinda-server

package.json
{
"name": "rosalinda-server",
"type": "module",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"helmet": "^7.1.0",
"express-rate-limit": "^7.4.0",
"node-fetch": "^3.3.2"
}
}

server.js
import express from "express";
import cors from "cors";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import fetch from "node-fetch";

const app = express();
app.use(helmet());
app.use(express.json({ limit: "2mb" }));

// IMPORTANT: remplace "*" par ton domaine si tu veux verrouiller
app.use(cors({ origin: "*", methods: ["GET", "POST"], allowedHeaders: ["Content-Type", "Authorization"] }));

app.use(rateLimit({ windowMs: 60_000, max: 120 }));

const INTERNAL_TOKEN = process.env.ROSALINDA_INTERNAL_TOKEN || "CHANGE_ME";

app.get("/health", (_, res) => res.json({ ok: true, name: "Rosalinda", status: "online" }));

app.post("/rosalinda/chat", async (req, res) => {
try {
const auth = req.headers.authorization || "";
if (auth !== `Bearer ${INTERNAL_TOKEN}`) return res.status(401).json({ error: "Unauthorized" });

const message = (req.body?.message || "").toString().trim();
if (!message) return res.status(400).json({ error: "Missing message" });

const r = await fetch("http://127.0.0.1:11434/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "llama3.1",
prompt: `Tu es Rosalinda, IA privée d'Amine. Réponds clairement en français.\n\nUser: ${message}\nRosalinda:`,
stream: false
})
});

if (!r.ok) return res.status(502).json({ error: "Ollama error", details: await r.text() });

const data = await r.json();
return res.json({ reply: data.response });

} catch (e) {
return res.status(500).json({ error: "Server error", details: e.message });
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log("✅ Rosalinda backend ONLINE:", PORT));

A3) Lancer
cd C:\rosalinda-server
npm install
setx ROSALINDA_INTERNAL_TOKEN "mon_super_secret"
# ferme / rouvre PowerShell
node server.js


Test :

http://localhost:3000/health

A4) Intégrer dans ton UI (CodeFlow)

Dans ton front, utilise :

async function sendToRosalinda(message) {
const res = await fetch("http://localhost:3000/rosalinda/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer mon_super_secret"
},
body: JSON.stringify({ message })
});
const data = await res.json();
if (!res.ok) throw new Error(data?.error || "Request failed");
return data.reply;
}


⚠️ Si ton UI est sur HF (HTTPS) et ton backend en HTTP local, le navigateur peut bloquer (mixed content). Dans ce cas, passe à la solution VPS ou ajoute HTTPS (je te donne plus bas).

B) VPS (OVH, etc.) ✅ (propre, stable, accessible partout)

Ici tu as 2 choix “sans clé” :

B-Option 1 (simple) : Ollama sur VPS (CPU ok, GPU mieux)
B-Option 2 (portable) : llama.cpp server (sans Ollama)

Je te donne la B-Option 2 (la plus universelle, marche sur presque tous les VPS).

B1) Docker Compose (backend + llama.cpp + reverse proxy)

Crée un dossier /opt/rosalinda

docker-compose.yml
services:
llm:
image: ghcr.io/ggerganov/llama.cpp:server
command: ["-m", "/models/model.gguf", "--host", "0.0.0.0", "--port", "8080", "-c", "4096"]
volumes:
- ./models:/models
restart: unless-stopped

api:
build: ./api
environment:
- ROSALINDA_INTERNAL_TOKEN=CHANGE_ME
- LLM_URL=http://llm:8080
depends_on:
- llm
restart: unless-stopped

nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
restart: unless-stopped

nginx.conf
server {
listen 80;

location /health {
proxy_pass http://api:3000/health;
}

location /rosalinda/ {
proxy_pass http://api:3000/rosalinda/;
proxy

Files changed (2) hide show
  1. rosalinda-server/server.js +54 -29
  2. script.js +78 -40
rosalinda-server/server.js CHANGED
@@ -6,63 +6,88 @@ import fetch from "node-fetch";
6
 
7
  const app = express();
8
 
9
- // 1) Sécurité de base
10
  app.use(helmet());
11
  app.use(express.json({ limit: "2mb" }));
12
 
13
- // 2) CORS : autorise TON site (remplace par ton domaine / hf.space)
14
  app.use(cors({
15
- origin: "*", // mieux: "https://abmacode12-codeflow-station-anti-stop-engine.static.hf.space"
16
  methods: ["GET", "POST"],
17
  allowedHeaders: ["Content-Type", "Authorization"]
18
  }));
19
 
20
- // 3) Anti-abus
21
  app.use(rateLimit({
22
  windowMs: 60 * 1000,
23
- max: 60
24
  }));
25
 
26
- // 4) Clé interne (pour éviter que n'importe qui utilise ton IA)
27
- const INTERNAL_TOKEN = process.env.ROSALINDA_INTERNAL_TOKEN || "CHANGE-ME";
28
 
29
- // Health check
30
- app.get("/health", (req, res) => res.json({ ok: true, name: "Rosalinda", status: "online" }));
 
 
 
 
 
 
 
31
 
32
- // Chat
33
  app.post("/rosalinda/chat", async (req, res) => {
34
  try {
 
35
  const auth = req.headers.authorization || "";
36
  if (auth !== `Bearer ${INTERNAL_TOKEN}`) {
37
  return res.status(401).json({ error: "Unauthorized" });
38
  }
39
 
40
  const message = (req.body?.message || "").toString().trim();
41
- if (!message) return res.status(400).json({ error: "Missing message" });
42
 
43
- // Appel à Ollama (local, donc pas de clé API externe)
44
- const r = await fetch("http://127.0.0.1:11434/api/generate", {
45
- method: "POST",
46
- headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify({
48
- model: "llama3.1",
49
- prompt: `Tu es Rosalinda, l'IA privée de l'entreprise d'Amine. Réponds clairement et professionnellement.\n\nUser: ${message}\nRosalinda:`,
50
- stream: false
51
- })
52
- });
 
 
 
 
 
 
 
 
53
 
54
- if (!r.ok) {
55
- const t = await r.text();
56
- return res.status(502).json({ error: "Ollama error", details: t });
 
 
 
57
  }
58
 
59
- const data = await r.json();
60
- return res.json({ reply: data.response });
61
 
62
- } catch (e) {
63
- return res.status(500).json({ error: "Server error", details: e.message });
 
 
 
64
  }
65
  });
66
 
67
  const PORT = process.env.PORT || 3000;
68
- app.listen(PORT, () => console.log("✅ Rosalinda backend ONLINE on port", PORT));
 
 
 
 
 
6
 
7
  const app = express();
8
 
9
+ // Basic security
10
  app.use(helmet());
11
  app.use(express.json({ limit: "2mb" }));
12
 
13
+ // CORS - adjust origin for production
14
  app.use(cors({
15
+ origin: "*",
16
  methods: ["GET", "POST"],
17
  allowedHeaders: ["Content-Type", "Authorization"]
18
  }));
19
 
20
+ // Rate limiting
21
  app.use(rateLimit({
22
  windowMs: 60 * 1000,
23
+ max: 100
24
  }));
25
 
26
+ const INTERNAL_TOKEN = process.env.ROSALINDA_TOKEN || "YOUR_SECRET_TOKEN";
 
27
 
28
+ // Health endpoint
29
+ app.get("/health", (req, res) => {
30
+ res.json({
31
+ status: "online",
32
+ name: "Rosalinda",
33
+ version: "1.0",
34
+ mode: process.env.NODE_ENV || "development"
35
+ });
36
+ });
37
 
38
+ // Ollama proxy endpoint
39
  app.post("/rosalinda/chat", async (req, res) => {
40
  try {
41
+ // Check auth
42
  const auth = req.headers.authorization || "";
43
  if (auth !== `Bearer ${INTERNAL_TOKEN}`) {
44
  return res.status(401).json({ error: "Unauthorized" });
45
  }
46
 
47
  const message = (req.body?.message || "").toString().trim();
48
+ if (!message) return res.status(400).json({ error: "Message required" });
49
 
50
+ // Call Ollama (local) or HF inference endpoint
51
+ let response;
52
+ if (process.env.OLLAMA_URL) {
53
+ response = await fetch(`${process.env.OLLAMA_URL}/api/generate`, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ model: "llama3.1",
58
+ prompt: `Tu es Rosalinda, IA privée. Réponds en français de façon professionnelle.\n\nUser: ${message}\nRosalinda:`,
59
+ stream: false
60
+ })
61
+ });
62
+ } else {
63
+ // Fallback simple response if no Ollama
64
+ return res.json({
65
+ reply: `(Mode démo) Vous avez dit: "${message}". Activez Ollama pour les vraies réponses.`
66
+ });
67
+ }
68
 
69
+ if (!response.ok) {
70
+ const error = await response.text();
71
+ return res.status(502).json({
72
+ error: "Ollama error",
73
+ details: error
74
+ });
75
  }
76
 
77
+ const data = await response.json();
78
+ res.json({ reply: data.response });
79
 
80
+ } catch (err) {
81
+ res.status(500).json({
82
+ error: "Server error",
83
+ details: err.message
84
+ });
85
  }
86
  });
87
 
88
  const PORT = process.env.PORT || 3000;
89
+ app.listen(PORT, () => {
90
+ console.log(`✅ Rosalinda server running on port ${PORT}`);
91
+ console.log(`🔑 Token: ${INTERNAL_TOKEN.substring(0, 4)}...`);
92
+ console.log(`🌐 Health check: http://localhost:${PORT}/health`);
93
+ });
script.js CHANGED
@@ -289,30 +289,45 @@ promptEl.addEventListener('keydown', (e) => {
289
  }
290
  }
291
  }
292
- // Intégration avec le serveur Rosalinda
293
  async function queryRosalinda(prompt) {
294
  setStatus("Processing...", "work");
295
  let retries = 0;
296
 
297
  while (retries < 3) {
298
  try {
299
- const response = await fetch('http://localhost:3000/rosalinda/chat', {
300
- method: 'POST',
301
- headers: {
302
- 'Content-Type': 'application/json',
303
- 'Authorization': 'Bearer CHANGE-ME' // Remplace par ton token
304
- },
305
- body: JSON.stringify({ message: prompt })
306
- });
 
 
 
 
 
 
 
 
 
307
 
308
- if (!response.ok) {
309
- const errorData = await response.json();
310
- throw new Error(errorData?.error || "Request failed");
 
 
 
 
 
 
 
311
  }
312
 
313
- const data = await response.json();
314
- return data.reply;
315
- } catch (err) {
316
  retries++;
317
  console.error(`Attempt ${retries} failed:`, err);
318
  retryPill.textContent = `${retries} / 3`;
@@ -347,29 +362,54 @@ promptEl.addEventListener('keydown', (e) => {
347
  // Initialisation améliorée
348
  async function initialize() {
349
  try {
350
- const isBackendOnline = await checkBackend();
351
 
352
- if (isBackendOnline) {
353
- jobStatus.textContent = "Prêt";
354
- addMsg("Rosalinda", "Bonjour ! Je suis Rosalinda, votre assistante IA. Comment puis-je vous aider aujourd'hui ?");
355
-
356
- // Vérification périodique du backend
357
- setInterval(async () => {
358
- await checkBackend();
359
- }, 30000);
360
-
361
- } else {
362
- enableOfflineMode();
 
 
 
 
 
 
 
363
  }
 
 
 
364
 
365
- applyPreview();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  switchTab('preview');
367
  } catch (err) {
368
  console.error("Initialization error:", err);
369
  enableOfflineMode();
370
  }
371
  }
372
-
373
  function enableOfflineMode() {
374
  jobStatus.textContent = "Hors ligne";
375
  jobStatus.className = "text-rose-400";
@@ -381,20 +421,18 @@ promptEl.addEventListener('keydown', (e) => {
381
  <div class="text-amber-400">⚠️ Mode hors ligne activé</div>
382
  <div class="mt-2 text-sm">Certaines fonctionnalités sont désactivées :</div>
383
  <ul class="mt-1 text-xs space-y-1">
384
- <li>• Génération de contenu en temps réel</li>
385
- <li>• Prévisualisation dynamique</li>
386
- <li>• Fonctionnalités avancées</li>
387
  </ul>
388
- <div class="mt-2 text-xs">Veuillez vérifier votre connexion et recharger la page.</div>
389
  `);
390
 
391
- // Disable online-only features
392
- document.getElementById('btnSend').disabled = true;
393
- document.getElementById('btnMic').disabled = true;
394
- document.getElementById('btnGenImg')?.disabled = true;
395
- document.getElementById('btnGenVid')?.disabled = true;
396
- document.getElementById('prompt').placeholder = "Mode hors ligne - reconnectez-vous";
397
- }
398
  initialize();
399
  applyPreview();
400
  switchTab('preview');
 
289
  }
290
  }
291
  }
292
+ // Intégration avec le serveur Rosalinda
293
  async function queryRosalinda(prompt) {
294
  setStatus("Processing...", "work");
295
  let retries = 0;
296
 
297
  while (retries < 3) {
298
  try {
299
+ // Try local first, fallback to HF Space if needed
300
+ const endpoints = [
301
+ 'http://localhost:3000/rosalinda/chat', // Local Ollama
302
+ 'https://YOUR_HF_SPACE.hf.space/rosalinda/chat' // HF Docker fallback
303
+ ];
304
+
305
+ // Try each endpoint until one works
306
+ for (const endpoint of endpoints) {
307
+ try {
308
+ const response = await fetch(endpoint, {
309
+ method: 'POST',
310
+ headers: {
311
+ 'Content-Type': 'application/json',
312
+ 'Authorization': 'Bearer YOUR_SECRET_TOKEN'
313
+ },
314
+ body: JSON.stringify({ message: prompt })
315
+ });
316
 
317
+ if (!response.ok) {
318
+ const errorData = await response.json();
319
+ throw new Error(errorData?.error || "Request failed");
320
+ }
321
+
322
+ const data = await response.json();
323
+ return data.reply;
324
+ } catch (e) {
325
+ console.log(`Failed with endpoint ${endpoint}, trying next...`);
326
+ }
327
  }
328
 
329
+ throw new Error("All endpoints failed");
330
+ } catch (err) {
 
331
  retries++;
332
  console.error(`Attempt ${retries} failed:`, err);
333
  retryPill.textContent = `${retries} / 3`;
 
362
  // Initialisation améliorée
363
  async function initialize() {
364
  try {
365
+ jobStatus.textContent = "Connecting...";
366
 
367
+ // Try to connect to any available backend
368
+ try {
369
+ const response = await fetch('http://localhost:3000/health');
370
+ if (response.ok) {
371
+ netStatus.textContent = "Local";
372
+ netStatus.className = "text-emerald-400";
373
+ }
374
+ } catch (localErr) {
375
+ try {
376
+ const response = await fetch('https://YOUR_HF_SPACE.hf.space/health');
377
+ if (response.ok) {
378
+ netStatus.textContent = "Cloud";
379
+ netStatus.className = "text-cyan-400";
380
+ }
381
+ } catch (cloudErr) {
382
+ enableOfflineMode();
383
+ return;
384
+ }
385
  }
386
+
387
+ jobStatus.textContent = "Prêt";
388
+ addMsg("Rosalinda", "Bonjour ! Je suis Rosalinda, votre assistante IA privée. Comment puis-je vous aider aujourd'hui ?");
389
 
390
+ // Vérification périodique du backend
391
+ setInterval(async () => {
392
+ try {
393
+ await fetch('http://localhost:3000/health');
394
+ netStatus.textContent = "Local";
395
+ netStatus.className = "text-emerald-400";
396
+ } catch (e) {
397
+ try {
398
+ await fetch('https://YOUR_HF_SPACE.hf.space/health');
399
+ netStatus.textContent = "Cloud";
400
+ netStatus.className = "text-cyan-400";
401
+ } catch (e) {
402
+ enableOfflineMode();
403
+ }
404
+ }
405
+ }, 30000);
406
+ applyPreview();
407
  switchTab('preview');
408
  } catch (err) {
409
  console.error("Initialization error:", err);
410
  enableOfflineMode();
411
  }
412
  }
 
413
  function enableOfflineMode() {
414
  jobStatus.textContent = "Hors ligne";
415
  jobStatus.className = "text-rose-400";
 
421
  <div class="text-amber-400">⚠️ Mode hors ligne activé</div>
422
  <div class="mt-2 text-sm">Certaines fonctionnalités sont désactivées :</div>
423
  <ul class="mt-1 text-xs space-y-1">
424
+ <li>• Vérifiez que le serveur Rosalinda est en marche</li>
425
+ <li>• Si local: <code class="bg-black/20 px-1">node server.js</code></li>
426
+ <li>• Si cloud: vérifiez l'URL de l'espace HF</li>
427
  </ul>
428
+ <div class="mt-2 text-xs">Pour le mode local: installer Ollama + lancer le serveur.</div>
429
  `);
430
 
431
+ // Enable features but they'll fail gracefully
432
+ document.getElementById('btnSend').disabled = false;
433
+ document.getElementById('btnMic').disabled = false;
434
+ document.getElementById('prompt').placeholder = "Essayez de relancer le serveur Rosalinda";
435
+ }
 
 
436
  initialize();
437
  applyPreview();
438
  switchTab('preview');