chat fixes

Focus: textarea gets refocused via inputRef after the AI response (or error) lands
Persistence: chat history saved to localStorage keyed by trip ID, loaded on mount — survives refreshes
This commit is contained in:
Logan
2026-06-21 14:55:34 -04:00
parent e7622c7e6d
commit a0fdf2486e
+17 -2
View File
@@ -497,6 +497,8 @@ interface SuggestionCard {
added?: boolean;
}
const CHAT_STORAGE_KEY = (tripId: string) => `drb-trip-chat-${tripId}`;
function AssistantPanel({
trip,
onAddEvent,
@@ -504,15 +506,26 @@ function AssistantPanel({
trip: TripRecord & { events: TripEvent[] };
onAddEvent: (event: TripEvent) => void;
}) {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const storageKey = CHAT_STORAGE_KEY(trip.trip_id);
const [messages, setMessages] = useState<ChatMessage[]>(() => {
try {
const saved = localStorage.getItem(storageKey);
return saved ? JSON.parse(saved) : [];
} catch { return []; }
});
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const bottomRef = useRef<HTMLDivElement>(null);
const bottomRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
useEffect(() => {
try { localStorage.setItem(storageKey, JSON.stringify(messages)); } catch { /* quota */ }
}, [messages, storageKey]);
async function send() {
const text = input.trim();
if (!text || loading) return;
@@ -540,6 +553,7 @@ function AssistantPanel({
]);
} finally {
setLoading(false);
inputRef.current?.focus();
}
}
@@ -703,6 +717,7 @@ function AssistantPanel({
<div className="px-3 py-3 border-t border-gray-800 shrink-0">
<div className="flex gap-2">
<textarea
ref={inputRef}
value={input}
onChange={(e) => { setInput(e.target.value); e.target.style.height = "auto"; e.target.style.height = `${Math.min(e.target.scrollHeight, 120)}px`; }}
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && (e.preventDefault(), send())}