mirror of
https://github.com/deiucanta/chatpad.git
synced 2026-03-11 09:04:31 +00:00
fix: auto scrolling
This commit is contained in:
parent
7cf8bd6352
commit
2ffee3bf64
3 changed files with 112 additions and 92 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
20
src/hooks/useChatScroll.ts
Normal file
20
src/hooks/useChatScroll.ts
Normal 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 }
|
||||
}
|
||||
|
|
@ -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} />
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in a new issue