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]);
|
}, [projectId]);
|
||||||
|
|
||||||
// On mount: load stored history; if empty, trigger Atlas greeting
|
// On mount: load stored history; if empty, trigger Atlas greeting exactly once
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (historyLoaded) return;
|
let cancelled = false; // guard against unmount during fetch
|
||||||
|
|
||||||
fetch(`/api/projects/${projectId}/atlas-chat`)
|
fetch(`/api/projects/${projectId}/atlas-chat`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then((data: { messages: ChatMessage[] }) => {
|
.then((data: { messages: ChatMessage[] }) => {
|
||||||
|
if (cancelled) return;
|
||||||
const stored = data.messages ?? [];
|
const stored = data.messages ?? [];
|
||||||
setMessages(stored);
|
setMessages(stored);
|
||||||
setHistoryLoaded(true);
|
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) {
|
if (stored.length === 0 && !initTriggered.current) {
|
||||||
initTriggered.current = true;
|
initTriggered.current = true;
|
||||||
sendToAtlas("__atlas_init__", true);
|
sendToAtlas("__atlas_init__", true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
if (cancelled) return;
|
||||||
setHistoryLoaded(true);
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [projectId]);
|
}, [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 handleSend = () => {
|
||||||
const text = input.trim();
|
const text = input.trim();
|
||||||
if (!text || isStreaming) return;
|
if (!text || isStreaming) return;
|
||||||
@@ -226,7 +242,22 @@ export function AtlasChat({ projectId }: AtlasChatProps) {
|
|||||||
|
|
||||||
{/* Messages */}
|
{/* Messages */}
|
||||||
{!isEmpty && (
|
{!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) => (
|
{messages.map((msg, i) => (
|
||||||
<MessageRow key={i} msg={msg} userInitial={userInitial} />
|
<MessageRow key={i} msg={msg} userInitial={userInitial} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user