| @@ -53,6 +53,7 @@ export type IChatProps = { | |||
| displayScene?: DisplayScene | |||
| useCurrentUserAvatar?: boolean | |||
| isResponsing?: boolean | |||
| canStopResponsing?: boolean | |||
| abortResponsing?: () => void | |||
| controlClearQuery?: number | |||
| controlFocus?: number | |||
| @@ -412,6 +413,7 @@ const Chat: FC<IChatProps> = ({ | |||
| displayScene, | |||
| useCurrentUserAvatar, | |||
| isResponsing, | |||
| canStopResponsing, | |||
| abortResponsing, | |||
| controlClearQuery, | |||
| controlFocus, | |||
| @@ -508,7 +510,7 @@ const Chat: FC<IChatProps> = ({ | |||
| { | |||
| !isHideSendInput && ( | |||
| <div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}> | |||
| {isResponsing && ( | |||
| {(isResponsing && canStopResponsing) && ( | |||
| <div className='flex justify-center mb-4'> | |||
| <Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponsing?.()}> | |||
| {stopIcon} | |||
| @@ -16,7 +16,7 @@ import type { IChatItem } from '@/app/components/app/chat' | |||
| import Chat from '@/app/components/app/chat' | |||
| import ConfigContext from '@/context/debug-configuration' | |||
| import { ToastContext } from '@/app/components/base/toast' | |||
| import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage } from '@/service/debug' | |||
| import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage, stopChatMessageResponding } from '@/service/debug' | |||
| import Button from '@/app/components/base/button' | |||
| import type { ModelConfig as BackendModelConfig } from '@/types/app' | |||
| import { promptVariablesToUserInputsForm } from '@/utils/model-config' | |||
| @@ -136,6 +136,7 @@ const Debug: FC<IDebug> = ({ | |||
| const doShowSuggestion = isShowSuggestion && !isResponsing | |||
| const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) | |||
| const [messageTaskId, setMessageTaskId] = useState('') | |||
| const onSend = async (message: string) => { | |||
| if (isResponsing) { | |||
| notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) | |||
| @@ -208,12 +209,13 @@ const Debug: FC<IDebug> = ({ | |||
| getAbortController: (abortController) => { | |||
| setAbortController(abortController) | |||
| }, | |||
| onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => { | |||
| onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { | |||
| responseItem.content = responseItem.content + message | |||
| if (isFirstMessage && newConversationId) { | |||
| setConversationId(newConversationId) | |||
| _newConversationId = newConversationId | |||
| } | |||
| setMessageTaskId(taskId) | |||
| if (messageId) | |||
| responseItem.id = messageId | |||
| @@ -375,8 +377,10 @@ const Debug: FC<IDebug> = ({ | |||
| feedbackDisabled | |||
| useCurrentUserAvatar | |||
| isResponsing={isResponsing} | |||
| abortResponsing={() => { | |||
| canStopResponsing={!!messageTaskId} | |||
| abortResponsing={async () => { | |||
| abortController?.abort() | |||
| await stopChatMessageResponding(appId, messageTaskId) | |||
| setResponsingFalse() | |||
| }} | |||
| isShowSuggestion={doShowSuggestion} | |||
| @@ -14,7 +14,7 @@ import { ToastContext } from '@/app/components/base/toast' | |||
| import Sidebar from '@/app/components/share/chat/sidebar' | |||
| import ConfigSence from '@/app/components/share/chat/config-scence' | |||
| import Header from '@/app/components/share/header' | |||
| import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, updateFeedback } from '@/service/share' | |||
| import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, stopChatMessageResponding, updateFeedback } from '@/service/share' | |||
| import type { ConversationItem, SiteInfo } from '@/models/share' | |||
| import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' | |||
| import type { Feedbacktype, IChatItem } from '@/app/components/app/chat' | |||
| @@ -332,6 +332,7 @@ const Main: FC<IMainProps> = ({ | |||
| const [isShowSuggestion, setIsShowSuggestion] = useState(false) | |||
| const doShowSuggestion = isShowSuggestion && !isResponsing | |||
| const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) | |||
| const [messageTaskId, setMessageTaskId] = useState('') | |||
| const handleSend = async (message: string) => { | |||
| if (isResponsing) { | |||
| notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) | |||
| @@ -376,12 +377,13 @@ const Main: FC<IMainProps> = ({ | |||
| getAbortController: (abortController) => { | |||
| setAbortController(abortController) | |||
| }, | |||
| onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => { | |||
| onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { | |||
| responseItem.content = responseItem.content + message | |||
| responseItem.id = messageId | |||
| if (isFirstMessage && newConversationId) | |||
| tempNewConversationId = newConversationId | |||
| setMessageTaskId(taskId) | |||
| // closesure new list is outdated. | |||
| const newListWithAnswer = produce( | |||
| getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), | |||
| @@ -532,8 +534,10 @@ const Main: FC<IMainProps> = ({ | |||
| isHideFeedbackEdit | |||
| onFeedback={handleFeedback} | |||
| isResponsing={isResponsing} | |||
| abortResponsing={() => { | |||
| canStopResponsing={!!messageTaskId} | |||
| abortResponsing={async () => { | |||
| abortController?.abort() | |||
| await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id) | |||
| setResponsingFalse() | |||
| }} | |||
| checkCanSend={checkCanSend} | |||
| @@ -23,7 +23,8 @@ const baseOptions = { | |||
| } | |||
| export type IOnDataMoreInfo = { | |||
| conversationId: string | undefined | |||
| conversationId?: string | |||
| taskId?: string | |||
| messageId: string | |||
| errorMessage?: string | |||
| } | |||
| @@ -101,6 +102,7 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted | |||
| // can not use format here. Because message is splited. | |||
| onData(unicodeToChar(bufferObj.answer), isFirstMessage, { | |||
| conversationId: bufferObj.conversation_id, | |||
| taskId: bufferObj.task_id, | |||
| messageId: bufferObj.id, | |||
| }) | |||
| isFirstMessage = false | |||
| @@ -15,6 +15,10 @@ export const sendChatMessage = async (appId: string, body: Record<string, any>, | |||
| }, { onData, onCompleted, onError, getAbortController }) | |||
| } | |||
| export const stopChatMessageResponding = async (appId: string, taskId: string) => { | |||
| return post(`apps/${appId}/chat-messages/${taskId}/stop`) | |||
| } | |||
| export const sendCompletionMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError }: { | |||
| onData: IOnData | |||
| onCompleted: IOnCompleted | |||
| @@ -34,6 +34,10 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom | |||
| }, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController }) | |||
| } | |||
| export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => { | |||
| return getAction('post', isInstalledApp)(getUrl(`chat-messages/${taskId}/stop`, isInstalledApp, installedAppId)) | |||
| } | |||
| export const sendCompletionMessage = async (body: Record<string, any>, { onData, onCompleted, onError }: { | |||
| onData: IOnData | |||
| onCompleted: IOnCompleted | |||