Unverified Commit f2b2effc authored by zxhlyh's avatar zxhlyh Committed by GitHub

fix: typing delay (#2200)

parent 301e0496
...@@ -43,6 +43,7 @@ const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) = ...@@ -43,6 +43,7 @@ const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) =
} }
export type IAnswerProps = { export type IAnswerProps = {
item: IChatItem item: IChatItem
index: number
feedbackDisabled: boolean feedbackDisabled: boolean
isHideFeedbackEdit: boolean isHideFeedbackEdit: boolean
onQueryChange: (query: string) => void onQueryChange: (query: string) => void
...@@ -59,14 +60,15 @@ export type IAnswerProps = { ...@@ -59,14 +60,15 @@ export type IAnswerProps = {
supportAnnotation?: boolean supportAnnotation?: boolean
appId?: string appId?: string
question: string question: string
onAnnotationEdited?: (question: string, answer: string) => void onAnnotationEdited?: (question: string, answer: string, index: number) => void
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string) => void onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
onAnnotationRemoved?: () => void onAnnotationRemoved?: (index: number) => void
allToolIcons?: Record<string, string | Emoji> allToolIcons?: Record<string, string | Emoji>
} }
// The component needs to maintain its own state to control whether to display input component // The component needs to maintain its own state to control whether to display input component
const Answer: FC<IAnswerProps> = ({ const Answer: FC<IAnswerProps> = ({
item, item,
index,
onQueryChange, onQueryChange,
feedbackDisabled = false, feedbackDisabled = false,
isHideFeedbackEdit = false, isHideFeedbackEdit = false,
...@@ -340,9 +342,9 @@ const Answer: FC<IAnswerProps> = ({ ...@@ -340,9 +342,9 @@ const Answer: FC<IAnswerProps> = ({
cached={hasAnnotation} cached={hasAnnotation}
query={question} query={question}
answer={content} answer={content}
onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content)} onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)}
onEdit={() => setIsShowReplyModal(true)} onEdit={() => setIsShowReplyModal(true)}
onRemoved={onAnnotationRemoved!} onRemoved={() => onAnnotationRemoved!(index)}
/> />
)} )}
...@@ -351,8 +353,8 @@ const Answer: FC<IAnswerProps> = ({ ...@@ -351,8 +353,8 @@ const Answer: FC<IAnswerProps> = ({
onHide={() => setIsShowReplyModal(false)} onHide={() => setIsShowReplyModal(false)}
query={question} query={question}
answer={content} answer={content}
onEdited={onAnnotationEdited!} onEdited={(editedQuery, editedAnswer) => onAnnotationEdited!(editedQuery, editedAnswer, index)}
onAdded={onAnnotationAdded!} onAdded={(annotationId, authorName, editedQuery, editedAnswer) => onAnnotationAdded!(annotationId, authorName, editedQuery, editedAnswer, index)}
appId={appId!} appId={appId!}
messageId={id} messageId={id}
annotationId={annotation?.id || ''} annotationId={annotation?.id || ''}
......
'use client' 'use client'
import type { FC, ReactNode } from 'react' import type { FC, ReactNode } from 'react'
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import Textarea from 'rc-textarea' import Textarea from 'rc-textarea'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import cn from 'classnames' import cn from 'classnames'
...@@ -197,38 +197,11 @@ const Chat: FC<IChatProps> = ({ ...@@ -197,38 +197,11 @@ const Chat: FC<IChatProps> = ({
logError(t('common.voiceInput.notAllow')) logError(t('common.voiceInput.notAllow'))
}) })
} }
const handleQueryChangeFromAnswer = useCallback((val: string) => {
return (
<div className={cn('px-3.5', 'h-full')}>
{isShowConfigElem && (configElem || null)}
{/* Chat List */}
<div className={cn((isShowConfigElem && configElem) ? 'h-0' : 'h-full', 'space-y-[30px]')}>
{chatList.map((item, index) => {
if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id
const citation = item.citation
return <Answer
key={item.id}
item={item}
onQueryChange={(val) => {
onQueryChange(val) onQueryChange(val)
handleSend(val) handleSend(val)
}} }, [])
feedbackDisabled={feedbackDisabled} const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
isHideFeedbackEdit={isHideFeedbackEdit}
onFeedback={onFeedback}
displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast}
answerIcon={answerIcon}
citation={citation}
dataSets={dataSets}
isShowCitation={isShowCitation}
isShowCitationHitInfo={isShowCitationHitInfo}
isShowTextToSpeech={isShowTextToSpeech}
supportAnnotation={supportAnnotation}
appId={appId}
question={chatList[index - 1]?.content}
onAnnotationEdited={(query, answer) => {
onChatListChange?.(chatList.map((item, i) => { onChatListChange?.(chatList.map((item, i) => {
if (i === index - 1) { if (i === index - 1) {
return { return {
...@@ -248,8 +221,8 @@ const Chat: FC<IChatProps> = ({ ...@@ -248,8 +221,8 @@ const Chat: FC<IChatProps> = ({
} }
return item return item
})) }))
}} }, [])
onAnnotationAdded={(annotationId, authorName, query, answer) => { const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
onChatListChange?.(chatList.map((item, i) => { onChatListChange?.(chatList.map((item, i) => {
if (i === index - 1) { if (i === index - 1) {
return { return {
...@@ -278,8 +251,8 @@ const Chat: FC<IChatProps> = ({ ...@@ -278,8 +251,8 @@ const Chat: FC<IChatProps> = ({
} }
return item return item
})) }))
}} }, [])
onAnnotationRemoved={() => { const handleAnnotationRemoved = useCallback((index: number) => {
onChatListChange?.(chatList.map((item, i) => { onChatListChange?.(chatList.map((item, i) => {
if (i === index) { if (i === index) {
return { return {
...@@ -293,7 +266,39 @@ const Chat: FC<IChatProps> = ({ ...@@ -293,7 +266,39 @@ const Chat: FC<IChatProps> = ({
} }
return item return item
})) }))
}} }, [])
return (
<div className={cn('px-3.5', 'h-full')}>
{isShowConfigElem && (configElem || null)}
{/* Chat List */}
<div className={cn((isShowConfigElem && configElem) ? 'h-0' : 'h-full', 'space-y-[30px]')}>
{chatList.map((item, index) => {
if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id
const citation = item.citation
return <Answer
key={item.id}
item={item}
index={index}
onQueryChange={handleQueryChangeFromAnswer}
feedbackDisabled={feedbackDisabled}
isHideFeedbackEdit={isHideFeedbackEdit}
onFeedback={onFeedback}
displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast}
answerIcon={answerIcon}
citation={citation}
dataSets={dataSets}
isShowCitation={isShowCitation}
isShowCitationHitInfo={isShowCitationHitInfo}
isShowTextToSpeech={isShowTextToSpeech}
supportAnnotation={supportAnnotation}
appId={appId}
question={chatList[index - 1]?.content}
onAnnotationEdited={handleAnnotationEdited}
onAnnotationAdded={handleAnnotationAdded}
onAnnotationRemoved={handleAnnotationRemoved}
allToolIcons={allToolIcons} allToolIcons={allToolIcons}
/> />
} }
...@@ -307,8 +312,6 @@ const Chat: FC<IChatProps> = ({ ...@@ -307,8 +312,6 @@ const Chat: FC<IChatProps> = ({
item={item} item={item}
isShowPromptLog={isShowPromptLog} isShowPromptLog={isShowPromptLog}
isResponsing={isResponsing} isResponsing={isResponsing}
// ['https://placekitten.com/360/360', 'https://placekitten.com/360/640']
imgSrcs={(item.message_files && item.message_files?.length > 0) ? item.message_files.map(item => item.url) : []}
/> />
) )
})} })}
......
...@@ -13,14 +13,14 @@ import ImageGallery from '@/app/components/base/image-gallery' ...@@ -13,14 +13,14 @@ import ImageGallery from '@/app/components/base/image-gallery'
type IQuestionProps = Pick<IChatItem, 'id' | 'content' | 'more' | 'useCurrentUserAvatar'> & { type IQuestionProps = Pick<IChatItem, 'id' | 'content' | 'more' | 'useCurrentUserAvatar'> & {
isShowPromptLog?: boolean isShowPromptLog?: boolean
item: IChatItem item: IChatItem
imgSrcs?: string[]
isResponsing?: boolean isResponsing?: boolean
} }
const Question: FC<IQuestionProps> = ({ id, content, imgSrcs, more, useCurrentUserAvatar, isShowPromptLog, item, isResponsing }) => { const Question: FC<IQuestionProps> = ({ id, content, more, useCurrentUserAvatar, isShowPromptLog, item, isResponsing }) => {
const { userProfile } = useContext(AppContext) const { userProfile } = useContext(AppContext)
const userName = userProfile?.name const userName = userProfile?.name
const ref = useRef(null) const ref = useRef(null)
const imgSrcs = item.message_files?.map(item => item.url)
return ( return (
<div className={`flex items-start justify-end ${isShowPromptLog && 'first-of-type:pt-[14px]'}`} key={id} ref={ref}> <div className={`flex items-start justify-end ${isShowPromptLog && 'first-of-type:pt-[14px]'}`} key={id} ref={ref}>
......
import { useState } from 'react' import { useCallback, useState } from 'react'
import produce from 'immer' import produce from 'immer'
import { useGetState } from 'ahooks' import { useGetState } from 'ahooks'
import type { ConversationItem } from '@/models/share' import type { ConversationItem } from '@/models/share'
...@@ -11,7 +11,7 @@ function useConversation() { ...@@ -11,7 +11,7 @@ function useConversation() {
const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([]) const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1') const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
// when set conversation id, we do not have set appId // when set conversation id, we do not have set appId
const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => { const setCurrConversationId = useCallback((id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
doSetCurrConversationId(id) doSetCurrConversationId(id)
if (isSetToLocalStroge && id !== '-1') { if (isSetToLocalStroge && id !== '-1') {
// conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2} // conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
...@@ -19,7 +19,7 @@ function useConversation() { ...@@ -19,7 +19,7 @@ function useConversation() {
conversationIdInfo[appId] = id conversationIdInfo[appId] = id
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo)) globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo))
} }
} }, [doSetCurrConversationId])
const getConversationIdFromStorage = (appId: string) => { const getConversationIdFromStorage = (appId: string) => {
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {} const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
......
/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/no-use-before-define */
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import cn from 'classnames' import cn from 'classnames'
import useSWR from 'swr' import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
...@@ -65,6 +65,7 @@ const Main: FC<IMainProps> = ({ ...@@ -65,6 +65,7 @@ const Main: FC<IMainProps> = ({
installedAppInfo, installedAppInfo,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
...@@ -123,7 +124,8 @@ const Main: FC<IMainProps> = ({ ...@@ -123,7 +124,8 @@ const Main: FC<IMainProps> = ({
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([]) const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
const [hasMore, setHasMore] = useState<boolean>(true) const [hasMore, setHasMore] = useState<boolean>(true)
const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true) const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true)
const onMoreLoaded = ({ data: conversations, has_more }: any) => { const [isShowSuggestion, setIsShowSuggestion] = useState(false)
const onMoreLoaded = useCallback(({ data: conversations, has_more }: any) => {
setHasMore(has_more) setHasMore(has_more)
if (isClearConversationList) { if (isClearConversationList) {
setConversationList(conversations) setConversationList(conversations)
...@@ -132,8 +134,8 @@ const Main: FC<IMainProps> = ({ ...@@ -132,8 +134,8 @@ const Main: FC<IMainProps> = ({
else { else {
setConversationList([...conversationList, ...conversations]) setConversationList([...conversationList, ...conversations])
} }
} }, [conversationList, setConversationList, isClearConversationList, clearConversationListFalse])
const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => { const onPinnedMoreLoaded = useCallback(({ data: conversations, has_more }: any) => {
setHasPinnedMore(has_more) setHasPinnedMore(has_more)
if (isClearPinnedConversationList) { if (isClearPinnedConversationList) {
setPinnedConversationList(conversations) setPinnedConversationList(conversations)
...@@ -142,9 +144,9 @@ const Main: FC<IMainProps> = ({ ...@@ -142,9 +144,9 @@ const Main: FC<IMainProps> = ({
else { else {
setPinnedConversationList([...pinnedConversationList, ...conversations]) setPinnedConversationList([...pinnedConversationList, ...conversations])
} }
} }, [pinnedConversationList, setPinnedConversationList, isClearPinnedConversationList, clearPinnedConversationListFalse])
const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0) const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0)
const noticeUpdateList = () => { const noticeUpdateList = useCallback(() => {
setHasMore(true) setHasMore(true)
clearConversationListTrue() clearConversationListTrue()
...@@ -152,25 +154,25 @@ const Main: FC<IMainProps> = ({ ...@@ -152,25 +154,25 @@ const Main: FC<IMainProps> = ({
clearPinnedConversationListTrue() clearPinnedConversationListTrue()
setControlUpdateConversationList(Date.now()) setControlUpdateConversationList(Date.now())
} }, [clearConversationListTrue, clearPinnedConversationListTrue])
const handlePin = async (id: string) => { const handlePin = useCallback(async (id: string) => {
await pinConversation(isInstalledApp, installedAppInfo?.id, id) await pinConversation(isInstalledApp, installedAppInfo?.id, id)
notify({ type: 'success', message: t('common.api.success') }) notify({ type: 'success', message: t('common.api.success') })
noticeUpdateList() noticeUpdateList()
} }, [isInstalledApp, installedAppInfo?.id, t, notify, noticeUpdateList])
const handleUnpin = async (id: string) => { const handleUnpin = useCallback(async (id: string) => {
await unpinConversation(isInstalledApp, installedAppInfo?.id, id) await unpinConversation(isInstalledApp, installedAppInfo?.id, id)
notify({ type: 'success', message: t('common.api.success') }) notify({ type: 'success', message: t('common.api.success') })
noticeUpdateList() noticeUpdateList()
} }, [isInstalledApp, installedAppInfo?.id, t, notify, noticeUpdateList])
const [isShowConfirm, { setTrue: showConfirm, setFalse: hideConfirm }] = useBoolean(false) const [isShowConfirm, { setTrue: showConfirm, setFalse: hideConfirm }] = useBoolean(false)
const [toDeleteConversationId, setToDeleteConversationId] = useState('') const [toDeleteConversationId, setToDeleteConversationId] = useState('')
const handleDelete = (id: string) => { const handleDelete = useCallback((id: string) => {
setToDeleteConversationId(id) setToDeleteConversationId(id)
hideSidebar() // mobile hideSidebar() // mobile
showConfirm() showConfirm()
} }, [hideSidebar, showConfirm])
const didDelete = async () => { const didDelete = async () => {
await delConversation(isInstalledApp, installedAppInfo?.id, toDeleteConversationId) await delConversation(isInstalledApp, installedAppInfo?.id, toDeleteConversationId)
...@@ -186,17 +188,51 @@ const Main: FC<IMainProps> = ({ ...@@ -186,17 +188,51 @@ const Main: FC<IMainProps> = ({
const [speechToTextConfig, setSpeechToTextConfig] = useState<SpeechToTextConfig | null>(null) const [speechToTextConfig, setSpeechToTextConfig] = useState<SpeechToTextConfig | null>(null)
const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null) const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
const [citationConfig, setCitationConfig] = useState<CitationConfig | null>(null) const [citationConfig, setCitationConfig] = useState<CitationConfig | null>(null)
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
const chatListDomRef = useRef<HTMLDivElement>(null)
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
const [abortController, setAbortController] = useState<AbortController | null>(null)
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false) const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false) const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false)
const handleStartChat = (inputs: Record<string, any>) => { const conversationIntroduction = currConversationInfo?.introduction || ''
const createNewChat = useCallback(async () => {
// if new chat is already exist, do not create new chat
abortController?.abort()
setResponsingFalse()
if (conversationList.some(item => item.id === '-1'))
return
setConversationList(produce(conversationList, (draft) => {
draft.unshift({
id: '-1',
name: t('share.chat.newChatDefaultName'),
inputs: newConversationInputs,
introduction: conversationIntroduction,
})
}))
}, [
abortController,
setResponsingFalse,
setConversationList,
conversationList,
newConversationInputs,
conversationIntroduction,
t,
])
const handleStartChat = useCallback((inputs: Record<string, any>) => {
createNewChat() createNewChat()
setConversationIdChangeBecauseOfNew(true) setConversationIdChangeBecauseOfNew(true)
setCurrInputs(inputs) setCurrInputs(inputs)
setChatStarted() setChatStarted()
// parse variables in introduction // parse variables in introduction
setChatList(generateNewChatListWithOpenstatement('', inputs)) setChatList(generateNewChatListWithOpenstatement('', inputs))
} }, [
createNewChat,
setConversationIdChangeBecauseOfNew,
setCurrInputs,
setChatStarted,
setChatList,
])
const hasSetInputs = (() => { const hasSetInputs = (() => {
if (!isNewConversation) if (!isNewConversation)
return true return true
...@@ -205,7 +241,6 @@ const Main: FC<IMainProps> = ({ ...@@ -205,7 +241,6 @@ const Main: FC<IMainProps> = ({
})() })()
const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
const conversationIntroduction = currConversationInfo?.introduction || ''
const [controlChatUpdateAllConversation, setControlChatUpdateAllConversation] = useState(0) const [controlChatUpdateAllConversation, setControlChatUpdateAllConversation] = useState(0)
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576 // onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
...@@ -293,25 +328,9 @@ const Main: FC<IMainProps> = ({ ...@@ -293,25 +328,9 @@ const Main: FC<IMainProps> = ({
} }
useEffect(handleConversationSwitch, [currConversationId, inited]) useEffect(handleConversationSwitch, [currConversationId, inited])
const handleConversationIdChange = (id: string) => {
if (id === '-1') {
createNewChat()
setConversationIdChangeBecauseOfNew(true)
}
else {
setConversationIdChangeBecauseOfNew(false)
}
// trigger handleConversationSwitch
setCurrConversationId(id, appId)
setIsShowSuggestion(false)
hideSidebar()
}
/* /*
* chat info. chat is under conversation. * chat info. chat is under conversation.
*/ */
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
const chatListDomRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
// scroll to bottom // scroll to bottom
if (chatListDomRef.current) if (chatListDomRef.current)
...@@ -319,22 +338,27 @@ const Main: FC<IMainProps> = ({ ...@@ -319,22 +338,27 @@ const Main: FC<IMainProps> = ({
}, [chatList, currConversationId]) }, [chatList, currConversationId])
// user can not edit inputs if user had send message // user can not edit inputs if user had send message
const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
const createNewChat = async () => {
// if new chat is already exist, do not create new chat
abortController?.abort()
setResponsingFalse()
if (conversationList.some(item => item.id === '-1'))
return
setConversationList(produce(conversationList, (draft) => { const handleConversationIdChange = useCallback((id: string) => {
draft.unshift({ if (id === '-1') {
id: '-1', createNewChat()
name: t('share.chat.newChatDefaultName'), setConversationIdChangeBecauseOfNew(true)
inputs: newConversationInputs, }
introduction: conversationIntroduction, else {
}) setConversationIdChangeBecauseOfNew(false)
}))
} }
// trigger handleConversationSwitch
setCurrConversationId(id, appId)
setIsShowSuggestion(false)
hideSidebar()
}, [
appId,
createNewChat,
hideSidebar,
setCurrConversationId,
setIsShowSuggestion,
setConversationIdChangeBecauseOfNew,
])
// sometime introduction is not applied to state // sometime introduction is not applied to state
const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => { const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => {
...@@ -446,14 +470,11 @@ const Main: FC<IMainProps> = ({ ...@@ -446,14 +470,11 @@ const Main: FC<IMainProps> = ({
})() })()
}, []) }, [])
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const logError = useCallback((message: string) => {
const [abortController, setAbortController] = useState<AbortController | null>(null)
const { notify } = useContext(ToastContext)
const logError = (message: string) => {
notify({ type: 'error', message }) notify({ type: 'error', message })
} }, [notify])
const checkCanSend = () => { const checkCanSend = useCallback(() => {
if (currConversationId !== '-1') if (currConversationId !== '-1')
return true return true
...@@ -480,10 +501,9 @@ const Main: FC<IMainProps> = ({ ...@@ -480,10 +501,9 @@ const Main: FC<IMainProps> = ({
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
} }, [currConversationId, currInputs, promptConfig, t, logError])
const [controlFocus, setControlFocus] = useState(0) const [controlFocus, setControlFocus] = useState(0)
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
const doShowSuggestion = isShowSuggestion && !isResponsing const doShowSuggestion = isShowSuggestion && !isResponsing
const [openingSuggestedQuestions, setOpeningSuggestedQuestions] = useState<string[]>([]) const [openingSuggestedQuestions, setOpeningSuggestedQuestions] = useState<string[]>([])
const [messageTaskId, setMessageTaskId] = useState('') const [messageTaskId, setMessageTaskId] = useState('')
...@@ -755,7 +775,7 @@ const Main: FC<IMainProps> = ({ ...@@ -755,7 +775,7 @@ const Main: FC<IMainProps> = ({
}, isInstalledApp, installedAppInfo?.id) }, isInstalledApp, installedAppInfo?.id)
} }
const handleFeedback = async (messageId: string, feedback: Feedbacktype) => { const handleFeedback = useCallback(async (messageId: string, feedback: Feedbacktype) => {
await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id) await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id)
const newChatList = chatList.map((item) => { const newChatList = chatList.map((item) => {
if (item.id === messageId) { if (item.id === messageId) {
...@@ -768,7 +788,19 @@ const Main: FC<IMainProps> = ({ ...@@ -768,7 +788,19 @@ const Main: FC<IMainProps> = ({
}) })
setChatList(newChatList) setChatList(newChatList)
notify({ type: 'success', message: t('common.api.success') }) notify({ type: 'success', message: t('common.api.success') })
} }, [isInstalledApp, installedAppInfo?.id, chatList, t, notify, setChatList])
const handleListChanged = useCallback((list: ConversationItem[]) => {
setConversationList(list)
setControlChatUpdateAllConversation(Date.now())
}, [setConversationList, setControlChatUpdateAllConversation])
const handlePinnedListChanged = useCallback((list: ConversationItem[]) => {
setPinnedConversationList(list)
setControlChatUpdateAllConversation(Date.now())
}, [setPinnedConversationList, setControlChatUpdateAllConversation])
const handleStartChatOnSidebar = useCallback(() => {
handleConversationIdChange('-1')
}, [handleConversationIdChange])
const renderSidebar = () => { const renderSidebar = () => {
if (!appId || !siteInfo || !promptConfig) if (!appId || !siteInfo || !promptConfig)
...@@ -776,16 +808,10 @@ const Main: FC<IMainProps> = ({ ...@@ -776,16 +808,10 @@ const Main: FC<IMainProps> = ({
return ( return (
<Sidebar <Sidebar
list={conversationList} list={conversationList}
onListChanged={(list) => { onListChanged={handleListChanged}
setConversationList(list)
setControlChatUpdateAllConversation(Date.now())
}}
isClearConversationList={isClearConversationList} isClearConversationList={isClearConversationList}
pinnedList={pinnedConversationList} pinnedList={pinnedConversationList}
onPinnedListChanged={(list) => { onPinnedListChanged={handlePinnedListChanged}
setPinnedConversationList(list)
setControlChatUpdateAllConversation(Date.now())
}}
isClearPinnedConversationList={isClearPinnedConversationList} isClearPinnedConversationList={isClearPinnedConversationList}
onMoreLoaded={onMoreLoaded} onMoreLoaded={onMoreLoaded}
onPinnedMoreLoaded={onPinnedMoreLoaded} onPinnedMoreLoaded={onPinnedMoreLoaded}
...@@ -801,11 +827,17 @@ const Main: FC<IMainProps> = ({ ...@@ -801,11 +827,17 @@ const Main: FC<IMainProps> = ({
onUnpin={handleUnpin} onUnpin={handleUnpin}
controlUpdateList={controlUpdateConversationList} controlUpdateList={controlUpdateConversationList}
onDelete={handleDelete} onDelete={handleDelete}
onStartChat={() => handleConversationIdChange('-1')} onStartChat={handleStartChatOnSidebar}
/> />
) )
} }
const handleAbortResponsing = useCallback(async () => {
await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
setHasStopResponded(true)
setResponsingFalse()
}, [appId, messageTaskId, isInstalledApp, installedAppInfo?.id])
if (appUnavailable) if (appUnavailable)
return <AppUnavailable isUnknwonReason={isUnknwonReason} /> return <AppUnavailable isUnknwonReason={isUnknwonReason} />
...@@ -824,7 +856,7 @@ const Main: FC<IMainProps> = ({ ...@@ -824,7 +856,7 @@ const Main: FC<IMainProps> = ({
icon_background={siteInfo.icon_background} icon_background={siteInfo.icon_background}
isMobile={isMobile} isMobile={isMobile}
onShowSideBar={showSidebar} onShowSideBar={showSidebar}
onCreateNewChat={() => handleConversationIdChange('-1')} onCreateNewChat={handleStartChatOnSidebar}
/> />
)} )}
...@@ -884,11 +916,7 @@ const Main: FC<IMainProps> = ({ ...@@ -884,11 +916,7 @@ const Main: FC<IMainProps> = ({
onFeedback={handleFeedback} onFeedback={handleFeedback}
isResponsing={isResponsing} isResponsing={isResponsing}
canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon} canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon}
abortResponsing={async () => { abortResponsing={handleAbortResponsing}
await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
setHasStopResponded(true)
setResponsingFalse()
}}
checkCanSend={checkCanSend} checkCanSend={checkCanSend}
controlFocus={controlFocus} controlFocus={controlFocus}
isShowSuggestion={doShowSuggestion} isShowSuggestion={doShowSuggestion}
......
import React, { useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
...@@ -76,6 +76,13 @@ const Sidebar: FC<ISidebarProps> = ({ ...@@ -76,6 +76,13 @@ const Sidebar: FC<ISidebarProps> = ({
checkHasPinned() checkHasPinned()
}, [controlUpdateList]) }, [controlUpdateList])
const handleUnpin = useCallback((id: string) => {
onUnpin(id)
}, [onUnpin])
const handlePin = useCallback((id: string) => {
onPin(id)
}, [onPin])
const maxListHeight = (isInstalledApp) ? 'max-h-[30vh]' : 'max-h-[40vh]' const maxListHeight = (isInstalledApp) ? 'max-h-[30vh]' : 'max-h-[40vh]'
return ( return (
...@@ -119,7 +126,7 @@ const Sidebar: FC<ISidebarProps> = ({ ...@@ -119,7 +126,7 @@ const Sidebar: FC<ISidebarProps> = ({
onMoreLoaded={onPinnedMoreLoaded} onMoreLoaded={onPinnedMoreLoaded}
isNoMore={isPinnedNoMore} isNoMore={isPinnedNoMore}
isPinned={true} isPinned={true}
onPinChanged={id => onUnpin(id)} onPinChanged={handleUnpin}
controlUpdate={controlUpdateList + 1} controlUpdate={controlUpdateList + 1}
onDelete={onDelete} onDelete={onDelete}
/> />
...@@ -142,7 +149,7 @@ const Sidebar: FC<ISidebarProps> = ({ ...@@ -142,7 +149,7 @@ const Sidebar: FC<ISidebarProps> = ({
onMoreLoaded={onMoreLoaded} onMoreLoaded={onMoreLoaded}
isNoMore={isNoMore} isNoMore={isNoMore}
isPinned={false} isPinned={false}
onPinChanged={id => onPin(id)} onPinChanged={handlePin}
controlUpdate={controlUpdateList + 1} controlUpdate={controlUpdateList + 1}
onDelete={onDelete} onDelete={onDelete}
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment