Hamdast1/src/app/components/PublicChatPage.tsx
2026-05-20 12:31:48 +03:30

299 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useRef, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { BottomNav } from "./BottomNav";
import { ChatHeader } from "./public-chat/ChatHeader";
import { ChatMessages, ChatMessage } from "./public-chat/ChatMessages";
import { ChatInput } from "./public-chat/ChatInput";
import { ChatHistoryModal } from "./public-chat/ChatHistoryModal";
import { AppBackground } from "./shared/AppBackground";
import {
loadChatList,
loadChat,
sendPublicChatMessage,
ChatListItem,
PublicChatMessage,
} from "../../services/publicChatService";
import { usePageTracking } from "../../hooks/usePageTracking";
import { backgroundImages } from "../../config/backgroundConfig";
export function PublicChatPage() {
const navigate = useNavigate();
usePageTracking("چت عمومی");
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [inputText, setInputText] = useState("");
const [showChatHistory, setShowChatHistory] = useState(false);
const [historyItems, setHistoryItems] = useState<ChatListItem[]>([]);
const [currentChatWorkflowID, setCurrentChatWorkflowID] = useState<string>("");
const [isLoading, setIsLoading] = useState(false);
const [isSending, setIsSending] = useState(false);
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
const messagesContainerRef = useRef<HTMLDivElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const isNearBottom = () => {
const container = messagesContainerRef.current;
if (!container) return true;
return (
container.scrollHeight - container.scrollTop - container.clientHeight < 100
);
};
const scrollToBottom = (smooth = true) => {
messagesEndRef.current?.scrollIntoView({
behavior: smooth ? "smooth" : "auto",
block: "end",
});
};
useEffect(() => {
if (shouldAutoScroll) {
scrollToBottom();
}
}, [messages, shouldAutoScroll]);
useEffect(() => {
const container = messagesContainerRef.current;
if (!container) return;
const handleScroll = () => {
setShouldAutoScroll(isNearBottom());
};
container.addEventListener("scroll", handleScroll, { passive: true });
return () => container.removeEventListener("scroll", handleScroll);
}, []);
const convertPublicChatMessagesToChatMessages = (
publicMessages: PublicChatMessage[],
): ChatMessage[] => {
const converted: ChatMessage[] = [];
publicMessages.forEach((msg) => {
if (msg.question) {
converted.push({
id: crypto.randomUUID(),
type: "user",
content: msg.question,
timestamp: msg.datetime1,
});
}
if (msg.answer) {
converted.push({
id: crypto.randomUUID(),
type: "other",
content: msg.answer,
author: "ربات",
timestamp: msg.datetime1,
});
}
});
return converted;
};
const handleSelectChat = async (chatId: string) => {
setShowChatHistory(false);
setIsLoading(true);
setCurrentChatWorkflowID(chatId);
const result = await loadChat(chatId);
if (result.success) {
const convertedMessages = convertPublicChatMessagesToChatMessages(result.data);
setMessages(convertedMessages);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
scrollToBottom(false);
});
});
} else {
console.error("Failed to load chat:", result.message);
alert("خطا در بارگذاری چت");
}
setIsLoading(false);
};
const handleSendMessage = async () => {
const trimmedText = inputText.trim();
if (!trimmedText || isSending) return;
const userMessage: ChatMessage = {
id: crypto.randomUUID(),
type: "user",
content: trimmedText,
timestamp: new Date().toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
}),
};
const loadingMessageId = crypto.randomUUID();
const loadingMessage: ChatMessage = {
id: loadingMessageId,
type: "loading",
content: "",
author: "ربات",
timestamp: "",
};
setShouldAutoScroll(true);
setMessages((prev) => [...prev, userMessage, loadingMessage]);
setInputText("");
setIsSending(true);
if (inputRef.current) {
inputRef.current.style.height = "auto";
}
const result = await sendPublicChatMessage(trimmedText, currentChatWorkflowID);
setMessages((prev) => prev.filter((msg) => msg.id !== loadingMessageId));
if (result.success && result.answer) {
if (result.newChatlistWorkflowID) {
setCurrentChatWorkflowID(result.newChatlistWorkflowID);
}
const botMessage: ChatMessage = {
id: crypto.randomUUID(),
type: "other",
content: result.answer,
author: "ربات",
timestamp: new Date().toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
}),
isTyping: true,
};
setMessages((prev) => [...prev, botMessage]);
} else {
alert(result.message || "خطا در ارسال پیام");
}
setIsSending(false);
inputRef.current?.focus();
};
const handleHistoryClick = async () => {
setShowChatHistory(true);
const result = await loadChatList();
if (result.success) {
setHistoryItems(result.data);
} else {
console.error("Failed to load chat list:", result.message);
alert(result.message || "خطا در بارگذاری تاریخچه");
}
};
const handleNewChat = () => {
setMessages([]);
setCurrentChatWorkflowID("");
setShouldAutoScroll(true);
requestAnimationFrame(() => {
inputRef.current?.focus();
});
};
return (
<div className="relative h-[100dvh] w-full overflow-hidden bg-black">
<AppBackground position="fixed" zIndex={0} imageUrl={backgroundImages.publicChat} />
<div className="relative z-10 mx-auto grid h-full w-full max-w-md grid-rows-[auto_minmax(0,1fr)_auto]">
<div className="shrink-0">
<ChatHeader onBack={() => navigate("/")} />
</div>
<main className="relative min-h-0 overflow-hidden">
<button
onClick={handleHistoryClick}
className="absolute left-4 top-3 z-20 px-3 py-1.5 rounded-full text-xs text-white font-bold"
style={{
background: "linear-gradient(135deg, rgba(138, 206, 224, 0.9) 0%, rgba(76, 127, 137, 0.9) 100%)",
border: "1px solid rgba(208, 240, 255, 0.6)",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.25)",
}}
>
تاریخچه
</button>
{isLoading ? (
<div className="flex h-full items-center justify-center px-5">
<p className="text-sm text-white">در حال بارگذاری...</p>
</div>
) : messages.length === 0 ? (
<div className="flex h-full items-center justify-center px-5">
<div className="max-w-sm rounded-2xl border-2 border-purple-400/50 bg-gradient-to-br from-purple-500/20 to-pink-500/20 p-5 shadow-2xl backdrop-blur-md">
<div className="text-center">
<div className="mb-2 text-3xl">🤖</div>
<p className="mb-1.5 text-base font-bold text-white">
با ربات همدست چت کن!
</p>
<p className="text-xs text-purple-200">
سوالاتت رو بپرس و جواب بگیر
</p>
</div>
</div>
</div>
) : (
<div className="h-full min-h-0">
<ChatMessages
messages={messages}
containerRef={messagesContainerRef}
endRef={messagesEndRef}
onTyping={() => {
if (shouldAutoScroll) {
scrollToBottom(false);
}
}}
/>
</div>
)}
</main>
<footer
className="shrink-0 border-white/10 "
style={{
paddingBottom: "calc(env(safe-area-inset-bottom, 0px) + 8px)",
}}
>
<div className="px-3 pt-2">
<ChatInput
inputText={inputText}
onInputChange={setInputText}
onSend={handleSendMessage}
onNewChat={handleNewChat}
inputRef={inputRef}
/>
</div>
<div className="px-2 pt-6">
<BottomNav />
</div>
</footer>
</div>
<ChatHistoryModal
isOpen={showChatHistory}
onClose={() => setShowChatHistory(false)}
historyItems={historyItems.map((item) => ({
id: item.chatlist_workflowID,
title: item.title || "چت عمومی",
date: item.datetime1,
lastMessage: "",
}))}
onSelectChat={handleSelectChat}
/>
</div>
);
}