mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
fix: multiple model configuration clear conversation by rerender (#2286)
This commit is contained in:
parent
6f7fd6613a
commit
68406b9906
|
@ -132,6 +132,7 @@ const EditAnnotationModal: FC<Props> = ({
|
|||
onRemove={() => {
|
||||
onRemove()
|
||||
setShowModal(false)
|
||||
onHide()
|
||||
}}
|
||||
text={t('appDebug.feature.annotation.removeConfirm') as string}
|
||||
/>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import cn from 'classnames'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import { useFormattingChangedDispatcher } from '../../../debug/hooks'
|
||||
import ChooseTool from './choose-tool'
|
||||
import SettingBuiltInTool from './setting-built-in-tool'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
|
@ -27,6 +28,7 @@ const AgentTools: FC = () => {
|
|||
const { t } = useTranslation()
|
||||
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
||||
const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
|
||||
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
|
||||
const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined)
|
||||
|
@ -49,6 +51,7 @@ const AgentTools: FC = () => {
|
|||
})
|
||||
setModelConfig(newModelConfig)
|
||||
setIsShowSettingTool(false)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -141,6 +144,7 @@ const AgentTools: FC = () => {
|
|||
draft.agentConfig.tools.splice(index, 1)
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
formattingChangedDispatcher()
|
||||
}}>
|
||||
<Trash03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
|
@ -167,6 +171,7 @@ const AgentTools: FC = () => {
|
|||
draft.agentConfig.tools.splice(index, 1)
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
formattingChangedDispatcher()
|
||||
}}>
|
||||
<Trash03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
|
@ -183,6 +188,7 @@ const AgentTools: FC = () => {
|
|||
(draft.agentConfig.tools[index] as any).enabled = enabled
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
formattingChangedDispatcher()
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import React, { useRef } from 'react'
|
|||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import { useBoolean, useScroll } from 'ahooks'
|
||||
import { useFormattingChangedDispatcher } from '../debug/hooks'
|
||||
import DatasetConfig from '../dataset-config'
|
||||
import ChatGroup from '../features/chat-group'
|
||||
import ExperienceEnchanceGroup from '../features/experience-enchance-group'
|
||||
|
@ -44,7 +45,6 @@ const Config: FC = () => {
|
|||
modelConfig,
|
||||
setModelConfig,
|
||||
setPrevPromptConfig,
|
||||
setFormattingChanged,
|
||||
moreLikeThisConfig,
|
||||
setMoreLikeThisConfig,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
|
@ -64,6 +64,7 @@ const Config: FC = () => {
|
|||
const { data: speech2textDefaultModel } = useDefaultModel(4)
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(5)
|
||||
const { setShowModerationSettingModal } = useModalContext()
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
|
||||
const promptTemplate = modelConfig.configs.prompt_template
|
||||
const promptVariables = modelConfig.configs.prompt_variables
|
||||
|
@ -73,9 +74,8 @@ const Config: FC = () => {
|
|||
draft.configs.prompt_template = newTemplate
|
||||
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
|
||||
})
|
||||
|
||||
if (modelConfig.configs.prompt_template !== newTemplate)
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
|
||||
setPrevPromptConfig(modelConfig.configs)
|
||||
setModelConfig(newModelConfig)
|
||||
|
@ -107,6 +107,7 @@ const Config: FC = () => {
|
|||
setSuggestedQuestionsAfterAnswerConfig(produce(suggestedQuestionsAfterAnswerConfig, (draft: SuggestedQuestionsAfterAnswerConfig) => {
|
||||
draft.enabled = value
|
||||
}))
|
||||
formattingChangedDispatcher()
|
||||
},
|
||||
speechToText: speechToTextConfig.enabled,
|
||||
setSpeechToText: (value) => {
|
||||
|
@ -125,6 +126,7 @@ const Config: FC = () => {
|
|||
setCitationConfig(produce(citationConfig, (draft: CitationConfig) => {
|
||||
draft.enabled = value
|
||||
}))
|
||||
formattingChangedDispatcher()
|
||||
},
|
||||
annotation: annotationConfig.enabled,
|
||||
setAnnotation: async (value) => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import { useFormattingChangedDispatcher } from '../debug/hooks'
|
||||
import FeaturePanel from '../base/feature-panel'
|
||||
import OperationBtn from '../base/operation-btn'
|
||||
import CardItem from './card-item/item'
|
||||
|
@ -26,25 +27,25 @@ const DatasetConfig: FC = () => {
|
|||
mode,
|
||||
dataSets: dataSet,
|
||||
setDataSets: setDataSet,
|
||||
setFormattingChanged,
|
||||
modelConfig,
|
||||
setModelConfig,
|
||||
showSelectDataSet,
|
||||
isAgent,
|
||||
} = useContext(ConfigContext)
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
|
||||
const hasData = dataSet.length > 0
|
||||
|
||||
const onRemove = (id: string) => {
|
||||
setDataSet(dataSet.filter(item => item.id !== id))
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
const handleSave = (newDataset: DataSet) => {
|
||||
const index = dataSet.findIndex(item => item.id === newDataset.id)
|
||||
|
||||
setDataSet([...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)])
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
const promptVariables = modelConfig.configs.prompt_variables
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
|
@ -9,16 +10,13 @@ import {
|
|||
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
|
||||
} from '../types'
|
||||
import {
|
||||
AgentStrategy,
|
||||
ModelModeType,
|
||||
} from '@/types/app'
|
||||
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 {
|
||||
ChatConfig,
|
||||
OnSend,
|
||||
} from '@/app/components/base/chat/types'
|
||||
import type { OnSend } from '@/app/components/base/chat/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
|
@ -26,7 +24,6 @@ import {
|
|||
fetchSuggestedQuestions,
|
||||
stopChatMessageResponding,
|
||||
} from '@/service/debug'
|
||||
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
|
||||
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'
|
||||
|
@ -39,66 +36,14 @@ const ChatItem: FC<ChatItemProps> = ({
|
|||
}) => {
|
||||
const { userProfile } = useAppContext()
|
||||
const {
|
||||
isAdvancedMode,
|
||||
modelConfig,
|
||||
appId,
|
||||
inputs,
|
||||
promptMode,
|
||||
speechToTextConfig,
|
||||
introduction,
|
||||
suggestedQuestions: openingSuggestedQuestions,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
citationConfig,
|
||||
moderationConfig,
|
||||
chatPromptConfig,
|
||||
completionPromptConfig,
|
||||
dataSets,
|
||||
datasetConfigs,
|
||||
visionConfig,
|
||||
annotationConfig,
|
||||
collectionList,
|
||||
textToSpeechConfig,
|
||||
} = useDebugConfigurationContext()
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
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: (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 config = useConfigFromDebugContext()
|
||||
const {
|
||||
chatList,
|
||||
isResponsing,
|
||||
|
@ -114,8 +59,9 @@ const ChatItem: FC<ChatItemProps> = ({
|
|||
[],
|
||||
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 currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model)
|
||||
const supportVision = currentModel?.features?.includes(ModelFeatureEnum.vision)
|
||||
|
@ -147,7 +93,7 @@ const ChatItem: FC<ChatItemProps> = ({
|
|||
onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
|
||||
},
|
||||
)
|
||||
}
|
||||
}, [appId, config, handleSend, inputs, modelAndParameter, textGenerationModelList, visionConfig.enabled])
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
|
@ -174,8 +120,9 @@ const ChatItem: FC<ChatItemProps> = ({
|
|||
chatList={chatList}
|
||||
isResponsing={isResponsing}
|
||||
noChatInput
|
||||
noStopResponding
|
||||
chatContainerclassName='p-4'
|
||||
chatFooterClassName='!-bottom-4'
|
||||
chatFooterClassName='p-4 pb-0'
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={doSend}
|
||||
showPromptLog
|
||||
|
|
|
@ -7,6 +7,7 @@ export type DebugWithMultipleModelContextType = {
|
|||
multipleModelConfigs: ModelAndParameter[]
|
||||
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
|
||||
onDebugWithMultipleModelChange: (singleModelConfig: ModelAndParameter) => void
|
||||
checkCanSend?: () => boolean
|
||||
}
|
||||
const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({
|
||||
multipleModelConfigs: [],
|
||||
|
@ -24,12 +25,14 @@ export const DebugWithMultipleModelContextProvider = ({
|
|||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
checkCanSend,
|
||||
}: DebugWithMultipleModelContextProviderProps) => {
|
||||
return (
|
||||
<DebugWithMultipleModelContext.Provider value={{
|
||||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
checkCanSend,
|
||||
}}>
|
||||
{children}
|
||||
</DebugWithMultipleModelContext.Provider>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { FC } from 'react'
|
||||
import type { CSSProperties, FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo } from 'react'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
|
@ -15,10 +15,12 @@ import { ModelStatusEnum } from '@/app/components/header/account-setting/model-p
|
|||
type DebugItemProps = {
|
||||
modelAndParameter: ModelAndParameter
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
const DebugItem: FC<DebugItemProps> = ({
|
||||
modelAndParameter,
|
||||
className,
|
||||
style,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mode } = useDebugConfigurationContext()
|
||||
|
@ -61,7 +63,10 @@ const DebugItem: FC<DebugItemProps> = ({
|
|||
}
|
||||
|
||||
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='flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'>
|
||||
#{index + 1}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { FC } from 'react'
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
|
||||
import DebugItem from './debug-item'
|
||||
|
@ -21,10 +22,16 @@ const DebugWithMultipleModel = () => {
|
|||
speechToTextConfig,
|
||||
visionConfig,
|
||||
} = useDebugConfigurationContext()
|
||||
const { multipleModelConfigs } = useDebugWithMultipleModelContext()
|
||||
const {
|
||||
multipleModelConfigs,
|
||||
checkCanSend,
|
||||
} = useDebugWithMultipleModelContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const handleSend = useCallback((message: string, files?: VisionFile[]) => {
|
||||
if (checkCanSend && !checkCanSend())
|
||||
return
|
||||
|
||||
eventEmitter?.emit({
|
||||
type: APP_CHAT_WITH_MULTIPLE_MODEL,
|
||||
payload: {
|
||||
|
@ -32,72 +39,90 @@ const DebugWithMultipleModel = () => {
|
|||
files,
|
||||
},
|
||||
} as any)
|
||||
}, [eventEmitter])
|
||||
}, [eventEmitter, checkCanSend])
|
||||
|
||||
const twoLine = multipleModelConfigs.length === 2
|
||||
const threeLine = multipleModelConfigs.length === 3
|
||||
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 (
|
||||
<div className='flex flex-col h-full'>
|
||||
<div
|
||||
className={`
|
||||
mb-3 overflow-auto
|
||||
${(twoLine || threeLine) && 'flex gap-2'}
|
||||
grow mb-3 relative px-6 overflow-auto
|
||||
`}
|
||||
style={{ height: mode === 'chat' ? 'calc(100% - 60px)' : '100%' }}
|
||||
>
|
||||
{
|
||||
(twoLine || threeLine) && multipleModelConfigs.map(modelConfig => (
|
||||
multipleModelConfigs.map((modelConfig, index) => (
|
||||
<DebugItem
|
||||
key={modelConfig.id}
|
||||
modelAndParameter={modelConfig}
|
||||
className={`
|
||||
h-full min-h-[200px]
|
||||
${twoLine && 'w-1/2'}
|
||||
${threeLine && 'w-1/3'}
|
||||
absolute left-6 top-0 min-h-[200px]
|
||||
${twoLine && index === 0 && 'mr-2'}
|
||||
${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>
|
||||
{
|
||||
mode === 'chat' && (
|
||||
<div className='shrink-0'>
|
||||
<div className='shrink-0 pb-4 px-6'>
|
||||
<ChatInput
|
||||
onSend={handleSend}
|
||||
speechToTextConfig={speechToTextConfig}
|
||||
|
@ -116,12 +141,14 @@ const DebugWithMultipleModelWrapper: FC<DebugWithMultipleModelContextType> = ({
|
|||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
checkCanSend,
|
||||
}) => {
|
||||
return (
|
||||
<DebugWithMultipleModelContextProvider
|
||||
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onDebugWithMultipleModelChange={onDebugWithMultipleModelChange}
|
||||
checkCanSend={checkCanSend}
|
||||
>
|
||||
<DebugWithMultipleModelMemoed />
|
||||
</DebugWithMultipleModelContextProvider>
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
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 {
|
|||
DebugWithSingleOrMultipleModelConfigs,
|
||||
ModelAndParameter,
|
||||
} 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) => {
|
||||
const localeDebugWithSingleOrMultipleModelConfigs = localStorage.getItem('app-debug-with-single-or-multiple-models')
|
||||
|
@ -52,3 +63,95 @@ export const useDebugWithSingleOrMultipleModel = (appId: string) => {
|
|||
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 @@
|
|||
import type { FC } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import produce, { setAutoFreeze } from 'immer'
|
||||
import { useBoolean, useGetState } from 'ahooks'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { setAutoFreeze } from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import dayjs from 'dayjs'
|
||||
import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
|
||||
import FormattingChanged from '../base/warning-mask/formatting-changed'
|
||||
import GroupName from '../base/group-name'
|
||||
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
|
||||
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 {
|
||||
APP_CHAT_WITH_MULTIPLE_MODEL,
|
||||
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
|
||||
} from './types'
|
||||
import { AgentStrategy, AppType, ModelModeType, TransferMethod } from '@/types/app'
|
||||
import PromptValuePanel, { replaceStringWithValues } 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 { AppType, ModelModeType, TransferMethod } from '@/types/app'
|
||||
import PromptValuePanel from '@/app/components/app/configuration/prompt-value-panel'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
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 type { ModelConfig as BackendModelConfig, VisionFile } from '@/types/app'
|
||||
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
|
||||
|
@ -32,7 +30,6 @@ import TextGeneration from '@/app/components/app/text-generate/item'
|
|||
import { IS_CE_EDITION } from '@/config'
|
||||
import type { Inputs } from '@/models/debug'
|
||||
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 { 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'
|
||||
|
@ -63,8 +60,6 @@ const Debug: FC<IDebug> = ({
|
|||
const {
|
||||
appId,
|
||||
mode,
|
||||
isFunctionCall,
|
||||
collectionList,
|
||||
modelModeType,
|
||||
hasSetBlockStatus,
|
||||
isAdvancedMode,
|
||||
|
@ -72,7 +67,6 @@ const Debug: FC<IDebug> = ({
|
|||
chatPromptConfig,
|
||||
completionPromptConfig,
|
||||
introduction,
|
||||
suggestedQuestions,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
speechToTextConfig,
|
||||
textToSpeechConfig,
|
||||
|
@ -81,79 +75,36 @@ const Debug: FC<IDebug> = ({
|
|||
moreLikeThisConfig,
|
||||
formattingChanged,
|
||||
setFormattingChanged,
|
||||
conversationId,
|
||||
setConversationId,
|
||||
controlClearChatMessage,
|
||||
dataSets,
|
||||
modelConfig,
|
||||
completionParams,
|
||||
hasSetContextVar,
|
||||
datasetConfigs,
|
||||
visionConfig,
|
||||
annotationConfig,
|
||||
setVisionConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { data: speech2textDefaultModel } = useDefaultModel(4)
|
||||
const { data: text2speechDefaultModel } = useDefaultModel(5)
|
||||
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
|
||||
const chatListDomRef = useRef<HTMLDivElement>(null)
|
||||
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
||||
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
|
||||
useEffect(() => {
|
||||
setAutoFreeze(false)
|
||||
return () => {
|
||||
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 [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false)
|
||||
const [isShowCannotQueryDataset, setShowCannotQueryDataset] = useState(false)
|
||||
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
|
||||
const [messageTaskId, setMessageTaskId] = useState('')
|
||||
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (formattingChanged && chatList.some(item => !item.isAnswer))
|
||||
if (formattingChanged)
|
||||
setIsShowFormattingChangeConfirm(true)
|
||||
|
||||
setFormattingChanged(false)
|
||||
}, [formattingChanged])
|
||||
|
||||
const debugWithSingleModelRef = React.useRef<DebugWithSingleModelRefType | null>(null)
|
||||
const handleClearConversation = () => {
|
||||
setConversationId(null)
|
||||
abortController?.abort()
|
||||
setResponsingFalse()
|
||||
setChatList(introduction
|
||||
? [{
|
||||
id: `${Date.now()}`,
|
||||
content: getIntroduction(),
|
||||
isAnswer: true,
|
||||
isOpeningStatement: true,
|
||||
suggestedQuestions,
|
||||
}]
|
||||
: [])
|
||||
setIsShowSuggestion(false)
|
||||
debugWithSingleModelRef.current?.handleRestart()
|
||||
}
|
||||
const clearConversation = async () => {
|
||||
if (debugWithMultipleModel) {
|
||||
|
@ -169,18 +120,21 @@ const Debug: FC<IDebug> = ({
|
|||
const handleConfirm = () => {
|
||||
clearConversation()
|
||||
setIsShowFormattingChangeConfirm(false)
|
||||
setFormattingChanged(false)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsShowFormattingChangeConfirm(false)
|
||||
setFormattingChanged(false)
|
||||
}
|
||||
|
||||
const { notify } = useContext(ToastContext)
|
||||
const logError = (message: string) => {
|
||||
const logError = useCallback((message: string) => {
|
||||
notify({ type: 'error', message })
|
||||
}
|
||||
}, [notify])
|
||||
const [completionFiles, setCompletionFiles] = useState<VisionFile[]>([])
|
||||
|
||||
const checkCanSend = () => {
|
||||
const checkCanSend = useCallback(() => {
|
||||
if (isAdvancedMode && mode === AppType.chat) {
|
||||
if (modelModeType === ModelModeType.completion) {
|
||||
if (!hasSetBlockStatus.history) {
|
||||
|
@ -214,319 +168,28 @@ const Debug: FC<IDebug> = ({
|
|||
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)) {
|
||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
|
||||
return false
|
||||
}
|
||||
return !hasEmptyInput
|
||||
}
|
||||
|
||||
const doShowSuggestion = isShowSuggestion && !isResponsing
|
||||
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
|
||||
const [userQuery, setUserQuery] = useState('')
|
||||
const onSend = async (message: string, files?: VisionFile[]) => {
|
||||
if (isResponsing) {
|
||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
|
||||
return false
|
||||
}
|
||||
|
||||
if (files?.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
|
||||
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])
|
||||
}, [
|
||||
completionFiles,
|
||||
hasSetBlockStatus.history,
|
||||
hasSetBlockStatus.query,
|
||||
inputs,
|
||||
isAdvancedMode,
|
||||
mode,
|
||||
modelConfig.configs.prompt_variables,
|
||||
t,
|
||||
logError,
|
||||
notify,
|
||||
modelModeType,
|
||||
])
|
||||
|
||||
const [completionRes, setCompletionRes] = useState('')
|
||||
const [messageId, setMessageId] = useState<string | null>(null)
|
||||
|
||||
const [completionFiles, setCompletionFiles] = useState<VisionFile[]>([])
|
||||
const sendTextCompletion = async () => {
|
||||
if (isResponsing) {
|
||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
|
||||
|
@ -685,13 +348,13 @@ const Debug: FC<IDebug> = ({
|
|||
setVisionConfig({
|
||||
...visionConfig,
|
||||
enabled: true,
|
||||
})
|
||||
}, true)
|
||||
}
|
||||
else {
|
||||
setVisionConfig({
|
||||
...visionConfig,
|
||||
enabled: false,
|
||||
})
|
||||
}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -699,17 +362,10 @@ const Debug: FC<IDebug> = ({
|
|||
useEffect(() => {
|
||||
handleVisionConfigInMultipleModel()
|
||||
}, [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 (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
<div className="shrink-0 pt-4 px-6">
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className='h2 '>{t('appDebug.inputs.title')}</div>
|
||||
<div className='flex items-center'>
|
||||
|
@ -761,6 +417,7 @@ const Debug: FC<IDebug> = ({
|
|||
multipleModelConfigs={multipleModelConfigs}
|
||||
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
|
||||
onDebugWithMultipleModelChange={handleChangeToSingleModel}
|
||||
checkCanSend={checkCanSend}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -770,47 +427,16 @@ const Debug: FC<IDebug> = ({
|
|||
<div className="flex flex-col grow">
|
||||
{/* Chat */}
|
||||
{mode === AppType.chat && (
|
||||
<div className="mt-[34px] h-full flex flex-col">
|
||||
<div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}>
|
||||
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
|
||||
<Chat
|
||||
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 className='grow h-0 overflow-hidden'>
|
||||
<DebugWithSingleModel
|
||||
ref={debugWithSingleModelRef}
|
||||
checkCanSend={checkCanSend}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Text Generation */}
|
||||
{mode === AppType.completion && (
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 px-6 pb-4">
|
||||
<GroupName name={t('appDebug.result')} />
|
||||
{(completionRes || isResponsing) && (
|
||||
<TextGeneration
|
||||
|
@ -830,12 +456,6 @@ const Debug: FC<IDebug> = ({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{isShowFormattingChangeConfirm && (
|
||||
<FormattingChanged
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{isShowCannotQueryDataset && (
|
||||
<CannotQueryDataset
|
||||
onConfirm={() => setShowCannotQueryDataset(false)}
|
||||
|
@ -844,6 +464,12 @@ const Debug: FC<IDebug> = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
{isShowFormattingChangeConfirm && (
|
||||
<FormattingChanged
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{!hasSetAPIKEY && (<HasNotSetAPIKEY isTrailFinished={!IS_CE_EDITION} onSetting={onSetting} />)}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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_RESTART = 'APP_CHAT_WITH_MULTIPLE_MODEL_RESTART'
|
||||
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'
|
|||
import Loading from '../../base/loading'
|
||||
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
|
||||
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 { APP_SIDEBAR_SHOULD_COLLAPSE } from './debug/types'
|
||||
import PublishWithMultipleModel from './debug/debug-with-multiple-model/publish-with-multiple-model'
|
||||
|
@ -45,7 +48,6 @@ import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, Trans
|
|||
import { PromptMode } from '@/models/debug'
|
||||
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 I18n from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
|
@ -111,10 +113,11 @@ const Configuration: FC = () => {
|
|||
embedding_model_name: '',
|
||||
},
|
||||
})
|
||||
const formattingChangedDispatcher = useFormattingChangedDispatcher()
|
||||
const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => {
|
||||
doSetAnnotationConfig(config)
|
||||
if (!notSetFormatChanged)
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
const [moderationConfig, setModerationConfig] = useState<ModerationConfig>({
|
||||
|
@ -203,7 +206,7 @@ const Configuration: FC = () => {
|
|||
return
|
||||
}
|
||||
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
if (data.find(item => !item.name)) { // has not loaded selected dataset
|
||||
const newSelected = produce(data, (draft: any) => {
|
||||
data.forEach((item, index) => {
|
||||
|
@ -299,7 +302,7 @@ const Configuration: FC = () => {
|
|||
transfer_methods: config.transfer_methods || [TransferMethod.local_file],
|
||||
})
|
||||
if (!notNoticeFormattingChanged)
|
||||
setFormattingChanged(true)
|
||||
formattingChangedDispatcher()
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -634,7 +637,6 @@ const Configuration: FC = () => {
|
|||
}
|
||||
|
||||
const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const {
|
||||
|
@ -820,7 +822,7 @@ const Configuration: FC = () => {
|
|||
)
|
||||
}
|
||||
</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
|
||||
hasSetAPIKEY={hasSettedApiKey}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
|
|
|
@ -97,18 +97,21 @@ const CacheCtrlBtn: FC<Props> = ({
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<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}
|
||||
: answer
|
||||
? (
|
||||
<TooltipPlus
|
||||
popupContent={t('appDebug.feature.annotation.add') as string}
|
||||
>
|
||||
<MessageFastPlus className='w-4 h-4' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)}
|
||||
<div
|
||||
className='p-1 rounded-md hover:bg-[#EEF4FF] hover:text-[#444CE7] cursor-pointer'
|
||||
onClick={handleAdd}
|
||||
>
|
||||
<MessageFastPlus className='w-4 h-4' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<TooltipPlus
|
||||
popupContent={t('appDebug.feature.annotation.edit') as string}
|
||||
>
|
||||
|
|
|
@ -14,7 +14,10 @@ type AgentContentProps = {
|
|||
const AgentContent: FC<AgentContentProps> = ({
|
||||
item,
|
||||
}) => {
|
||||
const { allToolIcons } = useChatContext()
|
||||
const {
|
||||
allToolIcons,
|
||||
isResponsing,
|
||||
} = useChatContext()
|
||||
const {
|
||||
annotation,
|
||||
agent_thoughts,
|
||||
|
@ -42,7 +45,7 @@ const AgentContent: FC<AgentContentProps> = ({
|
|||
<Thought
|
||||
thought={thought}
|
||||
allToolIcons={allToolIcons || {}}
|
||||
isFinished={!!thought.observation}
|
||||
isFinished={!!thought.observation || !isResponsing}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -15,9 +15,13 @@ import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal
|
|||
|
||||
type AnswerProps = {
|
||||
item: ChatItem
|
||||
question: string
|
||||
index: number
|
||||
}
|
||||
const Answer: FC<AnswerProps> = ({
|
||||
item,
|
||||
question,
|
||||
index,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
|
@ -56,7 +60,15 @@ const Answer: FC<AnswerProps> = ({
|
|||
<div className='relative pr-10'>
|
||||
<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'>
|
||||
<Operation item={item} />
|
||||
{
|
||||
!responsing && (
|
||||
<Operation
|
||||
item={item}
|
||||
question={question}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
responsing && !content && !hasAgentThoughts && (
|
||||
<div className='flex items-center justify-center w-6 h-5'>
|
||||
|
@ -75,7 +87,7 @@ const Answer: FC<AnswerProps> = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
annotation?.id && !annotation?.logAnnotation && (
|
||||
annotation?.id && annotation.authorName && (
|
||||
<EditTitle
|
||||
className='mt-1'
|
||||
title={t('appAnnotation.editBy', { author: annotation.authorName })}
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { useCurrentAnswerIsResponsing } from '../hooks'
|
||||
import { useChatContext } from '../context'
|
||||
import CopyBtn from '@/app/components/app/chat/copy-btn'
|
||||
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
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 = {
|
||||
item: ChatItem
|
||||
question: string
|
||||
index: number
|
||||
}
|
||||
const Operation: FC<OperationProps> = ({
|
||||
item,
|
||||
question,
|
||||
index,
|
||||
}) => {
|
||||
const { config } = useChatContext()
|
||||
const {
|
||||
config,
|
||||
onAnnotationAdded,
|
||||
onAnnotationEdited,
|
||||
onAnnotationRemoved,
|
||||
} = useChatContext()
|
||||
const [isShowReplyModal, setIsShowReplyModal] = useState(false)
|
||||
const responsing = useCurrentAnswerIsResponsing(item.id)
|
||||
const {
|
||||
id,
|
||||
isOpeningStatement,
|
||||
content,
|
||||
annotation,
|
||||
} = item
|
||||
const hasAnnotation = !!annotation?.id
|
||||
|
||||
return (
|
||||
<div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'>
|
||||
|
@ -36,6 +51,34 @@ const Operation: FC<OperationProps> = ({
|
|||
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 && (
|
||||
<div
|
||||
|
|
|
@ -2,23 +2,20 @@
|
|||
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import type { ChatProps } from './index'
|
||||
|
||||
export type ChatContextValue = {
|
||||
config?: ChatConfig
|
||||
isResponsing?: boolean
|
||||
chatList: ChatItem[]
|
||||
showPromptLog?: boolean
|
||||
questionIcon?: ReactNode
|
||||
answerIcon?: ReactNode
|
||||
allToolIcons?: Record<string, string | Emoji>
|
||||
onSend?: OnSend
|
||||
}
|
||||
export type ChatContextValue = Pick<ChatProps, 'config'
|
||||
| 'isResponsing'
|
||||
| 'chatList'
|
||||
| 'showPromptLog'
|
||||
| 'questionIcon'
|
||||
| 'answerIcon'
|
||||
| 'allToolIcons'
|
||||
| 'onSend'
|
||||
| 'onAnnotationEdited'
|
||||
| 'onAnnotationAdded'
|
||||
| 'onAnnotationRemoved'
|
||||
>
|
||||
|
||||
const ChatContext = createContext<ChatContextValue>({
|
||||
chatList: [],
|
||||
|
@ -38,6 +35,9 @@ export const ChatContextProvider = ({
|
|||
answerIcon,
|
||||
allToolIcons,
|
||||
onSend,
|
||||
onAnnotationEdited,
|
||||
onAnnotationAdded,
|
||||
onAnnotationRemoved,
|
||||
}: ChatContextProviderProps) => {
|
||||
return (
|
||||
<ChatContext.Provider value={{
|
||||
|
@ -49,6 +49,9 @@ export const ChatContextProvider = ({
|
|||
answerIcon,
|
||||
allToolIcons,
|
||||
onSend,
|
||||
onAnnotationEdited,
|
||||
onAnnotationAdded,
|
||||
onAnnotationRemoved,
|
||||
}}>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { produce } from 'immer'
|
||||
import { useGetState } from 'ahooks'
|
||||
import dayjs from 'dayjs'
|
||||
import type {
|
||||
ChatConfig,
|
||||
|
@ -19,12 +19,53 @@ import { TransferMethod } from '@/types/app'
|
|||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { ssePost } from '@/service/base'
|
||||
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
||||
import type { Annotation } from '@/models/log'
|
||||
|
||||
type GetAbortController = (abortController: AbortController) => void
|
||||
type SendCallback = {
|
||||
onGetConvesationMessages: (conversationId: 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 = (
|
||||
config: ChatConfig,
|
||||
promptVariablesConfig?: {
|
||||
|
@ -39,19 +80,31 @@ export const useChat = (
|
|||
const connversationId = useRef('')
|
||||
const hasStopResponded = useRef(false)
|
||||
const [isResponsing, setIsResponsing] = useState(false)
|
||||
const [chatList, setChatList, getChatList] = useGetState<ChatItem[]>(prevChatList || [])
|
||||
const [taskId, setTaskId] = useState('')
|
||||
const isResponsingRef = useRef(false)
|
||||
const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
|
||||
const chatListRef = useRef<ChatItem[]>(prevChatList || [])
|
||||
const taskIdRef = useRef('')
|
||||
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
const [conversationMessagesAbortController, setConversationMessagesAbortController] = useState<AbortController | null>(null)
|
||||
const [suggestedQuestionsAbortController, setSuggestedQuestionsAbortController] = useState<AbortController | null>(null)
|
||||
const abortControllerRef = useRef<AbortController | null>(null)
|
||||
const conversationMessagesAbortControllerRef = useRef<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 || {})
|
||||
}
|
||||
}, [promptVariablesConfig?.inputs, promptVariablesConfig?.promptVariables])
|
||||
useEffect(() => {
|
||||
if (config.opening_statement && !chatList.some(item => !item.isAnswer)) {
|
||||
setChatList([{
|
||||
if (config.opening_statement && !chatList.length) {
|
||||
handleUpdateChatList([{
|
||||
id: `${Date.now()}`,
|
||||
content: getIntroduction(config.opening_statement),
|
||||
isAnswer: true,
|
||||
|
@ -59,25 +112,31 @@ export const useChat = (
|
|||
suggestedQuestions: config.suggested_questions,
|
||||
}])
|
||||
}
|
||||
}, [config.opening_statement, config.suggested_questions, promptVariablesConfig?.inputs])
|
||||
}, [
|
||||
config.opening_statement,
|
||||
config.suggested_questions,
|
||||
getIntroduction,
|
||||
chatList,
|
||||
handleUpdateChatList,
|
||||
])
|
||||
|
||||
const handleStop = () => {
|
||||
if (stopChat && taskId)
|
||||
stopChat(taskId)
|
||||
if (abortController)
|
||||
abortController.abort()
|
||||
if (conversationMessagesAbortController)
|
||||
conversationMessagesAbortController.abort()
|
||||
if (suggestedQuestionsAbortController)
|
||||
suggestedQuestionsAbortController.abort()
|
||||
}
|
||||
|
||||
const handleRestart = () => {
|
||||
handleStop()
|
||||
const handleStop = useCallback(() => {
|
||||
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 = ''
|
||||
setIsResponsing(false)
|
||||
setChatList(config.opening_statement
|
||||
const newChatList = config.opening_statement
|
||||
? [{
|
||||
id: `${Date.now()}`,
|
||||
content: config.opening_statement,
|
||||
|
@ -85,10 +144,38 @@ export const useChat = (
|
|||
isOpeningStatement: true,
|
||||
suggestedQuestions: config.suggested_questions,
|
||||
}]
|
||||
: [])
|
||||
: []
|
||||
handleUpdateChatList(newChatList)
|
||||
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,
|
||||
data: any,
|
||||
{
|
||||
|
@ -97,62 +184,13 @@ export const useChat = (
|
|||
}: SendCallback,
|
||||
) => {
|
||||
setSuggestQuestions([])
|
||||
if (isResponsing) {
|
||||
if (isResponsingRef.current) {
|
||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
|
||||
return false
|
||||
}
|
||||
|
||||
if (promptVariablesConfig?.inputs && promptVariablesConfig?.promptVariables) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if (promptVariablesConfig?.inputs && promptVariablesConfig?.promptVariables)
|
||||
checkPromptVariables(promptVariablesConfig)
|
||||
|
||||
const questionId = `question-${Date.now()}`
|
||||
const questionItem = {
|
||||
|
@ -169,8 +207,8 @@ export const useChat = (
|
|||
isAnswer: true,
|
||||
}
|
||||
|
||||
const newList = [...getChatList(), questionItem, placeholderAnswerItem]
|
||||
setChatList(newList)
|
||||
const newList = [...chatListRef.current, questionItem, placeholderAnswerItem]
|
||||
handleUpdateChatList(newList)
|
||||
|
||||
// answer
|
||||
const responseItem: ChatItem = {
|
||||
|
@ -181,7 +219,7 @@ export const useChat = (
|
|||
isAnswer: true,
|
||||
}
|
||||
|
||||
setIsResponsing(true)
|
||||
handleResponsing(true)
|
||||
hasStopResponded.current = false
|
||||
|
||||
const bodyParams = {
|
||||
|
@ -211,7 +249,7 @@ export const useChat = (
|
|||
},
|
||||
{
|
||||
getAbortController: (abortController) => {
|
||||
setAbortController(abortController)
|
||||
abortControllerRef.current = abortController
|
||||
},
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
if (!isAgentMode) {
|
||||
|
@ -231,7 +269,7 @@ export const useChat = (
|
|||
if (isFirstMessage && newConversationId)
|
||||
connversationId.current = newConversationId
|
||||
|
||||
setTaskId(taskId)
|
||||
taskIdRef.current = taskId
|
||||
if (messageId)
|
||||
responseItem.id = messageId
|
||||
|
||||
|
@ -243,21 +281,21 @@ export const useChat = (
|
|||
})
|
||||
},
|
||||
async onCompleted(hasError?: boolean) {
|
||||
setIsResponsing(false)
|
||||
handleResponsing(false)
|
||||
|
||||
if (hasError)
|
||||
return
|
||||
|
||||
if (connversationId.current) {
|
||||
if (connversationId.current && !hasStopResponded.current) {
|
||||
const { data }: any = await onGetConvesationMessages(
|
||||
connversationId.current,
|
||||
newAbortController => setConversationMessagesAbortController(newAbortController),
|
||||
newAbortController => conversationMessagesAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
const newResponseItem = data.find((item: any) => item.id === responseItem.id)
|
||||
if (!newResponseItem)
|
||||
return
|
||||
|
||||
setChatList(produce(getChatList(), (draft) => {
|
||||
const newChatList = produce(chatListRef.current, (draft) => {
|
||||
const index = draft.findIndex(item => item.id === responseItem.id)
|
||||
if (index !== -1) {
|
||||
const requestion = draft[index - 1]
|
||||
|
@ -274,12 +312,13 @@ export const useChat = (
|
|||
},
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
handleUpdateChatList(newChatList)
|
||||
}
|
||||
if (config.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
responseItem.id,
|
||||
newAbortController => setSuggestedQuestionsAbortController(newAbortController),
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
|
@ -330,8 +369,9 @@ export const useChat = (
|
|||
id: messageEnd.metadata.annotation_reply.id,
|
||||
authorName: messageEnd.metadata.annotation_reply.account.name,
|
||||
})
|
||||
const baseState = chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId)
|
||||
const newListWithAnswer = produce(
|
||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||
baseState,
|
||||
(draft) => {
|
||||
if (!draft.find(item => item.id === questionId))
|
||||
draft.push({ ...questionItem })
|
||||
|
@ -340,38 +380,113 @@ export const useChat = (
|
|||
...responseItem,
|
||||
})
|
||||
})
|
||||
setChatList(newListWithAnswer)
|
||||
handleUpdateChatList(newListWithAnswer)
|
||||
return
|
||||
}
|
||||
responseItem.citation = messageEnd.metadata?.retriever_resources || []
|
||||
|
||||
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) => {
|
||||
if (!draft.find(item => item.id === questionId))
|
||||
draft.push({ ...questionItem })
|
||||
|
||||
draft.push({ ...responseItem })
|
||||
})
|
||||
setChatList(newListWithAnswer)
|
||||
handleUpdateChatList(newListWithAnswer)
|
||||
},
|
||||
onMessageReplace: (messageReplace) => {
|
||||
responseItem.content = messageReplace.answer
|
||||
},
|
||||
onError() {
|
||||
setIsResponsing(false)
|
||||
// role back placeholder answer
|
||||
setChatList(produce(getChatList(), (draft) => {
|
||||
handleResponsing(false)
|
||||
const newChatList = produce(chatListRef.current, (draft) => {
|
||||
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
|
||||
}))
|
||||
})
|
||||
handleUpdateChatList(newChatList)
|
||||
},
|
||||
})
|
||||
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 {
|
||||
chatList,
|
||||
getChatList,
|
||||
setChatList,
|
||||
conversationId: connversationId.current,
|
||||
isResponsing,
|
||||
|
@ -380,6 +495,9 @@ export const useChat = (
|
|||
suggestedQuestions,
|
||||
handleRestart,
|
||||
handleStop,
|
||||
handleAnnotationEdited,
|
||||
handleAnnotationAdded,
|
||||
handleAnnotationRemoved,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ import type {
|
|||
} from 'react'
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useThrottleEffect } from 'ahooks'
|
||||
import type {
|
||||
ChatConfig,
|
||||
|
@ -18,13 +20,17 @@ import ChatInput from './chat-input'
|
|||
import TryToAsk from './try-to-ask'
|
||||
import { ChatContextProvider } from './context'
|
||||
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 = {
|
||||
config: ChatConfig
|
||||
onSend?: OnSend
|
||||
chatList: ChatItem[]
|
||||
isResponsing: boolean
|
||||
config?: ChatConfig
|
||||
isResponsing?: boolean
|
||||
noStopResponding?: boolean
|
||||
onStopResponding?: () => void
|
||||
noChatInput?: boolean
|
||||
onSend?: OnSend
|
||||
chatContainerclassName?: string
|
||||
chatFooterClassName?: string
|
||||
suggestedQuestions?: string[]
|
||||
|
@ -32,12 +38,17 @@ export type ChatProps = {
|
|||
questionIcon?: ReactNode
|
||||
answerIcon?: ReactNode
|
||||
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> = ({
|
||||
config,
|
||||
onSend,
|
||||
chatList,
|
||||
isResponsing,
|
||||
noStopResponding,
|
||||
onStopResponding,
|
||||
noChatInput,
|
||||
chatContainerclassName,
|
||||
chatFooterClassName,
|
||||
|
@ -46,16 +57,46 @@ const Chat: FC<ChatProps> = ({
|
|||
questionIcon,
|
||||
answerIcon,
|
||||
allToolIcons,
|
||||
onAnnotationAdded,
|
||||
onAnnotationEdited,
|
||||
onAnnotationRemoved,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { t } = useTranslation()
|
||||
const chatContainerRef = useRef<HTMLDivElement>(null)
|
||||
const chatFooterRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleScrolltoBottom = () => {
|
||||
if (chatContainerRef.current)
|
||||
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight
|
||||
}
|
||||
|
||||
useThrottleEffect(() => {
|
||||
if (ref.current)
|
||||
ref.current.scrollTop = ref.current.scrollHeight
|
||||
handleScrolltoBottom()
|
||||
|
||||
if (chatContainerRef.current && chatFooterRef.current)
|
||||
chatFooterRef.current.style.width = `${chatContainerRef.current.clientWidth}px`
|
||||
}, [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 (
|
||||
<ChatContextProvider
|
||||
|
@ -67,19 +108,24 @@ const Chat: FC<ChatProps> = ({
|
|||
answerIcon={answerIcon}
|
||||
allToolIcons={allToolIcons}
|
||||
onSend={onSend}
|
||||
onAnnotationAdded={onAnnotationAdded}
|
||||
onAnnotationEdited={onAnnotationEdited}
|
||||
onAnnotationRemoved={onAnnotationRemoved}
|
||||
>
|
||||
<div className='relative h-full'>
|
||||
<div
|
||||
ref={ref}
|
||||
ref={chatContainerRef}
|
||||
className={`relative h-full overflow-y-auto ${chatContainerclassName}`}
|
||||
>
|
||||
{
|
||||
chatList.map((item) => {
|
||||
chatList.map((item, index) => {
|
||||
if (item.isAnswer) {
|
||||
return (
|
||||
<Answer
|
||||
key={item.id}
|
||||
item={item}
|
||||
question={chatList[index - 1]?.content}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -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) && (
|
||||
<div
|
||||
className={`sticky bottom-0 w-full backdrop-blur-[20px] ${chatFooterClassName}`}
|
||||
ref={chatFooterRef}
|
||||
style={{
|
||||
background: 'linear-gradient(0deg, #FFF 0%, rgba(255, 255, 255, 0.40) 100%)',
|
||||
}}
|
||||
>
|
||||
{
|
||||
hasTryToAsk && (
|
||||
<TryToAsk
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={onSend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!noChatInput && (
|
||||
<ChatInput
|
||||
visionConfig={config?.file_upload?.image}
|
||||
speechToTextConfig={config.speech_to_text}
|
||||
onSend={onSend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
!noStopResponding && isResponsing && (
|
||||
<div className='flex justify-center mb-2'>
|
||||
<Button className='py-0 px-3 h-7 bg-white shadow-xs' onClick={onStopResponding}>
|
||||
<StopCircle className='mr-[5px] w-3.5 h-3.5 text-gray-500' />
|
||||
<span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasTryToAsk && (
|
||||
<TryToAsk
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={onSend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!noChatInput && (
|
||||
<ChatInput
|
||||
visionConfig={config?.file_upload?.image}
|
||||
speechToTextConfig={config?.speech_to_text}
|
||||
onSend={onSend}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ChatContextProvider>
|
||||
|
|
|
@ -41,7 +41,10 @@ export type EnableType = {
|
|||
enabled: boolean
|
||||
}
|
||||
|
||||
export type ChatConfig = Omit<ModelConfig, 'model'>
|
||||
export type ChatConfig = Omit<ModelConfig, 'model'> & {
|
||||
supportAnnotation?: boolean
|
||||
appId?: string
|
||||
}
|
||||
|
||||
export type ChatItem = IChatItem
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ type IDebugConfiguration = {
|
|||
hasSetContextVar: boolean
|
||||
isShowVisionConfig: boolean
|
||||
visionConfig: VisionSettings
|
||||
setVisionConfig: (visionConfig: VisionSettings) => void
|
||||
setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void
|
||||
}
|
||||
|
||||
const DebugConfigurationContext = createContext<IDebugConfiguration>({
|
||||
|
|
Loading…
Reference in New Issue
Block a user