Unverified Commit 301e0496 authored by Joel's avatar Joel Committed by GitHub

fix: chatbot support agent (#2201)

parent 98660e1f
...@@ -3,9 +3,11 @@ import MPEGMode from 'lamejs/src/js/MPEGMode' ...@@ -3,9 +3,11 @@ import MPEGMode from 'lamejs/src/js/MPEGMode'
import Lame from 'lamejs/src/js/Lame' import Lame from 'lamejs/src/js/Lame'
import BitStream from 'lamejs/src/js/BitStream' import BitStream from 'lamejs/src/js/BitStream'
(window as any).MPEGMode = MPEGMode if (globalThis) {
;(window as any).Lame = Lame (globalThis as any).MPEGMode = MPEGMode
;(window as any).BitStream = BitStream ;(globalThis as any).Lame = Lame
;(globalThis as any).BitStream = BitStream
}
export const convertToMp3 = (recorder: any) => { export const convertToMp3 = (recorder: any) => {
const wav = lamejs.WavHeader.readHeader(recorder.getWAV()) const wav = lamejs.WavHeader.readHeader(recorder.getWAV())
......
...@@ -660,8 +660,6 @@ const Main: FC<IMainProps> = ({ ...@@ -660,8 +660,6 @@ const Main: FC<IMainProps> = ({
}) })
}, },
onThought(thought) { onThought(thought) {
// console.log(`${thought.id};${thought.thought};${thought.tool};${thought.tool_input}`)
isAgentMode = true isAgentMode = true
const response = responseItem as any const response = responseItem as any
if (thought.message_id && !hasSetResponseId) { if (thought.message_id && !hasSetResponseId) {
......
import { useState } from 'react' import { useState } from 'react'
import produce from 'immer' import produce from 'immer'
import { useGetState } from 'ahooks'
import type { ConversationItem } from '@/models/share' import type { ConversationItem } from '@/models/share'
const storageConversationIdKey = 'conversationIdInfo' const storageConversationIdKey = 'conversationIdInfo'
...@@ -8,7 +9,7 @@ type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'> ...@@ -8,7 +9,7 @@ type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
function useConversation() { function useConversation() {
const [conversationList, setConversationList] = useState<ConversationItem[]>([]) const [conversationList, setConversationList] = useState<ConversationItem[]>([])
const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([]) const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
const [currConversationId, doSetCurrConversationId] = useState<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 = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
doSetCurrConversationId(id) doSetCurrConversationId(id)
...@@ -53,6 +54,7 @@ function useConversation() { ...@@ -53,6 +54,7 @@ function useConversation() {
pinnedConversationList, pinnedConversationList,
setPinnedConversationList, setPinnedConversationList,
currConversationId, currConversationId,
getCurrConversationId,
setCurrConversationId, setCurrConversationId,
getConversationIdFromStorage, getConversationIdFromStorage,
isNewConversation, isNewConversation,
......
...@@ -5,7 +5,7 @@ import React, { useEffect, useRef, useState } from 'react' ...@@ -5,7 +5,7 @@ import React, { useEffect, useRef, useState } from 'react'
import cn from 'classnames' import cn from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import produce from 'immer' import produce, { setAutoFreeze } from 'immer'
import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import { checkOrSetAccessToken } from '../utils' import { checkOrSetAccessToken } from '../utils'
import AppUnavailable from '../../base/app-unavailable' import AppUnavailable from '../../base/app-unavailable'
...@@ -13,8 +13,20 @@ import useConversation from './hooks/use-conversation' ...@@ -13,8 +13,20 @@ import useConversation from './hooks/use-conversation'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import ConfigScene from '@/app/components/share/chatbot/config-scence' import ConfigScene from '@/app/components/share/chatbot/config-scence'
import Header from '@/app/components/share/header' import Header from '@/app/components/share/header'
import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, generationConversationName, sendChatMessage, stopChatMessageResponding, updateFeedback } from '@/service/share' import {
import type { ConversationItem, SiteInfo } from '@/models/share' fetchAppInfo,
fetchAppMeta,
fetchAppParams,
fetchChatList,
fetchConversations,
fetchSuggestedQuestions,
generationConversationName,
sendChatMessage,
stopChatMessageResponding,
updateFeedback,
} from '@/service/share'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import type { AppMeta, ConversationItem, SiteInfo } from '@/models/share'
import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type' import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
import Chat from '@/app/components/app/chat' import Chat from '@/app/components/app/chat'
...@@ -29,6 +41,7 @@ import LogoHeader from '@/app/components/base/logo/logo-embeded-chat-header' ...@@ -29,6 +41,7 @@ import LogoHeader from '@/app/components/base/logo/logo-embeded-chat-header'
import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar' import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'
import type { VisionFile, VisionSettings } from '@/types/app' import type { VisionFile, VisionSettings } from '@/types/app'
import { Resolution, TransferMethod } from '@/types/app' import { Resolution, TransferMethod } from '@/types/app'
import type { Annotation as AnnotationType } from '@/models/log'
export type IMainProps = { export type IMainProps = {
isInstalledApp?: boolean isInstalledApp?: boolean
...@@ -56,6 +69,8 @@ const Main: FC<IMainProps> = ({ ...@@ -56,6 +69,8 @@ const Main: FC<IMainProps> = ({
const [plan, setPlan] = useState<string>('basic') // basic/plus/pro const [plan, setPlan] = useState<string>('basic') // basic/plus/pro
const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false) const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
const [customConfig, setCustomConfig] = useState<any>(null) const [customConfig, setCustomConfig] = useState<any>(null)
const [appMeta, setAppMeta] = useState<AppMeta | null>(null)
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client. // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
useEffect(() => { useEffect(() => {
if (siteInfo?.title) { if (siteInfo?.title) {
...@@ -66,6 +81,14 @@ const Main: FC<IMainProps> = ({ ...@@ -66,6 +81,14 @@ const Main: FC<IMainProps> = ({
} }
}, [siteInfo?.title, canReplaceLogo]) }, [siteInfo?.title, canReplaceLogo])
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
useEffect(() => {
setAutoFreeze(false)
return () => {
setAutoFreeze(true)
}
}, [])
/* /*
* conversation info * conversation info
*/ */
...@@ -78,6 +101,7 @@ const Main: FC<IMainProps> = ({ ...@@ -78,6 +101,7 @@ const Main: FC<IMainProps> = ({
pinnedConversationList, pinnedConversationList,
setPinnedConversationList, setPinnedConversationList,
currConversationId, currConversationId,
getCurrConversationId,
setCurrConversationId, setCurrConversationId,
getConversationIdFromStorage, getConversationIdFromStorage,
isNewConversation, isNewConversation,
...@@ -189,14 +213,16 @@ const Main: FC<IMainProps> = ({ ...@@ -189,14 +213,16 @@ const Main: FC<IMainProps> = ({
id: `question-${item.id}`, id: `question-${item.id}`,
content: item.query, content: item.query,
isAnswer: false, isAnswer: false,
message_files: item.message_files, message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
}) })
newChatList.push({ newChatList.push({
id: item.id, id: item.id,
content: item.answer, content: item.answer,
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
feedback: item.feedback, feedback: item.feedback,
isAnswer: true, isAnswer: true,
citation: item.retriever_resources, citation: item.retriever_resources,
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
}) })
}) })
setChatList(newChatList) setChatList(newChatList)
...@@ -278,14 +304,15 @@ const Main: FC<IMainProps> = ({ ...@@ -278,14 +304,15 @@ const Main: FC<IMainProps> = ({
}, },
plan: 'basic', plan: 'basic',
} }
: fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id)]) : fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id), fetchAppMeta(isInstalledApp, installedAppInfo?.id)])
} }
// init // init
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
const [appData, conversationData, appParams]: any = await fetchInitData() const [appData, conversationData, appParams, appMeta]: any = await fetchInitData()
setAppMeta(appMeta)
const { app_id: appId, site: siteInfo, plan, can_replace_logo, custom_config }: any = appData const { app_id: appId, site: siteInfo, plan, can_replace_logo, custom_config }: any = appData
setAppId(appId) setAppId(appId)
setPlan(plan) setPlan(plan)
...@@ -384,6 +411,7 @@ const Main: FC<IMainProps> = ({ ...@@ -384,6 +411,7 @@ const Main: FC<IMainProps> = ({
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
const [messageTaskId, setMessageTaskId] = useState('') const [messageTaskId, setMessageTaskId] = useState('')
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false) const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true)
const [shouldReload, setShouldReload] = useState(false) const [shouldReload, setShouldReload] = useState(false)
const [userQuery, setUserQuery] = useState('') const [userQuery, setUserQuery] = useState('')
const [visionConfig, setVisionConfig] = useState<VisionSettings>({ const [visionConfig, setVisionConfig] = useState<VisionSettings>({
...@@ -393,6 +421,29 @@ const Main: FC<IMainProps> = ({ ...@@ -393,6 +421,29 @@ const Main: FC<IMainProps> = ({
transfer_methods: [TransferMethod.local_file], transfer_methods: [TransferMethod.local_file],
}) })
const updateCurrentQA = ({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: IChatItem
questionId: string
placeholderAnswerId: string
questionItem: IChatItem
}) => {
// closesure new list is outdated.
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
}
const handleSend = async (message: string, files?: VisionFile[]) => { const handleSend = async (message: string, files?: VisionFile[]) => {
if (isResponsing) { if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
...@@ -440,14 +491,20 @@ const Main: FC<IMainProps> = ({ ...@@ -440,14 +491,20 @@ const Main: FC<IMainProps> = ({
const newList = [...getChatList(), questionItem, placeholderAnswerItem] const newList = [...getChatList(), questionItem, placeholderAnswerItem]
setChatList(newList) setChatList(newList)
let isAgentMode = false
// answer // answer
const responseItem: IChatItem = { const responseItem: IChatItem = {
id: `${Date.now()}`, id: `${Date.now()}`,
content: '', content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true, isAnswer: true,
} }
let hasSetResponseId = false
let tempNewConversationId = '' const prevTempNewConversationId = getCurrConversationId() || '-1'
let tempNewConversationId = prevTempNewConversationId
setHasStopResponded(false) setHasStopResponded(false)
setResponsingTrue() setResponsingTrue()
...@@ -457,22 +514,34 @@ const Main: FC<IMainProps> = ({ ...@@ -457,22 +514,34 @@ const Main: FC<IMainProps> = ({
setAbortController(abortController) setAbortController(abortController)
}, },
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message if (!isAgentMode) {
responseItem.id = messageId responseItem.content = responseItem.content + message
}
else {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
}
if (messageId && !hasSetResponseId) {
responseItem.id = messageId
hasSetResponseId = true
}
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
tempNewConversationId = newConversationId tempNewConversationId = newConversationId
setMessageTaskId(taskId) setMessageTaskId(taskId)
// closesure new list is outdated. // has switched to other conversation
const newListWithAnswer = produce( if (prevTempNewConversationId !== getCurrConversationId()) {
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), setIsResponsingConCurrCon(false)
(draft) => { return
if (!draft.find(item => item.id === questionId)) }
draft.push({ ...questionItem }) updateCurrentQA({
responseItem,
draft.push({ ...responseItem }) questionId,
}) placeholderAnswerId,
setChatList(newListWithAnswer) questionItem,
})
}, },
async onCompleted(hasError?: boolean) { async onCompleted(hasError?: boolean) {
if (hasError) if (hasError)
...@@ -498,20 +567,105 @@ const Main: FC<IMainProps> = ({ ...@@ -498,20 +567,105 @@ const Main: FC<IMainProps> = ({
} }
setResponsingFalse() setResponsingFalse()
}, },
onMessageReplace: (messageReplace) => { onFile(file) {
setChatList(produce( const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
getChatList(), if (lastThought)
lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onThought(thought) {
isAgentMode = true
const response = responseItem as any
if (thought.message_id && !hasSetResponseId) {
response.id = thought.message_id
hasSetResponseId = true
}
// responseItem.id = thought.message_id;
if (response.agent_thoughts.length === 0) {
response.agent_thoughts.push(thought)
}
else {
const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
// thought changed but still the same thought, so update.
if (lastThought.id === thought.id) {
thought.thought = lastThought.thought
thought.message_files = lastThought.message_files
responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
}
else {
responseItem.agent_thoughts!.push(thought)
}
}
// has switched to other conversation
if (prevTempNewConversationId !== getCurrConversationId()) {
setIsResponsingConCurrCon(false)
return false
}
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onMessageEnd: (messageEnd) => {
if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id
responseItem.annotation = ({
id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name,
} as AnnotationType)
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({
...responseItem,
})
})
setChatList(newListWithAnswer)
return
}
// not support show citation
// responseItem.citation = messageEnd.retriever_resources
if (!isInstalledApp)
return
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => { (draft) => {
const current = draft.find(item => item.id === messageReplace.id) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
if (current) draft.push({ ...responseItem })
current.content = messageReplace.answer })
}, setChatList(newListWithAnswer)
)) },
onMessageReplace: (messageReplace) => {
if (isInstalledApp) {
responseItem.content = messageReplace.answer
}
else {
setChatList(produce(
getChatList(),
(draft) => {
const current = draft.find(item => item.id === messageReplace.id)
if (current)
current.content = messageReplace.answer
},
))
}
}, },
onError(errorMessage, errorCode) { onError() {
if (['provider_not_initialize', 'completion_request_error'].includes(errorCode as string))
setShouldReload(true)
setResponsingFalse() setResponsingFalse()
// role back placeholder answer // role back placeholder answer
setChatList(produce(getChatList(), (draft) => { setChatList(produce(getChatList(), (draft) => {
...@@ -629,7 +783,7 @@ const Main: FC<IMainProps> = ({ ...@@ -629,7 +783,7 @@ const Main: FC<IMainProps> = ({
isHideFeedbackEdit isHideFeedbackEdit
onFeedback={handleFeedback} onFeedback={handleFeedback}
isResponsing={isResponsing} isResponsing={isResponsing}
canStopResponsing={!!messageTaskId} canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon}
abortResponsing={async () => { abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id) await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
setHasStopResponded(true) setHasStopResponded(true)
...@@ -645,6 +799,7 @@ const Main: FC<IMainProps> = ({ ...@@ -645,6 +799,7 @@ const Main: FC<IMainProps> = ({
isShowCitation={citationConfig?.enabled && isInstalledApp} isShowCitation={citationConfig?.enabled && isInstalledApp}
answerIcon={<LogoAvatar className='relative shrink-0' />} answerIcon={<LogoAvatar className='relative shrink-0' />}
visionConfig={visionConfig} visionConfig={visionConfig}
allToolIcons={appMeta?.tool_icons || {}}
/> />
</div> </div>
</div>) </div>)
......
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