119 lines
4.9 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test LLM Local - WebGPU</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; line-height: 1.5; }
#chat-box { border: 1px solid #ccc; padding: 1rem; height: 400px; overflow-y: auto; border-radius: 8px; background: #f9f9f9; margin-bottom: 1rem; }
.message { margin-bottom: 1rem; padding: 0.5rem 1rem; border-radius: 8px; }
.user { background: #e3f2fd; align-self: flex-end; text-align: right; }
.bot { background: #fff; border: 1px solid #eee; }
#controls { display: flex; gap: 10px; }
input { flex-grow: 1; padding: 10px; border-radius: 4px; border: 1px solid #ddd; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:disabled { background: #ccc; }
#status { font-size: 0.9rem; color: #666; margin-bottom: 1rem; font-style: italic; }
</style>
</head>
<body>
<h2>🤖 Mon LLM Local (via Chrome WebGPU)</h2>
<div id="status">Initialisation... cliquez sur "Charger le modèle" pour commencer.</div>
<div id="chat-box"></div>
<div id="controls">
<input type="text" id="user-input" placeholder="Écrivez votre message ici..." disabled>
<button id="send-btn" disabled>Envoyer</button>
</div>
<button id="load-btn" style="margin-top: 10px; background-color: #28a745;">Charger le Modèle (Llama 3.2)</button>
<script type="module">
// Importation de WebLLM directement depuis le CDN
import { CreateMLCEngine } from "https://esm.run/@mlc-ai/web-llm";
// Configuration du modèle (ici Llama 3.2 1B, léger et rapide)
const selectedModel = "Llama-3.2-1B-Instruct-q4f16_1-MLC";
let engine;
const statusLabel = document.getElementById('status');
const chatBox = document.getElementById('chat-box');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const loadBtn = document.getElementById('load-btn');
// Fonction pour mettre à jour l'état de chargement
const initProgressCallback = (report) => {
statusLabel.innerText = report.text;
};
// 1. Chargement du moteur (Engine)
loadBtn.addEventListener('click', async () => {
loadBtn.disabled = true;
statusLabel.innerText = "Démarrage du téléchargement du modèle (peut prendre quelques minutes)...";
try {
// Création du moteur WebLLM
engine = await CreateMLCEngine(
selectedModel,
{ initProgressCallback: initProgressCallback }
);
statusLabel.innerText = "✅ Modèle chargé et prêt ! (GPU Actif)";
userInput.disabled = false;
sendBtn.disabled = false;
loadBtn.style.display = 'none';
userInput.focus();
} catch (err) {
statusLabel.innerText = "❌ Erreur : " + err.message;
loadBtn.disabled = false;
}
});
// 2. Fonction d'envoi de message
sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', (e) => { if(e.key === 'Enter') sendMessage() });
async function sendMessage() {
const text = userInput.value.trim();
if (!text) return;
// Affichage message utilisateur
appendMessage(text, 'user');
userInput.value = '';
// Création placeholder pour la réponse
const botMessageDiv = appendMessage("...", 'bot');
let fullResponse = "";
// Inférence (Génération)
const chunks = await engine.chat.completions.create({
messages: [{ role: "user", content: text }],
stream: true, // Important pour voir le texte s'écrire en temps réel
});
// Lecture du flux (Streaming)
for await (const chunk of chunks) {
const content = chunk.choices[0]?.delta?.content || "";
fullResponse += content;
botMessageDiv.innerText = fullResponse;
// Auto-scroll vers le bas
chatBox.scrollTop = chatBox.scrollHeight;
}
}
function appendMessage(text, sender) {
const div = document.createElement('div');
div.classList.add('message', sender);
div.innerText = text;
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
return div;
}
</script>
</body>
</html>