Kod Örnekleri

Farklı framework'ler için hazır kullanıma uygun text chat örnekleri

React Hook

Özel bir React Hook ile text chat entegrasyonu:

hooks/useTextChat.ts
import { useState, useCallback, useEffect } from 'react';

interface Message {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  timestamp: Date;
}

interface UseTextChatOptions {
  apiKey: string;
  assistantId: string;
  onError?: (error: Error) => void;
}

export function useTextChat({
  apiKey,
  assistantId,
  onError
}: UseTextChatOptions) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [isConnected, setIsConnected] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const [chatId, setChatId] = useState<string | null>(null);
  const [error, setError] = useState<Error | null>(null);

  const API_BASE = 'https://api.wespoke.ai/api/v1/embed';

  // Sohbet oturumu başlat
  useEffect(() => {
    async function initChat() {
      try {
        const response = await fetch(`${API_BASE}/chat`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            assistantId,
            metadata: {
              userId: 'user-' + Date.now()
            }
          })
        });

        const data = await response.json();
        if (data.success) {
          setChatId(data.data.chatId);
          setIsConnected(true);
        } else {
          throw new Error(data.error.message);
        }
      } catch (err) {
        const error = err instanceof Error ? err : new Error('Failed to connect');
        setError(error);
        onError?.(error);
      }
    }

    initChat();
  }, [apiKey, assistantId, onError]);

  // Mesaj gönder
  const sendMessage = useCallback(async (content: string) => {
    if (!chatId || !content.trim() || isStreaming) return;

    const userMessage: Message = {
      id: 'msg-' + Date.now(),
      role: 'user',
      content: content.trim(),
      timestamp: new Date()
    };
    setMessages(prev => [...prev, userMessage]);
    setIsStreaming(true);

    try {
      const response = await fetch(`${API_BASE}/chat/${chatId}/messages`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content: content.trim() })
      });

      // SSE streaming için EventSource kullanılmalı
      // Bu basitleştirilmiş bir örnektir

      if (!response.ok) {
        throw new Error('Failed to send message');
      }
    } catch (err) {
      const error = err instanceof Error ? err : new Error('Failed to send message');
      setError(error);
      onError?.(error);
    } finally {
      setIsStreaming(false);
    }
  }, [chatId, apiKey, isStreaming, onError]);

  // Sohbeti sonlandır
  const endChat = useCallback(async () => {
    if (!chatId) return;

    try {
      const response = await fetch(`${API_BASE}/chat/${chatId}/end`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`
        }
      });

      const data = await response.json();
      if (data.success) {
        setIsConnected(false);
        console.log('Sohbet sonlandı. Maliyet:', data.data.cost);
      }
    } catch (err) {
      const error = err instanceof Error ? err : new Error('Failed to end chat');
      setError(error);
      onError?.(error);
    }
  }, [chatId, apiKey, onError]);

  return {
    messages,
    isConnected,
    isStreaming,
    chatId,
    sendMessage,
    endChat,
    error
  };
}

// Kullanım
function ChatComponent() {
  const {
    messages,
    isConnected,
    isStreaming,
    sendMessage,
    endChat
  } = useTextChat({
    apiKey: process.env.NEXT_PUBLIC_WESPOKE_API_KEY!,
    assistantId: 'asst_xyz789',
    onError: (error) => console.error('Chat error:', error)
  });

  const [input, setInput] = useState('');

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

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4 mb-4">
        {messages.map((msg) => (
          <div
            key={msg.id}
            className={`p-4 rounded-lg ${
              msg.role === 'user'
                ? 'bg-blue-100 ml-auto max-w-[80%]'
                : 'bg-gray-100 mr-auto max-w-[80%]'
            }`}
          >
            <strong className="text-sm">
              {msg.role === 'user' ? 'Siz' : 'Asistan'}:
            </strong>
            <p className="mt-1">{msg.content}</p>
          </div>
        ))}
      </div>

      <div className="flex gap-2">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          disabled={!isConnected || isStreaming}
          placeholder="Mesajınızı yazın..."
          className="flex-1 px-4 py-2 border rounded-lg"
        />
        <button
          onClick={handleSend}
          disabled={!isConnected || isStreaming}
          className="px-6 py-2 bg-blue-500 text-white rounded-lg disabled:bg-gray-300"
        >
          Gönder
        </button>
        <button
          onClick={endChat}
          disabled={!isConnected}
          className="px-6 py-2 bg-red-500 text-white rounded-lg disabled:bg-gray-300"
        >
          Bitir
        </button>
      </div>
    </div>
  );
}

Vue 3 Composable

Vue 3 Composition API ile text chat:

composables/useTextChat.ts
import { ref, onMounted, onUnmounted } from 'vue';

interface Message {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  timestamp: Date;
}

export function useTextChat(apiKey: string, assistantId: string) {
  const messages = ref<Message[]>([]);
  const isConnected = ref(false);
  const isStreaming = ref(false);
  const chatId = ref<string | null>(null);
  const error = ref<Error | null>(null);

  const API_BASE = 'https://api.wespoke.ai/api/v1/embed';

  async function initChat() {
    try {
      const response = await fetch(`${API_BASE}/chat`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          assistantId,
          metadata: {
            userId: 'user-' + Date.now()
          }
        })
      });

      const data = await response.json();
      if (data.success) {
        chatId.value = data.data.chatId;
        isConnected.value = true;
      } else {
        throw new Error(data.error.message);
      }
    } catch (err) {
      error.value = err instanceof Error ? err : new Error('Failed to connect');
    }
  }

  async function sendMessage(content: string) {
    if (!chatId.value || !content.trim() || isStreaming.value) return;

    const userMessage: Message = {
      id: 'msg-' + Date.now(),
      role: 'user',
      content: content.trim(),
      timestamp: new Date()
    };
    messages.value.push(userMessage);
    isStreaming.value = true;

    try {
      const response = await fetch(`${API_BASE}/chat/${chatId.value}/messages`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content: content.trim() })
      });

      if (!response.ok) {
        throw new Error('Failed to send message');
      }
    } catch (err) {
      error.value = err instanceof Error ? err : new Error('Failed to send');
    } finally {
      isStreaming.value = false;
    }
  }

  async function endChat() {
    if (!chatId.value) return;

    try {
      const response = await fetch(`${API_BASE}/chat/${chatId.value}/end`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`
        }
      });

      const data = await response.json();
      if (data.success) {
        isConnected.value = false;
        console.log('Sohbet sonlandı. Maliyet:', data.data.cost);
      }
    } catch (err) {
      error.value = err instanceof Error ? err : new Error('Failed to end');
    }
  }

  onMounted(() => {
    initChat();
  });

  onUnmounted(() => {
    if (isConnected.value) {
      endChat();
    }
  });

  return {
    messages,
    isConnected,
    isStreaming,
    chatId,
    error,
    sendMessage,
    endChat
  };
}

