mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-06-04 11:14:10 +08:00
feat: edit question in Chat (#17961)
This commit is contained in:
parent
5dd9acbe44
commit
438463b1c4
@ -21,6 +21,7 @@ import { useFeatures } from '@/app/components/base/features/hooks'
|
|||||||
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
||||||
import type { InputForm } from '@/app/components/base/chat/chat/type'
|
import type { InputForm } from '@/app/components/base/chat/chat/type'
|
||||||
import { canFindTool } from '@/utils'
|
import { canFindTool } from '@/utils'
|
||||||
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
|
|
||||||
type DebugWithSingleModelProps = {
|
type DebugWithSingleModelProps = {
|
||||||
checkCanSend?: () => boolean
|
checkCanSend?: () => boolean
|
||||||
@ -125,10 +126,14 @@ const DebugWithSingleModel = (
|
|||||||
)
|
)
|
||||||
}, [appId, chatList, checkCanSend, completionParams, config, handleSend, inputs, modelConfig.mode, modelConfig.model_id, modelConfig.provider, textGenerationModelList])
|
}, [appId, chatList, checkCanSend, completionParams, config, handleSend, inputs, modelConfig.mode, modelConfig.model_id, modelConfig.provider, textGenerationModelList])
|
||||||
|
|
||||||
const doRegenerate = useCallback((chatItem: ChatItemInTree) => {
|
const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||||
const question = chatList.find(item => item.id === chatItem.parentMessageId)!
|
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||||
doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||||
|
editedQuestion ? editedQuestion.files : question.message_files,
|
||||||
|
true,
|
||||||
|
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||||
|
)
|
||||||
}, [chatList, doSend])
|
}, [chatList, doSend])
|
||||||
|
|
||||||
const allToolIcons = useMemo(() => {
|
const allToolIcons = useMemo(() => {
|
||||||
|
@ -22,6 +22,7 @@ import AnswerIcon from '@/app/components/base/answer-icon'
|
|||||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import type { FileEntity } from '../../file-uploader/types'
|
||||||
|
|
||||||
const ChatWrapper = () => {
|
const ChatWrapper = () => {
|
||||||
const {
|
const {
|
||||||
@ -139,22 +140,16 @@ const ChatWrapper = () => {
|
|||||||
isPublicAPI: !isInstalledApp,
|
isPublicAPI: !isInstalledApp,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}, [
|
}, [chatList, handleNewConversationCompleted, handleSend, currentConversationId, currentConversationInputs, newConversationInputs, isInstalledApp, appId])
|
||||||
chatList,
|
|
||||||
handleNewConversationCompleted,
|
|
||||||
handleSend,
|
|
||||||
currentConversationId,
|
|
||||||
currentConversationItem,
|
|
||||||
currentConversationInputs,
|
|
||||||
newConversationInputs,
|
|
||||||
isInstalledApp,
|
|
||||||
appId,
|
|
||||||
])
|
|
||||||
|
|
||||||
const doRegenerate = useCallback((chatItem: ChatItemInTree) => {
|
const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||||
const question = chatList.find(item => item.id === chatItem.parentMessageId)!
|
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||||
doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||||
|
editedQuestion ? editedQuestion.files : question.message_files,
|
||||||
|
true,
|
||||||
|
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||||
|
)
|
||||||
}, [chatList, doSend])
|
}, [chatList, doSend])
|
||||||
|
|
||||||
const messageList = useMemo(() => {
|
const messageList = useMemo(() => {
|
||||||
|
@ -2,7 +2,7 @@ import type {
|
|||||||
FC,
|
FC,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { memo, useEffect, useRef, useState } from 'react'
|
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type {
|
import type {
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
@ -19,9 +19,9 @@ import Citation from '@/app/components/base/chat/chat/citation'
|
|||||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||||
import type { AppData } from '@/models/share'
|
import type { AppData } from '@/models/share'
|
||||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { FileList } from '@/app/components/base/file-uploader'
|
import { FileList } from '@/app/components/base/file-uploader'
|
||||||
|
import ContentSwitch from '../content-switch'
|
||||||
|
|
||||||
type AnswerProps = {
|
type AnswerProps = {
|
||||||
item: ChatItem
|
item: ChatItem
|
||||||
@ -100,12 +100,19 @@ const Answer: FC<AnswerProps> = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
|
||||||
|
if (direction === 'prev')
|
||||||
|
item.prevSibling && switchSibling?.(item.prevSibling)
|
||||||
|
else
|
||||||
|
item.nextSibling && switchSibling?.(item.nextSibling)
|
||||||
|
}, [switchSibling, item.prevSibling, item.nextSibling])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mb-2 flex last:mb-0'>
|
<div className='mb-2 flex last:mb-0'>
|
||||||
<div className='relative h-10 w-10 shrink-0'>
|
<div className='relative h-10 w-10 shrink-0'>
|
||||||
{answerIcon || <AnswerIcon />}
|
{answerIcon || <AnswerIcon />}
|
||||||
{responding && (
|
{responding && (
|
||||||
<div className='absolute -left-[3px] -top-[3px] flex h-4 w-4 items-center rounded-full border-[0.5px] border-divider-subtle bg-background-section-burn pl-[6px] shadow-xs'>
|
<div className='absolute left-[-3px] top-[-3px] flex h-4 w-4 items-center rounded-full border-[0.5px] border-divider-subtle bg-background-section-burn pl-[6px] shadow-xs'>
|
||||||
<LoadingAnim type='avatar' />
|
<LoadingAnim type='avatar' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -208,23 +215,17 @@ const Answer: FC<AnswerProps> = ({
|
|||||||
<Citation data={citation} showHitInfo={config?.supportCitationHitInfo} />
|
<Citation data={citation} showHitInfo={config?.supportCitationHitInfo} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{item.siblingCount && item.siblingCount > 1 && item.siblingIndex !== undefined && <div className="flex items-center justify-center pt-3.5 text-sm">
|
{
|
||||||
<button
|
item.siblingCount && item.siblingCount > 1 && item.siblingIndex !== undefined && (
|
||||||
className={`${item.prevSibling ? 'opacity-100' : 'opacity-30'}`}
|
<ContentSwitch
|
||||||
disabled={!item.prevSibling}
|
count={item.siblingCount}
|
||||||
onClick={() => item.prevSibling && switchSibling?.(item.prevSibling)}
|
currentIndex={item.siblingIndex}
|
||||||
>
|
prevDisabled={!item.prevSibling}
|
||||||
<ChevronRight className="h-[14px] w-[14px] rotate-180 text-text-primary" />
|
nextDisabled={!item.nextSibling}
|
||||||
</button>
|
switchSibling={handleSwitchSibling}
|
||||||
<span className="px-2 text-xs text-text-primary">{item.siblingIndex + 1} / {item.siblingCount}</span>
|
/>
|
||||||
<button
|
)
|
||||||
className={`${item.nextSibling ? 'opacity-100' : 'opacity-30'}`}
|
}
|
||||||
disabled={!item.nextSibling}
|
|
||||||
onClick={() => item.nextSibling && switchSibling?.(item.nextSibling)}
|
|
||||||
>
|
|
||||||
<ChevronRight className="h-[14px] w-[14px] text-text-primary" />
|
|
||||||
</button>
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<More more={more} />
|
<More more={more} />
|
||||||
|
39
web/app/components/base/chat/chat/content-switch.tsx
Normal file
39
web/app/components/base/chat/chat/content-switch.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { ChevronRight } from '../../icons/src/vender/line/arrows'
|
||||||
|
|
||||||
|
export default function ContentSwitch({
|
||||||
|
count,
|
||||||
|
currentIndex,
|
||||||
|
prevDisabled,
|
||||||
|
nextDisabled,
|
||||||
|
switchSibling,
|
||||||
|
}: {
|
||||||
|
count?: number
|
||||||
|
currentIndex?: number
|
||||||
|
prevDisabled: boolean
|
||||||
|
nextDisabled: boolean
|
||||||
|
switchSibling: (direction: 'prev' | 'next') => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
count && count > 1 && currentIndex !== undefined && (
|
||||||
|
<div className="flex items-center justify-center pt-3.5 text-sm">
|
||||||
|
<button
|
||||||
|
className={`${prevDisabled ? 'opacity-30' : 'opacity-100'}`}
|
||||||
|
disabled={prevDisabled}
|
||||||
|
onClick={() => !prevDisabled && switchSibling('prev')}
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-[14px] w-[14px] rotate-180 text-text-primary" />
|
||||||
|
</button>
|
||||||
|
<span className="px-2 text-xs text-text-primary">
|
||||||
|
{currentIndex + 1} / {count}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className={`${nextDisabled ? 'opacity-30' : 'opacity-100'}`}
|
||||||
|
disabled={nextDisabled}
|
||||||
|
onClick={() => !nextDisabled && switchSibling('next')}
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-[14px] w-[14px] text-text-primary" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -208,7 +208,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sidebarCollapseState)
|
if (!sidebarCollapseState)
|
||||||
setTimeout(() => handleWindowResize(), 200)
|
setTimeout(() => handleWindowResize(), 200)
|
||||||
}, [sidebarCollapseState])
|
}, [handleWindowResize, sidebarCollapseState])
|
||||||
|
|
||||||
const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend
|
const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend
|
||||||
|
|
||||||
@ -265,6 +265,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
item={item}
|
item={item}
|
||||||
questionIcon={questionIcon}
|
questionIcon={questionIcon}
|
||||||
theme={themeBuilder?.theme}
|
theme={themeBuilder?.theme}
|
||||||
|
switchSibling={switchSibling}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -4,46 +4,137 @@ import type {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import type { ChatItem } from '../types'
|
import type { ChatItem } from '../types'
|
||||||
import type { Theme } from '../embedded-chatbot/theme/theme-context'
|
import type { Theme } from '../embedded-chatbot/theme/theme-context'
|
||||||
import { CssTransform } from '../embedded-chatbot/theme/utils'
|
import { CssTransform } from '../embedded-chatbot/theme/utils'
|
||||||
|
import ContentSwitch from './content-switch'
|
||||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
import { FileList } from '@/app/components/base/file-uploader'
|
import { FileList } from '@/app/components/base/file-uploader'
|
||||||
|
import ActionButton from '../../action-button'
|
||||||
|
import { RiClipboardLine, RiEditLine } from '@remixicon/react'
|
||||||
|
import Toast from '../../toast'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import Textarea from 'react-textarea-autosize'
|
||||||
|
import Button from '../../button'
|
||||||
|
import { useChatContext } from './context'
|
||||||
|
|
||||||
type QuestionProps = {
|
type QuestionProps = {
|
||||||
item: ChatItem
|
item: ChatItem
|
||||||
questionIcon?: ReactNode
|
questionIcon?: ReactNode
|
||||||
theme: Theme | null | undefined
|
theme: Theme | null | undefined
|
||||||
|
switchSibling?: (siblingMessageId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Question: FC<QuestionProps> = ({
|
const Question: FC<QuestionProps> = ({
|
||||||
item,
|
item,
|
||||||
questionIcon,
|
questionIcon,
|
||||||
theme,
|
theme,
|
||||||
|
switchSibling,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
content,
|
content,
|
||||||
message_files,
|
message_files,
|
||||||
} = item
|
} = item
|
||||||
|
|
||||||
|
const {
|
||||||
|
onRegenerate,
|
||||||
|
} = useChatContext()
|
||||||
|
|
||||||
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
const [editedContent, setEditedContent] = useState(content)
|
||||||
|
|
||||||
|
const handleEdit = useCallback(() => {
|
||||||
|
setIsEditing(true)
|
||||||
|
setEditedContent(content)
|
||||||
|
}, [content])
|
||||||
|
|
||||||
|
const handleResend = useCallback(() => {
|
||||||
|
setIsEditing(false)
|
||||||
|
onRegenerate?.(item, { message: editedContent, files: message_files })
|
||||||
|
}, [editedContent, message_files, item, onRegenerate])
|
||||||
|
|
||||||
|
const handleCancelEditing = useCallback(() => {
|
||||||
|
setIsEditing(false)
|
||||||
|
setEditedContent(content)
|
||||||
|
}, [content])
|
||||||
|
|
||||||
|
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
|
||||||
|
if (direction === 'prev')
|
||||||
|
item.prevSibling && switchSibling?.(item.prevSibling)
|
||||||
|
else
|
||||||
|
item.nextSibling && switchSibling?.(item.nextSibling)
|
||||||
|
}, [switchSibling, item.prevSibling, item.nextSibling])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mb-2 flex justify-end pl-14 last:mb-0'>
|
<div className='mb-2 flex justify-end pl-14 last:mb-0'>
|
||||||
<div className='group relative mr-4 max-w-full'>
|
<div className={cn('group relative mr-4 flex max-w-full items-start', isEditing && 'flex-1')}>
|
||||||
|
<div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}>
|
||||||
|
<div className="
|
||||||
|
absolutegap-0.5 hidden rounded-[10px] border-[0.5px] border-components-actionbar-border
|
||||||
|
bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex
|
||||||
|
">
|
||||||
|
<ActionButton onClick={() => {
|
||||||
|
copy(content)
|
||||||
|
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||||
|
}}>
|
||||||
|
<RiClipboardLine className='h-4 w-4' />
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton onClick={handleEdit}>
|
||||||
|
<RiEditLine className='h-4 w-4' />
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className='rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900'
|
className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900'
|
||||||
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
|
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!!message_files?.length && (
|
!!message_files?.length && (
|
||||||
<FileList
|
<FileList
|
||||||
|
className='mb-2'
|
||||||
files={message_files}
|
files={message_files}
|
||||||
showDeleteAction={false}
|
showDeleteAction={false}
|
||||||
showDownloadAction={true}
|
showDownloadAction={true}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Markdown content={content} />
|
{ !isEditing
|
||||||
|
? <Markdown content={content} />
|
||||||
|
: <div className="
|
||||||
|
flex flex-col gap-2 rounded-xl
|
||||||
|
border border-components-chat-input-border bg-components-panel-bg-blur p-[9px] shadow-md
|
||||||
|
">
|
||||||
|
<div className="max-h-[158px] overflow-y-auto overflow-x-hidden">
|
||||||
|
<Textarea
|
||||||
|
className={cn(
|
||||||
|
'body-lg-regular w-full p-1 leading-6 text-text-tertiary outline-none',
|
||||||
|
)}
|
||||||
|
autoFocus
|
||||||
|
minRows={1}
|
||||||
|
value={editedContent}
|
||||||
|
onChange={e => setEditedContent(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button variant='ghost' onClick={handleCancelEditing}>{t('common.operation.cancel')}</Button>
|
||||||
|
<Button variant='primary' onClick={handleResend}>{t('common.chat.resend')}</Button>
|
||||||
|
</div>
|
||||||
|
</div> }
|
||||||
|
{ !isEditing && <ContentSwitch
|
||||||
|
count={item.siblingCount}
|
||||||
|
currentIndex={item.siblingIndex}
|
||||||
|
prevDisabled={!item.prevSibling}
|
||||||
|
nextDisabled={!item.nextSibling}
|
||||||
|
switchSibling={handleSwitchSibling}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-1 h-[18px]' />
|
<div className='mt-1 h-[18px]' />
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,6 +24,7 @@ import AnswerIcon from '@/app/components/base/answer-icon'
|
|||||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import type { FileEntity } from '../../file-uploader/types'
|
||||||
|
|
||||||
const ChatWrapper = () => {
|
const ChatWrapper = () => {
|
||||||
const {
|
const {
|
||||||
@ -140,10 +141,14 @@ const ChatWrapper = () => {
|
|||||||
)
|
)
|
||||||
}, [currentConversationId, currentConversationInputs, newConversationInputs, chatList, handleSend, isInstalledApp, appId, handleNewConversationCompleted])
|
}, [currentConversationId, currentConversationInputs, newConversationInputs, chatList, handleSend, isInstalledApp, appId, handleNewConversationCompleted])
|
||||||
|
|
||||||
const doRegenerate = useCallback((chatItem: ChatItemInTree) => {
|
const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||||
const question = chatList.find(item => item.id === chatItem.parentMessageId)!
|
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||||
doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||||
|
editedQuestion ? editedQuestion.files : question.message_files,
|
||||||
|
true,
|
||||||
|
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||||
|
)
|
||||||
}, [chatList, doSend])
|
}, [chatList, doSend])
|
||||||
|
|
||||||
const messageList = useMemo(() => {
|
const messageList = useMemo(() => {
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
} from '@/service/debug'
|
} from '@/service/debug'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
||||||
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
|
|
||||||
type ChatWrapperProps = {
|
type ChatWrapperProps = {
|
||||||
showConversationVariableModal: boolean
|
showConversationVariableModal: boolean
|
||||||
@ -94,10 +95,14 @@ const ChatWrapper = (
|
|||||||
)
|
)
|
||||||
}, [handleSend, workflowStore, conversationId, chatList, appDetail])
|
}, [handleSend, workflowStore, conversationId, chatList, appDetail])
|
||||||
|
|
||||||
const doRegenerate = useCallback((chatItem: ChatItemInTree) => {
|
const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||||
const question = chatList.find(item => item.id === chatItem.parentMessageId)!
|
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||||
doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||||
|
editedQuestion ? editedQuestion.files : question.message_files,
|
||||||
|
true,
|
||||||
|
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||||
|
)
|
||||||
}, [chatList, doSend])
|
}, [chatList, doSend])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
|
@ -562,6 +562,7 @@ const translation = {
|
|||||||
inputPlaceholder: 'Talk to Bot',
|
inputPlaceholder: 'Talk to Bot',
|
||||||
thinking: 'Thinking...',
|
thinking: 'Thinking...',
|
||||||
thought: 'Thought',
|
thought: 'Thought',
|
||||||
|
resend: 'Resend',
|
||||||
},
|
},
|
||||||
promptEditor: {
|
promptEditor: {
|
||||||
placeholder: 'Write your prompt word here, enter \'{\' to insert a variable, enter \'/\' to insert a prompt content block',
|
placeholder: 'Write your prompt word here, enter \'{\' to insert a variable, enter \'/\' to insert a prompt content block',
|
||||||
|
@ -562,6 +562,7 @@ const translation = {
|
|||||||
inputPlaceholder: '和机器人聊天',
|
inputPlaceholder: '和机器人聊天',
|
||||||
thinking: '深度思考中...',
|
thinking: '深度思考中...',
|
||||||
thought: '已深度思考',
|
thought: '已深度思考',
|
||||||
|
resend: '重新发送',
|
||||||
},
|
},
|
||||||
promptEditor: {
|
promptEditor: {
|
||||||
placeholder: '在这里写你的提示词,输入\'{\' 插入变量、输入\'/\' 插入提示内容块',
|
placeholder: '在这里写你的提示词,输入\'{\' 插入变量、输入\'/\' 插入提示内容块',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user