6 min de lecture

Créer un chatbot SwiftUI avec ExyteChat et OpenAI (MacPaw)

Guide pas à pas pour créer une interface de chatbot iOS moderne et la connecter à OpenAI avec Swift Package Manager.

iOS Swift SwiftUI OpenAI ExyteChat
Créer un chatbot SwiftUI avec ExyteChat et OpenAI (MacPaw)

Introduction

Ce tutoriel montre comment construire une interface de chatbot SwiftUI propre et la connecter à OpenAI en utilisant le SDK MacPaw, en s’appuyant sur le projet de référence : GitHub – GuillaumeBlanchet/chatbot (laissez une étoile si ça vous plaît, ce sera très apprécié).

J’ai rédigé ce tutoriel parce que les autres que j’ai trouvés n’étaient pas à jour ou n’exploitaient pas les bibliothèques populaires existantes comme ExyteChat et MacPaw OpenAI.

Voici un court clip de démonstration du projet finalisé:

Ce que vous allez faire :

  • Créer une app iOS dans Xcode
  • Ajouter des dépendances via Swift Package Manager
  • Brancher un ChatViewModel simple pour appeler OpenAI
  • Afficher les messages avec l’UI de chat d’Exyte

Prérequis : Xcode 14+, iOS 15+ et une clé d’API OpenAI.

1) Créer le projet Xcode

Ouvrez Xcode et créez un nouveau projet :

Créer un projet Xcode

Choisissez iOS > App :

Sélectionner iOS App

Saisissez un nom de produit (p. ex. « chatbot ») et assurez‑vous que l’interface est SwiftUI :

Entrer le nom du projet

2) Ajouter les dépendances (Swift Package Manager)

Ajoutez les paquets suivants :

  • Exyte Chat UI (ExyteChat)
  • OpenAI (MacPaw)

Dans Xcode : Project navigator > sélectionnez votre projet > Package Dependencies > bouton « + ».

Ouvrir Package Dependencies

Recherchez ou collez les URLs des paquets et choisissez des versions adaptées (le projet de référence utilise ExyteChat v2.6.5 et MacPaw OpenAI v0.4.6).

Ajouter ExyteChat

Enfin, ajoutez les paquets à la cible de votre app :

Ajouter le paquet à la cible

3) Configurer le client OpenAI et le view model

Créez un nouveau fichier Swift ChatViewModel.swift et initialisez le client OpenAI de MacPaw avec votre clé d’API.

@MainActor
class ChatViewModel: ObservableObject {
    
    private let openAI: OpenAI
    @Published var messages: [Message] = []
    
    init() {
        // TODO: stocker cette clé API dans le trousseau (Keychain)
        self.openAI = OpenAI(apiToken: "YOUR_OPENAI_API_KEY_HERE")

        let welcomeMessage = createMessage(userId: "bot", text: "Salut ! Quoi de neuf ?")
        messages.append(welcomeMessage)
    }
    
    func send(draft: DraftMessage) {
        let userMessage = createMessage(userId: "user", text: draft.text, createdAt: draft.createdAt)
        messages.append(userMessage)
        
        // Créer un message initial du bot pour le streaming
        let botMessageId = UUID().uuidString
        let botMessage = createMessage(messageId: botMessageId, userId: "bot", text: "en train d’écrire...", status: .sending)
        messages.append(botMessage)
        
        // Démarrer la réponse OpenAI
        Task {
            await getOpenAIResponse(userText: draft.text, botMessageId: botMessageId)
        }
    }
    
