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> = ({
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
......
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>
......
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>
......
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)
}
})
}
......@@ -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 })}
......
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>
......
This diff is collapsed.
......@@ -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>({
......
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