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:
@@ -497,6 +497,8 @@ interface SuggestionCard {
|
|||||||
added?: boolean;
|
added?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHAT_STORAGE_KEY = (tripId: string) => `drb-trip-chat-${tripId}`;
|
||||||
|
|
||||||
function AssistantPanel({
|
function AssistantPanel({
|
||||||
trip,
|
trip,
|
||||||
onAddEvent,
|
onAddEvent,
|
||||||
@@ -504,15 +506,26 @@ function AssistantPanel({
|
|||||||
trip: TripRecord & { events: TripEvent[] };
|
trip: TripRecord & { events: TripEvent[] };
|
||||||
onAddEvent: (event: TripEvent) => void;
|
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 [input, setInput] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try { localStorage.setItem(storageKey, JSON.stringify(messages)); } catch { /* quota */ }
|
||||||
|
}, [messages, storageKey]);
|
||||||
|
|
||||||
async function send() {
|
async function send() {
|
||||||
const text = input.trim();
|
const text = input.trim();
|
||||||
if (!text || loading) return;
|
if (!text || loading) return;
|
||||||
@@ -540,6 +553,7 @@ function AssistantPanel({
|
|||||||
]);
|
]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
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="px-3 py-3 border-t border-gray-800 shrink-0">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<textarea
|
<textarea
|
||||||
|
ref={inputRef}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => { setInput(e.target.value); e.target.style.height = "auto"; e.target.style.height = `${Math.min(e.target.scrollHeight, 120)}px`; }}
|
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())}
|
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && (e.preventDefault(), send())}
|
||||||
|
|||||||
Reference in New Issue
Block a user