0%
Intégration de ChatGPT dans une application web

Intégration Web

Création d'une application web avec ChatGPT

10-15 min

Intégration de ChatGPT dans une application web

Architecture de base

Pour intégrer ChatGPT dans une application web, nous allons utiliser une architecture client-serveur :

Client (Frontend) <-> Serveur (Backend) <-> API ChatGPT

Structure du projet

chatgpt-app/
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   ├── services/
│   │   └── styles/
│   ├── package.json
│   └── index.html
├── backend/
│   ├── src/
│   │   ├── controllers/
│   │   ├── services/
│   │   └── config/
│   ├── package.json
│   └── .env
└── README.md

Backend (Node.js/Express)

Configuration du serveur

// backend/src/config/config.js
require('dotenv').config();

module.exports = {
  port: process.env.PORT || 3000,
  openaiApiKey: process.env.OPENAI_API_KEY,
  corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:5173'
};

Service ChatGPT

// backend/src/services/chatgpt.service.js
const { Configuration, OpenAIApi } = require('openai');
const config = require('../config/config');

const configuration = new Configuration({
  apiKey: config.openaiApiKey,
});

const openai = new OpenAIApi(configuration);

class ChatGPTService {
  async generateResponse(prompt, context = []) {
    try {
      const completion = await openai.createChatCompletion({
        model: "gpt-3.5-turbo",
        messages: [
          { role: "system", content: "Vous êtes un assistant utile et amical." },
          ...context,
          { role: "user", content: prompt }
        ],
        temperature: 0.7,
        max_tokens: 500
      });

      return completion.data.choices[0].message.content;
    } catch (error) {
      console.error('Erreur ChatGPT:', error);
      throw new Error('Erreur lors de la génération de la réponse');
    }
  }
}

module.exports = new ChatGPTService();

Contrôleur API

// backend/src/controllers/chat.controller.js
const ChatGPTService = require('../services/chatgpt.service');

class ChatController {
  async handleChat(req, res) {
    try {
      const { message, context } = req.body;
      
      if (!message) {
        return res.status(400).json({ error: 'Le message est requis' });
      }

      const response = await ChatGPTService.generateResponse(message, context);
      
      res.json({ response });
    } catch (error) {
      console.error('Erreur contrôleur:', error);
      res.status(500).json({ error: 'Erreur serveur' });
    }
  }
}

module.exports = new ChatController();

Routes API

// backend/src/routes/chat.routes.js
const express = require('express');
const router = express.Router();
const ChatController = require('../controllers/chat.controller');

router.post('/chat', ChatController.handleChat);

module.exports = router;

Serveur Express

// backend/src/server.js
const express = require('express');
const cors = require('cors');
const config = require('./config/config');
const chatRoutes = require('./routes/chat.routes');

const app = express();

app.use(cors({
  origin: config.corsOrigin
}));
app.use(express.json());

app.use('/api', chatRoutes);

app.listen(config.port, () => {
  console.log(`Serveur démarré sur le port ${config.port}`);
});

Frontend (React)

Service API

// frontend/src/services/api.service.js
const API_URL = 'http://localhost:3000/api';

export const chatService = {
  async sendMessage(message, context = []) {
    try {
      const response = await fetch(`${API_URL}/chat`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message, context }),
      });

      if (!response.ok) {
        throw new Error('Erreur réseau');
      }

      return await response.json();
    } catch (error) {
      console.error('Erreur API:', error);
      throw error;
    }
  }
};

Composant Chat

// frontend/src/components/Chat.jsx
import React, { useState, useRef, useEffect } from 'react';
import { chatService } from '../services/api.service';
import '../styles/Chat.css';