// Kullanım - ChatComponent.vue
/*
<template>
  <div class="chat-container">
    <div class="messages">
      <div
        v-for="msg in messages"
        :key="msg.id"
        :class="['message', msg.role]"
      >
        <strong>{{ msg.role === 'user' ? 'Siz' : 'Asistan' }}:</strong>
        <p>{{ msg.content }}</p>
      </div>
    </div>

    <div class="input-area">
      <input
        v-model="input"
        @keypress.enter="handleSend"
        :disabled="!isConnected || isStreaming"
        placeholder="Mesajınızı yazın..."
      />
      <button
        @click="handleSend"
        :disabled="!isConnected || isStreaming"
      >
        Gönder
      </button>
      <button
        @click="endChat"
        :disabled="!isConnected"
      >
        Bitir
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { useTextChat } from '@/composables/useTextChat';

const input = ref('');

const {
  messages,
  isConnected,
  isStreaming,
  sendMessage,
  endChat
} = useTextChat(
  'pk_live_abc123...',
  'asst_xyz789'
);

function handleSend() {
  if (input.value.trim()) {
    sendMessage(input.value);
    input.value = '';
  }
}
</script>
*/

Vanilla JavaScript + EventSource

Framework kullanmadan SSE streaming örneği:

chat.js - SSE Streaming
class WespokeTextChat {
  constructor(apiKey, assistantId) {
    this.apiKey = apiKey;
    this.assistantId = assistantId;
    this.chatId = null;
    this.isConnected = false;
    this.baseUrl = 'https://api.wespoke.ai/api/v1/embed';
  }

