Farklı framework'ler için hazır kullanıma uygun text chat örnekleri
Özel bir React Hook ile text chat entegrasyonu:
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 Composition API ile text chat:
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>
*/Framework kullanmadan SSE streaming örneği:
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 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;
}
}