Fix Atlas chat duplicate messages; add Reset button
- Add cleanup flag to useEffect to prevent state updates after unmount, eliminating the race condition on rapid navigation - Add handleReset: calls DELETE endpoint, clears state, re-triggers greeting - Add subtle "Reset" button (top-right of message area) so user can wipe polluted history and start fresh Made-with: Cursor
This commit is contained in:
@@ -147,34 +147,50 @@ export function AtlasChat({ projectId }: AtlasChatProps) {
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
// On mount: load stored history; if empty, trigger Atlas greeting
|
||||
// On mount: load stored history; if empty, trigger Atlas greeting exactly once
|
||||
useEffect(() => {
|
||||
if (historyLoaded) return;
|
||||
let cancelled = false; // guard against unmount during fetch
|
||||
|
||||
fetch(`/api/projects/${projectId}/atlas-chat`)
|
||||
.then(r => r.json())
|
||||
.then((data: { messages: ChatMessage[] }) => {
|
||||
if (cancelled) return;
|
||||
const stored = data.messages ?? [];
|
||||
setMessages(stored);
|
||||
setHistoryLoaded(true);
|
||||
|
||||
// Only trigger greeting if there's genuinely no history yet
|
||||
// Only greet if there is genuinely no history and we haven't triggered yet
|
||||
if (stored.length === 0 && !initTriggered.current) {
|
||||
initTriggered.current = true;
|
||||
sendToAtlas("__atlas_init__", true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return;
|
||||
setHistoryLoaded(true);
|
||||
// If we can't load, still try to greet on first open
|
||||
if (!initTriggered.current) {
|
||||
initTriggered.current = true;
|
||||
sendToAtlas("__atlas_init__", true);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { cancelled = true; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId]);
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!confirm("Clear this conversation and start fresh?")) return;
|
||||
try {
|
||||
await fetch(`/api/projects/${projectId}/atlas-chat`, { method: "DELETE" });
|
||||
setMessages([]);
|
||||
setHistoryLoaded(false);
|
||||
initTriggered.current = false;
|
||||
// Trigger fresh greeting
|
||||
setTimeout(() => {
|
||||
initTriggered.current = true;
|
||||
sendToAtlas("__atlas_init__", true);
|
||||
}, 100);
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = () => {
|
||||
const text = input.trim();
|
||||
if (!text || isStreaming) return;
|
||||
@@ -226,7 +242,22 @@ export function AtlasChat({ projectId }: AtlasChatProps) {
|
||||
|
||||
{/* Messages */}
|
||||
{!isEmpty && (
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "28px 32px" }}>
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "28px 32px", position: "relative" }}>
|
||||
{/* Reset button — top right, only visible on hover of the area */}
|
||||
<button
|
||||
onClick={handleReset}
|
||||
title="Reset conversation"
|
||||
style={{
|
||||
position: "absolute", top: 12, right: 16,
|
||||
background: "none", border: "none", cursor: "pointer",
|
||||
fontSize: "0.68rem", color: "#d0ccc4", fontFamily: "Outfit, sans-serif",
|
||||
padding: "3px 7px", borderRadius: 4, transition: "color 0.12s",
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = "#8a8478")}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = "#d0ccc4")}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
{messages.map((msg, i) => (
|
||||
<MessageRow key={i} msg={msg} userInitial={userInitial} />
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user