Une application IA puissante avec Nitro et Nuxt UI

Fait partie de la série Agents IA et serveur MCP au service du web agentique

Notre Agent IA et notre serveur MCP sont prêts. Dans cet article, nous allons les transformer en une application IA où les utilisateurs peuvent envoyer des messages, recevoir des réponses diffusées et voir quand l'Agent utilise un outil.

Pour la construire, nous utiliserons Nuxt avec Nuxt UI, qui contient un ensemble de composants spécialement conçus pour construire des applications IA.

De Nitro à Nuxt

Nuxt utilise Nitro comme moteur serveur. Nous pouvons donc conserver les routes serveur créées dans les articles précédents et ajouter un frontend autour d'elles.

Cette migration est mécanique et n'est pas le sujet de l'article, ce script s'en charge donc pour nous :

bash
# Supprimer les dépendances Nitro
pnpm remove nitropack h3

# Installer les dépendances Nuxt
pnpm add nuxt vue vue-router

# Mettre à jour les scripts de package.json
jq '.scripts = {
  "build": "nuxt build",
  "dev": "nuxt dev",
  "generate": "nuxt generate",
  "preview": "nuxt preview",
  "postinstall": "nuxt prepare"
}' package.json > tmp.json && mv tmp.json package.json

# Mettre à jour tsconfig.json pour Nuxt
cat > tsconfig.json <<'EOF'
{
  "files": [],
  "references": [
    { "path": "./.nuxt/tsconfig.app.json" },
    { "path": "./.nuxt/tsconfig.server.json" },
    { "path": "./.nuxt/tsconfig.shared.json" },
    { "path": "./.nuxt/tsconfig.node.json" }
  ]
}
EOF

# Créer la configuration Nuxt
cat > nuxt.config.ts <<'EOF'
import { defineNuxtConfig } from "nuxt/config"

export default defineNuxtConfig({
  runtimeConfig: {
    openAiApiKey: '',
    mcpEndpoint: '',
  },
  compatibilityDate: '2025-10-05',
})
EOF

# Supprimer l'ancienne config Nitro
rm nitro.config.ts

# Mettre en place la structure de base de l'app Nuxt
mkdir -p app/pages
cat > app/app.vue <<'EOF'
<template>
  <div>
    <NuxtRouteAnnouncer />
    <NuxtPage />
  </div>
</template>
EOF

cat > app/pages/index.vue <<'EOF'
<template>
  <div>
    <h1>Bienvenue dans l'application IA</h1>
  </div>
</template>
EOF

# Mettre à jour .env et .gitignore
sed -i '' 's/NITRO/NUXT/g' .env
echo ".nuxt" >> .gitignore

# Préparer Nuxt
pnpm run postinstall

Ajouter Nuxt UI

Installer Nuxt UI ne fait pas partie de cette série, vous pouvez simplement exécuter ce script :

bash
# Installer Nuxt UI et Tailwind CSS
pnpm add @nuxt/ui tailwindcss

# Créer le fichier CSS principal et importer les styles
mkdir -p app/assets/css
cat > app/assets/css/main.css <<'EOF'
@import 'tailwindcss';
@import '@nuxt/ui';
EOF

# Mettre à jour la config Nuxt pour activer Nuxt UI et inclure le CSS
cat > nuxt.config.ts <<'EOF'
import { defineNuxtConfig } from "nuxt/config"

export default defineNuxtConfig({
  modules: ['@nuxt/ui'],
  css: ['~/assets/css/main.css'],
  runtimeConfig: {
    openAiApiKey: '',
    mcpEndpoint: '',
  },
  compatibilityDate: '2025-10-05',
})
EOF

# Mettre à jour la mise en page principale pour utiliser le composant UApp de Nuxt UI
cat > app/app.vue <<'EOF'
<template>
  <UApp>
    <NuxtPage />
  </UApp>
</template>
EOF

Une fois terminé, nous pouvons démarrer le serveur de développement avec :

bash
pnpm run dev

L'application devrait maintenant démarrer correctement. Avant de tester le chat, vérifiez que la route /api/chat est présente et que votre fichier .env contient NUXT_OPEN_AI_API_KEY et NUXT_MCP_ENDPOINT.

Construire l'application

Maintenant que nous avons une application Nuxt fonctionnelle avec Nuxt UI, nous pouvons construire l'interface de chat. Nous nous concentrerons sur sa structure et son comportement, plutôt que sur un design parfait.

Dans la documentation de Nuxt, il existe un exemple entièrement fonctionnel d'une page avec une interface de chat que nous pouvons utiliser. Nous n'avons pas besoin de plus pour l'implémentation d'aujourd'hui.

Avant de l'utiliser, nous devons installer l'AI SDK pour Vue.

bash
pnpm add @ai-sdk/vue

Nous devons également installer @nuxtjs/mdc pour parser et rendre le Markdown à la volée.

bash
pnpm dlx nuxt module add @nuxtjs/mdc

Ensuite, remplacez app/pages/index.vue par cette version complète de l'exemple :

