fix: auto scrolling

This commit is contained in:
BetaHuhn 2023-09-05 12:29:42 +02:00
parent 7cf8bd6352
commit 2ffee3bf64
3 changed files with 112 additions and 92 deletions

View file

@ -21,7 +21,6 @@ import { Message, detaDB } from "../db";
import "../styles/markdown.scss";
import { CreatePromptModal } from "./CreatePromptModal";
import { LogoIcon } from "./Logo";
import { ScrollIntoView } from "./ScrollIntoView";
import { notifications } from "@mantine/notifications";
export function MessageItem({ message, onDeleted, handleUseMessage }: { message: Message, onDeleted: (key: string) => void, handleUseMessage: (key: string) => void }) {
@ -49,94 +48,92 @@ export function MessageItem({ message, onDeleted, handleUseMessage }: { message:
}
return (
<ScrollIntoView>
<Card
ref={ref}
withBorder
onClick={() => setExpanded(true)}
style={{ cursor: 'pointer' }}
sx={{
'&:hover': {
border: '1px solid #acacac'
}
}}
>
<Flex gap="sm">
{message.role === "user" && (
<ThemeIcon color="gray" size="lg">
<IconUser size={20} />
</ThemeIcon>
)}
{message.role === "assistant" && <LogoIcon style={{ height: 32 }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />}
<Box sx={{ flex: 1, width: 0 }} className="markdown">
<ReactMarkdown
children={message.content}
remarkPlugins={[remarkGfm]}
components={{
table: ({ node, ...props }) => (
<Table verticalSpacing="sm" highlightOnHover {...props} />
),
code: ({ node, inline, ...props }) =>
inline ? (
<Code {...props} />
) : (
<Box sx={{ position: "relative" }}>
<Code block {...props} />
<CopyButton value={String(props.children)}>
{({ copied, copy }) => (
<Tooltip
label={copied ? "Copied" : "Copy"}
position="left"
<Card
ref={ref}
withBorder
onClick={() => setExpanded(true)}
style={{ cursor: 'pointer' }}
sx={{
'&:hover': {
border: '1px solid #acacac'
}
}}
>
<Flex gap="sm">
{message.role === "user" && (
<ThemeIcon color="gray" size="lg">
<IconUser size={20} />
</ThemeIcon>
)}
{message.role === "assistant" && <LogoIcon style={{ height: 32 }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />}
<Box sx={{ flex: 1, width: 0 }} className="markdown">
<ReactMarkdown
children={message.content}
remarkPlugins={[remarkGfm]}
components={{
table: ({ node, ...props }) => (
<Table verticalSpacing="sm" highlightOnHover {...props} />
),
code: ({ node, inline, ...props }) =>
inline ? (
<Code {...props} />
) : (
<Box sx={{ position: "relative" }}>
<Code block {...props} />
<CopyButton value={String(props.children)}>
{({ copied, copy }) => (
<Tooltip
label={copied ? "Copied" : "Copy"}
position="left"
>
<ActionIcon
sx={{ position: "absolute", top: 4, right: 4 }}
onClick={copy}
>
<ActionIcon
sx={{ position: "absolute", top: 4, right: 4 }}
onClick={copy}
>
<IconCopy opacity={0.4} size={20} />
</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Box>
),
}}
/>
</Box>
</Flex>
<IconCopy opacity={0.4} size={20} />
</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Box>
),
}}
/>
</Box>
</Flex>
<Collapse in={isExpanded}>
<Collapse in={isExpanded}>
<Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 10 }}>
<Box>
<Text size="sm" color="dimmed">
{wordCount} words
</Text>
</Box>
<Box>
<Text size="sm" color="dimmed">
{wordCount} words
</Text>
</Box>
<Box style={{ display: 'flex' }}>
<CreatePromptModal content={message.content || ''} />
<Tooltip label="Re-use Message" position="top">
<ActionIcon onClick={() => handleUseMessage(message.content)}>
<IconArrowForward opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
<CopyButton value={message.content}>
{({ copied, copy }) => (
<Tooltip label={copied ? "Copied Message" : "Copy Message"} position="top">
<ActionIcon onClick={copy}>
<IconCopy opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
)}
</CopyButton>
<Tooltip label="Delete Message" position="top">
<ActionIcon onClick={handleDelete}>
<IconTrash opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
</Box>
</Box>
</Collapse>
</Card>
</ScrollIntoView>
<Box style={{ display: 'flex' }}>
<CreatePromptModal content={message.content || ''} />
<Tooltip label="Re-use Message" position="top">
<ActionIcon onClick={() => handleUseMessage(message.content)}>
<IconArrowForward opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
<CopyButton value={message.content}>
{({ copied, copy }) => (
<Tooltip label={copied ? "Copied Message" : "Copy Message"} position="top">
<ActionIcon onClick={copy}>
<IconCopy opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
)}
</CopyButton>
<Tooltip label="Delete Message" position="top">
<ActionIcon onClick={handleDelete}>
<IconTrash opacity={0.5} size={20} />
</ActionIcon>
</Tooltip>
</Box>
</Box>
</Collapse>
</Card>
);
}

View file

@ -0,0 +1,20 @@
import React, { useState } from 'react'
export function useChatScroll<T>(dep: T, elem = document.documentElement) {
const [autoScroll, setAutoScroll] = useState(true)
document.onscroll = () => {
const scrollBottom = Math.floor(elem.scrollTop + elem.clientHeight)
const val = scrollBottom >= elem.scrollHeight - 70
setAutoScroll(val)
}
React.useEffect(() => {
if (elem && autoScroll) {
elem.scrollTop = elem.scrollHeight;
}
}, [dep, autoScroll]);
return { autoScroll, setAutoScroll }
}

View file

@ -25,6 +25,7 @@ import { useChat, useChats, usePrompts, useSettings } from "../hooks/contexts";
import { CreatePromptModal } from "../components/CreatePromptModal";
import { config } from "../utils/config";
import { useHotkeys } from "@mantine/hooks";
import { useChatScroll } from "../hooks/useChatScroll";
export function ChatRoute() {
const chatId = useChatId();
@ -44,11 +45,8 @@ export function ChatRoute() {
dataFetch();
}, [chatId]);
// const messages = useLiveQuery(() => {
// if (!chatId) return [];
// return db.messages.where("chatId").equals(chatId).sortBy("createdAt");
// }, [chatId]);
const userMessages =
messages
?.filter((message) => message.role === "user")
@ -59,11 +57,14 @@ export function ChatRoute() {
const [submitting, setSubmitting] = useState(false);
const [generating, setGenerating] = useState(false);
const [coolDown, setCoolDown] = useState(false);
const [hasGenerated, setHasGenerated] = useState(false);
const [chatStream, setChatStream] = useState<any>(null);
const [promptKey, setPromptKey] = useState<string | null>(null);
const [newPromptTitle, setNewPromptTitle] = useState<string | null>(null);
const [selectedModel, setSelectedModel] = useState<string | null>(null);
const { setAutoScroll } = useChatScroll(messages)
const { setChats } = useChats()
const { chat, setChat } = useChat()
@ -151,6 +152,7 @@ export function ChatRoute() {
setMessages(current => [...current, userMessage as unknown as Message])
setContent("");
setPromptKey(null);
setHasGenerated(true)
const systemMessage = await detaDB.messages.put({
chatId,
@ -161,6 +163,7 @@ export function ChatRoute() {
setMessages(current => [...current, systemMessage as unknown as Message])
setGenerating(true)
setAutoScroll(true)
const messageId = systemMessage!.key as string
const chatCompletionStream = createStreamChatCompletion(
@ -290,7 +293,7 @@ export function ChatRoute() {
}
const undoGeneration = async () => {
if (!generating) return
if (!generating && !hasGenerated) return
if (chatStream) {
chatStream.abort()
setChatStream(null)
@ -363,7 +366,7 @@ export function ChatRoute() {
return (
<>
<Container pt="xl" pb={100}>
<Stack spacing="xs">
<Stack spacing="xs" id="test">
{messages?.map((message) => (
<MessageItem key={message.key} message={message} onDeleted={handleMessageDelete} handleUseMessage={handleUseMessage} />
))}