236 lines
7.5 KiB
TypeScript
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>
|
|
);
|
|
};
|