Spaces:
Sleeping
Sleeping
| import ReactMarkdown from 'react-markdown'; | |
| import remarkMath from 'remark-math'; | |
| import rehypeKatex from 'rehype-katex'; | |
| import rehypeRaw from 'rehype-raw'; | |
| import { getChatMarkdownComponents, getTitleMarkdownComponents } from '../utils/markdownComponents.jsx'; | |
| import SimpleChat from './SimpleChat.jsx'; | |
| import ChunkLoadingTips from './ChunkLoadingTips.jsx'; | |
| import React, { useState, useEffect } from 'react'; | |
| const ChunkPanel = ({ | |
| documentData, | |
| currentChunkIndex, | |
| showChat, | |
| isTransitioning, | |
| updateGlobalChatHistory, | |
| getGlobalChatHistory, | |
| addMessageToChunk, | |
| getCurrentChunkMessages, | |
| hasChunkMessages, | |
| isChunkCompleted, | |
| canEditChunk, | |
| setWaitingForFirstResponse, | |
| markChunkUnderstood, | |
| skipChunk, | |
| goToPrevChunk | |
| }) => { | |
| const chatMarkdownComponents = getChatMarkdownComponents(); | |
| const titleMarkdownComponents = getTitleMarkdownComponents(); | |
| const [isLoading, setIsLoading] = useState(false); | |
| // Generate greeting for chunks that don't have messages yet | |
| // Only for initial chunk (0) and when not transitioning | |
| useEffect(() => { | |
| if (documentData && showChat && !hasChunkMessages(currentChunkIndex) && currentChunkIndex === 0 && !isTransitioning) { | |
| generateGreeting(); | |
| } | |
| }, [currentChunkIndex, documentData, showChat, isTransitioning]); | |
| const generateGreeting = async () => { | |
| setIsLoading(true); | |
| if (setWaitingForFirstResponse) { | |
| setWaitingForFirstResponse(true); | |
| } | |
| try { | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| messages: [], | |
| currentChunk: documentData?.chunks?.[currentChunkIndex]?.text || '', | |
| document: documentData ? JSON.stringify(documentData) : '' | |
| }) | |
| }); | |
| const data = await response.json(); | |
| addMessageToChunk( | |
| { | |
| role: 'assistant', | |
| content: data.content || 'Hi! Welcome to your learning session. Let\'s explore this document together!' | |
| }, | |
| currentChunkIndex | |
| ); | |
| } catch (error) { | |
| console.error('Error generating greeting:', error); | |
| addMessageToChunk( | |
| { | |
| role: 'assistant', | |
| content: 'Hi! Welcome to your learning session. Let\'s explore this document together!' | |
| }, | |
| currentChunkIndex | |
| ); | |
| } finally { | |
| setIsLoading(false); | |
| if (setWaitingForFirstResponse) { | |
| setWaitingForFirstResponse(false); | |
| } | |
| } | |
| }; | |
| const handleSend = async (text) => { | |
| const userMessage = { role: 'user', content: text, chunkIndex: currentChunkIndex }; | |
| addMessageToChunk(userMessage, currentChunkIndex); | |
| setIsLoading(true); | |
| try { | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| messages: getGlobalChatHistory(), | |
| currentChunk: documentData?.chunks?.[currentChunkIndex]?.text || '', | |
| document: documentData ? JSON.stringify(documentData) : '' | |
| }) | |
| }); | |
| const data = await response.json(); | |
| addMessageToChunk( | |
| { role: 'assistant', content: data.content || 'Sorry, no response received.' }, | |
| currentChunkIndex | |
| ); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| addMessageToChunk( | |
| { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }, | |
| currentChunkIndex | |
| ); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| return ( | |
| <> | |
| {/* Chunk Header */} | |
| <div className="px-6 py-4 flex-shrink-0 bg-white rounded-t-lg border-b border-gray-200 z-10 flex items-center justify-between"> | |
| <div className="flex items-center flex-1"> | |
| {/* Previous Chunk Button */} | |
| <button | |
| onClick={goToPrevChunk} | |
| disabled={currentChunkIndex === 0} | |
| className="mr-3 p-2 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" | |
| title="Go to previous chunk" | |
| > | |
| <svg | |
| className="w-5 h-5" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| {/* Chunk Title */} | |
| <div className="font-semibold text-xl text-gray-900 text-left"> | |
| <ReactMarkdown | |
| remarkPlugins={[remarkMath]} | |
| rehypePlugins={[rehypeRaw, rehypeKatex]} | |
| components={titleMarkdownComponents} | |
| > | |
| {documentData?.chunks?.[currentChunkIndex]?.topic || "Loading..."} | |
| </ReactMarkdown> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {/* Skip Button */} | |
| <button | |
| onClick={skipChunk} | |
| className="p-2 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 transition-colors duration-200" | |
| title="Skip this chunk" | |
| > | |
| <svg | |
| className="w-5 h-5" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" | |
| clipRule="evenodd" | |
| /> | |
| <path | |
| fillRule="evenodd" | |
| d="M12.293 14.707a1 1 0 010-1.414L15.586 10l-3.293-3.293a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| {/* Understood Button */} | |
| <button | |
| onClick={markChunkUnderstood} | |
| className="p-2 rounded-full bg-green-100 hover:bg-green-200 text-green-600 transition-colors duration-200" | |
| title="Mark chunk as understood" | |
| > | |
| <svg | |
| className="w-5 h-5" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| {/* Chat Interface - Only shown when showChat is true and not transitioning */} | |
| {showChat && !isLoading && !isTransitioning && ( | |
| <SimpleChat | |
| messages={getGlobalChatHistory()} | |
| currentChunkIndex={currentChunkIndex} | |
| canEdit={canEditChunk(currentChunkIndex)} | |
| onSend={handleSend} | |
| isLoading={isLoading || isTransitioning} | |
| /> | |
| )} | |
| {/* Loading Tips - Shown when generating greeting */} | |
| {showChat && isLoading && !hasChunkMessages(currentChunkIndex) && ( | |
| <ChunkLoadingTips message="Preparing your lesson..." /> | |
| )} | |
| {/* Transition Loading - Shown when moving between chunks */} | |
| {showChat && isTransitioning && ( | |
| <ChunkLoadingTips message="Transitioning to next topic..." /> | |
| )} | |
| </> | |
| ); | |
| }; | |
| export default ChunkPanel; |