feat: improve chat streaming UX and add Mermaid diagram rendering (#438)
* feat: show current branch in empty chat state * fix: display current git branch for worktrees and update them with branch change * refactor: improve read tool output parsing with structured data * feat: add support for message part delta events * fix(chat): improve streaming rendering, scroll behavior, and assistant action visibility * feat: Add mermaid diagram support to chat markdown rendering * fix: update table download functionality to include success notification and remove unused MarkdownRenderer import * refactor: streamline Streamdown component props for improved readability * fix: preserve Streamdown code-block markers and use native Tauri cache clearing * feat: add context overview panel to view conversation details
This commit is contained in:
committed by
GitHub
parent
138772e66e
commit
4d71bb27eb
@@ -66,6 +66,7 @@ const PIN_THRESHOLD_RATIO = 0.10;
|
||||
export const useChatScrollManager = ({
|
||||
currentSessionId,
|
||||
sessionMessages,
|
||||
streamingMessageId,
|
||||
updateViewportAnchor,
|
||||
isSyncing,
|
||||
isMobile,
|
||||
@@ -117,6 +118,15 @@ export const useChatScrollManager = ({
|
||||
scrollEngine.scrollToPosition(Math.max(0, bottom), options);
|
||||
}, [markProgrammaticScroll, scrollEngine]);
|
||||
|
||||
const scrollPinnedToBottom = React.useCallback(() => {
|
||||
if (streamingMessageId) {
|
||||
scrollToBottomInternal({ followBottom: true });
|
||||
return;
|
||||
}
|
||||
|
||||
scrollToBottomInternal({ instant: true });
|
||||
}, [scrollToBottomInternal, streamingMessageId]);
|
||||
|
||||
const updateScrollButtonVisibility = React.useCallback(() => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) {
|
||||
@@ -250,13 +260,12 @@ export const useChatScrollManager = ({
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// When pinned and content grows, scroll to bottom instantly
|
||||
// When pinned and content grows, follow bottom with fast smooth scroll
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
markProgrammaticScroll();
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}, [getDistanceFromBottom, getPinThreshold, isSyncing, markProgrammaticScroll, scrollToBottomInternal, sessionMessages]);
|
||||
}, [getDistanceFromBottom, getPinThreshold, isSyncing, scrollPinnedToBottom, sessionMessages]);
|
||||
|
||||
// Use ResizeObserver to detect content changes and maintain pin
|
||||
React.useEffect(() => {
|
||||
@@ -266,11 +275,11 @@ export const useChatScrollManager = ({
|
||||
const observer = new ResizeObserver(() => {
|
||||
updateScrollButtonVisibility();
|
||||
|
||||
// Maintain pin when content grows - always instant for smooth experience
|
||||
// Maintain pin when content grows - fast smooth follow
|
||||
if (isPinnedRef.current) {
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -282,7 +291,7 @@ export const useChatScrollManager = ({
|
||||
if (isPinnedRef.current) {
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -293,7 +302,7 @@ export const useChatScrollManager = ({
|
||||
observer.disconnect();
|
||||
childObserver.disconnect();
|
||||
};
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollToBottomInternal, updateScrollButtonVisibility]);
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollPinnedToBottom, updateScrollButtonVisibility]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -315,14 +324,14 @@ export const useChatScrollManager = ({
|
||||
const handleMessageContentChange = React.useCallback(() => {
|
||||
updateScrollButtonVisibility();
|
||||
|
||||
// Maintain pin when content changes - always instant
|
||||
// Maintain pin when content changes - fast smooth follow
|
||||
if (isPinnedRef.current) {
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollToBottomInternal, updateScrollButtonVisibility]);
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollPinnedToBottom, updateScrollButtonVisibility]);
|
||||
|
||||
const getAnimationHandlers = React.useCallback((messageId: string): AnimationHandlers => {
|
||||
const existing = animationHandlersRef.current.get(messageId);
|
||||
@@ -336,7 +345,7 @@ export const useChatScrollManager = ({
|
||||
if (isPinnedRef.current) {
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -350,7 +359,7 @@ export const useChatScrollManager = ({
|
||||
if (isPinnedRef.current) {
|
||||
const distanceFromBottom = getDistanceFromBottom();
|
||||
if (distanceFromBottom > getPinThreshold()) {
|
||||
scrollToBottomInternal({ instant: true });
|
||||
scrollPinnedToBottom();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -360,7 +369,7 @@ export const useChatScrollManager = ({
|
||||
|
||||
animationHandlersRef.current.set(messageId, handlers);
|
||||
return handlers;
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollToBottomInternal, updateScrollButtonVisibility]);
|
||||
}, [getDistanceFromBottom, getPinThreshold, scrollPinnedToBottom, updateScrollButtonVisibility]);
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
|
||||
Reference in New Issue
Block a user