fix: performance issue when pasting long content into the chat input box (#4240)

This commit is contained in:
Faisal Amir 2024-12-06 16:48:01 +08:00 committed by GitHub
parent 3341a3be15
commit 21389070fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 61 additions and 15 deletions

View File

@ -1,15 +1,40 @@
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import { useMediaQuery } from '@janhq/joi'
import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { MainViewState } from '@/constants/screens'
import { LEFT_PANEL_WIDTH } from '../LeftPanelContainer'
import { RIGHT_PANEL_WIDTH } from '../RightPanelContainer'
import {
mainViewStateAtom,
showLeftPanelAtom,
showRightPanelAtom,
} from '@/helpers/atoms/App.atom'
import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom' import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
const CenterPanelContainer = ({ children }: PropsWithChildren) => { const CenterPanelContainer = ({ children }: PropsWithChildren) => {
const reduceTransparent = useAtomValue(reduceTransparentAtom) const reduceTransparent = useAtomValue(reduceTransparentAtom)
const matches = useMediaQuery('(max-width: 880px)')
const showLeftPanel = useAtomValue(showLeftPanelAtom)
const showRightPanel = useAtomValue(showRightPanelAtom)
const mainViewState = useAtomValue(mainViewStateAtom)
return ( return (
<div className={twMerge('flex h-full w-full')}> <div
className={twMerge('flex h-full w-full')}
style={{
maxWidth: matches
? '100%'
: mainViewState === MainViewState.Thread
? `calc(100% - (${showRightPanel ? Number(localStorage.getItem(RIGHT_PANEL_WIDTH)) : 0}px + ${showLeftPanel ? Number(localStorage.getItem(LEFT_PANEL_WIDTH)) : 0}px))`
: '100%',
}}
>
<div <div
className={twMerge( className={twMerge(
'h-full w-full overflow-hidden bg-[hsla(var(--center-panel-bg))]', 'h-full w-full overflow-hidden bg-[hsla(var(--center-panel-bg))]',

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { useEffect, useMemo } from 'react' import { useEffect } from 'react'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'

View File

@ -17,7 +17,7 @@ import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
type Props = PropsWithChildren type Props = PropsWithChildren
const DEFAULT_LEFT_PANEL_WIDTH = 200 const DEFAULT_LEFT_PANEL_WIDTH = 200
const LEFT_PANEL_WIDTH = 'leftPanelWidth' export const LEFT_PANEL_WIDTH = 'leftPanelWidth'
const LeftPanelContainer = ({ children }: Props) => { const LeftPanelContainer = ({ children }: Props) => {
const [leftPanelRef, setLeftPanelRef] = useState<HTMLDivElement | null>(null) const [leftPanelRef, setLeftPanelRef] = useState<HTMLDivElement | null>(null)

View File

@ -37,7 +37,7 @@ const MainViewContainer = () => {
} }
return ( return (
<div className={twMerge('relative flex w-full')}> <div className={twMerge('relative flex w-[calc(100%-48px)]')}>
<div className="w-full"> <div className="w-full">
<m.div <m.div
key={mainViewState} key={mainViewState}

View File

@ -16,13 +16,13 @@ import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
type Props = PropsWithChildren type Props = PropsWithChildren
const DEFAULT_RIGTH_PANEL_WIDTH = 280 const DEFAULT_RIGHT_PANEL_WIDTH = 280
const RIGHT_PANEL_WIDTH = 'rightPanelWidth' export const RIGHT_PANEL_WIDTH = 'rightPanelWidth'
const RightPanelContainer = ({ children }: Props) => { const RightPanelContainer = ({ children }: Props) => {
const [isResizing, setIsResizing] = useState(false) const [isResizing, setIsResizing] = useState(false)
const [threadRightPanelWidth, setRightPanelWidth] = useState( const [threadRightPanelWidth, setRightPanelWidth] = useState(
Number(localStorage.getItem(RIGHT_PANEL_WIDTH)) || DEFAULT_RIGTH_PANEL_WIDTH Number(localStorage.getItem(RIGHT_PANEL_WIDTH)) || DEFAULT_RIGHT_PANEL_WIDTH
) )
const [rightPanelRef, setRightPanelRef] = useState<HTMLDivElement | null>( const [rightPanelRef, setRightPanelRef] = useState<HTMLDivElement | null>(
null null
@ -55,11 +55,11 @@ const RightPanelContainer = ({ children }: Props) => {
mouseMoveEvent.clientX < mouseMoveEvent.clientX <
200 200
) { ) {
setRightPanelWidth(DEFAULT_RIGTH_PANEL_WIDTH) setRightPanelWidth(DEFAULT_RIGHT_PANEL_WIDTH)
setIsResizing(false) setIsResizing(false)
localStorage.setItem( localStorage.setItem(
RIGHT_PANEL_WIDTH, RIGHT_PANEL_WIDTH,
String(DEFAULT_RIGTH_PANEL_WIDTH) String(DEFAULT_RIGHT_PANEL_WIDTH)
) )
setShowRightPanel(false) setShowRightPanel(false)
} else { } else {
@ -77,8 +77,8 @@ const RightPanelContainer = ({ children }: Props) => {
useEffect(() => { useEffect(() => {
if (localStorage.getItem(RIGHT_PANEL_WIDTH) === null) { if (localStorage.getItem(RIGHT_PANEL_WIDTH) === null) {
setRightPanelWidth(DEFAULT_RIGTH_PANEL_WIDTH) setRightPanelWidth(DEFAULT_RIGHT_PANEL_WIDTH)
localStorage.setItem(RIGHT_PANEL_WIDTH, String(DEFAULT_RIGTH_PANEL_WIDTH)) localStorage.setItem(RIGHT_PANEL_WIDTH, String(DEFAULT_RIGHT_PANEL_WIDTH))
} }
window.addEventListener('mousemove', resize) window.addEventListener('mousemove', resize)
window.addEventListener('mouseup', stopResizing) window.addEventListener('mouseup', stopResizing)

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, ClipboardEvent } from 'react'
import { MessageStatus } from '@janhq/core' import { MessageStatus } from '@janhq/core'
import hljs from 'highlight.js' import hljs from 'highlight.js'
@ -67,7 +67,7 @@ const RichTextEditor = ({
placeholder, placeholder,
spellCheck, spellCheck,
}: RichTextEditorProps) => { }: RichTextEditorProps) => {
const [editor] = useState(() => withHistory(withReact(createEditor()))) const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const currentLanguage = useRef<string>('plaintext') const currentLanguage = useRef<string>('plaintext')
const hasStartBackticks = useRef<boolean>(false) const hasStartBackticks = useRef<boolean>(false)
const hasEndBackticks = useRef<boolean>(false) const hasEndBackticks = useRef<boolean>(false)
@ -80,6 +80,8 @@ const RichTextEditor = ({
const { sendChatMessage } = useSendChatMessage() const { sendChatMessage } = useSendChatMessage()
const { stopInference } = useActiveModel() const { stopInference } = useActiveModel()
const largeContentThreshold = 1000
// The decorate function identifies code blocks and marks the ranges // The decorate function identifies code blocks and marks the ranges
const decorate = useCallback( const decorate = useCallback(
(entry: [any, any]) => { (entry: [any, any]) => {
@ -324,6 +326,16 @@ const RichTextEditor = ({
[currentPrompt, editor, messages] [currentPrompt, editor, messages]
) )
const handlePaste = (event: ClipboardEvent<HTMLDivElement>) => {
const clipboardData = event.clipboardData || (window as any).clipboardData
const pastedData = clipboardData.getData('text')
if (pastedData.length > largeContentThreshold) {
event.preventDefault() // Prevent the default paste behavior
Transforms.insertText(editor, pastedData) // Insert the content directly into the editor
}
}
return ( return (
<Slate <Slate
editor={editor} editor={editor}
@ -362,9 +374,18 @@ const RichTextEditor = ({
> >
<Editable <Editable
ref={textareaRef} ref={textareaRef}
decorate={decorate} // Pass the decorate function decorate={(entry) => {
// Skip decorate if content exceeds threshold
if (
currentPrompt.length > largeContentThreshold ||
!currentPrompt.length
)
return []
return decorate(entry)
}}
renderLeaf={renderLeaf} // Pass the renderLeaf function renderLeaf={renderLeaf} // Pass the renderLeaf function
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onPaste={handlePaste} // Add the custom paste handler
className={twMerge( className={twMerge(
className, className,
disabled && disabled &&

View File

@ -337,7 +337,7 @@ const ChatInput = () => {
{activeSettingInputBox && ( {activeSettingInputBox && (
<div <div
className={twMerge( className={twMerge(
'absolute bottom-[6px] left-[1px] flex w-[calc(100%-10px)] items-center justify-between rounded-b-lg bg-[hsla(var(--center-panel-bg))] p-3 pr-0', 'absolute bottom-[5px] left-[1px] flex w-[calc(100%-10px)] items-center justify-between rounded-b-lg bg-[hsla(var(--center-panel-bg))] p-3 pr-0',
!activeThread && 'bg-transparent', !activeThread && 'bg-transparent',
stateModel.loading && 'bg-transparent' stateModel.loading && 'bg-transparent'
)} )}