feat: multiple model configuration (#2196)
Co-authored-by: Joel <iamjoel007@gmail.com>
This commit is contained in:
@@ -15,10 +15,12 @@ export type Resources = {
|
||||
type CitationProps = {
|
||||
data: CitationItem[]
|
||||
showHitInfo?: boolean
|
||||
containerClassName?: string
|
||||
}
|
||||
const Citation: FC<CitationProps> = ({
|
||||
data,
|
||||
showHitInfo,
|
||||
containerClassName = 'chat-answer-container',
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const elesRef = useRef<HTMLDivElement[]>([])
|
||||
@@ -46,7 +48,7 @@ const Citation: FC<CitationProps> = ({
|
||||
}, []), [data])
|
||||
|
||||
const handleAdjustResourcesLayout = () => {
|
||||
const containerWidth = document.querySelector('.chat-answer-container')!.clientWidth - 40
|
||||
const containerWidth = document.querySelector(`.${containerClassName}`)!.clientWidth - 40
|
||||
let totalWidth = 0
|
||||
for (let i = 0; i < resources.length; i++) {
|
||||
totalWidth += elesRef.current[i].clientWidth
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Dispatch, FC, ReactNode, RefObject, SetStateAction } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
import { File02 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import PromptLogModal from '@/app/components/base/prompt-log-modal'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
@@ -39,7 +40,7 @@ const Log: FC<LogProps> = ({
|
||||
children
|
||||
? children(setShowModal)
|
||||
: (
|
||||
<Tooltip selector='prompt-log-modal-trigger' content={t('common.operation.log') || ''}>
|
||||
<Tooltip selector={`prompt-log-modal-trigger-${uniqueId()}`} content={t('common.operation.log') || ''}>
|
||||
<div className={`
|
||||
hidden absolute -left-[14px] -top-[14px] group-hover:block w-7 h-7
|
||||
p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-md cursor-pointer
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
import {
|
||||
APP_CHAT_WITH_MULTIPLE_MODEL,
|
||||
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
|
||||
} from '../types'
|
||||
import {
|
||||
AgentStrategy,
|
||||
ModelModeType,
|
||||
} from '@/types/app'
|
||||
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 { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
fetchConvesationMessages,
|
||||
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'
|
||||
|
||||
type ChatItemProps = {
|
||||
modelAndParameter: ModelAndParameter
|
||||
}
|
||||
const ChatItem: FC<ChatItemProps> = ({
|
||||
modelAndParameter,
|
||||
}) => {
|
||||
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 {
|
||||
chatList,
|
||||
isResponsing,
|
||||
handleSend,
|
||||
suggestedQuestions,
|
||||
handleRestart,
|
||||
} = useChat(
|
||||
config,
|
||||
{
|
||||
inputs,
|
||||
promptVariables: modelConfig.configs.prompt_variables,
|
||||
},
|
||||
[],
|
||||
taskId => stopChatMessageResponding(appId, taskId),
|
||||
)
|
||||
|
||||
const doSend: OnSend = (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)
|
||||
|
||||
const configData = {
|
||||
...config,
|
||||
model: {
|
||||
provider: modelAndParameter.provider,
|
||||
name: modelAndParameter.model,
|
||||
mode: currentModel?.model_properties.mode,
|
||||
completion_params: modelAndParameter.parameters,
|
||||
},
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL)
|
||||
doSend(v.payload.message, v.payload.files)
|
||||
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL_RESTART)
|
||||
handleRestart()
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
if (!chatList.length)
|
||||
return null
|
||||
|
||||
return (
|
||||
<Chat
|
||||
config={config}
|
||||
chatList={chatList}
|
||||
isResponsing={isResponsing}
|
||||
noChatInput
|
||||
chatContainerclassName='p-4'
|
||||
chatFooterClassName='!-bottom-4'
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={doSend}
|
||||
showPromptLog
|
||||
questionIcon={<Avatar name={userProfile.name} size={40} />}
|
||||
allToolIcons={allToolIcons}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ChatItem)
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
|
||||
export type DebugWithMultipleModelContextType = {
|
||||
multipleModelConfigs: ModelAndParameter[]
|
||||
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
|
||||
onDebugWithMultipleModelChange: (singleModelConfig: ModelAndParameter) => void
|
||||
}
|
||||
const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({
|
||||
multipleModelConfigs: [],
|
||||
onMultipleModelConfigsChange: () => {},
|
||||
onDebugWithMultipleModelChange: () => {},
|
||||
})
|
||||
|
||||
export const useDebugWithMultipleModelContext = () => useContext(DebugWithMultipleModelContext)
|
||||
|
||||
type DebugWithMultipleModelContextProviderProps = {
|
||||
children: React.ReactNode
|
||||
} & DebugWithMultipleModelContextType
|
||||
export const DebugWithMultipleModelContextProvider = ({
|
||||
children,
|
||||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
}: DebugWithMultipleModelContextProviderProps) => {
|
||||
return (
|
||||
<DebugWithMultipleModelContext.Provider value={{
|
||||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
}}>
|
||||
{children}
|
||||
</DebugWithMultipleModelContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default DebugWithMultipleModelContext
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo } from 'react'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
import ModelParameterTrigger from './model-parameter-trigger'
|
||||
import ChatItem from './chat-item'
|
||||
import TextGenerationItem from './text-generation-item'
|
||||
import { useDebugWithMultipleModelContext } from './context'
|
||||
import { useDebugConfigurationContext } from '@/context/debug-configuration'
|
||||
import Dropdown from '@/app/components/base/dropdown'
|
||||
import type { Item } from '@/app/components/base/dropdown'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { ModelStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
type DebugItemProps = {
|
||||
modelAndParameter: ModelAndParameter
|
||||
className?: string
|
||||
}
|
||||
const DebugItem: FC<DebugItemProps> = ({
|
||||
modelAndParameter,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mode } = useDebugConfigurationContext()
|
||||
const {
|
||||
multipleModelConfigs,
|
||||
onMultipleModelConfigsChange,
|
||||
onDebugWithMultipleModelChange,
|
||||
} = useDebugWithMultipleModelContext()
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
|
||||
const index = multipleModelConfigs.findIndex(v => v.id === modelAndParameter.id)
|
||||
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
|
||||
const currentModel = currentProvider?.models.find(item => item.model === modelAndParameter.model)
|
||||
|
||||
const handleSelect = (item: Item) => {
|
||||
if (item.value === 'duplicate') {
|
||||
if (multipleModelConfigs.length >= 4)
|
||||
return
|
||||
|
||||
onMultipleModelConfigsChange(
|
||||
true,
|
||||
[
|
||||
...multipleModelConfigs.slice(0, index + 1),
|
||||
{
|
||||
...modelAndParameter,
|
||||
id: `${Date.now()}`,
|
||||
},
|
||||
...multipleModelConfigs.slice(index + 1),
|
||||
],
|
||||
)
|
||||
}
|
||||
if (item.value === 'debug-as-single-model')
|
||||
onDebugWithMultipleModelChange(modelAndParameter)
|
||||
if (item.value === 'remove') {
|
||||
onMultipleModelConfigsChange(
|
||||
true,
|
||||
multipleModelConfigs.filter(item => item.id !== modelAndParameter.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`}>
|
||||
<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}
|
||||
</div>
|
||||
<ModelParameterTrigger
|
||||
modelAndParameter={modelAndParameter}
|
||||
/>
|
||||
<Dropdown
|
||||
onSelect={handleSelect}
|
||||
items={[
|
||||
...(
|
||||
multipleModelConfigs.length <= 3
|
||||
? [
|
||||
{
|
||||
value: 'duplicate',
|
||||
text: t('appDebug.duplicateModel'),
|
||||
},
|
||||
]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
(modelAndParameter.provider && modelAndParameter.model)
|
||||
? [
|
||||
{
|
||||
value: 'debug-as-single-model',
|
||||
text: t('appDebug.debugAsSingleModel'),
|
||||
},
|
||||
]
|
||||
: []
|
||||
),
|
||||
]}
|
||||
secondItems={
|
||||
multipleModelConfigs.length > 2
|
||||
? [
|
||||
{
|
||||
value: 'remove',
|
||||
text: t('common.operation.remove'),
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ height: 'calc(100% - 40px)' }}>
|
||||
{
|
||||
mode === 'chat' && currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && (
|
||||
<ChatItem modelAndParameter={modelAndParameter} />
|
||||
)
|
||||
}
|
||||
{
|
||||
mode === 'completion' && currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && (
|
||||
<TextGenerationItem modelAndParameter={modelAndParameter}/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DebugItem)
|
||||
@@ -0,0 +1,131 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
|
||||
import DebugItem from './debug-item'
|
||||
import {
|
||||
DebugWithMultipleModelContextProvider,
|
||||
useDebugWithMultipleModelContext,
|
||||
} from './context'
|
||||
import type { DebugWithMultipleModelContextType } from './context'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import ChatInput from '@/app/components/base/chat/chat/chat-input'
|
||||
import type { VisionFile } from '@/app/components/base/chat/types'
|
||||
import { useDebugConfigurationContext } from '@/context/debug-configuration'
|
||||
|
||||
const DebugWithMultipleModel = () => {
|
||||
const {
|
||||
mode,
|
||||
speechToTextConfig,
|
||||
visionConfig,
|
||||
} = useDebugConfigurationContext()
|
||||
const { multipleModelConfigs } = useDebugWithMultipleModelContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const handleSend = useCallback((message: string, files?: VisionFile[]) => {
|
||||
eventEmitter?.emit({
|
||||
type: APP_CHAT_WITH_MULTIPLE_MODEL,
|
||||
payload: {
|
||||
message,
|
||||
files,
|
||||
},
|
||||
} as any)
|
||||
}, [eventEmitter])
|
||||
|
||||
const twoLine = multipleModelConfigs.length === 2
|
||||
const threeLine = multipleModelConfigs.length === 3
|
||||
const fourLine = multipleModelConfigs.length === 4
|
||||
|
||||
return (
|
||||
<div className='flex flex-col h-full'>
|
||||
<div
|
||||
className={`
|
||||
mb-3 overflow-auto
|
||||
${(twoLine || threeLine) && 'flex gap-2'}
|
||||
`}
|
||||
style={{ height: mode === 'chat' ? 'calc(100% - 60px)' : '100%' }}
|
||||
>
|
||||
{
|
||||
(twoLine || threeLine) && multipleModelConfigs.map(modelConfig => (
|
||||
<DebugItem
|
||||
key={modelConfig.id}
|
||||
modelAndParameter={modelConfig}
|
||||
className={`
|
||||
h-full min-h-[200px]
|
||||
${twoLine && 'w-1/2'}
|
||||
${threeLine && 'w-1/3'}
|
||||
`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{
|
||||
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'>
|
||||
<ChatInput
|
||||
onSend={handleSend}
|
||||
speechToTextConfig={speechToTextConfig}
|
||||
visionConfig={visionConfig}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DebugWithMultipleModelMemoed = memo(DebugWithMultipleModel)
|
||||
|
||||
const DebugWithMultipleModelWrapper: FC<DebugWithMultipleModelContextType> = ({
|
||||
onMultipleModelConfigsChange,
|
||||
multipleModelConfigs,
|
||||
onDebugWithMultipleModelChange,
|
||||
}) => {
|
||||
return (
|
||||
<DebugWithMultipleModelContextProvider
|
||||
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onDebugWithMultipleModelChange={onDebugWithMultipleModelChange}
|
||||
>
|
||||
<DebugWithMultipleModelMemoed />
|
||||
</DebugWithMultipleModelContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DebugWithMultipleModelWrapper)
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
import { useDebugWithMultipleModelContext } from './context'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import {
|
||||
MODEL_STATUS_TEXT,
|
||||
ModelStatusEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useDebugConfigurationContext } from '@/context/debug-configuration'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
|
||||
type ModelParameterTriggerProps = {
|
||||
modelAndParameter: ModelAndParameter
|
||||
}
|
||||
const ModelParameterTrigger: FC<ModelParameterTriggerProps> = ({
|
||||
modelAndParameter,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
mode,
|
||||
isAdvancedMode,
|
||||
} = useDebugConfigurationContext()
|
||||
const {
|
||||
multipleModelConfigs,
|
||||
onMultipleModelConfigsChange,
|
||||
onDebugWithMultipleModelChange,
|
||||
} = useDebugWithMultipleModelContext()
|
||||
const language = useLanguage()
|
||||
const index = multipleModelConfigs.findIndex(v => v.id === modelAndParameter.id)
|
||||
|
||||
const handleSelectModel = ({ modelId, provider }: { modelId: string; provider: string }) => {
|
||||
const newModelConfigs = [...multipleModelConfigs]
|
||||
newModelConfigs[index] = {
|
||||
...newModelConfigs[index],
|
||||
model: modelId,
|
||||
provider,
|
||||
}
|
||||
onMultipleModelConfigsChange(true, newModelConfigs)
|
||||
}
|
||||
const handleParamsChange = (params: any) => {
|
||||
const newModelConfigs = [...multipleModelConfigs]
|
||||
newModelConfigs[index] = {
|
||||
...newModelConfigs[index],
|
||||
parameters: params,
|
||||
}
|
||||
onMultipleModelConfigsChange(true, newModelConfigs)
|
||||
}
|
||||
|
||||
return (
|
||||
<ModelParameterModal
|
||||
mode={mode}
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
provider={modelAndParameter.provider}
|
||||
modelId={modelAndParameter.model}
|
||||
completionParams={modelAndParameter.parameters}
|
||||
onCompletionParamsChange={handleParamsChange}
|
||||
setModel={handleSelectModel}
|
||||
debugWithMultipleModel
|
||||
onDebugWithMultipleModelChange={() => onDebugWithMultipleModelChange(modelAndParameter)}
|
||||
renderTrigger={({
|
||||
open,
|
||||
currentProvider,
|
||||
currentModel,
|
||||
}) => (
|
||||
<div
|
||||
className={`
|
||||
flex items-center max-w-[200px] h-8 px-2 rounded-lg cursor-pointer
|
||||
${open && 'bg-gray-100'}
|
||||
${currentModel && currentModel.status !== ModelStatusEnum.active && '!bg-[#FFFAEB]'}
|
||||
`}
|
||||
>
|
||||
{
|
||||
currentProvider && (
|
||||
<ModelIcon
|
||||
className='mr-1 !w-4 !h-4'
|
||||
provider={currentProvider}
|
||||
modelName={currentModel?.model}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!currentProvider && (
|
||||
<div className='flex items-center justify-center mr-1 w-4 h-4 rounded border border-dashed border-primary-100'>
|
||||
<CubeOutline className='w-[11px] h-[11px] text-primary-600' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentModel && (
|
||||
<ModelName
|
||||
className='mr-0.5 text-gray-800'
|
||||
modelItem={currentModel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!currentModel && (
|
||||
<div className='mr-0.5 text-[13px] font-medium text-primary-600 truncate'>
|
||||
{t('common.modelProvider.selectModel')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<ChevronDown className={`w-3 h-3 ${(currentModel && currentProvider) ? 'text-gray-800' : 'text-primary-600'}`} />
|
||||
{
|
||||
currentModel && currentModel.status !== ModelStatusEnum.active && (
|
||||
<TooltipPlus popupContent={MODEL_STATUS_TEXT[currentModel.status][language]}>
|
||||
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
|
||||
</TooltipPlus>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ModelParameterTrigger)
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import type { ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
|
||||
type PublishWithMultipleModelProps = {
|
||||
multipleModelConfigs: ModelAndParameter[]
|
||||
onSelect: (v: ModelAndParameter) => void
|
||||
}
|
||||
const PublishWithMultipleModel: FC<PublishWithMultipleModelProps> = ({
|
||||
multipleModelConfigs,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const validModelConfigs: (ModelAndParameter & { modelItem: ModelItem })[] = []
|
||||
|
||||
multipleModelConfigs.forEach((item) => {
|
||||
const provider = textGenerationModelList.find(model => model.provider === item.provider)
|
||||
|
||||
if (provider) {
|
||||
const model = provider.models.find(model => model.model === item.model)
|
||||
|
||||
if (model) {
|
||||
validModelConfigs.push({
|
||||
id: item.id,
|
||||
model: item.model,
|
||||
provider: item.provider,
|
||||
modelItem: model,
|
||||
parameters: item.parameters,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleToggle = () => {
|
||||
if (validModelConfigs.length)
|
||||
setOpen(v => !v)
|
||||
}
|
||||
|
||||
const handleSelect = (item: ModelAndParameter) => {
|
||||
onSelect(item)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleToggle}>
|
||||
<Button
|
||||
type='primary'
|
||||
disabled={!validModelConfigs.length}
|
||||
className='pl-3 pr-2 h-8 text-[13px]'
|
||||
>
|
||||
{t('appDebug.operation.applyConfig')}
|
||||
<ChevronDown className='ml-0.5 w-3 h-3' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent>
|
||||
<div className='p-1 w-[168px] rounded-lg border-[0.5px] border-gray-200 shadow-lg bg-white'>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
{t('appDebug.publishAs')}
|
||||
</div>
|
||||
{
|
||||
validModelConfigs.map((item, index) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-100 cursor-pointer text-sm text-gray-500'
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
#{index + 1}
|
||||
<div
|
||||
className='ml-1 text-gray-700 truncate'
|
||||
title={item.modelItem.label[language]}
|
||||
>
|
||||
{item.modelItem.label[language]}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PublishWithMultipleModel
|
||||
@@ -0,0 +1,153 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { ModelAndParameter } from '../types'
|
||||
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
|
||||
import type {
|
||||
OnSend,
|
||||
TextGenerationConfig,
|
||||
} from '@/app/components/base/text-generation/types'
|
||||
import { useTextGeneration } from '@/app/components/base/text-generation/hooks'
|
||||
import TextGeneration from '@/app/components/app/text-generate/item'
|
||||
import { useDebugConfigurationContext } from '@/context/debug-configuration'
|
||||
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
|
||||
import { TransferMethod } from '@/app/components/base/chat/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type TextGenerationItemProps = {
|
||||
modelAndParameter: ModelAndParameter
|
||||
}
|
||||
const TextGenerationItem: FC<TextGenerationItemProps> = ({
|
||||
modelAndParameter,
|
||||
}) => {
|
||||
const {
|
||||
isAdvancedMode,
|
||||
modelConfig,
|
||||
appId,
|
||||
inputs,
|
||||
promptMode,
|
||||
speechToTextConfig,
|
||||
introduction,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
citationConfig,
|
||||
moderationConfig,
|
||||
externalDataToolsConfig,
|
||||
chatPromptConfig,
|
||||
completionPromptConfig,
|
||||
dataSets,
|
||||
datasetConfigs,
|
||||
visionConfig,
|
||||
moreLikeThisConfig,
|
||||
} = 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: TextGenerationConfig = {
|
||||
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,
|
||||
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
|
||||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
sensitive_word_avoidance: moderationConfig,
|
||||
external_data_tools: externalDataToolsConfig,
|
||||
more_like_this: moreLikeThisConfig,
|
||||
agent_mode: {
|
||||
enabled: false,
|
||||
tools: [],
|
||||
},
|
||||
dataset_configs: {
|
||||
...datasetConfigs,
|
||||
datasets: {
|
||||
datasets: [...postDatasets],
|
||||
} as any,
|
||||
},
|
||||
file_upload: {
|
||||
image: visionConfig,
|
||||
},
|
||||
}
|
||||
const {
|
||||
completion,
|
||||
handleSend,
|
||||
isResponsing,
|
||||
messageId,
|
||||
} = useTextGeneration()
|
||||
|
||||
const doSend: OnSend = (message, files) => {
|
||||
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
|
||||
const currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model)
|
||||
|
||||
const configData = {
|
||||
...config,
|
||||
model: {
|
||||
provider: modelAndParameter.provider,
|
||||
name: modelAndParameter.model,
|
||||
mode: currentModel?.model_properties.mode,
|
||||
completion_params: modelAndParameter.parameters,
|
||||
},
|
||||
}
|
||||
|
||||
const data: any = {
|
||||
inputs,
|
||||
model_config: configData,
|
||||
}
|
||||
|
||||
if (visionConfig.enabled && files && files?.length > 0) {
|
||||
data.files = files.map((item) => {
|
||||
if (item.transfer_method === TransferMethod.local_file) {
|
||||
return {
|
||||
...item,
|
||||
url: '',
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
handleSend(
|
||||
`apps/${appId}/completion-messages`,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL)
|
||||
doSend(v.payload.message, v.payload.files)
|
||||
})
|
||||
|
||||
const varList = modelConfig.configs.prompt_variables.map((item: any) => {
|
||||
return {
|
||||
label: item.key,
|
||||
value: inputs[item.key],
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<TextGeneration
|
||||
className='flex flex-col h-full overflow-y-auto border-none'
|
||||
innerClassName='grow flex flex-col'
|
||||
contentClassName='grow'
|
||||
content={completion}
|
||||
isLoading={!completion && isResponsing}
|
||||
isResponsing={isResponsing}
|
||||
isInstalledApp={false}
|
||||
messageId={messageId}
|
||||
isError={false}
|
||||
onRetry={() => { }}
|
||||
appId={appId}
|
||||
varList={varList}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(TextGenerationItem)
|
||||
54
web/app/components/app/configuration/debug/hooks.tsx
Normal file
54
web/app/components/app/configuration/debug/hooks.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type {
|
||||
DebugWithSingleOrMultipleModelConfigs,
|
||||
ModelAndParameter,
|
||||
} from './types'
|
||||
|
||||
export const useDebugWithSingleOrMultipleModel = (appId: string) => {
|
||||
const localeDebugWithSingleOrMultipleModelConfigs = localStorage.getItem('app-debug-with-single-or-multiple-models')
|
||||
|
||||
const debugWithSingleOrMultipleModelConfigs = useRef<DebugWithSingleOrMultipleModelConfigs>({})
|
||||
|
||||
if (localeDebugWithSingleOrMultipleModelConfigs) {
|
||||
try {
|
||||
debugWithSingleOrMultipleModelConfigs.current = JSON.parse(localeDebugWithSingleOrMultipleModelConfigs) || {}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const [
|
||||
debugWithMultipleModel,
|
||||
setDebugWithMultipleModel,
|
||||
] = useState(debugWithSingleOrMultipleModelConfigs.current[appId]?.multiple || false)
|
||||
|
||||
const [
|
||||
multipleModelConfigs,
|
||||
setMultipleModelConfigs,
|
||||
] = useState(debugWithSingleOrMultipleModelConfigs.current[appId]?.configs || [])
|
||||
|
||||
const handleMultipleModelConfigsChange = useCallback((
|
||||
multiple: boolean,
|
||||
modelConfigs: ModelAndParameter[],
|
||||
) => {
|
||||
const value = {
|
||||
multiple,
|
||||
configs: modelConfigs,
|
||||
}
|
||||
debugWithSingleOrMultipleModelConfigs.current[appId] = value
|
||||
localStorage.setItem('app-debug-with-single-or-multiple-models', JSON.stringify(debugWithSingleOrMultipleModelConfigs.current))
|
||||
setDebugWithMultipleModel(value.multiple)
|
||||
setMultipleModelConfigs(value.configs)
|
||||
}, [appId])
|
||||
|
||||
return {
|
||||
debugWithMultipleModel,
|
||||
multipleModelConfigs,
|
||||
handleMultipleModelConfigsChange,
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ 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 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'
|
||||
@@ -28,17 +34,30 @@ 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'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type IDebug = {
|
||||
hasSetAPIKEY: boolean
|
||||
onSetting: () => void
|
||||
inputs: Inputs
|
||||
modelParameterParams: Pick<ModelParameterModalProps, 'setModel' | 'onCompletionParamsChange'>
|
||||
debugWithMultipleModel: boolean
|
||||
multipleModelConfigs: ModelAndParameter[]
|
||||
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
|
||||
}
|
||||
|
||||
const Debug: FC<IDebug> = ({
|
||||
hasSetAPIKEY = true,
|
||||
onSetting,
|
||||
inputs,
|
||||
modelParameterParams,
|
||||
debugWithMultipleModel,
|
||||
multipleModelConfigs,
|
||||
onMultipleModelConfigsChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
@@ -72,7 +91,9 @@ const Debug: FC<IDebug> = ({
|
||||
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[]>([])
|
||||
@@ -119,7 +140,7 @@ const Debug: FC<IDebug> = ({
|
||||
setFormattingChanged(false)
|
||||
}, [formattingChanged])
|
||||
|
||||
const clearConversation = async () => {
|
||||
const handleClearConversation = () => {
|
||||
setConversationId(null)
|
||||
abortController?.abort()
|
||||
setResponsingFalse()
|
||||
@@ -134,6 +155,16 @@ const Debug: FC<IDebug> = ({
|
||||
: [])
|
||||
setIsShowSuggestion(false)
|
||||
}
|
||||
const clearConversation = async () => {
|
||||
if (debugWithMultipleModel) {
|
||||
eventEmitter?.emit({
|
||||
type: APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
|
||||
} as any)
|
||||
return
|
||||
}
|
||||
|
||||
handleClearConversation()
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
clearConversation()
|
||||
@@ -601,6 +632,21 @@ const Debug: FC<IDebug> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleSendTextCompletion = () => {
|
||||
if (debugWithMultipleModel) {
|
||||
eventEmitter?.emit({
|
||||
type: APP_CHAT_WITH_MULTIPLE_MODEL,
|
||||
payload: {
|
||||
message: '',
|
||||
files: completionFiles,
|
||||
},
|
||||
} as any)
|
||||
return
|
||||
}
|
||||
|
||||
sendTextCompletion()
|
||||
}
|
||||
|
||||
const varList = modelConfig.configs.prompt_variables.map((item: any) => {
|
||||
return {
|
||||
label: item.key,
|
||||
@@ -608,6 +654,51 @@ const Debug: FC<IDebug> = ({
|
||||
}
|
||||
})
|
||||
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
const handleChangeToSingleModel = (item: ModelAndParameter) => {
|
||||
const currentProvider = textGenerationModelList.find(modelItem => modelItem.provider === item.provider)
|
||||
const currentModel = currentProvider?.models.find(model => model.model === item.model)
|
||||
|
||||
modelParameterParams.setModel({
|
||||
modelId: item.model,
|
||||
provider: item.provider,
|
||||
mode: currentModel?.model_properties.mode as string,
|
||||
features: currentModel?.features,
|
||||
})
|
||||
modelParameterParams.onCompletionParamsChange(item.parameters)
|
||||
onMultipleModelConfigsChange(
|
||||
false,
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
const handleVisionConfigInMultipleModel = () => {
|
||||
if (debugWithMultipleModel && !visionConfig.enabled) {
|
||||
const supportedVision = multipleModelConfigs.some((modelConfig) => {
|
||||
const currentProvider = textGenerationModelList.find(modelItem => modelItem.provider === modelConfig.provider)
|
||||
const currentModel = currentProvider?.models.find(model => model.model === modelConfig.model)
|
||||
|
||||
return currentModel?.features?.includes(ModelFeatureEnum.vision)
|
||||
})
|
||||
|
||||
if (supportedVision) {
|
||||
setVisionConfig({
|
||||
...visionConfig,
|
||||
enabled: true,
|
||||
})
|
||||
}
|
||||
else {
|
||||
setVisionConfig({
|
||||
...visionConfig,
|
||||
enabled: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleVisionConfigInMultipleModel()
|
||||
}, [multipleModelConfigs])
|
||||
const allToolIcons = (() => {
|
||||
const icons: Record<string, any> = {}
|
||||
modelConfig.agentConfig.tools?.forEach((item: any) => {
|
||||
@@ -621,18 +712,40 @@ const Debug: FC<IDebug> = ({
|
||||
<div className="shrink-0">
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className='h2 '>{t('appDebug.inputs.title')}</div>
|
||||
{mode === 'chat' && (
|
||||
<Button className='flex items-center gap-1 !h-8 !bg-white' onClick={clearConversation}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66663 2.66629V5.99963H3.05463M3.05463 5.99963C3.49719 4.90505 4.29041 3.98823 5.30998 3.39287C6.32954 2.7975 7.51783 2.55724 8.68861 2.70972C9.85938 2.8622 10.9465 3.39882 11.7795 4.23548C12.6126 5.07213 13.1445 6.16154 13.292 7.33296M3.05463 5.99963H5.99996M13.3333 13.333V9.99963H12.946M12.946 9.99963C12.5028 11.0936 11.7093 12.0097 10.6898 12.6045C9.67038 13.1993 8.48245 13.4393 7.31203 13.2869C6.1416 13.1344 5.05476 12.5982 4.22165 11.7621C3.38854 10.926 2.8562 9.83726 2.70796 8.66629M12.946 9.99963H9.99996" stroke="#1C64F2" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className='text-primary-600 text-[13px] font-semibold'>{t('common.operation.refresh')}</span>
|
||||
</Button>
|
||||
)}
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
debugWithMultipleModel
|
||||
? (
|
||||
<>
|
||||
<Button
|
||||
className={`
|
||||
h-8 px-2.5 text-[13px] font-medium text-primary-600 bg-white
|
||||
${multipleModelConfigs.length >= 4 && 'opacity-30'}
|
||||
`}
|
||||
onClick={() => onMultipleModelConfigsChange(true, [...multipleModelConfigs, { id: `${Date.now()}`, model: '', provider: '', parameters: {} }])}
|
||||
disabled={multipleModelConfigs.length >= 4}
|
||||
>
|
||||
<Plus className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.modelProvider.addModel')}({multipleModelConfigs.length}/4)
|
||||
</Button>
|
||||
<div className='mx-2 w-[1px] h-[14px] bg-gray-200' />
|
||||
</>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{mode === 'chat' && (
|
||||
<Button className='flex items-center gap-1 !h-8 !bg-white' onClick={clearConversation}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66663 2.66629V5.99963H3.05463M3.05463 5.99963C3.49719 4.90505 4.29041 3.98823 5.30998 3.39287C6.32954 2.7975 7.51783 2.55724 8.68861 2.70972C9.85938 2.8622 10.9465 3.39882 11.7795 4.23548C12.6126 5.07213 13.1445 6.16154 13.292 7.33296M3.05463 5.99963H5.99996M13.3333 13.333V9.99963H12.946M12.946 9.99963C12.5028 11.0936 11.7093 12.0097 10.6898 12.6045C9.67038 13.1993 8.48245 13.4393 7.31203 13.2869C6.1416 13.1344 5.05476 12.5982 4.22165 11.7621C3.38854 10.926 2.8562 9.83726 2.70796 8.66629M12.946 9.99963H9.99996" stroke="#1C64F2" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className='text-primary-600 text-[13px] font-semibold'>{t('common.operation.refresh')}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PromptValuePanel
|
||||
appType={mode as AppType}
|
||||
onSend={sendTextCompletion}
|
||||
onSend={handleSendTextCompletion}
|
||||
inputs={inputs}
|
||||
visionConfig={{
|
||||
...visionConfig,
|
||||
@@ -641,81 +754,96 @@ const Debug: FC<IDebug> = ({
|
||||
onVisionFilesChange={setCompletionFiles}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
{
|
||||
debugWithMultipleModel && (
|
||||
<div className='grow mt-3 overflow-hidden'>
|
||||
<DebugWithMultipleModel
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
|
||||
onDebugWithMultipleModelChange={handleChangeToSingleModel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Text Generation */}
|
||||
{mode === AppType.completion && (
|
||||
<div className="mt-6">
|
||||
<GroupName name={t('appDebug.result')} />
|
||||
{(completionRes || isResponsing) && (
|
||||
<TextGeneration
|
||||
className="mt-2"
|
||||
content={completionRes}
|
||||
isLoading={!completionRes && isResponsing}
|
||||
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
|
||||
isResponsing={isResponsing}
|
||||
isInstalledApp={false}
|
||||
messageId={messageId}
|
||||
isError={false}
|
||||
onRetry={() => { }}
|
||||
supportAnnotation
|
||||
appId={appId}
|
||||
varList={varList}
|
||||
)
|
||||
}
|
||||
{
|
||||
!debugWithMultipleModel && (
|
||||
<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>
|
||||
)}
|
||||
{/* Text Generation */}
|
||||
{mode === AppType.completion && (
|
||||
<div className="mt-6">
|
||||
<GroupName name={t('appDebug.result')} />
|
||||
{(completionRes || isResponsing) && (
|
||||
<TextGeneration
|
||||
className="mt-2"
|
||||
content={completionRes}
|
||||
isLoading={!completionRes && isResponsing}
|
||||
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
|
||||
isResponsing={isResponsing}
|
||||
isInstalledApp={false}
|
||||
messageId={messageId}
|
||||
isError={false}
|
||||
onRetry={() => { }}
|
||||
supportAnnotation
|
||||
appId={appId}
|
||||
varList={varList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isShowFormattingChangeConfirm && (
|
||||
<FormattingChanged
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{isShowCannotQueryDataset && (
|
||||
<CannotQueryDataset
|
||||
onConfirm={() => setShowCannotQueryDataset(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isShowFormattingChangeConfirm && (
|
||||
<FormattingChanged
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{isShowCannotQueryDataset && (
|
||||
<CannotQueryDataset
|
||||
onConfirm={() => setShowCannotQueryDataset(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{!hasSetAPIKEY && (<HasNotSetAPIKEY isTrailFinished={!IS_CE_EDITION} onSetting={onSetting} />)}
|
||||
</>
|
||||
)
|
||||
|
||||
18
web/app/components/app/configuration/debug/types.ts
Normal file
18
web/app/components/app/configuration/debug/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export type ModelAndParameter = {
|
||||
id: string
|
||||
model: string
|
||||
provider: string
|
||||
parameters: Record<string, any>
|
||||
}
|
||||
|
||||
export type MultipleAndConfigs = {
|
||||
multiple: boolean
|
||||
configs: ModelAndParameter[]
|
||||
}
|
||||
|
||||
export type DebugWithSingleOrMultipleModelConfigs = {
|
||||
[k: string]: MultipleAndConfigs
|
||||
}
|
||||
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'
|
||||
@@ -13,6 +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 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'
|
||||
import AssistantTypePicker from './config/assistant-type-picker'
|
||||
import type {
|
||||
AnnotationReplyConfig,
|
||||
@@ -50,6 +54,7 @@ import type { FormValue } from '@/app/components/header/account-setting/model-pr
|
||||
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import { type Collection } from '@/app/components/tools/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
type PublichConfig = {
|
||||
modelConfig: ModelConfig
|
||||
@@ -524,8 +529,8 @@ const Configuration: FC = () => {
|
||||
else { return promptEmpty }
|
||||
})()
|
||||
const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar
|
||||
const handlePublish = async (isSilence?: boolean) => {
|
||||
const modelId = modelConfig.model_id
|
||||
const handlePublish = async (isSilence?: boolean, modelAndParameter?: ModelAndParameter) => {
|
||||
const modelId = modelAndParameter?.model || modelConfig.model_id
|
||||
const promptTemplate = modelConfig.configs.prompt_template
|
||||
const promptVariables = modelConfig.configs.prompt_variables
|
||||
|
||||
@@ -578,10 +583,10 @@ const Configuration: FC = () => {
|
||||
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
|
||||
},
|
||||
model: {
|
||||
provider: modelConfig.provider,
|
||||
provider: modelAndParameter?.provider || modelConfig.provider,
|
||||
name: modelId,
|
||||
mode: modelConfig.mode,
|
||||
completion_params: completionParams as any,
|
||||
completion_params: modelAndParameter?.parameters || completionParams as any,
|
||||
},
|
||||
dataset_configs: {
|
||||
...datasetConfigs,
|
||||
@@ -629,6 +634,26 @@ const Configuration: FC = () => {
|
||||
const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const {
|
||||
debugWithMultipleModel,
|
||||
multipleModelConfigs,
|
||||
handleMultipleModelConfigsChange,
|
||||
} = useDebugWithSingleOrMultipleModel(appId)
|
||||
|
||||
const handleDebugWithMultipleModelChange = () => {
|
||||
handleMultipleModelConfigsChange(
|
||||
true,
|
||||
[
|
||||
{ id: `${Date.now()}`, model: modelConfig.model_id, provider: modelConfig.provider, parameters: completionParams },
|
||||
{ id: `${Date.now()}-no-repeat`, model: '', provider: '', parameters: {} },
|
||||
],
|
||||
)
|
||||
eventEmitter?.emit({
|
||||
type: APP_SIDEBAR_SHOULD_COLLAPSE,
|
||||
} as any)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<Loading type='area' />
|
||||
@@ -709,7 +734,7 @@ const Configuration: FC = () => {
|
||||
<>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className='flex grow h-[200px]'>
|
||||
<div className="w-full sm:w-1/2 shrink-0 flex flex-col h-full">
|
||||
<div className={`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`}>
|
||||
{/* Header Left */}
|
||||
<div className='flex justify-between items-center px-6 h-14'>
|
||||
<div className='flex items-center'>
|
||||
@@ -743,22 +768,30 @@ const Configuration: FC = () => {
|
||||
</div>
|
||||
<Config />
|
||||
</div>
|
||||
{!isMobile && <div className="relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
{!isMobile && <div className="grow relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
{/* Header Right */}
|
||||
<div className='flex justify-end items-center flex-wrap px-6 h-14 space-x-2'>
|
||||
{/* Model and Parameters */}
|
||||
<ModelParameterModal
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
mode={mode}
|
||||
provider={modelConfig.provider}
|
||||
completionParams={completionParams}
|
||||
modelId={modelConfig.model_id}
|
||||
setModel={setModel as any}
|
||||
onCompletionParamsChange={(newParams: FormValue) => {
|
||||
setCompletionParams(newParams)
|
||||
}}
|
||||
/>
|
||||
<div className='w-[1px] h-[14px] bg-gray-200'></div>
|
||||
{
|
||||
!debugWithMultipleModel && (
|
||||
<>
|
||||
<ModelParameterModal
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
mode={mode}
|
||||
provider={modelConfig.provider}
|
||||
completionParams={completionParams}
|
||||
modelId={modelConfig.model_id}
|
||||
setModel={setModel as any}
|
||||
onCompletionParamsChange={(newParams: FormValue) => {
|
||||
setCompletionParams(newParams)
|
||||
}}
|
||||
debugWithMultipleModel={debugWithMultipleModel}
|
||||
onDebugWithMultipleModelChange={handleDebugWithMultipleModelChange}
|
||||
/>
|
||||
<div className='w-[1px] h-[14px] bg-gray-200'></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
|
||||
{isMobile && (
|
||||
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
|
||||
@@ -766,13 +799,37 @@ const Configuration: FC = () => {
|
||||
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
)}
|
||||
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
|
||||
{
|
||||
debugWithMultipleModel
|
||||
? (
|
||||
<PublishWithMultipleModel
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onSelect={item => handlePublish(false, item)}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={() => handlePublish(false)}
|
||||
className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}
|
||||
>
|
||||
{t('appDebug.operation.applyConfig')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '>
|
||||
<Debug
|
||||
hasSetAPIKEY={hasSettedApiKey}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
inputs={inputs}
|
||||
modelParameterParams={{
|
||||
setModel: setModel as any,
|
||||
onCompletionParamsChange: setCompletionParams,
|
||||
}}
|
||||
debugWithMultipleModel={debugWithMultipleModel}
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
@@ -829,6 +886,13 @@ const Configuration: FC = () => {
|
||||
hasSetAPIKEY={hasSettedApiKey}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
inputs={inputs}
|
||||
modelParameterParams={{
|
||||
setModel: setModel as any,
|
||||
onCompletionParamsChange: setCompletionParams,
|
||||
}}
|
||||
debugWithMultipleModel={debugWithMultipleModel}
|
||||
multipleModelConfigs={multipleModelConfigs}
|
||||
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
|
||||
@@ -49,6 +49,9 @@ export type IGenerationItemProps = {
|
||||
isShowTextToSpeech?: boolean
|
||||
appId?: string
|
||||
varList?: { label: string; value: string | number | object }[]
|
||||
innerClassName?: string
|
||||
contentClassName?: string
|
||||
footerClassName?: string
|
||||
}
|
||||
|
||||
export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
|
||||
@@ -95,6 +98,8 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
isShowTextToSpeech,
|
||||
appId,
|
||||
varList,
|
||||
innerClassName,
|
||||
contentClassName,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useParams()
|
||||
@@ -177,7 +182,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
|
||||
const handleOpenLogModal = async (setModal: Dispatch<SetStateAction<boolean>>) => {
|
||||
const data = await fetchTextGenerationMessge({
|
||||
appId: params.appId,
|
||||
appId: params.appId as string,
|
||||
messageId: messageId!,
|
||||
})
|
||||
setPromptLog(data.message as any || [])
|
||||
@@ -249,7 +254,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
)
|
||||
: (
|
||||
<div
|
||||
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
|
||||
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4', innerClassName)}
|
||||
style={mainStyle}
|
||||
>
|
||||
{(isTop && taskId) && (
|
||||
@@ -258,7 +263,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
{taskId}
|
||||
</div>)
|
||||
}
|
||||
<div className='flex'>
|
||||
<div className={`flex ${contentClassName}`}>
|
||||
<div className='grow w-0'>
|
||||
{isError
|
||||
? <div className='text-gray-400 text-sm'>{t('share.generation.batchFailed.outputPlaceholder')}</div>
|
||||
|
||||
Reference in New Issue
Block a user