Files
OPEN_CODEX_API/src/components/chat/ChatView.tsx
T
2025-10-22 12:52:23 -03:00

236 lines
7.5 KiB
TypeScript

import { useState, useEffect } from "react";
import { ChatHeader } from "@/components/ChatHeader";
import { ChatMessage } from "./ChatMessage";
import { ChatInput } from "./ChatInput";
import { ChatSidebar } from "./ChatSidebar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { chatService, StoredChat } from "@/services/chat";
import { useToast } from "@/hooks/use-toast";
import { getModelId } from "@/config/models";
interface Message {
id: string;
role: "user" | "assistant";
content: string;
model?: string;
attachments?: Array<{
name: string;
type: string;
size: number;
}>;
}
export const ChatView = () => {
const { toast } = useToast();
const [isChatSidebarCollapsed, setIsChatSidebarCollapsed] = useState(false);
const [selectedModel, setSelectedModel] = useState("GPT-4o");
// Inicia com "0" - será atualizado com o chat_id real após primeira resposta da API
const [currentChatId, setCurrentChatId] = useState("0");
const [systemPrompt, setSystemPrompt] = useState("Você é um assistente útil e prestativo.");
const [messages, setMessages] = useState<Message[]>([
{
id: "1",
role: "assistant",
content: "Olá! Sou o assistente HGTX Codex. Como posso ajudá-lo hoje?",
model: "GPT-4o",
},
]);
const [isLoading, setIsLoading] = useState(false);
// Salva o chat no localStorage sempre que as mensagens mudam
useEffect(() => {
if (messages.length > 1) { // Salva apenas se houver mensagens além da inicial
saveCurrentChat();
// Dispara evento customizado para a sidebar recarregar
window.dispatchEvent(new Event('chatUpdated'));
}
}, [messages]);
// Função para salvar o chat atual
const saveCurrentChat = () => {
try {
const chatTitle = chatService.generateChatTitle(
messages.find(m => m.role === 'user')?.content || 'Nova Conversa'
);
const storedChat: StoredChat = {
id: currentChatId,
title: chatTitle,
createdAt: new Date(),
updatedAt: new Date(),
model: selectedModel,
systemPrompt: systemPrompt,
messages: messages.map(msg => ({
...msg,
timestamp: new Date(),
})),
};
chatService.saveChat(storedChat);
} catch (error) {
console.error('Erro ao salvar chat:', error);
}
};
const handleNewChat = () => {
// Reseta para "0" - novo chat sempre começa com chat_id "0"
setCurrentChatId("0");
setMessages([
{
id: "1",
role: "assistant",
content: "Olá! Sou o assistente HGTX Codex. Como posso ajudá-lo hoje?",
model: selectedModel,
},
]);
// Dispara evento para atualizar a sidebar
window.dispatchEvent(new Event('chatUpdated'));
};
const handleLoadChat = (chat: StoredChat) => {
// Carrega um chat existente do histórico
setCurrentChatId(chat.id);
setSelectedModel(chat.model);
setSystemPrompt(chat.systemPrompt);
// Converte mensagens do StoredChat para Message
const loadedMessages: Message[] = chat.messages.map(msg => ({
id: msg.id,
role: msg.role,
content: msg.content,
model: msg.model,
attachments: msg.attachments,
}));
setMessages(loadedMessages);
};
const handleSendMessage = async (content: string, files?: File[]) => {
// Adiciona mensagem do usuário na interface
const newMessage: Message = {
id: Date.now().toString(),
role: "user",
content,
attachments: files?.map((file) => ({
name: file.name,
type: file.type,
size: file.size,
})),
};
setMessages((prev) => [...prev, newMessage]);
setIsLoading(true);
try {
// Envia mensagem para a API
// Converte o nome do modelo para o ID da API
const modelId = getModelId(selectedModel);
const response = await chatService.sendMessage({
chatId: currentChatId,
message: content,
modelId: modelId,
personalidade: systemPrompt,
anexos: files,
});
// Adiciona resposta da IA na interface
if (response.success) {
// IMPORTANTE: Atualiza o chat_id com o valor retornado pela API
// Isso mantém o contexto da conversa para as próximas mensagens
if (response.chat_id && response.chat_id !== currentChatId) {
console.log(`Chat ID atualizado: ${currentChatId}${response.chat_id}`);
setCurrentChatId(response.chat_id);
}
const aiResponse: Message = {
id: (Date.now() + 1).toString(),
role: "assistant",
content: response.response,
model: selectedModel,
};
setMessages((prev) => [...prev, aiResponse]);
} else {
throw new Error('Resposta inválida da API');
}
} catch (error: any) {
console.error('Erro ao enviar mensagem:', error);
// Mostra toast de erro para o usuário
toast({
title: "Erro ao enviar mensagem",
description: error.message || "Não foi possível comunicar com a IA. Tente novamente.",
variant: "destructive",
});
// Adiciona mensagem de erro na interface
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
role: "assistant",
content: "Desculpe, ocorreu um erro ao processar sua mensagem. Por favor, tente novamente.",
model: selectedModel,
};
setMessages((prev) => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex h-full w-full pb-16 md:pb-0">
{/* Chat Sidebar - Hidden on mobile */}
<div className="hidden md:block">
<ChatSidebar
isCollapsed={isChatSidebarCollapsed}
onToggleCollapse={() => setIsChatSidebarCollapsed(!isChatSidebarCollapsed)}
onNewChat={handleNewChat}
onSelectChat={handleLoadChat}
currentChatId={currentChatId}
/>
</div>
{/* Main Chat Area */}
<div className="flex-1 flex flex-col w-full">
<ChatHeader
showModelSelector={true}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
systemPrompt={systemPrompt}
onSystemPromptChange={setSystemPrompt}
/>
<ScrollArea className="flex-1">
<div className="max-w-4xl mx-auto px-2 md:px-4">
{messages.map((message) => (
<ChatMessage
key={message.id}
role={message.role}
content={message.content}
model={message.model}
attachments={message.attachments}
/>
))}
{isLoading && (
<div className="flex gap-4 py-6 px-6 bg-muted/30">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center">
<div className="w-5 h-5 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin" />
</div>
<div className="flex gap-1 items-center">
<div className="w-2 h-2 bg-primary rounded-full animate-bounce" />
<div className="w-2 h-2 bg-primary rounded-full animate-bounce delay-100" />
<div className="w-2 h-2 bg-primary rounded-full animate-bounce delay-200" />
</div>
</div>
)}
</div>
</ScrollArea>
<ChatInput onSendMessage={handleSendMessage} />
</div>
</div>
);
};