Exemples pratiques

Écrit par Stanislas

Dernière mise à jour Il y a 7 mois


Des exemples complets et prêts à l'emploi pour intégrer l'API Swiftask Public Bot dans votre application. Apprenez à travers des implémentations pratiques qui combinent l'authentification, les sessions, la messagerie et les abonnements.

Présentation

Ce guide fournit cinq exemples complets et prêts à l'emploi qui illustrent différents modèles d'intégration. Chaque exemple s'appuie sur les guides précédents et montre comment combiner plusieurs fonctionnalités API dans des applications cohérentes. Vous pouvez copier ces exemples et les adapter à votre cas d'utilisation spécifique.

Les exemples vont d'une simple implémentation client complète à des hooks React, une API de composition Vue, des modèles de démarrage rapide plus simples et une gestion robuste des erreurs.


Prérequis

Avant d'utiliser ces exemples, assurez-vous de disposer des éléments suivants :

  1. Lu les guides fondamentaux — Comprendre Authentification , Sessions , Opérations de chat et Abonnements

  2. Apollo Client installénpm install @apollo/client graphql

  3. Votre jeton client — Dans la section Développeur de votre agent Swiftask

  4. Connaissances de base en JavaScript — Ces exemples utilisent async/await et JavaScript moderne

Exemple 1 : client de chat complet

Un client de chat complet qui gère l'authentification, la gestion des sessions, l'envoi de messages et le streaming en temps réel dans une seule classe.

import { ApolloClient, InMemoryCache, createHttpLink, gql, split } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';

class SwiftaskChatClient {
  constructor(clientToken) {
    this.clientToken = clientToken;
    this.clientUuid = this.getOrCreateClientUuid();
    this.apiUrl = 'https://graphql.swiftask.ai';
    this.config = null;
    this.client = null;
  }

  getOrCreateClientUuid() {
    let uuid = localStorage.getItem('swiftask_client_uuid');
    if (!uuid) {
      uuid = crypto.randomUUID();
      localStorage.setItem('swiftask_client_uuid', uuid);
    }
    return uuid;
  }

  async authenticate() {
    console.log('Authenticating...');

    const response = await fetch(`${this.apiUrl}/public/widget-bot/${this.clientToken}`, {
      method: 'GET',
      headers: {
        'x-client-uuid': this.clientUuid,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error?.message || 'Authentication failed');
    }

    const { data } = await response.json();
    this.config = data;

    console.log('✓ Authenticated successfully');
    this.setupApolloClient();

    return this.config;
  }

  setupApolloClient() {
    const httpLink = createHttpLink({
      uri: `${this.apiUrl}/graphql`,
    });

    const authLink = setContext((_, { headers }) => ({
      headers: {
        ...headers,
        authorization: `Bearer ${this.config.accessToken}`,
        'x-workspace-id': this.config.workspaceId,
        'x-client': 'widget',
      },
    }));

    const wsLink = new WebSocketLink({
      uri: 'wss://graphql.swiftask.ai/graphql',
      options: {
        reconnect: true,
        connectionParams: {
          authorization: `Bearer ${this.config.accessToken}`,
          workspaceId: this.config.workspaceId,
        },
      },
    });

    const splitLink = split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
      },
      wsLink,
      authLink.concat(httpLink)
    );