    private func getOpenAIResponse(userText: String, botMessageId: String) async {
        // Créer le contexte de conversation avec tous les messages
        var chatMessages: [ChatQuery.ChatCompletionMessageParam] = [
            .system(.init(content: .textContent("Tu es un chatbot enjoué qui apporte de la joie et de l’humour à chaque conversation. Tu réponds de façon très courte et concise.")))
        ]
        
        // Ajouter l’historique récent (10 derniers messages pour garder un contexte gérable)
        let recentMessages = messages.suffix(10)
        for message in recentMessages {
            if message.user.isCurrentUser {
                chatMessages.append(.user(.init(content: .string(message.text))))
            } else if message.id != botMessageId { // Ne pas inclure le message en cours de génération
                chatMessages.append(.assistant(.init(content: .textContent(message.text))))
            }
        }
        
        let query = ChatQuery(
            messages: chatMessages,
            model: .gpt5_nano,
            stream: false
        )
        
        // Récupérer la réponse complète auprès d’OpenAI
        // TODO: ajouter la gestion d’erreurs (voir le projet GitHub)
        let result = try! await openAI.chats(query: query)
        
        if let content = result.choices.first?.message.content {
            // Mettre à jour le message du bot avec la réponse complète
            if let messageIndex = messages.firstIndex(where: { $0.id == botMessageId }) {
                var updatedMessage = messages[messageIndex]
                updatedMessage.text = content
                updatedMessage.status = .sent
                messages[messageIndex] = updatedMessage
            }
        }
    }
}

Astuce sécurité : en production, stockez la clé d’API dans le Trousseau (Keychain) ou via une couche de configuration — ne la codez pas en dur.

4) Construire l’UI de chat avec ExyteChat

Créez une vue SwiftUI simple qui affiche vos messages et en envoie de nouveaux :

struct ChatGoalView: View {
    @StateObject private var chatViewModel = ChatViewModel()
    
    var body: some View {
        ChatView(messages: chatViewModel.messages) { draft in
            chatViewModel.send(draft: draft)
        }
        // Si vous supprimez ceci, l’interface permettra d’envoyer des images, de l’audio, etc.
        // Vous pouvez décider de supporter ces entrées, mais il faudra gérer le message
        // dans le view model et envoyer le média au bon modèle/endpoint pour
        // l’analyser avec votre chatbot.
        .setAvailableInputs([AvailableInputType.text])
        .navigationTitle("Ton chatbot amusant")
        .navigationBarTitleDisplayMode(.inline)
    }
}

Intégrez ChatGoalView dans le point d’entrée de votre app (p. ex. ContentView).

5) Lancer l’app

Sélectionnez un simulateur et appuyez sur Cmd+R. Vous devriez pouvoir taper un message et recevoir une réponse.

Si vous souhaitez comparer avec un exemple fonctionnel, consultez le projet de référence : GitHub – GuillaumeBlanchet/chatbot.

Diffuser la réponse du chatbot (streaming)

streaming

Si vous voulez diffuser la réponse du chatbot en streaming, utilisez la méthode chatsStream du wrapper OpenAI fourni par la bibliothèque MacPaw avec le paramètre stream défini à true dans l’objet ChatQuery :

    let query = ChatQuery(
        // Le client OpenAI de MacPaw ne supporte pas encore gpt5_nano pour le streaming, on utilise donc gpt4_1_nano
        messages: chatMessages, model: .gpt4_1_nano,
        temperature: 0.7,
        stream: true
    )
    
    var streamText = ""
    
    for try await result in openAI.chatsStream(query: query) {
        // etc.

Consultez la version streaming du chatbot dans le projet de référence : GitHub – branche « streaming ».

À noter : la version streaming n’est pas encore prise en charge pour les modèles GPT‑5 dans la bibliothèque MacPaw. Voir l’issue correspondante : MacPaw/OpenAI #378 pour plus de détails et mises à jour.

Prochaines étapes

  • Personnaliser les prompts système pour modifier la personnalité de l’assistant ;
  • Ajouter des états d’erreur et des relances pour une UX plus robuste ;
  • Mettre en place un système RAG (Retrieval‑Augmented Generation) pour améliorer les réponses du chatbot avec la documentation de votre entreprise ;
  • Ajouter d’autres types d’entrée au chatbot (images, audio, etc.) ;
  • etc.