258 lines
15 KiB
React
258 lines
15 KiB
React
/* ========================================
|
||
ChatHistory Component — padhle (Refined Workspace)
|
||
======================================== */
|
||
|
||
import { useEffect, useRef } from "react";
|
||
import Message from "../message/Message";
|
||
import SelectorFlow from "../selector-flow/SelectorFlow";
|
||
import "./ChatHistory.css";
|
||
|
||
const ChatHistory = ({
|
||
messages,
|
||
activeSubject,
|
||
activeGrade,
|
||
activeChapter,
|
||
onPromptClick,
|
||
isTyping,
|
||
selectorStep,
|
||
selectorData,
|
||
}) => {
|
||
const hasMessages = messages.length > 0;
|
||
const isSelector = selectorStep !== "chat";
|
||
|
||
const suggestions = [
|
||
{
|
||
id: 1,
|
||
icon: "eco",
|
||
iconFilled: true,
|
||
color: "--color-accent-green",
|
||
colorBg: "--color-accent-green-bg",
|
||
keywords: ["photosynthesis"],
|
||
text: "Explain how ",
|
||
textEnd: " works in simple terms",
|
||
},
|
||
{
|
||
id: 2,
|
||
icon: "calculate",
|
||
iconFilled: false,
|
||
color: "--color-accent-blue",
|
||
colorBg: "--color-accent-blue-bg",
|
||
keywords: ["derivatives"],
|
||
text: "Help me understand ",
|
||
textEnd: " in calculus",
|
||
},
|
||
{
|
||
id: 3,
|
||
icon: "account_balance",
|
||
iconFilled: false,
|
||
color: "--color-accent-orange",
|
||
colorBg: "--color-accent-orange-bg",
|
||
keywords: ["Industrial"],
|
||
text: "What caused the ",
|
||
textEnd: " Revolution?",
|
||
},
|
||
{
|
||
id: 4,
|
||
icon: "edit",
|
||
iconFilled: true,
|
||
color: "--color-accent-purple",
|
||
colorBg: "--color-accent-purple-bg",
|
||
keywords: ["Romeo and Juliet"],
|
||
text: "Help me analyze themes in ",
|
||
textEnd: "",
|
||
},
|
||
];
|
||
|
||
const fullPromptTexts = suggestions.map(
|
||
(s) => s.text + s.keywords.join(" ") + s.textEnd,
|
||
);
|
||
|
||
const streamingMsg = messages.find((m) => m.streaming);
|
||
const historyRef = useRef(null);
|
||
|
||
// Scroll only the chat pane, and only when a message is added. Streaming
|
||
// token updates must not move the page or restart a smooth-scroll animation.
|
||
useEffect(() => {
|
||
const history = historyRef.current;
|
||
if (history) history.scrollTop = history.scrollHeight;
|
||
}, [messages.length]);
|
||
|
||
/* ─── Messages mode ─── */
|
||
if (hasMessages) {
|
||
return (
|
||
<div className="chat-history" ref={historyRef}>
|
||
<div className="chat-history__list">
|
||
{messages.map((message) => (
|
||
<Message
|
||
key={message.id}
|
||
message={message}
|
||
/>
|
||
))}
|
||
{isTyping && !streamingMsg && (
|
||
<Message
|
||
message={{
|
||
id: "typing",
|
||
role: "assistant",
|
||
text: "",
|
||
streaming: true,
|
||
}}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Selector mode (grade → subject → chapter) ─── */
|
||
if (isSelector && selectorData) {
|
||
return (
|
||
<div className="chat-history">
|
||
<SelectorFlow
|
||
title={selectorData.title}
|
||
subtitle={selectorData.subtitle}
|
||
selected={selectorData.selected}
|
||
items={selectorData.items}
|
||
onSelect={selectorData.onSelect}
|
||
step={selectorStep}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Welcome / suggestions mode ─── */
|
||
return (
|
||
<div className="chat-history">
|
||
<div className="chat-history__focus">
|
||
{/* Welcome Header */}
|
||
<div className="chat-history__welcome">
|
||
<div className="chat-history__welcome-icon">
|
||
<span
|
||
className="material-symbols-outlined"
|
||
style={{
|
||
fontWeight: 200,
|
||
}}
|
||
>
|
||
menu_book
|
||
</span>
|
||
<span className="material-symbols-outlined chat-history__welcome-sparkle">
|
||
auto_awesome
|
||
</span>
|
||
</div>
|
||
<h2 className="chat-history__welcome-title">
|
||
Welcome to padhle
|
||
</h2>
|
||
<p className="chat-history__welcome-subtitle">
|
||
Your AI tutor for grades 6–12.
|
||
Ask anything!
|
||
</p>
|
||
</div>
|
||
|
||
{/* Bento Grid Suggestions */}
|
||
<div className="chat-history__bento">
|
||
{suggestions.map((suggestion) => {
|
||
const colorVar =
|
||
suggestion.color.replace(
|
||
"--",
|
||
"var(",
|
||
);
|
||
const bgColorVar =
|
||
suggestion.colorBg.replace(
|
||
"--",
|
||
"var(",
|
||
);
|
||
|
||
return (
|
||
<button
|
||
key={
|
||
suggestion.id
|
||
}
|
||
className="chat-history__bento-card"
|
||
onClick={() => {
|
||
const text =
|
||
fullPromptTexts[
|
||
suggestion.id -
|
||
1
|
||
];
|
||
onPromptClick(
|
||
text,
|
||
);
|
||
}}
|
||
>
|
||
<div
|
||
className="chat-history__bento-icon-wrap"
|
||
style={{
|
||
backgroundColor: `var(${bgColorVar})`,
|
||
}}
|
||
>
|
||
<span
|
||
className="material-symbols-outlined"
|
||
style={{
|
||
color: `var(${colorVar})`,
|
||
fontWeight: suggestion.iconFilled
|
||
? 700
|
||
: 200,
|
||
}}
|
||
>
|
||
{
|
||
suggestion.icon
|
||
}
|
||
</span>
|
||
</div>
|
||
<div className="chat-history__bento-text">
|
||
<span className="chat-history__bento-text-main">
|
||
{
|
||
suggestion.text
|
||
}
|
||
</span>
|
||
<span
|
||
className="chat-history__bento-text-highlight"
|
||
style={{
|
||
color: `var(${colorVar})`,
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{
|
||
suggestion
|
||
.keywords[0]
|
||
}
|
||
</span>
|
||
{
|
||
suggestion.textEnd
|
||
}
|
||
</div>
|
||
<span className="material-symbols-outlined chat-history__bento-arrow">
|
||
arrow_forward
|
||
</span>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Info Panel */}
|
||
<div className="chat-history__info">
|
||
<div className="chat-history__info-icon-wrap">
|
||
<span className="material-symbols-outlined">
|
||
lightbulb
|
||
</span>
|
||
</div>
|
||
<div className="chat-history__info-content">
|
||
<h4 className="chat-history__info-title">
|
||
Focused on your learning
|
||
</h4>
|
||
<p className="chat-history__info-text">
|
||
I'll answer questions
|
||
related to your selected
|
||
subject and chapter
|
||
<br />
|
||
using your textbook
|
||
content.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ChatHistory;
|