Exemples JavaScript/Node.js
Cette section présente des exemples pratiques d'utilisation de l'API YODI avec JavaScript et Node.js.
Installation et configuration
npm install yodi-sdk axios dotenv
Configuration de base
// config.js
require('dotenv').config();
const { YodiClient } = require('yodi-sdk');
const client = new YodiClient({
apiKey: process.env.YODI_API_KEY,
baseURL: 'https://api.yodi.tg/v1'
});
module.exports = client;
Configuration avec Fetch (vanilla JavaScript)
// yodi-client.js
class YodiAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://api.yodi.tg/v1';
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
},
...options
};
if (options.body) {
config.body = JSON.stringify(options.body);
}
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
async createChatCompletion(params) {
return this.request('/chat/completions', {
body: params
});
}
async createEmbeddings(params) {
return this.request('/embeddings', {
body: params
});
}
async streamChatCompletion(params, onChunk) {
const response = await fetch(`${this.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
...params,
stream: true
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') return;
try {
const parsed = JSON.parse(data);
onChunk(parsed);
} catch (e) {
console.warn('Failed to parse chunk:', data);
}
}
}
}
} finally {
reader.releaseLock();
}
}
}
// Utilisation
const yodi = new YodiAPI(process.env.YODI_API_KEY);
Chat et conversation
Chat simple
const client = require('./config');
async function simpleChat() {
try {
const response = await client.createChatCompletion({
model: "yodi-1",
messages: [
{
role: "system",
content: "Tu es un assistant IA serviable et amical."
},
{
role: "user",
content: "Bonjour ! Comment ça va ?"
}
],
temperature: 0.7,
max_tokens: 200
});
return response.choices[0].message.content;
} catch (error) {
console.error('Erreur lors du chat:', error);
throw error;
}
}
// Utilisation
simpleChat().then(console.log);
Chatbot avec mémoire
class ChatBot {
constructor(systemPrompt = "Tu es un assistant IA serviable.") {
this.messages = [{ role: "system", content: systemPrompt }];
this.client = client;
}
async chat(userMessage) {
this.messages.push({ role: "user", content: userMessage });
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: this.messages,
temperature: 0.7,
max_tokens: 300
});
const assistantMessage = response.choices[0].message.content;
this.messages.push({ role: "assistant", content: assistantMessage });
return assistantMessage;
} catch (error) {
console.error('Erreur lors du chat:', error);
throw error;
}
}
getHistory() {
return this.messages.filter(msg => msg.role !== "system");
}
clearHistory() {
const systemMsg = this.messages.find(msg => msg.role === "system");
this.messages = systemMsg ? [systemMsg] : [];
}
exportConversation() {
return {
timestamp: new Date().toISOString(),
messages: this.getHistory()
};
}
}
// Utilisation avec interface simple
async function interactiveChat() {
const bot = new ChatBot("Tu es un expert en développement JavaScript.");
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log("Chatbot JavaScript prêt ! Tapez 'quit' pour quitter.\n");
const askQuestion = () => {
rl.question('Vous: ', async (input) => {
if (input.toLowerCase().includes('quit')) {
console.log('Au revoir !');
rl.close();
return;
}
try {
const response = await bot.chat(input);
console.log(`Bot: ${response}\n`);
askQuestion();
} catch (error) {
console.error('Erreur:', error.message);
askQuestion();
}
});
};
askQuestion();
}
// interactiveChat();
Chat streaming en temps réel
async function streamingChat(userMessage) {
const yodi = new YodiAPI(process.env.YODI_API_KEY);
let fullResponse = '';
await yodi.streamChatCompletion({
model: "yodi-1",
messages: [
{ role: "user", content: userMessage }
],
temperature: 0.7,
max_tokens: 500
}, (chunk) => {
if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta) {
const content = chunk.choices[0].delta.content || '';
fullResponse += content;
process.stdout.write(content); // Affichage en temps réel
}
});
console.log('\n'); // Nouvelle ligne à la fin
return fullResponse;
}
// Utilisation
// streamingChat("Explique-moi le JavaScript moderne en détail.");
� Génération de contenu
Générateur de contenu web
class WebContentGenerator {
constructor() {
this.client = client;
}
async generateBlogPost(topic, keywords, options = {}) {
const {
tone = 'professionnel',
length = 'moyen',
language = 'fr'
} = options;
const lengthConfig = {
court: { maxTokens: 500, sections: 3 },
moyen: { maxTokens: 1000, sections: 4 },
long: { maxTokens: 1500, sections: 5 }
};
const config = lengthConfig[length] || lengthConfig.moyen;
const prompt = `Écris un article de blog ${tone} sur le sujet : ${topic}
Mots-clés à intégrer : ${keywords.join(', ')}
Structure requise :
1. Titre accrocheur
2. Introduction engageante
3. ${config.sections} sections principales avec sous-titres
4. Conclusion avec appel à l'action
Optimisation SEO : Intégrer naturellement les mots-clés
Ton : ${tone}
Langue : ${language}
Article :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: config.maxTokens
});
return response.choices[0].message.content;
} catch (error) {
console.error('Erreur génération article:', error);
throw error;
}
}
async generateSEOMetadata(content) {
const prompt = `Génère les métadonnées SEO pour ce contenu :
Contenu : ${content.substring(0, 500)}...
Génère :
1. Title tag (50-60 caractères)
2. Meta description (150-160 caractères)
3. 5 mots-clés pertinents
4. URL slug suggérée
5. Balises structurées (h1, h2)
Format JSON :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-instruct",
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
max_tokens: 300
});
return JSON.parse(response.choices[0].message.content);
} catch (error) {
console.error('Erreur génération SEO:', error);
return null;
}
}
async generateSocialMediaPosts(theme, platforms) {
const platformConfigs = {
twitter: { limit: 280, hashtags: 3, tone: "concis" },
linkedin: { limit: 1300, hashtags: 5, tone: "professionnel" },
instagram: { limit: 2200, hashtags: 10, tone: "engageant" },
facebook: { limit: 500, hashtags: 3, tone: "conversationnel" }
};
const posts = {};
for (const platform of platforms) {
if (!platformConfigs[platform]) continue;
const config = platformConfigs[platform];
const prompt = `Crée un post ${platform} sur : ${theme}
Contraintes :
- Maximum ${config.limit} caractères
- Ton ${config.tone}
- ${config.hashtags} hashtags pertinents
- Optimisé pour l'engagement
Post :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 0.9,
max_tokens: 200
});
posts[platform] = response.choices[0].message.content;
} catch (error) {
console.error(`Erreur post ${platform}:`, error);
posts[platform] = null;
}
}
return posts;
}
}
// Utilisation
async function exempleGenerationContenu() {
const generator = new WebContentGenerator();
// Générer un article de blog
const article = await generator.generateBlogPost(
"Intelligence artificielle dans le e-commerce",
["IA", "e-commerce", "automatisation", "conversion"],
{ tone: "technique", length: "moyen" }
);
console.log("Article généré :", article);
// Générer les métadonnées SEO
const seoData = await generator.generateSEOMetadata(article);
console.log("SEO Metadata :", seoData);
// Générer des posts sociaux
const socialPosts = await generator.generateSocialMediaPosts(
"Lancement de notre nouvelle IA",
["twitter", "linkedin", "instagram"]
);
console.log("Posts sociaux :", socialPosts);
}
// exempleGenerationContenu();
E-mail marketing automatisé
class EmailMarketingBot {
constructor() {
this.client = client;
}
async generateEmailCampaign(campaignData) {
const {
type, // newsletter, promotion, welcome, follow-up
audience, // segment d'audience
product, // produit/service
goal, // objectif de la campagne
brandTone // ton de la marque
} = campaignData;
const templates = {
newsletter: {
subject: "Informations et nouveautés",
structure: "Actualités + contenu de valeur + CTA doux"
},
promotion: {
subject: "Offre spéciale limitée",
structure: "Accroche + avantages + urgence + CTA fort"
},
welcome: {
subject: "Bienvenue dans la communauté",
structure: "Accueil + présentation + premiers pas + ressources"
},
follow_up: {
subject: "Suite de votre intérêt",
structure: "Rappel contexte + valeur ajoutée + next step"
}
};
const template = templates[type] || templates.newsletter;
// Générer les lignes d'objet
const subjects = await this.generateSubjectLines(campaignData, template);
// Générer le contenu de l'email
const emailContent = await this.generateEmailContent(campaignData, template);
// Générer les variantes A/B
const variants = await this.generateABVariants(emailContent);
return {
subjects,
content: emailContent,
variants,
metadata: {
type,
audience,
estimatedOpenRate: this.estimateOpenRate(type, audience),
recommendedSendTime: this.getOptimalSendTime(audience)
}
};
}
async generateSubjectLines(campaignData, template, count = 5) {
const prompt = `Génère ${count} lignes d'objet d'email pour une campagne ${campaignData.type}.
Contexte :
- Audience : ${campaignData.audience}
- Produit : ${campaignData.product}
- Objectif : ${campaignData.goal}
- Ton : ${campaignData.brandTone}
Critères :
- Maximum 50 caractères
- ${template.subject}
- Éviter les mots spam
- Créer de la curiosité
Lignes d'objet :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 1.0,
max_tokens: 300
});
return response.choices[0].message.content
.split('\n')
.filter(line => line.trim())
.slice(0, count);
} catch (error) {
console.error('Erreur génération sujets:', error);
return [];
}
}
async generateEmailContent(campaignData, template) {
const prompt = `Crée le contenu d'un email ${campaignData.type} professionnel.
Contexte :
- Audience : ${campaignData.audience}
- Produit/Service : ${campaignData.product}
- Objectif : ${campaignData.goal}
- Ton : ${campaignData.brandTone}
Structure : ${template.structure}
Le contenu doit :
- Être personnalisé et engageant
- Inclure un CTA clair
- Être optimisé pour mobile
- Respecter les bonnes pratiques emailing
Contenu HTML :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: 800
});
return response.choices[0].message.content;
} catch (error) {
console.error('Erreur génération contenu:', error);
return null;
}
}
async generateABVariants(originalContent, variantCount = 2) {
const variants = [];
for (let i = 0; i < variantCount; i++) {
const prompt = `Crée une variante de cet email pour un test A/B.
Email original :
${originalContent}
Variante ${i + 1} :
- Modifie le ton et l'approche
- Garde le même objectif
- Change la structure du CTA
- Teste une approche différente
Variante :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 0.8,
max_tokens: 600
});
variants.push({
id: `variant_${i + 1}`,
content: response.choices[0].message.content,
changes: `Variante ${i + 1} - Approche alternative`
});
} catch (error) {
console.error(`Erreur variante ${i + 1}:`, error);
}
}
return variants;
}
estimateOpenRate(type, audience) {
const baseRates = {
newsletter: 0.22,
promotion: 0.18,
welcome: 0.35,
follow_up: 0.25
};
return baseRates[type] || 0.20;
}
getOptimalSendTime(audience) {
const schedules = {
B2B: "Mardi-Jeudi 10h-11h",
B2C: "Samedi-Dimanche 11h-13h",
mixed: "Mercredi 14h-16h"
};
return schedules[audience] || schedules.mixed;
}
}
// Utilisation
async function exempleEmailMarketing() {
const emailBot = new EmailMarketingBot();
const campaign = await emailBot.generateEmailCampaign({
type: "promotion",
audience: "B2C",
product: "Plateforme IA YODI",
goal: "Augmenter les conversions d'essai gratuit",
brandTone: "innovant et accessible"
});
console.log("Campagne email générée :");
console.log("Sujets :", campaign.subjects);
console.log("Contenu :", campaign.content);
console.log("Métadonnées :", campaign.metadata);
}
// exempleEmailMarketing();
� Analyse et traitement de données
Analyseur de feedback client
class FeedbackAnalyzer {
constructor() {
this.client = client;
}
async analyzeSentiment(text) {
const prompt = `Analyse le sentiment de ce texte client :
"${text}"
Fournis :
1. Sentiment global (positif/négatif/neutre)
2. Score de confiance (0-100%)
3. Émotions détectées
4. Points clés mentionnés
5. Suggestions d'amélioration
Format JSON :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-instruct",
messages: [{ role: "user", content: prompt }],
temperature: 0.2,
max_tokens: 400
});
return JSON.parse(response.choices[0].message.content);
} catch (error) {
console.error('Erreur analyse sentiment:', error);
return null;
}
}
async categorizeFeedback(feedbacks) {
const categories = {};
const sentiments = { positive: 0, negative: 0, neutral: 0 };
for (const feedback of feedbacks) {
try {
const analysis = await this.analyzeSentiment(feedback.text);
if (analysis) {
// Catégoriser par sentiment
const sentiment = analysis.sentiment.toLowerCase();
sentiments[sentiment] = (sentiments[sentiment] || 0) + 1;
// Catégoriser par thème
const themes = analysis.points_clés || [];
themes.forEach(theme => {
categories[theme] = (categories[theme] || 0) + 1;
});
}
// Délai pour éviter la limitation de taux
await new Promise(resolve => setTimeout(resolve, 200));
} catch (error) {
console.error('Erreur traitement feedback:', error);
}
}
return {
sentimentDistribution: sentiments,
themeCategories: categories,
totalAnalyzed: feedbacks.length
};
}
async generateInsights(analysisResults) {
const { sentimentDistribution, themeCategories } = analysisResults;
const prompt = `Génère des insights business à partir de cette analyse de feedback :
Distribution des sentiments :
${JSON.stringify(sentimentDistribution, null, 2)}
Catégories de thèmes :
${JSON.stringify(themeCategories, null, 2)}
Fournis :
1. Principales tendances identifiées
2. Points d'amélioration prioritaires
3. Forces à capitaliser
4. Recommandations d'actions
5. Métriques de suivi suggérées
Insights :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-1",
messages: [{ role: "user", content: prompt }],
temperature: 0.4,
max_tokens: 600
});
return response.choices[0].message.content;
} catch (error) {
console.error('Erreur génération insights:', error);
return null;
}
}
}
// Utilisation
async function exempleFeedbackAnalysis() {
const analyzer = new FeedbackAnalyzer();
const feedbacks = [
{ id: 1, text: "Excellent service, très rapide et efficace !" },
{ id: 2, text: "Le produit est bien mais le support client pourrait être amélioré." },
{ id: 3, text: "Interface confuse, j'ai eu du mal à naviguer." },
{ id: 4, text: "Parfait ! Exactement ce que je cherchais." },
{ id: 5, text: "Prix un peu élevé mais qualité au rendez-vous." }
];
// Analyser un feedback individuel
const singleAnalysis = await analyzer.analyzeSentiment(feedbacks[0].text);
console.log("Analyse individuelle :", singleAnalysis);
// Analyser tous les feedbacks
const globalAnalysis = await analyzer.categorizeFeedback(feedbacks);
console.log("Analyse globale :", globalAnalysis);
// Générer des insights
const insights = await analyzer.generateInsights(globalAnalysis);
console.log("Insights business :", insights);
}
// exempleFeedbackAnalysis();
Extracteur de données structurées
class DataExtractor {
constructor() {
this.client = client;
}
async extractFromText(text, schema) {
const schemaStr = JSON.stringify(schema, null, 2);
const prompt = `Extrait les informations du texte selon ce schéma :
Schéma JSON requis :
${schemaStr}
Texte à analyser :
${text}
Instructions :
- Retourne uniquement du JSON valide
- Si une information n'existe pas, utilise null
- Respecte exactement les types du schéma
- Sois précis dans l'extraction
JSON :`;
try {
const response = await this.client.createChatCompletion({
model: "yodi-instruct",
messages: [{ role: "user", content: prompt }],
temperature: 0.1,
max_tokens: 500
});
return JSON.parse(response.choices[0].message.content);
} catch (error) {
console.error('Erreur extraction données:', error);
return null;
}
}
async extractContactInfo(text) {
const schema = {
nom: "string",
prenom: "string",
email: "string",
telephone: "string",
entreprise: "string",
poste: "string",
adresse: "string"
};
return this.extractFromText(text, schema);
}
async extractOrderInfo(text) {
const schema = {
numero_commande: "string",
date_commande: "string",
montant_total: "number",
devise: "string",
articles: "array",
statut: "string",
adresse_livraison: "string"
};
return this.extractFromText(text, schema);
}
async extractEventInfo(text) {
const schema = {
nom_evenement: "string",
date_debut: "string",
date_fin: "string",
lieu: "string",
organisateur: "string",
prix: "number",
categories: "array",
description: "string"
};
return this.extractFromText(text, schema);
}
async batchExtraction(texts, extractionType) {
const results = [];
for (let i = 0; i < texts.length; i++) {
const text = texts[i];
try {
let extracted;
switch (extractionType) {
case 'contact':
extracted = await this.extractContactInfo(text);
break;
case 'order':
extracted = await this.extractOrderInfo(text);
break;
case 'event':
extracted = await this.extractEventInfo(text);
break;
default:
throw new Error(`Type d'extraction non supporté: ${extractionType}`);
}
results.push({
index: i,
success: true,
data: extracted
});
} catch (error) {
results.push({
index: i,
success: false,
error: error.message
});
}
// Délai entre les extractions
if (i < texts.length - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}
}
// Utilisation
async function exempleExtraction() {
const extractor = new DataExtractor();
// Exemple email de contact
const emailText = `
De: marie.martin@entreprise.com
Sujet: Demande d'information
Bonjour,
Je suis Marie Martin, Directrice Marketing chez TechCorp.
Vous pouvez me joindre au 06.12.34.56.78.
Notre adresse : 123 Rue de la Technologie, 75001 Paris
Cordialement,
Marie Martin
`;
const contactInfo = await extractor.extractContactInfo(emailText);
console.log("Contact extrait :", contactInfo);
// Exemple de commande
const orderText = `
Commande #CMD-2024-001
Date: 15 janvier 2024
Total: 299.99 EUR
Articles:
- Laptop Dell XPS 13 (1x) - 1299€
- Souris sans fil (1x) - 29.99€
Statut: En préparation
Livraison: 10 Avenue des Champs, 75008 Paris
`;
const orderInfo = await extractor.extractOrderInfo(orderText);
console.log("Commande extraite :", orderInfo);
}
// exempleExtraction();
� Recherche sémantique
Moteur de recherche avec embeddings
class SemanticSearchEngine {
constructor() {
this.client = client;
this.documents = [];
this.embeddings = [];
}
async addDocuments(documents) {
this.documents.push(...documents);
// Créer les embeddings par batch
const batchSize = 10;
for (let i = 0; i < documents.length; i += batchSize) {
const batch = documents.slice(i, i + batchSize);
try {
const response = await this.client.createEmbeddings({
model: "yodi-embed",
input: batch
});
const batchEmbeddings = response.data.map(item => item.embedding);
this.embeddings.push(...batchEmbeddings);
} catch (error) {
console.error('Erreur création embeddings:', error);
throw error;
}
// Délai entre les batches
await new Promise(resolve => setTimeout(resolve, 200));
}
}
async search(query, topK = 5) {
if (this.embeddings.length === 0) {
return [];
}
try {
// Créer l'embedding de la requête
const queryResponse = await this.client.createEmbeddings({
model: "yodi-embed",
input: query
});
const queryEmbedding = queryResponse.data[0].embedding;
// Calculer les similarités cosinus
const similarities = this.embeddings.map(embedding =>
this.cosineSimilarity(queryEmbedding, embedding)
);
// Trier par similarité décroissante
const sortedResults = similarities
.map((similarity, index) => ({
document: this.documents[index],
similarity,
index
}))
.sort((a, b) => b.similarity - a.similarity)
.slice(0, topK);
return sortedResults;
} catch (error) {
console.error('Erreur lors de la recherche:', error);
return [];
}
}
cosineSimilarity(a, b) {
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dotProduct / (magnitudeA * magnitudeB);
}
async saveIndex(filename) {
const data = {
documents: this.documents,
embeddings: this.embeddings,
timestamp: new Date().toISOString()
};
require('fs').writeFileSync(filename, JSON.stringify(data));
}
async loadIndex(filename) {
try {
const data = JSON.parse(require('fs').readFileSync(filename, 'utf8'));
this.documents = data.documents;
this.embeddings = data.embeddings;
return true;
} catch (error) {
console.error('Erreur chargement index:', error);
return false;
}
}
}
// Utilisation
async function exempleRechercheSemantiqueI() {
const searchEngine = new SemanticSearchEngine();
// Documents d'exemple
const documents = [
"L'intelligence artificielle transforme le commerce électronique avec des recommandations personnalisées.",
"Les chatbots automatisent le service client et réduisent les coûts opérationnels.",
"L'analyse prédictive optimise la gestion des stocks et anticipe la demande.",
"La sécurité des données est cruciale dans les applications d'IA.",
"Le machine learning améliore la détection de fraude en ligne.",
"L'automatisation des processus métier augmente l'efficacité.",
"La personnalisation de l'expérience utilisateur booste les conversions."
];
// Ajouter les documents
await searchEngine.addDocuments(documents);
// Sauvegarder l'index
searchEngine.saveIndex('search_index.json');
// Effectuer des recherches
const queries = [
"Comment l'IA améliore-t-elle l'expérience client ?",
"Sécurité et protection des données",
"Optimisation des ventes en ligne"
];
for (const query of queries) {
console.log(`\nRecherche : "${query}"`);
const results = await searchEngine.search(query, 3);
results.forEach((result, index) => {
console.log(`${index + 1}. Similarité: ${result.similarity.toFixed(3)}`);
console.log(` Document: ${result.document}`);
});
}
}
// exempleRechercheSemantiqueI();
Intégration avec React/Frontend
Hook React pour YODI
// hooks/useYodi.js
import { useState, useCallback } from 'react';
const YODI_API_BASE = 'https://api.yodi.tg/v1';
export function useYodi(apiKey) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const makeRequest = useCallback(async (endpoint, options = {}) => {
const url = `${YODI_API_BASE}${endpoint}`;
const config = {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...options.headers
},
...options
};
if (options.body) {
config.body = JSON.stringify(options.body);
}
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}, [apiKey]);
const chat = useCallback(async (messages, options = {}) => {
setIsLoading(true);
setError(null);
try {
const response = await makeRequest('/chat/completions', {
body: {
model: "yodi-1",
messages,
temperature: 0.7,
max_tokens: 300,
...options
}
});
return response.choices[0].message.content;
} catch (err) {
setError(err.message);
throw err;
} finally {
setIsLoading(false);
}
}, [makeRequest]);
const generateEmbedding = useCallback(async (text) => {
setIsLoading(true);
setError(null);
try {
const response = await makeRequest('/embeddings', {
body: {
model: "yodi-embed",
input: text
}
});
return response.data[0].embedding;
} catch (err) {
setError(err.message);
throw err;
} finally {
setIsLoading(false);
}
}, [makeRequest]);
const streamChat = useCallback(async (messages, onChunk, options = {}) => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(`${YODI_API_BASE}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: "yodi-1",
messages,
stream: true,
temperature: 0.7,
max_tokens: 500,
...options
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') return;
try {
const parsed = JSON.parse(data);
if (parsed.choices && parsed.choices[0] && parsed.choices[0].delta) {
onChunk(parsed.choices[0].delta.content || '');
}
} catch (e) {
console.warn('Failed to parse chunk:', data);
}
}
}
}
} catch (err) {
setError(err.message);
throw err;
} finally {
setIsLoading(false);
}
}, [apiKey]);
return {
chat,
generateEmbedding,
streamChat,
isLoading,
error
};
}
// Composant de chat React
import React, { useState } from 'react';
import { useYodi } from './hooks/useYodi';
export function ChatComponent({ apiKey }) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [streamingContent, setStreamingContent] = useState('');
const { chat, streamChat, isLoading, error } = useYodi(apiKey);
const handleSend = async () => {
if (!input.trim()) return;
const userMessage = { role: 'user', content: input };
const newMessages = [...messages, userMessage];
setMessages(newMessages);
setInput('');
setStreamingContent('');
try {
let fullResponse = '';
await streamChat(newMessages, (chunk) => {
fullResponse += chunk;
setStreamingContent(fullResponse);
});
// Ajouter la réponse complète
setMessages(prev => [...prev, {
role: 'assistant',
content: fullResponse
}]);
setStreamingContent('');
} catch (err) {
console.error('Erreur chat:', err);
}
};
return (
<div className="chat-container">
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.role}`}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{streamingContent && (
<div className="message assistant streaming">
<strong>assistant:</strong> {streamingContent}
<span className="cursor">|</span>
</div>
)}
</div>
<div className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
disabled={isLoading}
placeholder="Tapez votre message..."
/>
<button onClick={handleSend} disabled={isLoading || !input.trim()}>
Envoyer
</button>
</div>
{error && <div className="error">Erreur: {error}</div>}
</div>
);
}