    this.client = new ApolloClient({
      link: splitLink,
      cache: new InMemoryCache(),
    });
  }

  async createSession(title = 'New Chat') {
    console.log('Creating session:', title);

    const CREATE_SESSION = gql`
      mutation CreateTodoChatSession($data: TodoChatSessionInput!) {
        createTodoChatSession(data: $data) {
          id
          title
          createdAt
          defaultBotId
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: CREATE_SESSION,
      variables: {
        data: {
          todoId: this.config.todoId,
          title,
        },
      },
    });

    console.log('✓ Session created:', data.createTodoChatSession.id);
    return data.createTodoChatSession;
  }

  async sendMessage(sessionId, message) {
    console.log('Sending message:', message);

    const SEND_MESSAGE = gql`
      mutation SendNewMessage($newMessageData: NewMessageInput!) {
        sendNewMessage(newMessageData: $newMessageData) {
          id
          message
          createdAt
          sessionId
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: SEND_MESSAGE,
      variables: {
        newMessageData: {
          message,
          sessionId,
          isForAiReply: true,
        },
      },
    });

    console.log('✓ Message sent:', data.sendNewMessage.id);
    return data.sendNewMessage;
  }

  subscribeToMessages(sessionId, onMessage) {
    console.log('Subscribing to messages for session:', sessionId);

    const NEW_MESSAGE_SUBSCRIPTION = gql`
      subscription NewMessage($sessionId: Float!) {
        newMessage(sessionId: $sessionId) {
          id
          message
          createdAt
          sentBy {
            firstName
          }
          isBotReply
        }
      }
    `;

    return this.client
      .subscribe({
        query: NEW_MESSAGE_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          console.log('New message:', data.newMessage.message);
          onMessage(data.newMessage);
        },
        error: (error) => {
          console.error('Subscription error:', error);
        },
        complete: () => {
          console.log('Subscription completed');
        },
      });
  }

  subscribeToStream(sessionId, onChunk, onComplete) {
    console.log('Subscribing to message stream for session:', sessionId);

    const STREAM_SUBSCRIPTION = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
          botResponseMessageId
          isStoppable
        }
      }
    `;

    let fullMessage = '';

    return this.client
      .subscribe({
        query: STREAM_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          fullMessage += data.onMessageStream.messageChunk;
          onChunk(fullMessage, data.onMessageStream);
        },
        error: (error) => {
          console.error('Stream error:', error);
        },
        complete: () => {
          console.log('Stream completed');
          if (onComplete) onComplete(fullMessage);
        },
      });
  }

  subscribeToToolCalls(sessionId, onToolCall) {
    const TOOL_SUBSCRIPTION = gql`
      subscription OnAgentToolCall($sessionId: Float!) {
        onAgentToolCall(sessionId: $sessionId) {
          tool
          status
          toolImage
        }
      }
    `;

    return this.client
      .subscribe({
        query: TOOL_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          onToolCall(data.onAgentToolCall);
        },
      });
  }

  async stopGeneration(sessionId, botId) {
    const STOP_MUTATION = gql`
      mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
        stopAIResponse(sessionId: $sessionId, botId: $botId)
      }
    `;

    await this.client.mutate({
      mutation: STOP_MUTATION,
      variables: { sessionId, botId },
    });
  }

  getClient() {
    return this.client;
  }

  getConfig() {
    return this.config;
  }
}

// Usage example
async function main() {
  const chat = new SwiftaskChatClient('your_client_token_here');

  try {
    // Step 1: Authenticate
    const config = await chat.authenticate();
    console.log('Bot slug:', config.botSlug);

    // Step 2: Create a new session
    const session = await chat.createSession('My First Chat');

    // Step 3: Subscribe to new messages
    const messageSub = chat.subscribeToMessages(session.id, (message) => {
      const sender = message.isBotReply ? '🤖 Bot' : '👤 You';
      console.log(`${sender}: ${message.message}`);
    });

    // Step 4: Subscribe to streaming
    const streamSub = chat.subscribeToStream(
      session.id,
      (fullMessage) => {
        console.log('Streaming:', fullMessage);
      },
      (finalMessage) => {
        console.log('✓ Response complete');
      }
    );

    // Step 5: Subscribe to tool calls
    const toolSub = chat.subscribeToToolCalls(session.id, (toolCall) => {
      if (toolCall.status === 'START') {
        console.log(`🔧 Using ${toolCall.tool}...`);
      } else if (toolCall.status === 'END') {
        console.log(`✓ ${toolCall.tool} completed`);
      }
    });

    // Step 6: Send a message
    await chat.sendMessage(session.id, 'Hello! What can you help me with?');

    // The response will come through the subscriptions

    // Cleanup when done
    setTimeout(() => {
      messageSub.unsubscribe();
      streamSub.unsubscribe();
      toolSub.unsubscribe();
    }, 30000); // Cleanup after 30 seconds for demo
  } catch (error) {
    console.error('Error:', error);
  }
}

// Run the example
main();

Exemple 2 : intégration du hook React

Utilisez un hook React personnalisé pour simplifier l'intégration de Swiftask dans les applications React.

import { useState, useEffect, useCallback, useRef } from 'react';
import { SwiftaskChatClient } from './SwiftaskChatClient';

export function useSwiftaskChat(clientToken) {
  const [client, setClient] = useState(null);
  const [session, setSession] = useState(null);
  const [messages, setMessages] = useState([]);
  const [streamingMessage, setStreamingMessage] = useState('');
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const subscriptionsRef = useRef([]);

  // Initialize client and create session
  useEffect(() => {
    const initClient = async () => {
      try {
        const chatClient = new SwiftaskChatClient(clientToken);
        await chatClient.authenticate();

        const newSession = await chatClient.createSession('Chat Session');

        setClient(chatClient);
        setSession(newSession);
        setIsLoading(false);
      } catch (err) {
        setError(err.message);
        setIsLoading(false);
      }
    };

    initClient();
  }, [clientToken]);

  // Setup subscriptions
  useEffect(() => {
    if (!client || !session) return;

    // Subscribe to new messages
    const messageSub = client.subscribeToMessages(session.id, (message) => {
      setMessages((prev) => [...prev, message]);
      setStreamingMessage('');
    });

    // Subscribe to streaming
    const streamSub = client.subscribeToStream(
      session.id,
      (fullMessage) => {
        setStreamingMessage(fullMessage);
      },
      () => {
        setStreamingMessage('');
      }
    );

    subscriptionsRef.current = [messageSub, streamSub];

    // Cleanup
    return () => {
      subscriptionsRef.current.forEach((sub) => sub.unsubscribe());
    };
  }, [client, session]);

  const sendMessage = useCallback(
    async (text) => {
      if (!client || !session) return;

      try {
        await client.sendMessage(session.id, text);
      } catch (err) {
        setError(err.message);
      }
    },
    [client, session]
  );

  return {
    messages,
    streamingMessage,
    sendMessage,
    isLoading,
    error,
    session,
  };
}

// React component using the hook
function ChatComponent() {
  const { messages, streamingMessage, sendMessage, isLoading, error } = useSwiftaskChat(
    'your_client_token'
  );
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (input.trim()) {
      sendMessage(input);
      setInput('');
    }
  };

  if (isLoading) return <div className="loading">Loading chat...</div>;

  if (error) return <div className="error">Error: {error}</div>;

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((msg) => (
          <div key={msg.id} className={`message ${msg.isBotReply ? 'bot' : 'user'}`}>
            <strong>{msg.isBotReply ? 'Bot' : 'You'}:</strong> {msg.message}
          </div>
        ))}
        {streamingMessage && <div className="message bot streaming">Bot: {streamingMessage}</div>}
      </div>

      <div className="input-area">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Type a message..."
          disabled={isLoading}
        />
        <button onClick={handleSend} disabled={isLoading}>
          Send
        </button>
      </div>
    </div>
  );
}

export default ChatComponent;

Exemple 3 : intégration de l'API de composition Vue

Intégrez Swiftask dans les applications Vue 3 à l'aide de l'API de composition.

import { ref, onMounted, onUnmounted } from 'vue';
import { SwiftaskChatClient } from './SwiftaskChatClient';

export function useSwiftaskChat(clientToken) {
  const client = ref(null);
  const session = ref(null);
  const messages = ref([]);
  const streamingMessage = ref('');
  const isLoading = ref(true);
  const error = ref(null);
  const subscriptions = [];

  const initialize = async () => {
    try {
      const chatClient = new SwiftaskChatClient(clientToken);
      await chatClient.authenticate();

      const newSession = await chatClient.createSession('Chat Session');

      client.value = chatClient;
      session.value = newSession;

      setupSubscriptions();

      isLoading.value = false;
    } catch (err) {
      error.value = err.message;
      isLoading.value = false;
    }
  };

  const setupSubscriptions = () => {
    const messageSub = client.value.subscribeToMessages(session.value.id, (message) => {
      messages.value.push(message);
      streamingMessage.value = '';
    });

    const streamSub = client.value.subscribeToStream(
      session.value.id,
      (fullMessage) => {
        streamingMessage.value = fullMessage;
      },
      () => {
        streamingMessage.value = '';
      }
    );

    subscriptions.push(messageSub, streamSub);
  };

  const sendMessage = async (text) => {
    if (!client.value || !session.value) return;
    try {
      await client.value.sendMessage(session.value.id, text);
    } catch (err) {
      error.value = err.message;
    }
  };

  onMounted(initialize);

  onUnmounted(() => {
    subscriptions.forEach((sub) => sub.unsubscribe());
  });

  return {
    messages,
    streamingMessage,
    sendMessage,
    isLoading,
    error,
  };
}

// Vue component using the composition function
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ChatComponent',
  setup() {
    const { messages, streamingMessage, sendMessage, isLoading, error } = useSwiftaskChat(
      'your_client_token'
    );

    const input = ref('');

    const handleSend = () => {
      if (input.value.trim()) {
        sendMessage(input.value);
        input.value = '';
      }
    };

    return {
      messages,
      streamingMessage,
      input,
      handleSend,
      isLoading,
      error,
    };
  },

  template: `
    <div class="chat-container" v-if="!isLoading">
      <div v-if="error" class="error">Error: {{ error }}</div>

      <div class="messages">
        <div v-for="msg in messages" :key="msg.id" :class="['message', msg.isBotReply ? 'bot' : 'user']">
          <strong>{{ msg.isBotReply ? 'Bot' : 'You' }}:</strong> {{ msg.message }}
        </div>
        <div v-if="streamingMessage" class="message bot streaming">
          Bot: {{ streamingMessage }}
        </div>
      </div>

      <div class="input-area">
        <input
          v-model="input"
          @keypress.enter="handleSend"
          placeholder="Type a message..."
          :disabled="isLoading"
        />
        <button @click="handleSend" :disabled="isLoading">Send</button>
      </div>
    </div>

    <div v-else class="loading">Loading chat...</div>
  `,
});

Exemple 4 : démarrage rapide avec une session de démarrage

Pour les cas d'utilisation simples, utilisez la session de démarrage créée automatiquement sans en créer une nouvelle.

class SimpleSwiftaskChat {
  constructor(clientToken) {
    this.clientToken = clientToken;
    this.clientUuid = crypto.randomUUID();
    this.client = null;
    this.sessionId = null;
  }

  async initialize() {
    // Step 1: Authenticate
    const response = await fetch(
      `https://graphql.swiftask.ai/public/widget-bot/${this.clientToken}`,
      {
        headers: { 'x-client-uuid': this.clientUuid },
      }
    );

    const { data } = await response.json();

    // Step 2: Setup GraphQL client
    const httpLink = createHttpLink({
      uri: 'https://graphql.swiftask.ai/graphql',
    });

    const authLink = setContext((_, { headers }) => ({
      headers: {
        ...headers,
        authorization: `Bearer ${data.accessToken}`,
        'x-workspace-id': data.workspaceId,
        'x-client': 'widget',
      },
    }));

    this.client = new ApolloClient({
      link: authLink.concat(httpLink),
      cache: new InMemoryCache(),
    });

    // Step 3: Use the starter session (no need to create)
    this.sessionId = data.starterSessionId;

    console.log('Ready to chat in session:', this.sessionId);
  }

  async sendMessage(messageText) {
    const SEND_MESSAGE = gql`
      mutation SendNewMessage($newMessageData: NewMessageInput!) {
        sendNewMessage(newMessageData: $newMessageData) {
          id
          message
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: SEND_MESSAGE,
      variables: {
        newMessageData: {
          message: messageText,
          sessionId: this.sessionId,
          isForAiReply: true,
        },
      },
    });

    return data.sendNewMessage;
  }

  subscribeToStream(onChunk) {
    const STREAM = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
        }
      }
    `;

    let fullMessage = '';

    return this.client
      .subscribe({
        query: STREAM,
        variables: { sessionId: this.sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          fullMessage += data.onMessageStream.messageChunk;
          onChunk(fullMessage);
        },
      });
  }
}

