mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-16 12:36:07 +00:00
💄 style: improve editing scroll experience (#7149)
This commit is contained in:
+1
-1
@@ -64,7 +64,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
|
||||
|
||||
const placement = displayMode === 'chat' && userRole === 'user' ? 'end' : 'start';
|
||||
|
||||
const actionBar = useMemo(() => <ActionsBar id={id} />, [id]);
|
||||
const actionBar = useMemo(() => <ActionsBar id={id} index={index} />, [id]);
|
||||
|
||||
return (
|
||||
<ChatItem
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ActionEvent, ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo, use, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { chatSelectors } from '@/store/chat/selectors';
|
||||
import { MessageRoleType } from '@/types/message';
|
||||
@@ -29,9 +30,10 @@ const ActionsBar = memo<ActionsBarProps>((props) => {
|
||||
interface ActionsProps {
|
||||
id: string;
|
||||
inPortalThread?: boolean;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const Actions = memo<ActionsProps>(({ id, inPortalThread }) => {
|
||||
const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
||||
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
|
||||
const { t } = useTranslation('common');
|
||||
const [
|
||||
@@ -58,12 +60,15 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread }) => {
|
||||
s.toggleMessageEditing,
|
||||
]);
|
||||
const { message } = App.useApp();
|
||||
const virtuosoRef = use(VirtuosoContext);
|
||||
|
||||
const handleActionClick = useCallback(
|
||||
async (action: ActionEvent) => {
|
||||
switch (action.key) {
|
||||
case 'edit': {
|
||||
toggleMessageEditing(id, true);
|
||||
|
||||
virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
|
||||
}
|
||||
}
|
||||
if (!item) return;
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { ChatItem } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { MouseEventHandler, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { MouseEventHandler, ReactNode, memo, use, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
@@ -64,6 +65,7 @@ const Item = memo<ChatListItemProps>(
|
||||
endRender,
|
||||
disableEditing,
|
||||
inPortalThread = false,
|
||||
index,
|
||||
}) => {
|
||||
const { t } = useTranslation('common');
|
||||
const { styles, cx } = useStyles();
|
||||
@@ -188,6 +190,7 @@ const Item = memo<ChatListItemProps>(
|
||||
);
|
||||
|
||||
const onChange = useCallback((value: string) => updateMessageContent(id, value), [id]);
|
||||
const virtuosoRef = use(VirtuosoContext);
|
||||
|
||||
const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(
|
||||
(e) => {
|
||||
@@ -195,6 +198,8 @@ const Item = memo<ChatListItemProps>(
|
||||
if (item.id === 'default' || item.error) return;
|
||||
if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
|
||||
toggleMessageEditing(id, true);
|
||||
|
||||
virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
|
||||
}
|
||||
},
|
||||
[item, disableEditing],
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { RefObject, createContext } from 'react';
|
||||
import { VirtuosoHandle } from 'react-virtuoso';
|
||||
|
||||
export const VirtuosoContext = createContext<RefObject<VirtuosoHandle | null> | null>(null);
|
||||
@@ -13,6 +13,7 @@ import { chatSelectors } from '@/store/chat/selectors';
|
||||
|
||||
import AutoScroll from '../AutoScroll';
|
||||
import SkeletonList from '../SkeletonList';
|
||||
import { VirtuosoContext } from './VirtuosoContext';
|
||||
|
||||
interface VirtualizedListProps {
|
||||
dataSource: string[];
|
||||
@@ -69,38 +70,40 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
|
||||
);
|
||||
|
||||
return (
|
||||
<Flexbox height={'100%'}>
|
||||
<Virtuoso
|
||||
atBottomStateChange={setAtBottom}
|
||||
atBottomThreshold={50 * (mobile ? 2 : 1)}
|
||||
computeItemKey={(_, item) => item}
|
||||
data={dataSource}
|
||||
followOutput={getFollowOutput}
|
||||
increaseViewportBy={overscan}
|
||||
initialTopMostItemIndex={dataSource?.length - 1}
|
||||
isScrolling={setIsScrolling}
|
||||
itemContent={itemContent}
|
||||
overscan={overscan}
|
||||
ref={virtuosoRef}
|
||||
/>
|
||||
<AutoScroll
|
||||
atBottom={atBottom}
|
||||
isScrolling={isScrolling}
|
||||
onScrollToBottom={(type) => {
|
||||
const virtuoso = virtuosoRef.current;
|
||||
switch (type) {
|
||||
case 'auto': {
|
||||
virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
|
||||
break;
|
||||
<VirtuosoContext value={virtuosoRef}>
|
||||
<Flexbox height={'100%'}>
|
||||
<Virtuoso
|
||||
atBottomStateChange={setAtBottom}
|
||||
atBottomThreshold={50 * (mobile ? 2 : 1)}
|
||||
computeItemKey={(_, item) => item}
|
||||
data={dataSource}
|
||||
followOutput={getFollowOutput}
|
||||
increaseViewportBy={overscan}
|
||||
initialTopMostItemIndex={dataSource?.length - 1}
|
||||
isScrolling={setIsScrolling}
|
||||
itemContent={itemContent}
|
||||
overscan={overscan}
|
||||
ref={virtuosoRef}
|
||||
/>
|
||||
<AutoScroll
|
||||
atBottom={atBottom}
|
||||
isScrolling={isScrolling}
|
||||
onScrollToBottom={(type) => {
|
||||
const virtuoso = virtuosoRef.current;
|
||||
switch (type) {
|
||||
case 'auto': {
|
||||
virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
|
||||
break;
|
||||
}
|
||||
case 'click': {
|
||||
virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'click': {
|
||||
virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Flexbox>
|
||||
}}
|
||||
/>
|
||||
</Flexbox>
|
||||
</VirtuosoContext>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const ThreadChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
|
||||
const isParentMessage = index <= threadStartMessageIndex;
|
||||
|
||||
const actionBar = useMemo(
|
||||
() => !isParentMessage && <ActionsBar id={id} inPortalThread />,
|
||||
() => !isParentMessage && <ActionsBar id={id} inPortalThread index={index} />,
|
||||
[id, isParentMessage],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user