fix: ghost session cleanup, new chat switching, and markdown overflow handling (#287)
This commit is contained in:
committed by
GitHub
Unverified
parent
89028756e1
commit
76df84e68c
@@ -346,9 +346,9 @@ function MessageBubble({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
<p className="whitespace-pre-wrap break-words text-sm">{text}</p>
|
<p className="whitespace-pre-wrap break-words break-all text-sm">{text}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
<div className="prose prose-sm dark:prose-invert max-w-none break-words break-all">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
@@ -357,7 +357,7 @@ function MessageBubble({
|
|||||||
const isInline = !match && !className;
|
const isInline = !match && !className;
|
||||||
if (isInline) {
|
if (isInline) {
|
||||||
return (
|
return (
|
||||||
<code className="bg-background/50 px-1.5 py-0.5 rounded text-sm font-mono" {...props}>
|
<code className="bg-background/50 px-1.5 py-0.5 rounded text-sm font-mono break-words break-all" {...props}>
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
@@ -372,7 +372,7 @@ function MessageBubble({
|
|||||||
},
|
},
|
||||||
a({ href, children }) {
|
a({ href, children }) {
|
||||||
return (
|
return (
|
||||||
<a href={href} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
|
<a href={href} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline break-words break-all">
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export function Chat() {
|
|||||||
const abortRun = useChatStore((s) => s.abortRun);
|
const abortRun = useChatStore((s) => s.abortRun);
|
||||||
const clearError = useChatStore((s) => s.clearError);
|
const clearError = useChatStore((s) => s.clearError);
|
||||||
|
|
||||||
|
const cleanupEmptySession = useChatStore((s) => s.cleanupEmptySession);
|
||||||
|
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const [streamingTimestamp, setStreamingTimestamp] = useState<number>(0);
|
const [streamingTimestamp, setStreamingTimestamp] = useState<number>(0);
|
||||||
|
|
||||||
@@ -54,8 +56,11 @@ export function Chat() {
|
|||||||
})();
|
})();
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
// If the user navigates away without sending any messages, remove the
|
||||||
|
// empty session so it doesn't linger as a ghost entry in the sidebar.
|
||||||
|
cleanupEmptySession();
|
||||||
};
|
};
|
||||||
}, [isGatewayRunning, loadHistory, loadSessions]);
|
}, [isGatewayRunning, loadHistory, loadSessions, cleanupEmptySession]);
|
||||||
|
|
||||||
// Auto-scroll on new messages, streaming, or activity changes
|
// Auto-scroll on new messages, streaming, or activity changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ interface ChatState {
|
|||||||
switchSession: (key: string) => void;
|
switchSession: (key: string) => void;
|
||||||
newSession: () => void;
|
newSession: () => void;
|
||||||
deleteSession: (key: string) => Promise<void>;
|
deleteSession: (key: string) => Promise<void>;
|
||||||
|
cleanupEmptySession: () => void;
|
||||||
loadHistory: (quiet?: boolean) => Promise<void>;
|
loadHistory: (quiet?: boolean) => Promise<void>;
|
||||||
sendMessage: (text: string, attachments?: Array<{ fileName: string; mimeType: string; fileSize: number; stagedPath: string; preview: string | null }>) => Promise<void>;
|
sendMessage: (text: string, attachments?: Array<{ fileName: string; mimeType: string; fileSize: number; stagedPath: string; preview: string | null }>) => Promise<void>;
|
||||||
abortRun: () => Promise<void>;
|
abortRun: () => Promise<void>;
|
||||||
@@ -973,9 +974,12 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!dedupedSessions.find((s) => s.key === nextSessionKey) && dedupedSessions.length > 0) {
|
if (!dedupedSessions.find((s) => s.key === nextSessionKey) && dedupedSessions.length > 0) {
|
||||||
// Current session not found at all — switch to the first available session
|
// Current session not found in the backend list
|
||||||
|
const isNewEmptySession = get().messages.length === 0;
|
||||||
|
if (!isNewEmptySession) {
|
||||||
nextSessionKey = dedupedSessions[0].key;
|
nextSessionKey = dedupedSessions[0].key;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const sessionsWithCurrent = !dedupedSessions.find((s) => s.key === nextSessionKey) && nextSessionKey
|
const sessionsWithCurrent = !dedupedSessions.find((s) => s.key === nextSessionKey) && nextSessionKey
|
||||||
? [
|
? [
|
||||||
@@ -1153,6 +1157,27 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Cleanup empty session on navigate away ──
|
||||||
|
|
||||||
|
cleanupEmptySession: () => {
|
||||||
|
const { currentSessionKey, messages } = get();
|
||||||
|
// Only remove non-main sessions that were never used (no messages sent).
|
||||||
|
// This mirrors the "leavingEmpty" logic in switchSession so that creating
|
||||||
|
// a new session and immediately navigating away doesn't leave a ghost entry
|
||||||
|
// in the sidebar.
|
||||||
|
const isEmptyNonMain = !currentSessionKey.endsWith(':main') && messages.length === 0;
|
||||||
|
if (!isEmptyNonMain) return;
|
||||||
|
set((s) => ({
|
||||||
|
sessions: s.sessions.filter((sess) => sess.key !== currentSessionKey),
|
||||||
|
sessionLabels: Object.fromEntries(
|
||||||
|
Object.entries(s.sessionLabels).filter(([k]) => k !== currentSessionKey),
|
||||||
|
),
|
||||||
|
sessionLastActivity: Object.fromEntries(
|
||||||
|
Object.entries(s.sessionLastActivity).filter(([k]) => k !== currentSessionKey),
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
// ── Load chat history ──
|
// ── Load chat history ──
|
||||||
|
|
||||||
loadHistory: async (quiet = false) => {
|
loadHistory: async (quiet = false) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user