create-automaticgpt-template
Version:
AutomaticGPT - A production-ready Expo template with AI chat, authentication, conversation management, analytics, and sharing features
196 lines (166 loc) • 5.44 kB
text/typescript
/**
* useConversation Hook
* Manages individual conversation state and messages
*/
import { useState, useEffect, useCallback } from 'react';
import { db, type Conversation, type Message } from '@/lib/supabase';
import { useAuth } from '@/features/auth/hooks/useAuth';
interface UseConversationReturn {
conversation: Conversation | null;
messages: Message[];
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
addMessage: (
content: string,
role: Message['role'],
metadata?: Record<string, any>
) => Promise<Message | null>;
updateMessage: (messageId: string, updates: Partial<Message>) => Promise<boolean>;
deleteMessage: (messageId: string) => Promise<boolean>;
generateTitle: () => Promise<string | null>;
}
export const useConversation = (conversationId: string | null): UseConversationReturn => {
const [conversation, setConversation] = useState<Conversation | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const { user } = useAuth();
const fetchConversation = useCallback(async () => {
if (!conversationId) {
setConversation(null);
setMessages([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Fetch conversation details and messages in parallel
const [conversationResult, messagesResult] = await Promise.all([
db.getConversation(conversationId),
db.getMessages(conversationId),
]);
if (conversationResult.error) {
throw new Error(conversationResult.error.message);
}
if (messagesResult.error) {
throw new Error(messagesResult.error.message);
}
setConversation(conversationResult.data);
setMessages(messagesResult.data || []);
} catch (err) {
console.error('Error fetching conversation:', err);
setError(err instanceof Error ? err : new Error('Failed to fetch conversation'));
} finally {
setLoading(false);
}
}, [conversationId]);
useEffect(() => {
fetchConversation();
}, [fetchConversation]);
const addMessage = useCallback(
async (
content: string,
role: Message['role'],
metadata: Record<string, any> = {}
): Promise<Message | null> => {
if (!conversationId) {
throw new Error('No conversation ID provided');
}
try {
const messageData = {
conversation_id: conversationId,
content,
role,
metadata,
};
const { data, error: createError } = await db.createMessage(messageData);
if (createError) {
throw new Error(createError.message);
}
if (data) {
// Add to local state
setMessages((prev) => [...prev, data]);
return data;
}
return null;
} catch (err) {
console.error('Error adding message:', err);
setError(err instanceof Error ? err : new Error('Failed to add message'));
return null;
}
},
[conversationId]
);
const updateMessage = useCallback(
async (messageId: string, updates: Partial<Message>): Promise<boolean> => {
try {
const { error: updateError } = await db.updateMessage(messageId, updates);
if (updateError) {
throw new Error(updateError.message);
}
// Update local state
setMessages((prev) =>
prev.map((msg) => (msg.id === messageId ? { ...msg, ...updates } : msg))
);
return true;
} catch (err) {
console.error('Error updating message:', err);
setError(err instanceof Error ? err : new Error('Failed to update message'));
return false;
}
},
[]
);
const deleteMessage = useCallback(async (messageId: string): Promise<boolean> => {
try {
const { error: deleteError } = await db.deleteMessage(messageId);
if (deleteError) {
throw new Error(deleteError.message);
}
// Remove from local state
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
return true;
} catch (err) {
console.error('Error deleting message:', err);
setError(err instanceof Error ? err : new Error('Failed to delete message'));
return false;
}
}, []);
const generateTitle = useCallback(async (): Promise<string | null> => {
if (!conversationId) {
return null;
}
try {
const { data, error: titleError } = await db.generateConversationTitle(conversationId);
if (titleError) {
throw new Error(titleError.message);
}
if (data && conversation) {
// Update the conversation title in local state
setConversation((prev) => (prev ? { ...prev, title: data } : null));
return data;
}
return null;
} catch (err) {
console.error('Error generating title:', err);
setError(err instanceof Error ? err : new Error('Failed to generate title'));
return null;
}
}, [conversationId, conversation]);
const refetch = useCallback(async () => {
await fetchConversation();
}, [fetchConversation]);
return {
conversation,
messages,
loading,
error,
refetch,
addMessage,
updateMessage,
deleteMessage,
generateTitle,
};
};