Unverified Commit 68406b99 authored by zxhlyh's avatar zxhlyh Committed by GitHub

fix: multiple model configuration clear conversation by rerender (#2286)

parent 6f7fd661
...@@ -132,6 +132,7 @@ const EditAnnotationModal: FC<Props> = ({ ...@@ -132,6 +132,7 @@ const EditAnnotationModal: FC<Props> = ({
onRemove={() => { onRemove={() => {
onRemove() onRemove()
setShowModal(false) setShowModal(false)
onHide()
}} }}
text={t('appDebug.feature.annotation.removeConfirm') as string} text={t('appDebug.feature.annotation.removeConfirm') as string}
/> />
......
...@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next' ...@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import produce from 'immer' import produce from 'immer'
import { useFormattingChangedDispatcher } from '../../../debug/hooks'
import ChooseTool from './choose-tool' import ChooseTool from './choose-tool'
import SettingBuiltInTool from './setting-built-in-tool' import SettingBuiltInTool from './setting-built-in-tool'
import Panel from '@/app/components/app/configuration/base/feature-panel' import Panel from '@/app/components/app/configuration/base/feature-panel'
...@@ -27,6 +28,7 @@ const AgentTools: FC = () => { ...@@ -27,6 +28,7 @@ const AgentTools: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [isShowChooseTool, setIsShowChooseTool] = useState(false) const [isShowChooseTool, setIsShowChooseTool] = useState(false)
const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext) const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null) const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined) const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined)
...@@ -49,6 +51,7 @@ const AgentTools: FC = () => { ...@@ -49,6 +51,7 @@ const AgentTools: FC = () => {
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
setIsShowSettingTool(false) setIsShowSettingTool(false)
formattingChangedDispatcher()
} }
return ( return (
...@@ -141,6 +144,7 @@ const AgentTools: FC = () => { ...@@ -141,6 +144,7 @@ const AgentTools: FC = () => {
draft.agentConfig.tools.splice(index, 1) draft.agentConfig.tools.splice(index, 1)
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
formattingChangedDispatcher()
}}> }}>
<Trash03 className='w-4 h-4 text-gray-500' /> <Trash03 className='w-4 h-4 text-gray-500' />
</div> </div>
...@@ -167,6 +171,7 @@ const AgentTools: FC = () => { ...@@ -167,6 +171,7 @@ const AgentTools: FC = () => {
draft.agentConfig.tools.splice(index, 1) draft.agentConfig.tools.splice(index, 1)
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
formattingChangedDispatcher()
}}> }}>
<Trash03 className='w-4 h-4 text-gray-500' /> <Trash03 className='w-4 h-4 text-gray-500' />
</div> </div>
...@@ -183,6 +188,7 @@ const AgentTools: FC = () => { ...@@ -183,6 +188,7 @@ const AgentTools: FC = () => {
(draft.agentConfig.tools[index] as any).enabled = enabled (draft.agentConfig.tools[index] as any).enabled = enabled
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
formattingChangedDispatcher()
}} /> }} />
</div> </div>
</div> </div>
......
...@@ -4,6 +4,7 @@ import React, { useRef } from 'react' ...@@ -4,6 +4,7 @@ import React, { useRef } from 'react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import produce from 'immer' import produce from 'immer'
import { useBoolean, useScroll } from 'ahooks' import { useBoolean, useScroll } from 'ahooks'
import { useFormattingChangedDispatcher } from '../debug/hooks'
import DatasetConfig from '../dataset-config' import DatasetConfig from '../dataset-config'
import ChatGroup from '../features/chat-group' import ChatGroup from '../features/chat-group'
import ExperienceEnchanceGroup from '../features/experience-enchance-group' import ExperienceEnchanceGroup from '../features/experience-enchance-group'
...@@ -44,7 +45,6 @@ const Config: FC = () => { ...@@ -44,7 +45,6 @@ const Config: FC = () => {
modelConfig, modelConfig,
setModelConfig, setModelConfig,
setPrevPromptConfig, setPrevPromptConfig,
setFormattingChanged,
moreLikeThisConfig, moreLikeThisConfig,
setMoreLikeThisConfig, setMoreLikeThisConfig,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
...@@ -64,6 +64,7 @@ const Config: FC = () => { ...@@ -64,6 +64,7 @@ const Config: FC = () => {
const { data: speech2textDefaultModel } = useDefaultModel(4) const { data: speech2textDefaultModel } = useDefaultModel(4)
const { data: text2speechDefaultModel } = useDefaultModel(5) const { data: text2speechDefaultModel } = useDefaultModel(5)
const { setShowModerationSettingModal } = useModalContext() const { setShowModerationSettingModal } = useModalContext()
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const promptTemplate = modelConfig.configs.prompt_template const promptTemplate = modelConfig.configs.prompt_template
const promptVariables = modelConfig.configs.prompt_variables const promptVariables = modelConfig.configs.prompt_variables
...@@ -73,9 +74,8 @@ const Config: FC = () => { ...@@ -73,9 +74,8 @@ const Config: FC = () => {
draft.configs.prompt_template = newTemplate draft.configs.prompt_template = newTemplate
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables] draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
}) })
if (modelConfig.configs.prompt_template !== newTemplate) if (modelConfig.configs.prompt_template !== newTemplate)
setFormattingChanged(true) formattingChangedDispatcher()
setPrevPromptConfig(modelConfig.configs) setPrevPromptConfig(modelConfig.configs)
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
...@@ -107,6 +107,7 @@ const Config: FC = () => { ...@@ -107,6 +107,7 @@ const Config: FC = () => {
setSuggestedQuestionsAfterAnswerConfig(produce(suggestedQuestionsAfterAnswerConfig, (draft: SuggestedQuestionsAfterAnswerConfig) => { setSuggestedQuestionsAfterAnswerConfig(produce(suggestedQuestionsAfterAnswerConfig, (draft: SuggestedQuestionsAfterAnswerConfig) => {
draft.enabled = value draft.enabled = value
})) }))
formattingChangedDispatcher()
}, },
speechToText: speechToTextConfig.enabled, speechToText: speechToTextConfig.enabled,
setSpeechToText: (value) => { setSpeechToText: (value) => {
...@@ -125,6 +126,7 @@ const Config: FC = () => { ...@@ -125,6 +126,7 @@ const Config: FC = () => {
setCitationConfig(produce(citationConfig, (draft: CitationConfig) => { setCitationConfig(produce(citationConfig, (draft: CitationConfig) => {
draft.enabled = value draft.enabled = value
})) }))
formattingChangedDispatcher()
}, },
annotation: annotationConfig.enabled, annotation: annotationConfig.enabled,
setAnnotation: async (value) => { setAnnotation: async (value) => {
......
...@@ -4,6 +4,7 @@ import React from 'react' ...@@ -4,6 +4,7 @@ import React from 'react'
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 from 'immer'
import { useFormattingChangedDispatcher } from '../debug/hooks'
import FeaturePanel from '../base/feature-panel' import FeaturePanel from '../base/feature-panel'
import OperationBtn from '../base/operation-btn' import OperationBtn from '../base/operation-btn'
import CardItem from './card-item/item' import CardItem from './card-item/item'
...@@ -26,25 +27,25 @@ const DatasetConfig: FC = () => { ...@@ -26,25 +27,25 @@ const DatasetConfig: FC = () => {
mode, mode,
dataSets: dataSet, dataSets: dataSet,
setDataSets: setDataSet, setDataSets: setDataSet,
setFormattingChanged,
modelConfig, modelConfig,
setModelConfig, setModelConfig,
showSelectDataSet, showSelectDataSet,
isAgent, isAgent,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const hasData = dataSet.length > 0 const hasData = dataSet.length > 0
const onRemove = (id: string) => { const onRemove = (id: string) => {
setDataSet(dataSet.filter(item => item.id !== id)) setDataSet(dataSet.filter(item => item.id !== id))
setFormattingChanged(true) formattingChangedDispatcher()
} }
const handleSave = (newDataset: DataSet) => { const handleSave = (newDataset: DataSet) => {
const index = dataSet.findIndex(item => item.id === newDataset.id) const index = dataSet.findIndex(item => item.id === newDataset.id)
setDataSet([...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)]) setDataSet([...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)])
setFormattingChanged(true) formattingChangedDispatcher()
} }
const promptVariables = modelConfig.configs.prompt_variables const promptVariables = modelConfig.configs.prompt_variables
......
import type { FC } from 'react' import type { FC } from 'react'
import { import {
memo, memo,
useCallback,
useMemo, useMemo,
} from 'react' } from 'react'
import type { ModelAndParameter } from '../types' import type { ModelAndParameter } from '../types'
...@@ -9,16 +10,13 @@ import { ...@@ -9,16 +10,13 @@ import {
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} from '../types' } from '../types'
import { import {
AgentStrategy, useConfigFromDebugContext,
ModelModeType, useFormattingChangedSubscription,
} from '@/types/app' } from '../hooks'
import Chat from '@/app/components/base/chat/chat' import Chat from '@/app/components/base/chat/chat'
import { useChat } from '@/app/components/base/chat/chat/hooks' import { useChat } from '@/app/components/base/chat/chat/hooks'
import { useDebugConfigurationContext } from '@/context/debug-configuration' import { useDebugConfigurationContext } from '@/context/debug-configuration'
import type { import type { OnSend } from '@/app/components/base/chat/types'
ChatConfig,
OnSend,
} from '@/app/components/base/chat/types'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { import {
...@@ -26,7 +24,6 @@ import { ...@@ -26,7 +24,6 @@ import {
fetchSuggestedQuestions, fetchSuggestedQuestions,
stopChatMessageResponding, stopChatMessageResponding,
} from '@/service/debug' } from '@/service/debug'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import Avatar from '@/app/components/base/avatar' import Avatar from '@/app/components/base/avatar'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
...@@ -39,66 +36,14 @@ const ChatItem: FC<ChatItemProps> = ({ ...@@ -39,66 +36,14 @@ const ChatItem: FC<ChatItemProps> = ({
}) => { }) => {
const { userProfile } = useAppContext() const { userProfile } = useAppContext()
const { const {
isAdvancedMode,
modelConfig, modelConfig,
appId, appId,
inputs, inputs,
promptMode,
speechToTextConfig,
introduction,
suggestedQuestions: openingSuggestedQuestions,
suggestedQuestionsAfterAnswerConfig,
citationConfig,
moderationConfig,
chatPromptConfig,
completionPromptConfig,
dataSets,
datasetConfigs,
visionConfig, visionConfig,
annotationConfig,
collectionList, collectionList,
textToSpeechConfig,
} = useDebugConfigurationContext() } = useDebugConfigurationContext()
const { textGenerationModelList } = useProviderContext() const { textGenerationModelList } = useProviderContext()
const postDatasets = dataSets.map(({ id }) => ({ const config = useConfigFromDebugContext()
dataset: {
enabled: true,
id,
},
}))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
const config: ChatConfig = {
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode,
chat_prompt_config: isAdvancedMode ? chatPromptConfig : {},
completion_prompt_config: isAdvancedMode ? completionPromptConfig : {},
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
dataset_query_variable: contextVar || '',
opening_statement: introduction,
more_like_this: {
enabled: false,
},
suggested_questions: openingSuggestedQuestions,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
text_to_speech: textToSpeechConfig,
speech_to_text: speechToTextConfig,
retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig,
agent_mode: {
...modelConfig.agentConfig,
strategy: (modelAndParameter.provider === 'openai' && modelConfig.mode === ModelModeType.chat) ? AgentStrategy.functionCall : AgentStrategy.react,
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: {
image: visionConfig,
},
annotation_reply: annotationConfig,
}
const { const {
chatList, chatList,
isResponsing, isResponsing,
...@@ -114,8 +59,9 @@ const ChatItem: FC<ChatItemProps> = ({ ...@@ -114,8 +59,9 @@ const ChatItem: FC<ChatItemProps> = ({
[], [],
taskId => stopChatMessageResponding(appId, taskId), taskId => stopChatMessageResponding(appId, taskId),
) )
useFormattingChangedSubscription(chatList)
const doSend: OnSend = (message, files) => { const doSend: OnSend = useCallback((message, files) => {
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider) const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
const currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model) const currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model)
const supportVision = currentModel?.features?.includes(ModelFeatureEnum.vision) const supportVision = currentModel?.features?.includes(ModelFeatureEnum.vision)
...@@ -147,7 +93,7 @@ const ChatItem: FC<ChatItemProps> = ({ ...@@ -147,7 +93,7 @@ const ChatItem: FC<ChatItemProps> = ({
onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController), onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
}, },
) )
} }, [appId, config, handleSend, inputs, modelAndParameter, textGenerationModelList, visionConfig.enabled])
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => { eventEmitter?.useSubscription((v: any) => {
...@@ -174,8 +120,9 @@ const ChatItem: FC<ChatItemProps> = ({ ...@@ -174,8 +120,9 @@ const ChatItem: FC<ChatItemProps> = ({
chatList={chatList} chatList={chatList}
isResponsing={isResponsing} isResponsing={isResponsing}
noChatInput noChatInput
noStopResponding
chatContainerclassName='p-4' chatContainerclassName='p-4'
chatFooterClassName='!-bottom-4' chatFooterClassName='p-4 pb-0'
suggestedQuestions={suggestedQuestions} suggestedQuestions={suggestedQuestions}
onSend={doSend} onSend={doSend}
showPromptLog showPromptLog
......
...@@ -7,6 +7,7 @@ export type DebugWithMultipleModelContextType = { ...@@ -7,6 +7,7 @@ export type DebugWithMultipleModelContextType = {
multipleModelConfigs: ModelAndParameter[] multipleModelConfigs: ModelAndParameter[]
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
onDebugWithMultipleModelChange: (singleModelConfig: ModelAndParameter) => void onDebugWithMultipleModelChange: (singleModelConfig: ModelAndParameter) => void
checkCanSend?: () => boolean
} }
const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({ const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({
multipleModelConfigs: [], multipleModelConfigs: [],
...@@ -24,12 +25,14 @@ export const DebugWithMultipleModelContextProvider = ({ ...@@ -24,12 +25,14 @@ export const DebugWithMultipleModelContextProvider = ({
onMultipleModelConfigsChange, onMultipleModelConfigsChange,
multipleModelConfigs, multipleModelConfigs,
onDebugWithMultipleModelChange, onDebugWithMultipleModelChange,
checkCanSend,
}: DebugWithMultipleModelContextProviderProps) => { }: DebugWithMultipleModelContextProviderProps) => {
return ( return (
<DebugWithMultipleModelContext.Provider value={{ <DebugWithMultipleModelContext.Provider value={{
onMultipleModelConfigsChange, onMultipleModelConfigsChange,
multipleModelConfigs, multipleModelConfigs,
onDebugWithMultipleModelChange, onDebugWithMultipleModelChange,
checkCanSend,
}}> }}>
{children} {children}
</DebugWithMultipleModelContext.Provider> </DebugWithMultipleModelContext.Provider>
......
import type { FC } from 'react' import type { CSSProperties, FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { memo } from 'react' import { memo } from 'react'
import type { ModelAndParameter } from '../types' import type { ModelAndParameter } from '../types'
...@@ -15,10 +15,12 @@ import { ModelStatusEnum } from '@/app/components/header/account-setting/model-p ...@@ -15,10 +15,12 @@ import { ModelStatusEnum } from '@/app/components/header/account-setting/model-p
type DebugItemProps = { type DebugItemProps = {
modelAndParameter: ModelAndParameter modelAndParameter: ModelAndParameter
className?: string className?: string
style?: CSSProperties
} }
const DebugItem: FC<DebugItemProps> = ({ const DebugItem: FC<DebugItemProps> = ({
modelAndParameter, modelAndParameter,
className, className,
style,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { mode } = useDebugConfigurationContext() const { mode } = useDebugConfigurationContext()
...@@ -61,7 +63,10 @@ const DebugItem: FC<DebugItemProps> = ({ ...@@ -61,7 +63,10 @@ const DebugItem: FC<DebugItemProps> = ({
} }
return ( return (
<div className={`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`}> <div
className={`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`}
style={style}
>
<div className='shrink-0 flex items-center justify-between h-10 px-3 border-b-[0.5px] border-b-black/5'> <div className='shrink-0 flex items-center justify-between h-10 px-3 border-b-[0.5px] border-b-black/5'>
<div className='flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'> <div className='flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'>
#{index + 1} #{index + 1}
......
...@@ -2,6 +2,7 @@ import type { FC } from 'react' ...@@ -2,6 +2,7 @@ import type { FC } from 'react'
import { import {
memo, memo,
useCallback, useCallback,
useMemo,
} from 'react' } from 'react'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types' import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
import DebugItem from './debug-item' import DebugItem from './debug-item'
...@@ -21,10 +22,16 @@ const DebugWithMultipleModel = () => { ...@@ -21,10 +22,16 @@ const DebugWithMultipleModel = () => {
speechToTextConfig, speechToTextConfig,
visionConfig, visionConfig,
} = useDebugConfigurationContext() } = useDebugConfigurationContext()
const { multipleModelConfigs } = useDebugWithMultipleModelContext() const {
multipleModelConfigs,
checkCanSend,
} = useDebugWithMultipleModelContext()
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
const handleSend = useCallback((message: string, files?: VisionFile[]) => { const handleSend = useCallback((message: string, files?: VisionFile[]) => {
if (checkCanSend && !checkCanSend())
return
eventEmitter?.emit({ eventEmitter?.emit({
type: APP_CHAT_WITH_MULTIPLE_MODEL, type: APP_CHAT_WITH_MULTIPLE_MODEL,
payload: { payload: {
...@@ -32,72 +39,90 @@ const DebugWithMultipleModel = () => { ...@@ -32,72 +39,90 @@ const DebugWithMultipleModel = () => {
files, files,
}, },
} as any) } as any)
}, [eventEmitter]) }, [eventEmitter, checkCanSend])
const twoLine = multipleModelConfigs.length === 2 const twoLine = multipleModelConfigs.length === 2
const threeLine = multipleModelConfigs.length === 3 const threeLine = multipleModelConfigs.length === 3
const fourLine = multipleModelConfigs.length === 4 const fourLine = multipleModelConfigs.length === 4
const size = useMemo(() => {
let width = ''
let height = ''
if (twoLine) {
width = 'calc(50% - 4px - 24px)'
height = '100%'
}
if (threeLine) {
width = 'calc(33.3% - 5.33px - 16px)'
height = '100%'
}
if (fourLine) {
width = 'calc(50% - 4px - 24px)'
height = 'calc(50% - 4px)'
}
return {
width,
height,
}
}, [twoLine, threeLine, fourLine])
const position = useCallback((idx: number) => {
let translateX = '0'
let translateY = '0'
if (twoLine && idx === 1)
translateX = 'calc(100% + 8px)'
if (threeLine && idx === 1)
translateX = 'calc(100% + 8px)'
if (threeLine && idx === 2)
translateX = 'calc(200% + 16px)'
if (fourLine && idx === 1)
translateX = 'calc(100% + 8px)'
if (fourLine && idx === 2)
translateY = 'calc(100% + 8px)'
if (fourLine && idx === 3) {
translateX = 'calc(100% + 8px)'
translateY = 'calc(100% + 8px)'
}
return {
translateX,
translateY,
}
}, [twoLine, threeLine, fourLine])
return ( return (
<div className='flex flex-col h-full'> <div className='flex flex-col h-full'>
<div <div
className={` className={`
mb-3 overflow-auto grow mb-3 relative px-6 overflow-auto
${(twoLine || threeLine) && 'flex gap-2'}
`} `}
style={{ height: mode === 'chat' ? 'calc(100% - 60px)' : '100%' }} style={{ height: mode === 'chat' ? 'calc(100% - 60px)' : '100%' }}
> >
{ {
(twoLine || threeLine) && multipleModelConfigs.map(modelConfig => ( multipleModelConfigs.map((modelConfig, index) => (
<DebugItem <DebugItem
key={modelConfig.id} key={modelConfig.id}
modelAndParameter={modelConfig} modelAndParameter={modelConfig}
className={` className={`
h-full min-h-[200px] absolute left-6 top-0 min-h-[200px]
${twoLine && 'w-1/2'} ${twoLine && index === 0 && 'mr-2'}
${threeLine && 'w-1/3'} ${threeLine && (index === 0 || index === 1) && 'mr-2'}
${fourLine && (index === 0 || index === 2) && 'mr-2'}
${fourLine && (index === 0 || index === 1) && 'mb-2'}
`} `}
style={{
width: size.width,
height: size.height,
transform: `translateX(${position(index).translateX}) translateY(${position(index).translateY})`,
}}
/> />
)) ))
} }
{
fourLine && (
<>
<div
className='flex space-x-2 mb-2 min-h-[200px]'
style={{ height: 'calc(50% - 4px)' }}
>
{
multipleModelConfigs.slice(0, 2).map(modelConfig => (
<DebugItem
key={modelConfig.id}
modelAndParameter={modelConfig}
className='w-1/2 h-full'
/>
))
}
</div>
<div
className='flex space-x-2 min-h-[200px]'
style={{ height: 'calc(50% - 4px)' }}
>
{
multipleModelConfigs.slice(2, 4).map(modelConfig => (
<DebugItem
key={modelConfig.id}
modelAndParameter={modelConfig}
className='w-1/2 h-full'
/>
))
}
</div>
</>
)
}
</div> </div>
{ {
mode === 'chat' && ( mode === 'chat' && (
<div className='shrink-0'> <div className='shrink-0 pb-4 px-6'>
<ChatInput <ChatInput
onSend={handleSend} onSend={handleSend}
speechToTextConfig={speechToTextConfig} speechToTextConfig={speechToTextConfig}
...@@ -116,12 +141,14 @@ const DebugWithMultipleModelWrapper: FC<DebugWithMultipleModelContextType> = ({ ...@@ -116,12 +141,14 @@ const DebugWithMultipleModelWrapper: FC<DebugWithMultipleModelContextType> = ({
onMultipleModelConfigsChange, onMultipleModelConfigsChange,
multipleModelConfigs, multipleModelConfigs,
onDebugWithMultipleModelChange, onDebugWithMultipleModelChange,
checkCanSend,
}) => { }) => {
return ( return (
<DebugWithMultipleModelContextProvider <DebugWithMultipleModelContextProvider
onMultipleModelConfigsChange={onMultipleModelConfigsChange} onMultipleModelConfigsChange={onMultipleModelConfigsChange}
multipleModelConfigs={multipleModelConfigs} multipleModelConfigs={multipleModelConfigs}
onDebugWithMultipleModelChange={onDebugWithMultipleModelChange} onDebugWithMultipleModelChange={onDebugWithMultipleModelChange}
checkCanSend={checkCanSend}
> >
<DebugWithMultipleModelMemoed /> <DebugWithMultipleModelMemoed />
</DebugWithMultipleModelContextProvider> </DebugWithMultipleModelContextProvider>
......
import {
forwardRef,
memo,
useCallback,
useImperativeHandle,
useMemo,
} from 'react'
import {
useConfigFromDebugContext,
useFormattingChangedSubscription,
} from '../hooks'
import Chat from '@/app/components/base/chat/chat'
import { useChat } from '@/app/components/base/chat/chat/hooks'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import type { OnSend } from '@/app/components/base/chat/types'
import { useProviderContext } from '@/context/provider-context'
import {
fetchConvesationMessages,
fetchSuggestedQuestions,
stopChatMessageResponding,
} from '@/service/debug'
import Avatar from '@/app/components/base/avatar'
import { useAppContext } from '@/context/app-context'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
type DebugWithSingleModelProps = {
checkCanSend?: () => boolean
}
export type DebugWithSingleModelRefType = {
handleRestart: () => void
}
const DebugWithSingleModel = forwardRef<DebugWithSingleModelRefType, DebugWithSingleModelProps>(({
checkCanSend,
}, ref) => {
const { userProfile } = useAppContext()
const {
modelConfig,
appId,
inputs,
visionConfig,
collectionList,
completionParams,
} = useDebugConfigurationContext()
const { textGenerationModelList } = useProviderContext()
const config = useConfigFromDebugContext()
const {
chatList,
isResponsing,
handleSend,
suggestedQuestions,
handleStop,
handleRestart,
handleAnnotationAdded,
handleAnnotationEdited,
handleAnnotationRemoved,
} = useChat(
{
...config,
supportAnnotation: true,
appId,
},
{
inputs,
promptVariables: modelConfig.configs.prompt_variables,
},
[],
taskId => stopChatMessageResponding(appId, taskId),
)
useFormattingChangedSubscription(chatList)
const doSend: OnSend = useCallback((message, files) => {
if (checkCanSend && !checkCanSend())
return
const currentProvider = textGenerationModelList.find(item => item.provider === modelConfig.provider)
const currentModel = currentProvider?.models.find(model => model.model === modelConfig.model_id)
const supportVision = currentModel?.features?.includes(ModelFeatureEnum.vision)
const configData = {
...config,
model: {
provider: modelConfig.provider,
name: modelConfig.model_id,
mode: modelConfig.mode,
completion_params: completionParams,
},
}
const data: any = {
query: message,
inputs,
model_config: configData,
}
if (visionConfig.enabled && files?.length && supportVision)
data.files = files
handleSend(
`apps/${appId}/chat-messages`,
data,
{
onGetConvesationMessages: (conversationId, getAbortController) => fetchConvesationMessages(appId, conversationId, getAbortController),
onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
},
)
}, [appId, checkCanSend, completionParams, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled])
const allToolIcons = useMemo(() => {
const icons: Record<string, any> = {}
modelConfig.agentConfig.tools?.forEach((item: any) => {
icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon
})
return icons
}, [collectionList, modelConfig.agentConfig.tools])
useImperativeHandle(ref, () => {
return {
handleRestart,
}
}, [handleRestart])
return (
<Chat
config={config}
chatList={chatList}
isResponsing={isResponsing}
chatContainerclassName='p-6'
chatFooterClassName='px-6 pt-10 pb-4'
suggestedQuestions={suggestedQuestions}
onSend={doSend}
onStopResponding={handleStop}
showPromptLog
questionIcon={<Avatar name={userProfile.name} size={40} />}
allToolIcons={allToolIcons}
onAnnotationEdited={handleAnnotationEdited}
onAnnotationAdded={handleAnnotationAdded}
onAnnotationRemoved={handleAnnotationRemoved}
/>
)
})
DebugWithSingleModel.displayName = 'DebugWithSingleModel'
export default memo(DebugWithSingleModel)
...@@ -7,6 +7,17 @@ import type { ...@@ -7,6 +7,17 @@ import type {
DebugWithSingleOrMultipleModelConfigs, DebugWithSingleOrMultipleModelConfigs,
ModelAndParameter, ModelAndParameter,
} from './types' } from './types'
import { ORCHESTRATE_CHANGED } from './types'
import type {
ChatConfig,
ChatItem,
} from '@/app/components/base/chat/types'
import {
AgentStrategy,
} from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import { useEventEmitterContextContext } from '@/context/event-emitter'
export const useDebugWithSingleOrMultipleModel = (appId: string) => { export const useDebugWithSingleOrMultipleModel = (appId: string) => {
const localeDebugWithSingleOrMultipleModelConfigs = localStorage.getItem('app-debug-with-single-or-multiple-models') const localeDebugWithSingleOrMultipleModelConfigs = localStorage.getItem('app-debug-with-single-or-multiple-models')
...@@ -52,3 +63,95 @@ export const useDebugWithSingleOrMultipleModel = (appId: string) => { ...@@ -52,3 +63,95 @@ export const useDebugWithSingleOrMultipleModel = (appId: string) => {
handleMultipleModelConfigsChange, handleMultipleModelConfigsChange,
} }
} }
export const useConfigFromDebugContext = () => {
const {
isAdvancedMode,
modelConfig,
appId,
promptMode,
speechToTextConfig,
introduction,
suggestedQuestions: openingSuggestedQuestions,
suggestedQuestionsAfterAnswerConfig,
citationConfig,
moderationConfig,
chatPromptConfig,
completionPromptConfig,
dataSets,
datasetConfigs,
visionConfig,
annotationConfig,
textToSpeechConfig,
isFunctionCall,
} = useDebugConfigurationContext()
const postDatasets = dataSets.map(({ id }) => ({
dataset: {
enabled: true,
id,
},
}))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
const config: ChatConfig = {
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode,
chat_prompt_config: isAdvancedMode ? chatPromptConfig : {},
completion_prompt_config: isAdvancedMode ? completionPromptConfig : {},
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
dataset_query_variable: contextVar || '',
opening_statement: introduction,
more_like_this: {
enabled: false,
},
suggested_questions: openingSuggestedQuestions,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
text_to_speech: textToSpeechConfig,
speech_to_text: speechToTextConfig,
retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig,
agent_mode: {
...modelConfig.agentConfig,
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: {
image: visionConfig,
},
annotation_reply: annotationConfig,
supportAnnotation: true,
appId,
}
return config
}
export const useFormattingChangedDispatcher = () => {
const { eventEmitter } = useEventEmitterContextContext()
const dispatcher = useCallback(() => {
eventEmitter?.emit({
type: ORCHESTRATE_CHANGED,
} as any)
}, [eventEmitter])
return dispatcher
}
export const useFormattingChangedSubscription = (chatList: ChatItem[]) => {
const {
formattingChanged,
setFormattingChanged,
} = useDebugConfigurationContext()
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
if (v.type === ORCHESTRATE_CHANGED) {
if (chatList.some(item => item.isAnswer) && !formattingChanged)
setFormattingChanged(true)
}
})
}
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
import type { FC } from 'react' import type { FC } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import React, { useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import cn from 'classnames' import { setAutoFreeze } from 'immer'
import produce, { setAutoFreeze } from 'immer' import { useBoolean } from 'ahooks'
import { useBoolean, useGetState } from 'ahooks'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import dayjs from 'dayjs'
import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api' import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
import FormattingChanged from '../base/warning-mask/formatting-changed' import FormattingChanged from '../base/warning-mask/formatting-changed'
import GroupName from '../base/group-name' import GroupName from '../base/group-name'
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset' import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
import DebugWithMultipleModel from './debug-with-multiple-model' import DebugWithMultipleModel from './debug-with-multiple-model'
import DebugWithSingleModel from './debug-with-single-model'
import type { DebugWithSingleModelRefType } from './debug-with-single-model'
import type { ModelAndParameter } from './types' import type { ModelAndParameter } from './types'
import { import {
APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL,
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} from './types' } from './types'
import { AgentStrategy, AppType, ModelModeType, TransferMethod } from '@/types/app' import { AppType, ModelModeType, TransferMethod } from '@/types/app'
import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' import PromptValuePanel from '@/app/components/app/configuration/prompt-value-panel'
import type { IChatItem } from '@/app/components/app/chat/type'
import Chat from '@/app/components/app/chat'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import { fetchConvesationMessages, fetchSuggestedQuestions, sendChatMessage, sendCompletionMessage, stopChatMessageResponding } from '@/service/debug' import { sendCompletionMessage } from '@/service/debug'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import type { ModelConfig as BackendModelConfig, VisionFile } from '@/types/app' import type { ModelConfig as BackendModelConfig, VisionFile } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config' import { promptVariablesToUserInputsForm } from '@/utils/model-config'
...@@ -32,7 +30,6 @@ import TextGeneration from '@/app/components/app/text-generate/item' ...@@ -32,7 +30,6 @@ import TextGeneration from '@/app/components/app/text-generate/item'
import { IS_CE_EDITION } from '@/config' import { IS_CE_EDITION } from '@/config'
import type { Inputs } from '@/models/debug' import type { Inputs } from '@/models/debug'
import { fetchFileUploadConfig } from '@/service/common' import { fetchFileUploadConfig } from '@/service/common'
import type { Annotation as AnnotationType } from '@/models/log'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
...@@ -63,8 +60,6 @@ const Debug: FC<IDebug> = ({ ...@@ -63,8 +60,6 @@ const Debug: FC<IDebug> = ({
const { const {
appId, appId,
mode, mode,
isFunctionCall,
collectionList,
modelModeType, modelModeType,
hasSetBlockStatus, hasSetBlockStatus,
isAdvancedMode, isAdvancedMode,
...@@ -72,7 +67,6 @@ const Debug: FC<IDebug> = ({ ...@@ -72,7 +67,6 @@ const Debug: FC<IDebug> = ({
chatPromptConfig, chatPromptConfig,
completionPromptConfig, completionPromptConfig,
introduction, introduction,
suggestedQuestions,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
speechToTextConfig, speechToTextConfig,
textToSpeechConfig, textToSpeechConfig,
...@@ -81,79 +75,36 @@ const Debug: FC<IDebug> = ({ ...@@ -81,79 +75,36 @@ const Debug: FC<IDebug> = ({
moreLikeThisConfig, moreLikeThisConfig,
formattingChanged, formattingChanged,
setFormattingChanged, setFormattingChanged,
conversationId,
setConversationId,
controlClearChatMessage,
dataSets, dataSets,
modelConfig, modelConfig,
completionParams, completionParams,
hasSetContextVar, hasSetContextVar,
datasetConfigs, datasetConfigs,
visionConfig, visionConfig,
annotationConfig,
setVisionConfig, setVisionConfig,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
const { data: speech2textDefaultModel } = useDefaultModel(4)
const { data: text2speechDefaultModel } = useDefaultModel(5) const { data: text2speechDefaultModel } = useDefaultModel(5)
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
const chatListDomRef = useRef<HTMLDivElement>(null)
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
useEffect(() => { useEffect(() => {
setAutoFreeze(false) setAutoFreeze(false)
return () => { return () => {
setAutoFreeze(true) setAutoFreeze(true)
} }
}, []) }, [])
useEffect(() => {
// scroll to bottom
if (chatListDomRef.current)
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
}, [chatList])
const getIntroduction = () => replaceStringWithValues(introduction, modelConfig.configs.prompt_variables, inputs)
useEffect(() => {
if (introduction && !chatList.some(item => !item.isAnswer)) {
setChatList([{
id: `${Date.now()}`,
content: getIntroduction(),
isAnswer: true,
isOpeningStatement: true,
suggestedQuestions,
}])
}
}, [introduction, suggestedQuestions, modelConfig.configs.prompt_variables, inputs])
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
const [abortController, setAbortController] = useState<AbortController | null>(null)
const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false) const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false)
const [isShowCannotQueryDataset, setShowCannotQueryDataset] = useState(false) const [isShowCannotQueryDataset, setShowCannotQueryDataset] = useState(false)
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
const [messageTaskId, setMessageTaskId] = useState('')
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
useEffect(() => { useEffect(() => {
if (formattingChanged && chatList.some(item => !item.isAnswer)) if (formattingChanged)
setIsShowFormattingChangeConfirm(true) setIsShowFormattingChangeConfirm(true)
setFormattingChanged(false)
}, [formattingChanged]) }, [formattingChanged])
const debugWithSingleModelRef = React.useRef<DebugWithSingleModelRefType | null>(null)
const handleClearConversation = () => { const handleClearConversation = () => {
setConversationId(null) debugWithSingleModelRef.current?.handleRestart()
abortController?.abort()
setResponsingFalse()
setChatList(introduction
? [{
id: `${Date.now()}`,
content: getIntroduction(),
isAnswer: true,
isOpeningStatement: true,
suggestedQuestions,
}]
: [])
setIsShowSuggestion(false)
} }
const clearConversation = async () => { const clearConversation = async () => {
if (debugWithMultipleModel) { if (debugWithMultipleModel) {
...@@ -169,18 +120,21 @@ const Debug: FC<IDebug> = ({ ...@@ -169,18 +120,21 @@ const Debug: FC<IDebug> = ({
const handleConfirm = () => { const handleConfirm = () => {
clearConversation() clearConversation()
setIsShowFormattingChangeConfirm(false) setIsShowFormattingChangeConfirm(false)
setFormattingChanged(false)
} }
const handleCancel = () => { const handleCancel = () => {
setIsShowFormattingChangeConfirm(false) setIsShowFormattingChangeConfirm(false)
setFormattingChanged(false)
} }
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const logError = (message: string) => { const logError = useCallback((message: string) => {
notify({ type: 'error', message }) notify({ type: 'error', message })
} }, [notify])
const [completionFiles, setCompletionFiles] = useState<VisionFile[]>([])
const checkCanSend = () => { const checkCanSend = useCallback(() => {
if (isAdvancedMode && mode === AppType.chat) { if (isAdvancedMode && mode === AppType.chat) {
if (modelModeType === ModelModeType.completion) { if (modelModeType === ModelModeType.completion) {
if (!hasSetBlockStatus.history) { if (!hasSetBlockStatus.history) {
...@@ -214,319 +168,28 @@ const Debug: FC<IDebug> = ({ ...@@ -214,319 +168,28 @@ const Debug: FC<IDebug> = ({
return false return false
} }
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (completionFiles.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) { if (completionFiles.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') }) notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
} }, [
completionFiles,
const doShowSuggestion = isShowSuggestion && !isResponsing hasSetBlockStatus.history,
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) hasSetBlockStatus.query,
const [userQuery, setUserQuery] = useState('') inputs,
const onSend = async (message: string, files?: VisionFile[]) => { isAdvancedMode,
if (isResponsing) { mode,
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) modelConfig.configs.prompt_variables,
return false t,
} logError,
notify,
if (files?.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) { modelModeType,
notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') }) ])
return false
}
const postDatasets = dataSets.map(({ id }) => ({
dataset: {
enabled: true,
id,
},
}))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
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 postModelConfig: BackendModelConfig = {
text_to_speech: {
enabled: false,
},
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode,
chat_prompt_config: {},
completion_prompt_config: {},
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
dataset_query_variable: contextVar || '',
opening_statement: introduction,
more_like_this: {
enabled: false,
},
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
speech_to_text: speechToTextConfig,
retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig,
agent_mode: {
...modelConfig.agentConfig,
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
},
model: {
provider: modelConfig.provider,
name: modelConfig.model_id,
mode: modelConfig.mode,
completion_params: completionParams as any,
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: {
image: visionConfig,
},
annotation_reply: annotationConfig,
}
if (isAdvancedMode) {
postModelConfig.chat_prompt_config = chatPromptConfig
postModelConfig.completion_prompt_config = completionPromptConfig
}
const data: Record<string, any> = {
conversation_id: conversationId,
inputs,
query: message,
model_config: postModelConfig,
}
if (visionConfig.enabled && files && files?.length > 0) {
data.files = files.map((item) => {
if (item.transfer_method === TransferMethod.local_file) {
return {
...item,
url: '',
}
}
return item
})
}
// qustion
const questionId = `question-${Date.now()}`
const questionItem = {
id: questionId,
content: message,
isAnswer: false,
message_files: files,
}
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
const placeholderAnswerItem = {
id: placeholderAnswerId,
content: '',
isAnswer: true,
}
const newList = [...getChatList(), questionItem, placeholderAnswerItem]
setChatList(newList)
let isAgentMode = false
// answer
const responseItem: IChatItem = {
id: `${Date.now()}`,
content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true,
}
let hasSetResponseId = false
let _newConversationId: null | string = null
setHasStopResponded(false)
setResponsingTrue()
setIsShowSuggestion(false)
sendChatMessage(appId, data, {
getAbortController: (abortController) => {
setAbortController(abortController)
},
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
// console.log('onData', message)
if (!isAgentMode) {
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) {
setConversationId(newConversationId)
_newConversationId = newConversationId
}
setMessageTaskId(taskId)
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
async onCompleted(hasError?: boolean) {
setResponsingFalse()
if (hasError)
return
if (_newConversationId) {
const { data }: any = await fetchConvesationMessages(appId, _newConversationId as string)
const newResponseItem = data.find((item: any) => item.id === responseItem.id)
if (!newResponseItem)
return
setChatList(produce(getChatList(), (draft) => {
const index = draft.findIndex(item => item.id === responseItem.id)
if (index !== -1) {
const requestion = draft[index - 1]
draft[index - 1] = {
...requestion,
log: newResponseItem.message,
}
draft[index] = {
...draft[index],
more: {
time: dayjs.unix(newResponseItem.created_at).format('hh:mm A'),
tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens,
latency: newResponseItem.provider_response_latency.toFixed(2),
},
}
}
}))
}
if (suggestedQuestionsAfterAnswerConfig.enabled && !getHasStopResponded()) {
const { data }: any = await fetchSuggestedQuestions(appId, responseItem.id)
setSuggestQuestions(data)
setIsShowSuggestion(true)
}
},
onFile(file) {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].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
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)
}
}
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
}
responseItem.citation = messageEnd.metadata?.retriever_resources || []
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)
},
onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer
},
onError() {
setResponsingFalse()
// role back placeholder answer
setChatList(produce(getChatList(), (draft) => {
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
}))
},
})
return true
}
useEffect(() => {
if (controlClearChatMessage)
setChatList([])
}, [controlClearChatMessage])
const [completionRes, setCompletionRes] = useState('') const [completionRes, setCompletionRes] = useState('')
const [messageId, setMessageId] = useState<string | null>(null) const [messageId, setMessageId] = useState<string | null>(null)
const [completionFiles, setCompletionFiles] = useState<VisionFile[]>([])
const sendTextCompletion = async () => { const sendTextCompletion = async () => {
if (isResponsing) { if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
...@@ -685,13 +348,13 @@ const Debug: FC<IDebug> = ({ ...@@ -685,13 +348,13 @@ const Debug: FC<IDebug> = ({
setVisionConfig({ setVisionConfig({
...visionConfig, ...visionConfig,
enabled: true, enabled: true,
}) }, true)
} }
else { else {
setVisionConfig({ setVisionConfig({
...visionConfig, ...visionConfig,
enabled: false, enabled: false,
}) }, true)
} }
} }
} }
...@@ -699,17 +362,10 @@ const Debug: FC<IDebug> = ({ ...@@ -699,17 +362,10 @@ const Debug: FC<IDebug> = ({
useEffect(() => { useEffect(() => {
handleVisionConfigInMultipleModel() handleVisionConfigInMultipleModel()
}, [multipleModelConfigs, mode]) }, [multipleModelConfigs, mode])
const allToolIcons = (() => {
const icons: Record<string, any> = {}
modelConfig.agentConfig.tools?.forEach((item: any) => {
icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon
})
return icons
})()
return ( return (
<> <>
<div className="shrink-0"> <div className="shrink-0 pt-4 px-6">
<div className='flex items-center justify-between mb-2'> <div className='flex items-center justify-between mb-2'>
<div className='h2 '>{t('appDebug.inputs.title')}</div> <div className='h2 '>{t('appDebug.inputs.title')}</div>
<div className='flex items-center'> <div className='flex items-center'>
...@@ -761,6 +417,7 @@ const Debug: FC<IDebug> = ({ ...@@ -761,6 +417,7 @@ const Debug: FC<IDebug> = ({
multipleModelConfigs={multipleModelConfigs} multipleModelConfigs={multipleModelConfigs}
onMultipleModelConfigsChange={onMultipleModelConfigsChange} onMultipleModelConfigsChange={onMultipleModelConfigsChange}
onDebugWithMultipleModelChange={handleChangeToSingleModel} onDebugWithMultipleModelChange={handleChangeToSingleModel}
checkCanSend={checkCanSend}
/> />
</div> </div>
) )
...@@ -770,47 +427,16 @@ const Debug: FC<IDebug> = ({ ...@@ -770,47 +427,16 @@ const Debug: FC<IDebug> = ({
<div className="flex flex-col grow"> <div className="flex flex-col grow">
{/* Chat */} {/* Chat */}
{mode === AppType.chat && ( {mode === AppType.chat && (
<div className="mt-[34px] h-full flex flex-col"> <div className='grow h-0 overflow-hidden'>
<div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}> <DebugWithSingleModel
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}> ref={debugWithSingleModelRef}
<Chat checkCanSend={checkCanSend}
chatList={chatList} />
query={userQuery}
onQueryChange={setUserQuery}
onSend={onSend}
checkCanSend={checkCanSend}
feedbackDisabled
useCurrentUserAvatar
isResponsing={isResponsing}
canStopResponsing={!!messageTaskId}
abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId)
setHasStopResponded(true)
setResponsingFalse()
}}
isShowSuggestion={doShowSuggestion}
suggestionList={suggestQuestions}
isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel}
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
isShowCitation={citationConfig.enabled}
isShowCitationHitInfo
isShowPromptLog
visionConfig={{
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
supportAnnotation
appId={appId}
onChatListChange={setChatList}
allToolIcons={allToolIcons}
/>
</div>
</div>
</div> </div>
)} )}
{/* Text Generation */} {/* Text Generation */}
{mode === AppType.completion && ( {mode === AppType.completion && (
<div className="mt-6"> <div className="mt-6 px-6 pb-4">
<GroupName name={t('appDebug.result')} /> <GroupName name={t('appDebug.result')} />
{(completionRes || isResponsing) && ( {(completionRes || isResponsing) && (
<TextGeneration <TextGeneration
...@@ -830,12 +456,6 @@ const Debug: FC<IDebug> = ({ ...@@ -830,12 +456,6 @@ const Debug: FC<IDebug> = ({
)} )}
</div> </div>
)} )}
{isShowFormattingChangeConfirm && (
<FormattingChanged
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
)}
{isShowCannotQueryDataset && ( {isShowCannotQueryDataset && (
<CannotQueryDataset <CannotQueryDataset
onConfirm={() => setShowCannotQueryDataset(false)} onConfirm={() => setShowCannotQueryDataset(false)}
...@@ -844,6 +464,12 @@ const Debug: FC<IDebug> = ({ ...@@ -844,6 +464,12 @@ const Debug: FC<IDebug> = ({
</div> </div>
) )
} }
{isShowFormattingChangeConfirm && (
<FormattingChanged
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
)}
{!hasSetAPIKEY && (<HasNotSetAPIKEY isTrailFinished={!IS_CE_EDITION} onSetting={onSetting} />)} {!hasSetAPIKEY && (<HasNotSetAPIKEY isTrailFinished={!IS_CE_EDITION} onSetting={onSetting} />)}
</> </>
) )
......
...@@ -16,3 +16,4 @@ export type DebugWithSingleOrMultipleModelConfigs = { ...@@ -16,3 +16,4 @@ export type DebugWithSingleOrMultipleModelConfigs = {
export const APP_CHAT_WITH_MULTIPLE_MODEL = 'APP_CHAT_WITH_MULTIPLE_MODEL' export const APP_CHAT_WITH_MULTIPLE_MODEL = 'APP_CHAT_WITH_MULTIPLE_MODEL'
export const APP_CHAT_WITH_MULTIPLE_MODEL_RESTART = 'APP_CHAT_WITH_MULTIPLE_MODEL_RESTART' export const APP_CHAT_WITH_MULTIPLE_MODEL_RESTART = 'APP_CHAT_WITH_MULTIPLE_MODEL_RESTART'
export const APP_SIDEBAR_SHOULD_COLLAPSE = 'APP_SIDEBAR_SHOULD_COLLAPSE' export const APP_SIDEBAR_SHOULD_COLLAPSE = 'APP_SIDEBAR_SHOULD_COLLAPSE'
export const ORCHESTRATE_CHANGED = 'ORCHESTRATE_CHANGED'
...@@ -13,7 +13,10 @@ import Button from '../../base/button' ...@@ -13,7 +13,10 @@ import Button from '../../base/button'
import Loading from '../../base/loading' import Loading from '../../base/loading'
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config' import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal' import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
import { useDebugWithSingleOrMultipleModel } from './debug/hooks' import {
useDebugWithSingleOrMultipleModel,
useFormattingChangedDispatcher,
} from './debug/hooks'
import type { ModelAndParameter } from './debug/types' import type { ModelAndParameter } from './debug/types'
import { APP_SIDEBAR_SHOULD_COLLAPSE } from './debug/types' import { APP_SIDEBAR_SHOULD_COLLAPSE } from './debug/types'
import PublishWithMultipleModel from './debug/debug-with-multiple-model/publish-with-multiple-model' import PublishWithMultipleModel from './debug/debug-with-multiple-model/publish-with-multiple-model'
...@@ -45,7 +48,6 @@ import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, Trans ...@@ -45,7 +48,6 @@ import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, Trans
import { PromptMode } from '@/models/debug' import { PromptMode } from '@/models/debug'
import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG, supportFunctionCallModels } from '@/config' import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG, supportFunctionCallModels } from '@/config'
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
import I18n from '@/context/i18n'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Drawer from '@/app/components/base/drawer' import Drawer from '@/app/components/base/drawer'
...@@ -111,10 +113,11 @@ const Configuration: FC = () => { ...@@ -111,10 +113,11 @@ const Configuration: FC = () => {
embedding_model_name: '', embedding_model_name: '',
}, },
}) })
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => { const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => {
doSetAnnotationConfig(config) doSetAnnotationConfig(config)
if (!notSetFormatChanged) if (!notSetFormatChanged)
setFormattingChanged(true) formattingChangedDispatcher()
} }
const [moderationConfig, setModerationConfig] = useState<ModerationConfig>({ const [moderationConfig, setModerationConfig] = useState<ModerationConfig>({
...@@ -203,7 +206,7 @@ const Configuration: FC = () => { ...@@ -203,7 +206,7 @@ const Configuration: FC = () => {
return return
} }
setFormattingChanged(true) formattingChangedDispatcher()
if (data.find(item => !item.name)) { // has not loaded selected dataset if (data.find(item => !item.name)) { // has not loaded selected dataset
const newSelected = produce(data, (draft: any) => { const newSelected = produce(data, (draft: any) => {
data.forEach((item, index) => { data.forEach((item, index) => {
...@@ -299,7 +302,7 @@ const Configuration: FC = () => { ...@@ -299,7 +302,7 @@ const Configuration: FC = () => {
transfer_methods: config.transfer_methods || [TransferMethod.local_file], transfer_methods: config.transfer_methods || [TransferMethod.local_file],
}) })
if (!notNoticeFormattingChanged) if (!notNoticeFormattingChanged)
setFormattingChanged(true) formattingChangedDispatcher()
} }
const { const {
...@@ -634,7 +637,6 @@ const Configuration: FC = () => { ...@@ -634,7 +637,6 @@ const Configuration: FC = () => {
} }
const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false) const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
const { locale } = useContext(I18n)
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
const { const {
...@@ -820,7 +822,7 @@ const Configuration: FC = () => { ...@@ -820,7 +822,7 @@ const Configuration: FC = () => {
) )
} }
</div> </div>
<div className='flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '> <div className='flex flex-col grow h-0 rounded-tl-2xl border-t border-l bg-gray-50 '>
<Debug <Debug
hasSetAPIKEY={hasSettedApiKey} hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })} onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
......
...@@ -97,18 +97,21 @@ const CacheCtrlBtn: FC<Props> = ({ ...@@ -97,18 +97,21 @@ const CacheCtrlBtn: FC<Props> = ({
</div> </div>
</div> </div>
) )
: ( : answer
<TooltipPlus ? (
popupContent={t('appDebug.feature.annotation.add') as string} <TooltipPlus
> popupContent={t('appDebug.feature.annotation.add') as string}
<div
className='p-1 rounded-md hover:bg-[#EEF4FF] hover:text-[#444CE7] cursor-pointer'
onClick={handleAdd}
> >
<MessageFastPlus className='w-4 h-4' /> <div
</div> className='p-1 rounded-md hover:bg-[#EEF4FF] hover:text-[#444CE7] cursor-pointer'
</TooltipPlus> onClick={handleAdd}
)} >
<MessageFastPlus className='w-4 h-4' />
</div>
</TooltipPlus>
)
: null
}
<TooltipPlus <TooltipPlus
popupContent={t('appDebug.feature.annotation.edit') as string} popupContent={t('appDebug.feature.annotation.edit') as string}
> >
......
...@@ -14,7 +14,10 @@ type AgentContentProps = { ...@@ -14,7 +14,10 @@ type AgentContentProps = {
const AgentContent: FC<AgentContentProps> = ({ const AgentContent: FC<AgentContentProps> = ({
item, item,
}) => { }) => {
const { allToolIcons } = useChatContext() const {
allToolIcons,
isResponsing,
} = useChatContext()
const { const {
annotation, annotation,
agent_thoughts, agent_thoughts,
...@@ -42,7 +45,7 @@ const AgentContent: FC<AgentContentProps> = ({ ...@@ -42,7 +45,7 @@ const AgentContent: FC<AgentContentProps> = ({
<Thought <Thought
thought={thought} thought={thought}
allToolIcons={allToolIcons || {}} allToolIcons={allToolIcons || {}}
isFinished={!!thought.observation} isFinished={!!thought.observation || !isResponsing}
/> />
)} )}
......
...@@ -15,9 +15,13 @@ import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal ...@@ -15,9 +15,13 @@ import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal
type AnswerProps = { type AnswerProps = {
item: ChatItem item: ChatItem
question: string
index: number
} }
const Answer: FC<AnswerProps> = ({ const Answer: FC<AnswerProps> = ({
item, item,
question,
index,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
...@@ -56,7 +60,15 @@ const Answer: FC<AnswerProps> = ({ ...@@ -56,7 +60,15 @@ const Answer: FC<AnswerProps> = ({
<div className='relative pr-10'> <div className='relative pr-10'>
<AnswerTriangle className='absolute -left-2 top-0 w-2 h-3 text-gray-100' /> <AnswerTriangle className='absolute -left-2 top-0 w-2 h-3 text-gray-100' />
<div className='group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900'> <div className='group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900'>
<Operation item={item} /> {
!responsing && (
<Operation
item={item}
question={question}
index={index}
/>
)
}
{ {
responsing && !content && !hasAgentThoughts && ( responsing && !content && !hasAgentThoughts && (
<div className='flex items-center justify-center w-6 h-5'> <div className='flex items-center justify-center w-6 h-5'>
...@@ -75,7 +87,7 @@ const Answer: FC<AnswerProps> = ({ ...@@ -75,7 +87,7 @@ const Answer: FC<AnswerProps> = ({
) )
} }
{ {
annotation?.id && !annotation?.logAnnotation && ( annotation?.id && annotation.authorName && (
<EditTitle <EditTitle
className='mt-1' className='mt-1'
title={t('appAnnotation.editBy', { author: annotation.authorName })} title={t('appAnnotation.editBy', { author: annotation.authorName })}
......
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react'
import type { ChatItem } from '../../types' import type { ChatItem } from '../../types'
import { useCurrentAnswerIsResponsing } from '../hooks' import { useCurrentAnswerIsResponsing } from '../hooks'
import { useChatContext } from '../context' import { useChatContext } from '../context'
import CopyBtn from '@/app/components/app/chat/copy-btn' import CopyBtn from '@/app/components/app/chat/copy-btn'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
import AudioBtn from '@/app/components/base/audio-btn' import AudioBtn from '@/app/components/base/audio-btn'
import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
type OperationProps = { type OperationProps = {
item: ChatItem item: ChatItem
question: string
index: number
} }
const Operation: FC<OperationProps> = ({ const Operation: FC<OperationProps> = ({
item, item,
question,
index,
}) => { }) => {
const { config } = useChatContext() const {
config,
onAnnotationAdded,
onAnnotationEdited,
onAnnotationRemoved,
} = useChatContext()
const [isShowReplyModal, setIsShowReplyModal] = useState(false)
const responsing = useCurrentAnswerIsResponsing(item.id) const responsing = useCurrentAnswerIsResponsing(item.id)
const { const {
id,
isOpeningStatement, isOpeningStatement,
content, content,
annotation, annotation,
} = item } = item
const hasAnnotation = !!annotation?.id
return ( return (
<div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'> <div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'>
...@@ -36,6 +51,34 @@ const Operation: FC<OperationProps> = ({ ...@@ -36,6 +51,34 @@ const Operation: FC<OperationProps> = ({
className='hidden group-hover:block' className='hidden group-hover:block'
/> />
)} )}
{(!isOpeningStatement && config?.supportAnnotation && config.annotation_reply?.enabled) && (
<AnnotationCtrlBtn
appId={config?.appId || ''}
messageId={id}
annotationId={annotation?.id || ''}
className='hidden group-hover:block ml-1 shrink-0'
cached={hasAnnotation}
query={question}
answer={content}
onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)}
onEdit={() => setIsShowReplyModal(true)}
onRemoved={() => onAnnotationRemoved?.(index)}
/>
)}
<EditReplyModal
isShow={isShowReplyModal}
onHide={() => setIsShowReplyModal(false)}
query={question}
answer={content}
onEdited={(editedQuery, editedAnswer) => onAnnotationEdited?.(editedQuery, editedAnswer, index)}
onAdded={(annotationId, authorName, editedQuery, editedAnswer) => onAnnotationAdded?.(annotationId, authorName, editedQuery, editedAnswer, index)}
appId={config?.appId || ''}
messageId={id}
annotationId={annotation?.id || ''}
createdAt={annotation?.created_at}
onRemove={() => onAnnotationRemoved?.(index)}
/>
{ {
annotation?.id && ( annotation?.id && (
<div <div
......
...@@ -2,23 +2,20 @@ ...@@ -2,23 +2,20 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { createContext, useContext } from 'use-context-selector' import { createContext, useContext } from 'use-context-selector'
import type { import type { ChatProps } from './index'
ChatConfig,
ChatItem,
OnSend,
} from '../types'
import type { Emoji } from '@/app/components/tools/types'
export type ChatContextValue = { export type ChatContextValue = Pick<ChatProps, 'config'
config?: ChatConfig | 'isResponsing'
isResponsing?: boolean | 'chatList'
chatList: ChatItem[] | 'showPromptLog'
showPromptLog?: boolean | 'questionIcon'
questionIcon?: ReactNode | 'answerIcon'
answerIcon?: ReactNode | 'allToolIcons'
allToolIcons?: Record<string, string | Emoji> | 'onSend'
onSend?: OnSend | 'onAnnotationEdited'
} | 'onAnnotationAdded'
| 'onAnnotationRemoved'
>
const ChatContext = createContext<ChatContextValue>({ const ChatContext = createContext<ChatContextValue>({
chatList: [], chatList: [],
...@@ -38,6 +35,9 @@ export const ChatContextProvider = ({ ...@@ -38,6 +35,9 @@ export const ChatContextProvider = ({
answerIcon, answerIcon,
allToolIcons, allToolIcons,
onSend, onSend,
onAnnotationEdited,
onAnnotationAdded,
onAnnotationRemoved,
}: ChatContextProviderProps) => { }: ChatContextProviderProps) => {
return ( return (
<ChatContext.Provider value={{ <ChatContext.Provider value={{
...@@ -49,6 +49,9 @@ export const ChatContextProvider = ({ ...@@ -49,6 +49,9 @@ export const ChatContextProvider = ({
answerIcon, answerIcon,
allToolIcons, allToolIcons,
onSend, onSend,
onAnnotationEdited,
onAnnotationAdded,
onAnnotationRemoved,
}}> }}>
{children} {children}
</ChatContext.Provider> </ChatContext.Provider>
......
import { import {
useCallback,
useEffect, useEffect,
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { produce } from 'immer' import { produce } from 'immer'
import { useGetState } from 'ahooks'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { import type {
ChatConfig, ChatConfig,
...@@ -19,12 +19,53 @@ import { TransferMethod } from '@/types/app' ...@@ -19,12 +19,53 @@ import { TransferMethod } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { ssePost } from '@/service/base' import { ssePost } from '@/service/base'
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
import type { Annotation } from '@/models/log'
type GetAbortController = (abortController: AbortController) => void type GetAbortController = (abortController: AbortController) => void
type SendCallback = { type SendCallback = {
onGetConvesationMessages: (conversationId: string, getAbortController: GetAbortController) => Promise<any> onGetConvesationMessages: (conversationId: string, getAbortController: GetAbortController) => Promise<any>
onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any> onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any>
} }
export const useCheckPromptVariables = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const checkPromptVariables = useCallback((promptVariablesConfig: {
inputs: Inputs
promptVariables: PromptVariable[]
}) => {
const {
promptVariables,
inputs,
} = promptVariablesConfig
let hasEmptyInput = ''
const requiredVars = promptVariables.filter(({ key, name, required, type }) => {
if (type === 'api')
return false
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res
})
if (requiredVars?.length) {
requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput)
return
if (!inputs[key])
hasEmptyInput = name
})
}
if (hasEmptyInput) {
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }) })
return false
}
}, [notify, t])
return checkPromptVariables
}
export const useChat = ( export const useChat = (
config: ChatConfig, config: ChatConfig,
promptVariablesConfig?: { promptVariablesConfig?: {
...@@ -39,19 +80,31 @@ export const useChat = ( ...@@ -39,19 +80,31 @@ export const useChat = (
const connversationId = useRef('') const connversationId = useRef('')
const hasStopResponded = useRef(false) const hasStopResponded = useRef(false)
const [isResponsing, setIsResponsing] = useState(false) const [isResponsing, setIsResponsing] = useState(false)
const [chatList, setChatList, getChatList] = useGetState<ChatItem[]>(prevChatList || []) const isResponsingRef = useRef(false)
const [taskId, setTaskId] = useState('') const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
const chatListRef = useRef<ChatItem[]>(prevChatList || [])
const taskIdRef = useRef('')
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([]) const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
const [abortController, setAbortController] = useState<AbortController | null>(null) const abortControllerRef = useRef<AbortController | null>(null)
const [conversationMessagesAbortController, setConversationMessagesAbortController] = useState<AbortController | null>(null) const conversationMessagesAbortControllerRef = useRef<AbortController | null>(null)
const [suggestedQuestionsAbortController, setSuggestedQuestionsAbortController] = useState<AbortController | null>(null) const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
const checkPromptVariables = useCheckPromptVariables()
const getIntroduction = (str: string) => {
const handleUpdateChatList = useCallback((newChatList: ChatItem[]) => {
setChatList(newChatList)
chatListRef.current = newChatList
}, [])
const handleResponsing = useCallback((isResponsing: boolean) => {
setIsResponsing(isResponsing)
isResponsingRef.current = isResponsing
}, [])
const getIntroduction = useCallback((str: string) => {
return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {}) return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {})
} }, [promptVariablesConfig?.inputs, promptVariablesConfig?.promptVariables])
useEffect(() => { useEffect(() => {
if (config.opening_statement && !chatList.some(item => !item.isAnswer)) { if (config.opening_statement && !chatList.length) {
setChatList([{ handleUpdateChatList([{
id: `${Date.now()}`, id: `${Date.now()}`,
content: getIntroduction(config.opening_statement), content: getIntroduction(config.opening_statement),
isAnswer: true, isAnswer: true,
...@@ -59,25 +112,31 @@ export const useChat = ( ...@@ -59,25 +112,31 @@ export const useChat = (
suggestedQuestions: config.suggested_questions, suggestedQuestions: config.suggested_questions,
}]) }])
} }
}, [config.opening_statement, config.suggested_questions, promptVariablesConfig?.inputs]) }, [
config.opening_statement,
const handleStop = () => { config.suggested_questions,
if (stopChat && taskId) getIntroduction,
stopChat(taskId) chatList,
if (abortController) handleUpdateChatList,
abortController.abort() ])
if (conversationMessagesAbortController)
conversationMessagesAbortController.abort()
if (suggestedQuestionsAbortController)
suggestedQuestionsAbortController.abort()
}
const handleRestart = () => { const handleStop = useCallback(() => {
handleStop()
hasStopResponded.current = true hasStopResponded.current = true
handleResponsing(false)
if (stopChat && taskIdRef.current)
stopChat(taskIdRef.current)
if (abortControllerRef.current)
abortControllerRef.current.abort()
if (conversationMessagesAbortControllerRef.current)
conversationMessagesAbortControllerRef.current.abort()
if (suggestedQuestionsAbortControllerRef.current)
suggestedQuestionsAbortControllerRef.current.abort()
}, [stopChat, handleResponsing])
const handleRestart = useCallback(() => {
handleStop()
connversationId.current = '' connversationId.current = ''
setIsResponsing(false) const newChatList = config.opening_statement
setChatList(config.opening_statement
? [{ ? [{
id: `${Date.now()}`, id: `${Date.now()}`,
content: config.opening_statement, content: config.opening_statement,
...@@ -85,10 +144,38 @@ export const useChat = ( ...@@ -85,10 +144,38 @@ export const useChat = (
isOpeningStatement: true, isOpeningStatement: true,
suggestedQuestions: config.suggested_questions, suggestedQuestions: config.suggested_questions,
}] }]
: []) : []
handleUpdateChatList(newChatList)
setSuggestQuestions([]) setSuggestQuestions([])
} }, [
const handleSend = async ( config,
handleStop,
handleUpdateChatList,
])
const updateCurrentQA = useCallback(({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: ChatItem
questionId: string
placeholderAnswerId: string
questionItem: ChatItem
}) => {
const newListWithAnswer = produce(
chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
handleUpdateChatList(newListWithAnswer)
}, [handleUpdateChatList])
const handleSend = useCallback(async (
url: string, url: string,
data: any, data: any,
{ {
...@@ -97,62 +184,13 @@ export const useChat = ( ...@@ -97,62 +184,13 @@ export const useChat = (
}: SendCallback, }: SendCallback,
) => { ) => {
setSuggestQuestions([]) setSuggestQuestions([])
if (isResponsing) { if (isResponsingRef.current) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
return false return false
} }
if (promptVariablesConfig?.inputs && promptVariablesConfig?.promptVariables) { if (promptVariablesConfig?.inputs && promptVariablesConfig?.promptVariables)
const { checkPromptVariables(promptVariablesConfig)
promptVariables,
inputs,
} = promptVariablesConfig
let hasEmptyInput = ''
const requiredVars = promptVariables.filter(({ key, name, required, type }) => {
if (type === 'api')
return false
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res
})
if (requiredVars?.length) {
requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput)
return
if (!inputs[key])
hasEmptyInput = name
})
}
if (hasEmptyInput) {
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }) })
return false
}
}
const updateCurrentQA = ({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: ChatItem
questionId: string
placeholderAnswerId: string
questionItem: ChatItem
}) => {
// 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 questionId = `question-${Date.now()}` const questionId = `question-${Date.now()}`
const questionItem = { const questionItem = {
...@@ -169,8 +207,8 @@ export const useChat = ( ...@@ -169,8 +207,8 @@ export const useChat = (
isAnswer: true, isAnswer: true,
} }
const newList = [...getChatList(), questionItem, placeholderAnswerItem] const newList = [...chatListRef.current, questionItem, placeholderAnswerItem]
setChatList(newList) handleUpdateChatList(newList)
// answer // answer
const responseItem: ChatItem = { const responseItem: ChatItem = {
...@@ -181,7 +219,7 @@ export const useChat = ( ...@@ -181,7 +219,7 @@ export const useChat = (
isAnswer: true, isAnswer: true,
} }
setIsResponsing(true) handleResponsing(true)
hasStopResponded.current = false hasStopResponded.current = false
const bodyParams = { const bodyParams = {
...@@ -211,7 +249,7 @@ export const useChat = ( ...@@ -211,7 +249,7 @@ export const useChat = (
}, },
{ {
getAbortController: (abortController) => { getAbortController: (abortController) => {
setAbortController(abortController) abortControllerRef.current = abortController
}, },
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
if (!isAgentMode) { if (!isAgentMode) {
...@@ -231,7 +269,7 @@ export const useChat = ( ...@@ -231,7 +269,7 @@ export const useChat = (
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
connversationId.current = newConversationId connversationId.current = newConversationId
setTaskId(taskId) taskIdRef.current = taskId
if (messageId) if (messageId)
responseItem.id = messageId responseItem.id = messageId
...@@ -243,21 +281,21 @@ export const useChat = ( ...@@ -243,21 +281,21 @@ export const useChat = (
}) })
}, },
async onCompleted(hasError?: boolean) { async onCompleted(hasError?: boolean) {
setIsResponsing(false) handleResponsing(false)
if (hasError) if (hasError)
return return
if (connversationId.current) { if (connversationId.current && !hasStopResponded.current) {
const { data }: any = await onGetConvesationMessages( const { data }: any = await onGetConvesationMessages(
connversationId.current, connversationId.current,
newAbortController => setConversationMessagesAbortController(newAbortController), newAbortController => conversationMessagesAbortControllerRef.current = newAbortController,
) )
const newResponseItem = data.find((item: any) => item.id === responseItem.id) const newResponseItem = data.find((item: any) => item.id === responseItem.id)
if (!newResponseItem) if (!newResponseItem)
return return
setChatList(produce(getChatList(), (draft) => { const newChatList = produce(chatListRef.current, (draft) => {
const index = draft.findIndex(item => item.id === responseItem.id) const index = draft.findIndex(item => item.id === responseItem.id)
if (index !== -1) { if (index !== -1) {
const requestion = draft[index - 1] const requestion = draft[index - 1]
...@@ -274,12 +312,13 @@ export const useChat = ( ...@@ -274,12 +312,13 @@ export const useChat = (
}, },
} }
} }
})) })
handleUpdateChatList(newChatList)
} }
if (config.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) { if (config.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
const { data }: any = await onGetSuggestedQuestions( const { data }: any = await onGetSuggestedQuestions(
responseItem.id, responseItem.id,
newAbortController => setSuggestedQuestionsAbortController(newAbortController), newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
) )
setSuggestQuestions(data) setSuggestQuestions(data)
} }
...@@ -330,8 +369,9 @@ export const useChat = ( ...@@ -330,8 +369,9 @@ export const useChat = (
id: messageEnd.metadata.annotation_reply.id, id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name, authorName: messageEnd.metadata.annotation_reply.account.name,
}) })
const baseState = chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId)
const newListWithAnswer = produce( const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), baseState,
(draft) => { (draft) => {
if (!draft.find(item => item.id === questionId)) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem }) draft.push({ ...questionItem })
...@@ -340,38 +380,113 @@ export const useChat = ( ...@@ -340,38 +380,113 @@ export const useChat = (
...responseItem, ...responseItem,
}) })
}) })
setChatList(newListWithAnswer) handleUpdateChatList(newListWithAnswer)
return return
} }
responseItem.citation = messageEnd.metadata?.retriever_resources || [] responseItem.citation = messageEnd.metadata?.retriever_resources || []
const newListWithAnswer = produce( const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => { (draft) => {
if (!draft.find(item => item.id === questionId)) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem }) draft.push({ ...questionItem })
draft.push({ ...responseItem }) draft.push({ ...responseItem })
}) })
setChatList(newListWithAnswer) handleUpdateChatList(newListWithAnswer)
}, },
onMessageReplace: (messageReplace) => { onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer responseItem.content = messageReplace.answer
}, },
onError() { onError() {
setIsResponsing(false) handleResponsing(false)
// role back placeholder answer const newChatList = produce(chatListRef.current, (draft) => {
setChatList(produce(getChatList(), (draft) => {
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1) draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
})) })
handleUpdateChatList(newChatList)
}, },
}) })
return true return true
} }, [
checkPromptVariables,
config.suggested_questions_after_answer,
updateCurrentQA,
t,
notify,
promptVariablesConfig,
handleUpdateChatList,
handleResponsing,
])
const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
setChatList(chatListRef.current.map((item, i) => {
if (i === index - 1) {
return {
...item,
content: query,
}
}
if (i === index) {
return {
...item,
content: answer,
annotation: {
...item.annotation,
logAnnotation: undefined,
} as any,
}
}
return item
}))
}, [])
const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
setChatList(chatListRef.current.map((item, i) => {
if (i === index - 1) {
return {
...item,
content: query,
}
}
if (i === index) {
const answerItem = {
...item,
content: item.content,
annotation: {
id: annotationId,
authorName,
logAnnotation: {
content: answer,
account: {
id: '',
name: authorName,
email: '',
},
},
} as Annotation,
}
return answerItem
}
return item
}))
}, [])
const handleAnnotationRemoved = useCallback((index: number) => {
setChatList(chatListRef.current.map((item, i) => {
if (i === index) {
return {
...item,
content: item.content,
annotation: {
...(item.annotation || {}),
id: '',
} as Annotation,
}
}
return item
}))
}, [])
return { return {
chatList, chatList,
getChatList,
setChatList, setChatList,
conversationId: connversationId.current, conversationId: connversationId.current,
isResponsing, isResponsing,
...@@ -380,6 +495,9 @@ export const useChat = ( ...@@ -380,6 +495,9 @@ export const useChat = (
suggestedQuestions, suggestedQuestions,
handleRestart, handleRestart,
handleStop, handleStop,
handleAnnotationEdited,
handleAnnotationAdded,
handleAnnotationRemoved,
} }
} }
......
...@@ -4,8 +4,10 @@ import type { ...@@ -4,8 +4,10 @@ import type {
} from 'react' } from 'react'
import { import {
memo, memo,
useEffect,
useRef, useRef,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next'
import { useThrottleEffect } from 'ahooks' import { useThrottleEffect } from 'ahooks'
import type { import type {
ChatConfig, ChatConfig,
...@@ -18,13 +20,17 @@ import ChatInput from './chat-input' ...@@ -18,13 +20,17 @@ import ChatInput from './chat-input'
import TryToAsk from './try-to-ask' import TryToAsk from './try-to-ask'
import { ChatContextProvider } from './context' import { ChatContextProvider } from './context'
import type { Emoji } from '@/app/components/tools/types' import type { Emoji } from '@/app/components/tools/types'
import Button from '@/app/components/base/button'
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
export type ChatProps = { export type ChatProps = {
config: ChatConfig
onSend?: OnSend
chatList: ChatItem[] chatList: ChatItem[]
isResponsing: boolean config?: ChatConfig
isResponsing?: boolean
noStopResponding?: boolean
onStopResponding?: () => void
noChatInput?: boolean noChatInput?: boolean
onSend?: OnSend
chatContainerclassName?: string chatContainerclassName?: string
chatFooterClassName?: string chatFooterClassName?: string
suggestedQuestions?: string[] suggestedQuestions?: string[]
...@@ -32,12 +38,17 @@ export type ChatProps = { ...@@ -32,12 +38,17 @@ export type ChatProps = {
questionIcon?: ReactNode questionIcon?: ReactNode
answerIcon?: ReactNode answerIcon?: ReactNode
allToolIcons?: Record<string, string | Emoji> allToolIcons?: Record<string, string | Emoji>
onAnnotationEdited?: (question: string, answer: string, index: number) => void
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
onAnnotationRemoved?: (index: number) => void
} }
const Chat: FC<ChatProps> = ({ const Chat: FC<ChatProps> = ({
config, config,
onSend, onSend,
chatList, chatList,
isResponsing, isResponsing,
noStopResponding,
onStopResponding,
noChatInput, noChatInput,
chatContainerclassName, chatContainerclassName,
chatFooterClassName, chatFooterClassName,
...@@ -46,16 +57,46 @@ const Chat: FC<ChatProps> = ({ ...@@ -46,16 +57,46 @@ const Chat: FC<ChatProps> = ({
questionIcon, questionIcon,
answerIcon, answerIcon,
allToolIcons, allToolIcons,
onAnnotationAdded,
onAnnotationEdited,
onAnnotationRemoved,
}) => { }) => {
const ref = useRef<HTMLDivElement>(null) const { t } = useTranslation()
const chatContainerRef = useRef<HTMLDivElement>(null)
const chatFooterRef = useRef<HTMLDivElement>(null) const chatFooterRef = useRef<HTMLDivElement>(null)
const handleScrolltoBottom = () => {
if (chatContainerRef.current)
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight
}
useThrottleEffect(() => { useThrottleEffect(() => {
if (ref.current) handleScrolltoBottom()
ref.current.scrollTop = ref.current.scrollHeight
if (chatContainerRef.current && chatFooterRef.current)
chatFooterRef.current.style.width = `${chatContainerRef.current.clientWidth}px`
}, [chatList], { wait: 500 }) }, [chatList], { wait: 500 })
const hasTryToAsk = config.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend useEffect(() => {
if (chatFooterRef.current && chatContainerRef.current) {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { blockSize } = entry.borderBoxSize[0]
chatContainerRef.current!.style.paddingBottom = `${blockSize}px`
handleScrolltoBottom()
}
})
resizeObserver.observe(chatFooterRef.current)
return () => {
resizeObserver.disconnect()
}
}
}, [chatFooterRef, chatContainerRef])
const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend
return ( return (
<ChatContextProvider <ChatContextProvider
...@@ -67,19 +108,24 @@ const Chat: FC<ChatProps> = ({ ...@@ -67,19 +108,24 @@ const Chat: FC<ChatProps> = ({
answerIcon={answerIcon} answerIcon={answerIcon}
allToolIcons={allToolIcons} allToolIcons={allToolIcons}
onSend={onSend} onSend={onSend}
onAnnotationAdded={onAnnotationAdded}
onAnnotationEdited={onAnnotationEdited}
onAnnotationRemoved={onAnnotationRemoved}
> >
<div className='relative h-full'> <div className='relative h-full'>
<div <div
ref={ref} ref={chatContainerRef}
className={`relative h-full overflow-y-auto ${chatContainerclassName}`} className={`relative h-full overflow-y-auto ${chatContainerclassName}`}
> >
{ {
chatList.map((item) => { chatList.map((item, index) => {
if (item.isAnswer) { if (item.isAnswer) {
return ( return (
<Answer <Answer
key={item.id} key={item.id}
item={item} item={item}
question={chatList[index - 1]?.content}
index={index}
/> />
) )
} }
...@@ -91,35 +137,41 @@ const Chat: FC<ChatProps> = ({ ...@@ -91,35 +137,41 @@ const Chat: FC<ChatProps> = ({
) )
}) })
} }
</div>
<div
className={`absolute bottom-0 ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`}
ref={chatFooterRef}
style={{
background: 'linear-gradient(0deg, #F9FAFB 40%, rgba(255, 255, 255, 0.00) 100%)',
}}
>
{ {
(hasTryToAsk || !noChatInput) && ( !noStopResponding && isResponsing && (
<div <div className='flex justify-center mb-2'>
className={`sticky bottom-0 w-full backdrop-blur-[20px] ${chatFooterClassName}`} <Button className='py-0 px-3 h-7 bg-white shadow-xs' onClick={onStopResponding}>
ref={chatFooterRef} <StopCircle className='mr-[5px] w-3.5 h-3.5 text-gray-500' />
style={{ <span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span>
background: 'linear-gradient(0deg, #FFF 0%, rgba(255, 255, 255, 0.40) 100%)', </Button>
}}
>
{
hasTryToAsk && (
<TryToAsk
suggestedQuestions={suggestedQuestions}
onSend={onSend}
/>
)
}
{
!noChatInput && (
<ChatInput
visionConfig={config?.file_upload?.image}
speechToTextConfig={config.speech_to_text}
onSend={onSend}
/>
)
}
</div> </div>
) )
} }
{
hasTryToAsk && (
<TryToAsk
suggestedQuestions={suggestedQuestions}
onSend={onSend}
/>
)
}
{
!noChatInput && (
<ChatInput
visionConfig={config?.file_upload?.image}
speechToTextConfig={config?.speech_to_text}
onSend={onSend}
/>
)
}
</div> </div>
</div> </div>
</ChatContextProvider> </ChatContextProvider>
......
...@@ -41,7 +41,10 @@ export type EnableType = { ...@@ -41,7 +41,10 @@ export type EnableType = {
enabled: boolean enabled: boolean
} }
export type ChatConfig = Omit<ModelConfig, 'model'> export type ChatConfig = Omit<ModelConfig, 'model'> & {
supportAnnotation?: boolean
appId?: string
}
export type ChatItem = IChatItem export type ChatItem = IChatItem
......
...@@ -96,7 +96,7 @@ type IDebugConfiguration = { ...@@ -96,7 +96,7 @@ type IDebugConfiguration = {
hasSetContextVar: boolean hasSetContextVar: boolean
isShowVisionConfig: boolean isShowVisionConfig: boolean
visionConfig: VisionSettings visionConfig: VisionSettings
setVisionConfig: (visionConfig: VisionSettings) => void setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void
} }
const DebugConfigurationContext = createContext<IDebugConfiguration>({ const DebugConfigurationContext = createContext<IDebugConfiguration>({
......
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