const Chat = () => {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);
  const messagesEndRef = useRef(null);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!input.trim()) return;

    const userMessage = input.trim();
    setInput('');
    setLoading(true);

    // Ajouter le message de l'utilisateur
    setMessages(prev => [...prev, { role: 'user', content: userMessage }]);

    try {
      // Préparer le contexte
      const context = messages.map(msg => ({
        role: msg.role,
        content: msg.content
      }));

      // Envoyer la requête
      const { response } = await chatService.sendMessage(userMessage, context);

      // Ajouter la réponse
      setMessages(prev => [...prev, { role: 'assistant', content: response }]);
    } catch (error) {
      setMessages(prev => [...prev, { 
        role: 'system', 
        content: 'Désolé, une erreur est survenue. Veuillez réessayer.' 
      }]);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((message, index) => (
          <div key={index} className={`message ${message.role}`}>
            {message.content}
          </div>
        ))}
        {loading && (
          <div className="message assistant loading">
            <div className="typing-indicator">
              <span></span>
              <span></span>
              <span></span>
            </div>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      
      <form onSubmit={handleSubmit} className="input-form">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Tapez votre message..."
          disabled={loading}
        />
        <button type="submit" disabled={loading}>
          Envoyer
        </button>
      </form>
    </div>
  );
};

export default Chat;

Styles CSS

/* frontend/src/styles/Chat.css */
.chat-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.messages {
  flex-grow: 1;
  overflow-y: auto;
  padding: 20px;
  background: #f5f5f5;
  border-radius: 10px;
  margin-bottom: 20px;
}

.message {
  margin-bottom: 15px;
  padding: 10px 15px;
  border-radius: 15px;
  max-width: 70%;
}

.message.user {
  background: #007bff;
  color: white;
  margin-left: auto;
}

.message.assistant {
  background: white;
  color: #333;
  margin-right: auto;
}

.message.system {
  background: #ff4444;
  color: white;
  margin: 10px auto;
  text-align: center;
}

.input-form {
  display: flex;
  gap: 10px;
}

input {
  flex-grow: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
  font-size: 16px;
}

button {
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
}

button:disabled {
  background: #cccccc;
  cursor: not-allowed;
}

.typing-indicator {
  display: flex;
  gap: 5px;
}

.typing-indicator span {
  width: 8px;
  height: 8px;
  background: #666;
  border-radius: 50%;
  animation: typing 1s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
  animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes typing {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

Sécurité et bonnes pratiques

1. Protection des clés API

// backend/.env
OPENAI_API_KEY=votre_clé_api
PORT=3000
CORS_ORIGIN=http://localhost:5173

2. Rate Limiting

// backend/src/middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limite chaque IP à 100 requêtes par fenêtre
});

module.exports = limiter;

3. Validation des entrées

// backend/src/middleware/validator.js
const { body, validationResult } = require('express-validator');

const chatValidation = [
  body('message')
    .trim()
    .notEmpty()
    .withMessage('Le message est requis')
    .isLength({ max: 1000 })
    .withMessage('Le message ne doit pas dépasser 1000 caractères'),
  
  body('context')
    .optional()
    .isArray()
    .withMessage('Le contexte doit être un tableau')
];

const validate = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  next();
};

module.exports = {
  chatValidation,
  validate
};

Déploiement

Configuration de production

// backend/src/config/production.js
module.exports = {
  port: process.env.PORT || 3000,
  openaiApiKey: process.env.OPENAI_API_KEY,
  corsOrigin: process.env.CORS_ORIGIN,
  rateLimit: {
    windowMs: 15 * 60 * 1000,
    max: 100
  }
};

Script de déploiement

#!/bin/bash
# deploy.sh

# Build frontend
cd frontend
npm run build

# Deploy backend
cd ../backend
npm install --production
pm2 start src/server.js --name "chatgpt-app"

Tests

Tests unitaires (Jest)

// backend/src/tests/chat.service.test.js
const ChatGPTService = require('../services/chatgpt.service');

describe('ChatGPTService', () => {
  test('generateResponse retourne une réponse valide', async () => {
    const prompt = 'Bonjour';
    const response = await ChatGPTService.generateResponse(prompt);
    
    expect(response).toBeDefined();
    expect(typeof response).toBe('string');
    expect(response.length).toBeGreaterThan(0);
  });
});

Tests d’intégration

// backend/src/tests/chat.integration.test.js
const request = require('supertest');
const app = require('../server');

describe('Chat API', () => {
  test('POST /api/chat retourne une réponse valide', async () => {
    const response = await request(app)
      .post('/api/chat')
      .send({ message: 'Bonjour' });
    
    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('response');
  });
});

Ressources supplémentaires

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !