import { faBook, faBookOpen, faExclamationTriangle, faLock, faPaperPlane, faRobot, faUser } from '@fortawesome/free-solid-svg-icons'; import {Dispatch, RefObject, SetStateAction, useContext, useEffect, useRef, useState,} from 'react'; import QuillSense, {Conversation, ConversationType, Message} from "@/lib/models/QuillSense"; import {ChapterContext} from "@/context/ChapterContext"; import {BookContext} from "@/context/BookContext"; import {AlertContext} from "@/context/AlertContext"; import {SessionContext} from '@/context/SessionContext'; import System from "@/lib/models/System"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext"; interface QuillConversationProps { disabled: boolean; selectedConversation: string; setSelectConversation: Dispatch>; } type ContextType = 'none' | 'chapter' | 'book'; export default function QuillConversation( { disabled, selectedConversation, setSelectConversation, }: QuillConversationProps) { const t = useTranslations(); const {lang} = useContext(LangContext); const {session} = useContext(SessionContext); const {errorMessage} = useContext(AlertContext); const {book} = useContext(BookContext); const {chapter} = useContext(ChapterContext); const {setTotalPrice} = 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 isGeminiEnabled: boolean = QuillSense.isGeminiEnabled(session); const isSubTierTwo: boolean = QuillSense.getSubLevel(session) >= 2; const hasAccess: boolean = isGeminiEnabled || 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() { return (
{t('quillConversation.loadingMessage')}
); } function WelcomeMessage() { return (

{t('quillConversation.welcomeTitle')}

{t('quillConversation.welcomeDescription')}

{t('quillConversation.welcomeTip')}

); } function ContextAlert() { 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 System.authGetQueryToServer( `quillsense/conversation`, session.accessToken, "fr", {id: selectedConversation}, ); if (response) { setMessages(response.messages); setMode((response.type as ConversationType) ?? '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 System.authPostToServer('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; }); setTotalPrice((prevTotal: number): number => prevTotal + (response.totalPrice || 0)); if (selectedConversation === '') { setSelectConversation(response.id); } } } catch (e: unknown) { if (e instanceof Error) { errorMessage(t('quillConversation.sendError')); } else { errorMessage(t('quillConversation.genericError')); } setIsLoading(false); } } if (!hasAccess) { return (

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

{t('quillConversation.accessRequired.description')}