import {AlertTriangle, Book, BookOpen, Bot, Send, User} from 'lucide-react'; import React, {Dispatch, RefObject, SetStateAction, useContext, useEffect, useRef, useState,} from 'react'; import {Conversation, ConversationType, Message} from "@/lib/types/quillsense"; import {getSubLevel, isGeminiEnabled} from "@/lib/utils/quillsense"; import {ChapterContext, ChapterContextProps} from "@/context/ChapterContext"; import {BookContext, BookContextProps} from "@/context/BookContext"; import {AlertContext, AlertContextProps} from "@/context/AlertContext"; import {SessionContext, SessionContextProps} from '@/context/SessionContext'; import {apiGet, apiPost} from '@/lib/api/client'; import {useTranslations} from '@/lib/i18n'; import {LangContext, LangContextProps} from "@/context/LangContext"; import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext"; import Button from "@/components/ui/Button"; import IconButton from "@/components/ui/IconButton"; import IconContainer from "@/components/ui/IconContainer"; import LockCard from "@/components/ui/LockCard"; interface QuillConversationProps { disabled: boolean; selectedConversation: string; setSelectConversation: Dispatch>; } type ContextType = 'none' | 'chapter' | 'book'; export default function QuillConversation( { disabled, selectedConversation, setSelectConversation, }: QuillConversationProps): React.JSX.Element { const t = useTranslations(); const {lang}: LangContextProps = useContext(LangContext); const {session}: SessionContextProps = useContext(SessionContext); const {errorMessage}: AlertContextProps = useContext(AlertContext); const {book}: BookContextProps = useContext(BookContext); const {chapter}: ChapterContextProps = useContext(ChapterContext); const {setTotalPrice, setTotalCredits}: AIUsageContextProps = useContext(AIUsageContext) const [inputText, setInputText] = useState(''); const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [contextType, setContextType] = useState('none'); const [showContextAlert, setShowContextAlert] = useState(false); const [pendingContextType, setPendingContextType] = useState('none'); const messageContainerRef: RefObject = useRef(null); const textareaRef: RefObject = useRef(null); const [mode, setMode] = useState('chatbot'); const geminiEnabled: boolean = isGeminiEnabled(session); const isSubTierTwo: boolean = getSubLevel(session) >= 2; const hasAccess: boolean = geminiEnabled || isSubTierTwo; function adjustTextareaHeight(): void { const textarea: HTMLTextAreaElement | null = textareaRef.current; if (textarea) { textarea.style.height = 'auto'; const newHeight: number = Math.min(Math.max(textarea.scrollHeight, 42), 120); textarea.style.height = `${newHeight}px`; } } function scrollToBottom(): void { const messageContainer: HTMLDivElement | null = messageContainerRef.current; if (messageContainer) { messageContainer.scrollTop = messageContainer.scrollHeight; } } function LoadingMessage(): React.JSX.Element { return (
{t('quillConversation.loadingMessage')}
); } function WelcomeMessage(): React.JSX.Element { return (

{t('quillConversation.welcomeTitle')}

{t('quillConversation.welcomeDescription')}

{t('quillConversation.welcomeTip')}

); } function ContextAlert(): React.JSX.Element { const contextDescription: string = pendingContextType === 'chapter' ? t('quillConversation.contextAlert.chapter') : t('quillConversation.contextAlert.book'); return (

{t('quillConversation.contextAlert.title')}

{contextDescription}

); } function handleContextChange(type: ContextType): void { if (type === 'none') { setContextType('none'); } else { setPendingContextType(type); setShowContextAlert(true); } } useEffect((): void => { if (selectedConversation !== '' && hasAccess) { getMessages().then(); } }, []); useEffect((): void => { scrollToBottom(); }, [messages, isLoading]); useEffect((): void => { adjustTextareaHeight(); }, [inputText]); async function getMessages(): Promise { try { const response: Conversation = await apiGet( `quillsense/conversation`, session.accessToken, "fr", {id: selectedConversation}, ); if (response) { setMessages(response.messages); setMode(response.type ?? 'chatbot'); } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t('quillConversation.genericError')); } } } function getCurrentTime(): string { const now: Date = new Date(); return now.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); } async function handleSend(): Promise { if (!inputText.trim()) { errorMessage(t('quillConversation.emptyMessageError')); return; } try { const tempId: number = Date.now(); const newMessage: Message = { id: tempId, message: inputText, type: 'user', date: getCurrentTime(), }; setMessages((prevMessages: Message[]): Message[] => [...prevMessages, newMessage]); setInputText(''); setIsLoading(true); const response: Conversation = await apiPost('quillsense/chatbot/send', { message: inputText, bookId: book?.bookId || null, chapterId: chapter?.chapterId || null, conversationId: selectedConversation ?? '', mode: mode, contextType: contextType, version: chapter?.chapterContent.version || null, }, session.accessToken, lang); setIsLoading(false); if (response) { setMessages((prevMessages: Message[]): Message[] => { const userMessageFromServer: Message | undefined = response.messages.find( (msg: Message): boolean => msg.type === 'user', ); const aiMessageFromServer: Message | undefined = response.messages.find( (msg: Message): boolean => msg.type === 'model', ); const updatedMessages: Message[] = prevMessages.map( (msg: Message): Message => msg.id === tempId && userMessageFromServer ? { ...msg, id: userMessageFromServer.id, date: userMessageFromServer.date, } : msg, ); return aiMessageFromServer ? [...updatedMessages, aiMessageFromServer] : updatedMessages; }); if (response.useYourKey) { setTotalPrice((prevTotal: number): number => prevTotal + (response.totalPrice || 0)); } else { setTotalCredits(response.totalPrice || 0); } if (selectedConversation === '') { setSelectConversation(response.id); } } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t('quillConversation.genericError')); } setIsLoading(false); } } if (!hasAccess) { return (