fix: chat agent mode content copy (#2418)

This commit is contained in:
zxhlyh 2024-02-07 21:23:47 +08:00 committed by GitHub
parent 71e5828d41
commit 1b04382a9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 166 additions and 133 deletions

View File

@ -1,23 +1,24 @@
import type { FC } from 'react'
import { memo } from 'react'
import type {
ChatItem,
VisionFile,
} from '../../types'
import { useChatContext } from '../context'
import { Markdown } from '@/app/components/base/markdown'
import Thought from '@/app/components/app/chat/thought'
import ImageGallery from '@/app/components/base/image-gallery'
import type { Emoji } from '@/app/components/tools/types'
type AgentContentProps = {
item: ChatItem
responsing?: boolean
allToolIcons?: Record<string, string | Emoji>
}
const AgentContent: FC<AgentContentProps> = ({
item,
responsing,
allToolIcons,
}) => {
const {
allToolIcons,
isResponsing,
} = useChatContext()
const {
annotation,
agent_thoughts,
@ -45,7 +46,7 @@ const AgentContent: FC<AgentContentProps> = ({
<Thought
thought={thought}
allToolIcons={allToolIcons || {}}
isFinished={!!thought.observation || !isResponsing}
isFinished={!!thought.observation || !responsing}
/>
)}
@ -58,4 +59,4 @@ const AgentContent: FC<AgentContentProps> = ({
)
}
export default AgentContent
export default memo(AgentContent)

View File

@ -1,4 +1,5 @@
import type { FC } from 'react'
import { memo } from 'react'
import type { ChatItem } from '../../types'
import { Markdown } from '@/app/components/base/markdown'
@ -19,4 +20,4 @@ const BasicContent: FC<BasicContentProps> = ({
return <Markdown content={content} />
}
export default BasicContent
export default memo(BasicContent)

View File

@ -1,8 +1,13 @@
import type { FC } from 'react'
import type {
FC,
ReactNode,
} from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { useChatContext } from '../context'
import { useCurrentAnswerIsResponsing } from '../hooks'
import type {
ChatConfig,
ChatItem,
} from '../../types'
import Operation from './operation'
import AgentContent from './agent-content'
import BasicContent from './basic-content'
@ -12,23 +17,27 @@ import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/gen
import LoadingAnim from '@/app/components/app/chat/loading-anim'
import Citation from '@/app/components/app/chat/citation'
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
import type { Emoji } from '@/app/components/tools/types'
type AnswerProps = {
item: ChatItem
question: string
index: number
config?: ChatConfig
answerIcon?: ReactNode
responsing?: boolean
allToolIcons?: Record<string, string | Emoji>
}
const Answer: FC<AnswerProps> = ({
item,
question,
index,
config,
answerIcon,
responsing,
allToolIcons,
}) => {
const { t } = useTranslation()
const {
config,
answerIcon,
} = useChatContext()
const responsing = useCurrentAnswerIsResponsing(item.id)
const {
content,
citation,
@ -83,7 +92,11 @@ const Answer: FC<AnswerProps> = ({
}
{
hasAgentThoughts && (
<AgentContent item={item} />
<AgentContent
item={item}
responsing={responsing}
allToolIcons={allToolIcons}
/>
)
}
{
@ -108,4 +121,4 @@ const Answer: FC<AnswerProps> = ({
)
}
export default Answer
export default memo(Answer)

View File

@ -1,4 +1,5 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { formatNumber } from '@/utils/format'
@ -42,4 +43,4 @@ const More: FC<MoreProps> = ({
)
}
export default More
export default memo(More)

View File

@ -1,8 +1,11 @@
import type { FC } from 'react'
import { useState } from 'react'
import {
memo,
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { useCurrentAnswerIsResponsing } from '../hooks'
import { useChatContext } from '../context'
import CopyBtn from '@/app/components/app/chat/copy-btn'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
@ -34,17 +37,24 @@ const Operation: FC<OperationProps> = ({
onFeedback,
} = useChatContext()
const [isShowReplyModal, setIsShowReplyModal] = useState(false)
const responsing = useCurrentAnswerIsResponsing(item.id)
const {
id,
isOpeningStatement,
content,
content: messageContent,
annotation,
feedback,
agent_thoughts,
} = item
const hasAnnotation = !!annotation?.id
const [localFeedback, setLocalFeedback] = useState(feedback)
const content = useMemo(() => {
if (agent_thoughts?.length)
return agent_thoughts.reduce((acc, cur) => acc + cur.thought, '')
return messageContent
}, [agent_thoughts, messageContent])
const handleFeedback = async (rating: 'like' | 'dislike' | null) => {
if (!config?.supportFeedback || !onFeedback)
return
@ -56,7 +66,7 @@ const Operation: FC<OperationProps> = ({
return (
<div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'>
{
!isOpeningStatement && !responsing && (
!isOpeningStatement && (
<CopyBtn
value={content}
className='hidden group-hover:block'
@ -159,4 +169,4 @@ const Operation: FC<OperationProps> = ({
)
}
export default Operation
export default memo(Operation)

View File

@ -1,4 +1,5 @@
import type { FC } from 'react'
import { memo } from 'react'
import type { ChatItem } from '../../types'
import { useChatContext } from '../context'
@ -32,4 +33,4 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
)
}
export default SuggestedQuestions
export default memo(SuggestedQuestions)

View File

@ -1,5 +1,6 @@
import type { FC } from 'react'
import {
memo,
useRef,
useState,
} from 'react'
@ -126,100 +127,102 @@ const ChatInput: FC<ChatInputProps> = ({
)
return (
<div
className={`
relative p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
${isDragActive && 'border-primary-600'}
`}
>
{
visionConfig?.enabled && (
<>
<div className='absolute bottom-2 left-2 flex items-center'>
<ChatImageUploader
settings={visionConfig}
onUpload={onUpload}
disabled={files.length >= visionConfig.number_limits}
/>
<div className='mx-1 w-[1px] h-4 bg-black/5' />
</div>
<div className='pl-[52px]'>
<ImageList
list={files}
onRemove={onRemove}
onReUpload={onReUpload}
onImageLinkLoadSuccess={onImageLinkLoadSuccess}
onImageLinkLoadError={onImageLinkLoadError}
/>
</div>
</>
)
}
<Textarea
<div className='relative'>
<div
className={`
block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
${visionConfig?.enabled && 'pl-12'}
p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
${isDragActive && 'border-primary-600'}
`}
value={query}
onChange={handleContentChange}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onPaste={onPaste}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
autoSize
/>
<div className='absolute bottom-[7px] right-2 flex items-center h-8'>
<div className='flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'>
{query.trim().length}
</div>
>
{
query
? (
<div className='flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
<XCircle className='w-4 h-4 text-[#98A2B3]' />
visionConfig?.enabled && (
<>
<div className='absolute bottom-2 left-2 flex items-center'>
<ChatImageUploader
settings={visionConfig}
onUpload={onUpload}
disabled={files.length >= visionConfig.number_limits}
/>
<div className='mx-1 w-[1px] h-4 bg-black/5' />
</div>
)
: speechToTextConfig?.enabled
<div className='pl-[52px]'>
<ImageList
list={files}
onRemove={onRemove}
onReUpload={onReUpload}
onImageLinkLoadSuccess={onImageLinkLoadSuccess}
onImageLinkLoadError={onImageLinkLoadError}
/>
</div>
</>
)
}
<Textarea
className={`
block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
${visionConfig?.enabled && 'pl-12'}
`}
value={query}
onChange={handleContentChange}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onPaste={onPaste}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
autoSize
/>
<div className='absolute bottom-[7px] right-2 flex items-center h-8'>
<div className='flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'>
{query.trim().length}
</div>
{
query
? (
<div
className='group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
onClick={handleVoiceInputShow}
>
<Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
<Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
<div className='flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
<XCircle className='w-4 h-4 text-[#98A2B3]' />
</div>
)
: null
: speechToTextConfig?.enabled
? (
<div
className='group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
onClick={handleVoiceInputShow}
>
<Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
<Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
</div>
)
: null
}
<div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
{isMobile
? sendBtn
: (
<TooltipPlus
popupContent={
<div>
<div>{t('common.operation.send')} Enter</div>
<div>{t('common.operation.lineBreak')} Shift Enter</div>
</div>
}
>
{sendBtn}
</TooltipPlus>
)}
</div>
{
voiceInputShow && (
<VoiceInput
onCancel={() => setVoiceInputShow(false)}
onConverted={text => setQuery(text)}
/>
)
}
<div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
{isMobile
? sendBtn
: (
<TooltipPlus
popupContent={
<div>
<div>{t('common.operation.send')} Enter</div>
<div>{t('common.operation.lineBreak')} Shift Enter</div>
</div>
}
>
{sendBtn}
</TooltipPlus>
)}
</div>
{
voiceInputShow && (
<VoiceInput
onCancel={() => setVoiceInputShow(false)}
onConverted={text => setQuery(text)}
/>
)
}
</div>
)
}
export default ChatInput
export default memo(ChatInput)

View File

@ -14,7 +14,6 @@ import type {
PromptVariable,
VisionFile,
} from '../types'
import { useChatContext } from './context'
import { TransferMethod } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast'
import { ssePost } from '@/service/base'
@ -507,14 +506,3 @@ export const useChat = (
handleAnnotationRemoved,
}
}
export const useCurrentAnswerIsResponsing = (answerId: string) => {
const {
isResponsing,
chatList,
} = useChatContext()
const isLast = answerId === chatList[chatList.length - 1]?.id
return isLast && isResponsing
}

View File

@ -140,12 +140,17 @@ const Chat: FC<ChatProps> = ({
{
chatList.map((item, index) => {
if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1]?.id
return (
<Answer
key={item.id}
item={item}
question={chatList[index - 1]?.content}
index={index}
config={config}
answerIcon={answerIcon}
responsing={isLast && isResponsing}
allToolIcons={allToolIcons}
/>
)
}
@ -153,6 +158,9 @@ const Chat: FC<ChatProps> = ({
<Question
key={item.id}
item={item}
showPromptLog={showPromptLog}
questionIcon={questionIcon}
isResponsing={isResponsing}
/>
)
})

View File

@ -1,7 +1,12 @@
import type { FC } from 'react'
import { useRef } from 'react'
import type {
FC,
ReactNode,
} from 'react'
import {
memo,
useRef,
} from 'react'
import type { ChatItem } from '../types'
import { useChatContext } from './context'
import { QuestionTriangle } from '@/app/components/base/icons/src/vender/solid/general'
import { User } from '@/app/components/base/icons/src/public/avatar'
import Log from '@/app/components/app/chat/log'
@ -10,16 +15,17 @@ import ImageGallery from '@/app/components/base/image-gallery'
type QuestionProps = {
item: ChatItem
showPromptLog?: boolean
questionIcon?: ReactNode
isResponsing?: boolean
}
const Question: FC<QuestionProps> = ({
item,
showPromptLog,
isResponsing,
questionIcon,
}) => {
const ref = useRef(null)
const {
showPromptLog,
isResponsing,
questionIcon,
} = useChatContext()
const {
content,
message_files,
@ -59,4 +65,4 @@ const Question: FC<QuestionProps> = ({
)
}
export default Question
export default memo(Question)

View File

@ -1,4 +1,5 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { OnSend } from '../types'
import { Star04 } from '@/app/components/base/icons/src/vender/solid/shapes'
@ -51,4 +52,4 @@ const TryToAsk: FC<TryToAskProps> = ({
)
}
export default TryToAsk
export default memo(TryToAsk)