// Usage - much simpler!
async function main() {
  const chat = new SimpleSwiftaskChat('your_client_token');
  await chat.initialize();

  // Start chatting immediately
  await chat.sendMessage('Hello!');

  chat.subscribeToStream((fullMessage) => {
    console.log('Response:', fullMessage);
  });
}

Exemple 5 : gestion robuste des erreurs et logique de réessai

Implémentation prête pour la production avec gestion complète des erreurs et réessais automatiques.

class RobustSwiftaskChat {
  constructor(clientToken, options = {}) {
    this.clientToken = clientToken;
    this.clientUuid = crypto.randomUUID();
    this.client = null;
    this.sessionId = null;
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.onError = options.onError || console.error;
    this.onSuccess = options.onSuccess || console.log;
  }

  async retryOperation(operation, retries = this.maxRetries) {
    try {
      return await operation();
    } catch (error) {
      if (retries > 0 && this.isRetryableError(error)) {
        console.log(`Retrying... (${this.maxRetries - retries + 1}/${this.maxRetries})`);
        await this.sleep(this.retryDelay);
        return this.retryOperation(operation, retries - 1);
      }
      throw error;
    }
  }

  isRetryableError(error) {
    // Don't retry auth errors
    if (error.message?.includes('Unauthorized') || error.message?.includes('Authentication failed')) {
      return false;
    }
    // Retry network errors
    if (error instanceof TypeError || error.message?.includes('network')) {
      return true;
    }
    // Retry server errors (5xx)
    if (error.status >= 500) {
      return true;
    }
    return true;
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async initialize() {
    try {
      await this.retryOperation(async () => {
        const response = await fetch(
          `https://graphql.swiftask.ai/public/widget-bot/${this.clientToken}`,
          {
            headers: { 'x-client-uuid': this.clientUuid },
          }
        );

        if (!response.ok) {
          throw new Error(`Authentication failed: ${response.status}`);
        }

        const { data } = await response.json();

        // Setup client
        const httpLink = createHttpLink({
          uri: 'https://graphql.swiftask.ai/graphql',
        });

        const authLink = setContext((_, { headers }) => ({
          headers: {
            ...headers,
            authorization: `Bearer ${data.accessToken}`,
            'x-workspace-id': data.workspaceId,
            'x-client': 'widget',
          },
        }));

        this.client = new ApolloClient({
          link: authLink.concat(httpLink),
          cache: new InMemoryCache(),
        });

        this.sessionId = data.starterSessionId;
        this.onSuccess('Chat initialized successfully');
      });
    } catch (error) {
      this.onError('Failed to initialize chat:', error);
      throw error;
    }
  }

  async sendMessage(messageText) {
    try {
      return await this.retryOperation(async () => {
        const SEND_MESSAGE = gql`
          mutation SendNewMessage($newMessageData: NewMessageInput!) {
            sendNewMessage(newMessageData: $newMessageData) {
              id
              message
            }
          }
        `;

        const { data } = await this.client.mutate({
          mutation: SEND_MESSAGE,
          variables: {
            newMessageData: {
              message: messageText,
              sessionId: this.sessionId,
              isForAiReply: true,
            },
          },
        });

        return data.sendNewMessage;
      });
    } catch (error) {
      this.onError('Failed to send message:', error);
      throw error;
    }
  }

  subscribeToStreamWithErrorHandling(onChunk, onError) {
    const STREAM = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
        }
      }
    `;

    let fullMessage = '';
    let retryCount = 0;

    const subscribe = () => {
      return this.client
        .subscribe({
          query: STREAM,
          variables: { sessionId: this.sessionId },
        })
        .subscribe({
          next: ({ data }) => {
            fullMessage += data.onMessageStream.messageChunk;
            onChunk(fullMessage);
            retryCount = 0; // Reset retry count on success
          },
          error: (error) => {
            console.error('Stream error:', error);

            if (error.message?.includes('authorization')) {
              onError('Authentication expired. Please refresh.');
            } else if (error.message?.includes('network')) {
              retryCount++;
              if (retryCount < 3) {
                console.log(`Reconnecting... (${retryCount}/3)`);
                setTimeout(() => {
                  subscribe();
                }, 5000);
              } else {
                onError('Connection lost. Please try again.');
              }
            } else {
              onError('Stream error: ' + error.message);
            }
          },
          complete: () => {
            this.onSuccess('Stream completed');
          },
        });
    };

    return subscribe();
  }
}

// Usage with error handling
async function main() {
  const chat = new RobustSwiftaskChat('your_client_token', {
    maxRetries: 3,
    retryDelay: 1000,
    onError: (message, error) => {
      console.error(message, error);
      showErrorToUser(message);
    },
    onSuccess: (message) => {
      console.log(message);
    },
  });

  try {
    await chat.initialize();

    await chat.sendMessage('Hello!');

    chat.subscribeToStreamWithErrorHandling(
      (fullMessage) => {
        updateUI(fullMessage);
      },
      (errorMessage) => {
        showErrorToUser(errorMessage);
      }
    );
  } catch (error) {
    console.error('Fatal error:', error);
  }
}

function showErrorToUser(message) {
  // Show error in UI
  const errorElement = document.createElement('div');
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  document.body.appendChild(errorElement);
}

function updateUI(message) {
  const messageElement = document.getElementById('response');
  messageElement.textContent = message;
}

Modèles courants

Gestion de plusieurs messages

// Send multiple messages in sequence
async function sendMultipleMessages(chat, sessionId, messages) {
  for (const msg of messages) {
    await chat.sendMessage(sessionId, msg);
    await new Promise((resolve) => setTimeout(resolve, 500)); // Delay between messages
  }
}

// Usage
await sendMultipleMessages(chat, sessionId, [
  'What is your pricing?',
  'Do you offer discounts?',
  'Can I try for free?',
]);

Gestion de la persistance des sessions

// Save session ID to localStorage
function saveSession(sessionId) {
  localStorage.setItem('swiftask_session_id', sessionId);
}

// Retrieve session on page reload
function getPersistedSession() {
  return localStorage.getItem('swiftask_session_id');
}

// Usage
const persistedSessionId = getPersistedSession();
if (persistedSessionId) {
  // Continue with existing session
  await chat.sendMessage(persistedSessionId, 'Hello again!');
} else {
  // Create new session
  const session = await chat.createSession();
  saveSession(session.id);
}

Gestion de la validation des entrées utilisateur

function validateMessage(message) {
  if (!message || message.trim().length === 0) {
    throw new Error('Message cannot be empty');
  }

  if (message.length > 5000) {
    throw new Error('Message is too long (max 5000 characters)');
  }

  return message.trim();
}

// Usage
const handleSend = async (input) => {
  try {
    const validatedMessage = validateMessage(input);
    await chat.sendMessage(sessionId, validatedMessage);
  } catch (error) {
    showErrorToUser(error.message);
  }
};