vue
<script setup lang="ts">
import { Chat } from '@ai-sdk/vue'

const input = ref('')

const chat = new Chat({
  onError(error) {
    console.error('Chat error:', error)
  }
})

function handleSubmit(e: Event) {
  e.preventDefault()
  chat.sendMessage({ text: input.value })
  input.value = ''
}
</script>

<template>
  <UDashboardPanel>
    <template #body>
      <UContainer>
        <UChatMessages :messages="chat.messages" :status="chat.status">
          <template #content="{ message }">
            <template v-for="(part, index) in message.parts" :key="index">
              <MDC
                v-if="part.type === 'text'"
                :value="part.text"
                :cache-key="`${message.id}-${index}`"
                unwrap="p"
              />

              <div v-else-if="part.type === 'reasoning'">
                {{ part.state === 'streaming' ? 'Réflexion en cours...' : 'Réflexion terminée' }}
              </div>

              <div v-else-if="part.type === 'dynamic-tool' && part.toolName === 'addition'">
                <template v-if="part.state === 'input-streaming'">
                  <template v-if="part.input && (part.input as { a: number; b: number }).a !== undefined && (part.input as { a: number; b: number }).b !== undefined">
                    Addition : {{ (part.input as { a: number; b: number }).a }} + {{ (part.input as { a: number; b: number }).b }}
                  </template>
                  <template v-else>
                    Addition en cours...
                  </template>
                </template>
                <template v-else>
                  Addition terminée : {{ (part.input as { a: number; b: number }).a }} + {{ (part.input as { a: number; b: number }).b }}
                </template>
              </div>
            </template>
          </template>
        </UChatMessages>
      </UContainer>
    </template>

    <template #footer>
      <UContainer>
        <UChatPrompt v-model="input" :error="chat.error" @submit="handleSubmit">
          <UChatPromptSubmit :status="chat.status" @stop="chat.stop" @reload="chat.regenerate" />
        </UChatPrompt>
      </UContainer>
    </template>
  </UDashboardPanel>
</template>

Saisissez « What is 2 + 2? » dans le champ de saisie. Avec la route API en cours d'exécution et les variables d'environnement configurées, vous devriez recevoir une réponse de l'Agent.

Notre application IA en action
Notre application IA en action

Pour vérifier que l'Agent a utilisé l'outil d'addition, ouvrez les outils de développement de votre navigateur et inspectez le flux d'événements de la requête /api/chat. Vous devriez voir quelque chose comme ceci :

Le flux d'événements de l'endpoint /api/chat
Le flux d'événements de l'endpoint /api/chat

C'est le même flux de messages d'interface que celui observé avec curl dans le terminal.

Comprendre l'intégration du chat

Nous avons construit une application IA qui utilise notre Agent. Voici ce qui relie le frontend au backend.

D'abord, la réponse de /api/chat n'est pas la sortie brute du modèle. toUIMessageStreamResponse convertit le résultat de l'Agent en un flux conçu pour une interface utilisateur.

UIMessage est l'interface qui définit la structure d'un message dans une application IA. Le frontend n'a pas besoin de transformer le flux de messages reçu, car ils sont déjà au bon format.

L'AI SDK fournit aussi la classe Chat pour gérer l'état du chat. Elle gère le flux de messages, ajoute les nouveaux messages, les envoie dans le bon format avec sendMessage({ text: input.value }), et gère les états de statut et d'erreur.

Vous pourriez implémenter vous-même la requête POST, l'analyse de la réponse diffusée, les mises à jour des messages, l'annulation et la gestion des erreurs. Chat s'occupe de ces détails afin que nous puissions nous concentrer sur l'expérience, notamment l'affichage des outils utilisés, de leur statut et de leurs entrées en temps réel.

Afficher les outils

Chaque message est composé de parts. Un message utilisateur peut avoir des parts de texte et de fichier, tandis qu'un message assistant peut avoir des parts de texte, de raisonnement, d'appel d'outil et de fichier.

Sous le capot, l'AI SDK gère ces parts pendant qu'il reçoit les fragments envoyés par le serveur. Il assemble les fragments de texte en un message et met à jour l'état des parts d'appel d'outil à mesure que l'Agent les utilise.

Le slot #content de l'exemple complet itère sur ces parts. Il affiche le texte Markdown, indique lorsque le modèle raisonne et montre l'avancement ainsi que les entrées de l'outil d'addition.

Note

Cette approche est flexible et facile à personnaliser selon vos besoins.

Au final, cela ressemble à ceci :

L'application IA en action.

Montrer l'avancement de l'Agent donne confiance aux utilisateurs que leur demande est prise en compte, en particulier lorsqu'un appel d'outil prend du temps. Dans le dernier article, nous déploierons l'application et le serveur MCP sur Cloudflare.

Pd

Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web et le parcours humain qui l'entoure.

Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.

Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.

J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous, cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !

Continuer la lectureDéployer notre Agent IA dans le monde avec Cloudflare

Réactions

Discussions

Ajouter un commentaire

Vous devez être connecté pour accéder à cette fonctionnalité.