fixed page state while generating a message

This commit is contained in:
2026-09-19 05:42:26 -04:00
parent b2d8109019
commit 616282cb35
8 changed files with 411 additions and 228 deletions
@@ -2,167 +2,256 @@
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,
messages,
activeSubject,
activeGrade,
activeChapter,
onPromptClick,
isTyping,
selectorStep,
selectorData,
}) => {
const hasMessages = messages.length > 0;
const isSelector = selectorStep !== "chat";
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 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 fullPromptTexts = suggestions.map(
(s) => s.text + s.keywords.join(" ") + s.textEnd,
);
const streamingMsg = messages.find((m) => m.streaming);
const streamingMsg = messages.find((m) => m.streaming);
const historyRef = useRef(null);
/* ─── Messages mode ─── */
if (hasMessages) {
return (
<div className="chat-history">
<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 ref={(el) => el && el.scrollIntoView({ behavior: "smooth", block: "end" })} />
</div>
</div>
);
}
// 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]);
/* ─── 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>
);
}
/* ─── 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>
);
}
/* ─── 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>
/* ─── 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>
);
}
{/* Bento Grid Suggestions */}
<div className="chat-history__bento">
{suggestions.map((suggestion) => {
const colorVar = suggestion.color.replace("--", "var(");
const bgColorVar = suggestion.colorBg.replace("--", "var(");
/* ─── 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>
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>
{/* 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>
<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;