  async init() {
    try {
      const response = await fetch(`${this.baseUrl}/chat`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          assistantId: this.assistantId,
          metadata: {
            userId: 'user-' + Date.now()
          }
        })
      });

      const data = await response.json();
      if (data.success) {
        this.chatId = data.data.chatId;
        this.isConnected = true;
        return data.data;
      } else {
        throw new Error(data.error.message);
      }
    } catch (error) {
      console.error('Init error:', error);
      throw error;
    }
  }

  async sendMessage(content, callbacks = {}) {
    if (!this.chatId || !content.trim()) {
      throw new Error('Chat not initialized or empty message');
    }

    // Not: Gerçek SSE implementasyonu için EventSource kullanılmalı
    // Bu örnek basitleştirilmiştir

    try {
      const response = await fetch(`${this.baseUrl}/chat/${this.chatId}/messages`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content: content.trim() })
      });

      if (!response.ok) {
        throw new Error('Failed to send message');
      }

      // SSE eventlerini dinle
      // callbacks.onMessageStart?.()
      // callbacks.onMessageChunk?.(chunk)
      // callbacks.onMessageComplete?.(message)
      // callbacks.onToolStarted?.(tool)
      // callbacks.onDone?.()

    } catch (error) {
      console.error('Send message error:', error);
      callbacks.onError?.(error);
      throw error;
    }
  }

  async getMessages(limit = 100, offset = 0) {
    if (!this.chatId) {
      throw new Error('Chat not initialized');
    }

    try {
      const response = await fetch(
        `${this.baseUrl}/chat/${this.chatId}/messages?limit=${limit}&offset=${offset}`,
        {
          headers: {
            'Authorization': `Bearer ${this.apiKey}`
          }
        }
      );

      const data = await response.json();
      if (data.success) {
        return data.data.messages;
      } else {
        throw new Error(data.error.message);
      }
    } catch (error) {
      console.error('Get messages error:', error);
      throw error;
    }
  }

  async getState() {
    if (!this.chatId) {
      throw new Error('Chat not initialized');
    }

    try {
      const response = await fetch(
        `${this.baseUrl}/chat/${this.chatId}/state`,
        {
          headers: {
            'Authorization': `Bearer ${this.apiKey}`
          }
        }
      );

      const data = await response.json();
      if (data.success) {
        return data.data;
      } else {
        throw new Error(data.error.message);
      }
    } catch (error) {
      console.error('Get state error:', error);
      throw error;
    }
  }

  async end() {
    if (!this.chatId) {
      throw new Error('Chat not initialized');
    }

    try {
      const response = await fetch(
        `${this.baseUrl}/chat/${this.chatId}/end`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${this.apiKey}`
          }
        }
      );

      const data = await response.json();
      if (data.success) {
        this.isConnected = false;
        return data.data;
      } else {
        throw new Error(data.error.message);
      }
    } catch (error) {
      console.error('End chat error:', error);
      throw error;
    }
  }
}

// Kullanım
const chat = new WespokeTextChat('pk_live_abc123...', 'asst_xyz789');

// Sohbet başlat
await chat.init();
console.log('Chat başladı:', chat.chatId);

// Mesaj gönder
await chat.sendMessage('Merhaba', {
  onMessageStart: () => console.log('Asistan yanıt veriyor...'),
  onMessageChunk: (chunk) => console.log('Chunk:', chunk.content),
  onMessageComplete: (msg) => console.log('Tam mesaj:', msg.content),
  onError: (error) => console.error('Hata:', error)
});

// Durum kontrol et
const state = await chat.getState();
console.log('Maliyet tahmini:', state.estimatedCost?.amount);

// Sohbeti bitir
const result = await chat.end();
console.log('Final maliyet:', result.cost);

Hata Yönetimi En İyi Uygulamaları

Hata Yönetimi
// Hata tipleri
type ErrorCode =
  | 'INVALID_API_KEY'
  | 'INSUFFICIENT_CREDITS'
  | 'DOMAIN_NOT_WHITELISTED'
  | 'MISSING_ASSISTANT_ID'
  | 'ASSISTANT_NOT_FOUND'
  | 'NO_PUBLISHED_VERSION'
  | 'MISSING_CONTENT'
  | 'CHAT_NOT_FOUND'
  | 'CHAT_ENDED'
  | 'CHAT_ALREADY_ENDED'
  | 'STREAM_ERROR';

interface WespokeError {
  code: ErrorCode;
  message: string;
  retryable?: boolean;
}

// Kullanıcı dostu hata mesajları
function getUserFriendlyErrorMessage(errorCode: ErrorCode): string {
  const messages: Record<ErrorCode, string> = {
    'INVALID_API_KEY': 'API anahtarı geçersiz. Lütfen kontrol edin.',
    'INSUFFICIENT_CREDITS': 'Yetersiz kredi. Lütfen kredi yükleyin.',
    'DOMAIN_NOT_WHITELISTED': 'Bu domain izin listesinde değil.',
    'MISSING_ASSISTANT_ID': 'Asistan ID eksik.',
    'ASSISTANT_NOT_FOUND': 'Asistan bulunamadı.',
    'NO_PUBLISHED_VERSION': 'Asistanın yayınlanmış versiyonu yok.',
    'MISSING_CONTENT': 'Mesaj içeriği boş olamaz.',
    'CHAT_NOT_FOUND': 'Sohbet oturumu bulunamadı.',
    'CHAT_ENDED': 'Sohbet oturumu sonlandırılmış.',
    'CHAT_ALREADY_ENDED': 'Sohbet zaten sonlandırılmış.',
    'STREAM_ERROR': 'Bağlantı hatası. Lütfen tekrar deneyin.'
  };

  return messages[errorCode] || 'Bir hata oluştu. Lütfen tekrar deneyin.';
}

// Hata yönetimi wrapper
async function handleApiCall<T>(
  apiCall: () => Promise<Response>
): Promise<T> {
  try {
    const response = await apiCall();
    const data = await response.json();

    if (!data.success) {
      const errorMessage = getUserFriendlyErrorMessage(data.error.code);
      throw new Error(errorMessage);
    }

    return data.data;
  } catch (error) {
    // Network hatası
    if (error instanceof TypeError) {
      throw new Error('Bağlantı hatası. İnternet bağlantınızı kontrol edin.');
    }

    throw error;
  }
}