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:
2026-03-02 17:02:13 -08:00
parent d3a5655948
commit 11d6f14645

View File

@@ -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} />
))}