feat: webapp support change inputs after conversation started (#16901)

This commit is contained in:
KVOJJJin 2025-03-27 11:58:16 +08:00 committed by GitHub
parent 0722beeb0b
commit c23135c9e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 76 additions and 36 deletions

View File

@ -29,6 +29,7 @@ const ChatWrapper = () => {
appPrevChatTree,
currentConversationId,
currentConversationItem,
currentConversationInputs,
inputsForms,
newConversationInputs,
newConversationInputsRef,
@ -69,7 +70,7 @@ const ChatWrapper = () => {
} = useChat(
appConfig,
{
inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any,
inputs: (currentConversationId ? currentConversationInputs : newConversationInputs) as any,
inputsForm: inputsForms,
},
appPrevChatTree,
@ -77,7 +78,7 @@ const ChatWrapper = () => {
clearChatList,
setClearChatList,
)
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current
const inputDisabled = useMemo(() => {
let hasEmptyInput = ''
let fileIsUploading = false
@ -124,7 +125,7 @@ const ChatWrapper = () => {
const data: any = {
query: message,
files,
inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs,
inputs: currentConversationId ? currentConversationInputs : newConversationInputs,
conversation_id: currentConversationId,
parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null,
}
@ -144,6 +145,7 @@ const ChatWrapper = () => {
handleSend,
currentConversationId,
currentConversationItem,
currentConversationInputs,
newConversationInputs,
isInstalledApp,
appId,
@ -245,7 +247,7 @@ const ChatWrapper = () => {
chatFooterClassName='pb-4'
chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`}
onSend={doSend}
inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs}
inputs={currentConversationId ? currentConversationInputs as any : newConversationInputs}
inputsForm={inputsForms}
onRegenerate={doRegenerate}
onStopResponding={handleStop}

View File

@ -54,6 +54,8 @@ export type ChatWithHistoryContextValue = {
setClearChatList: (state: boolean) => void
isResponding?: boolean
setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
}
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
@ -85,5 +87,7 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
setClearChatList: () => {},
isResponding: false,
setIsResponding: () => {},
currentConversationInputs: {},
setCurrentConversationInputs: () => {},
})
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)

View File

@ -120,7 +120,7 @@ const HeaderInMobile = () => {
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
</div>
<div className='p-4'>
<InputsFormContent showTip />
<InputsFormContent />
</div>
</div>
</div>

View File

@ -263,6 +263,17 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return conversationItem
}, [conversationList, currentConversationId, pinnedConversationList])
const currentConversationLatestInputs = useMemo(() => {
if (!currentConversationId || !appChatListData?.data.length)
return {}
return appChatListData.data.slice().pop().inputs || {}
}, [appChatListData, currentConversationId])
const [currentConversationInputs, setCurrentConversationInputs] = useState<Record<string, any>>(currentConversationLatestInputs || {})
useEffect(() => {
if (currentConversationItem)
setCurrentConversationInputs(currentConversationLatestInputs || {})
}, [currentConversationItem, currentConversationLatestInputs])
const { notify } = useToastContext()
const checkInputsRequired = useCallback((silent?: boolean) => {
let hasEmptyInput = ''
@ -464,5 +475,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}
}

View File

@ -157,6 +157,8 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} = useChatWithHistory(installedAppInfo)
return (
@ -198,6 +200,8 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}}>
<ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider>

View File

@ -17,20 +17,24 @@ const InputsFormContent = ({ showTip }: Props) => {
appParams,
inputsForms,
currentConversationId,
currentConversationItem,
currentConversationInputs,
setCurrentConversationInputs,
newConversationInputs,
newConversationInputsRef,
handleNewConversationInputsChange,
} = useChatWithHistoryContext()
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputs
const readonly = !!currentConversationId
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputs
const handleFormChange = useCallback((variable: string, value: any) => {
setCurrentConversationInputs({
...currentConversationInputs,
[variable]: value,
})
handleNewConversationInputsChange({
...newConversationInputsRef.current,
[variable]: value,
})
}, [newConversationInputsRef, handleNewConversationInputsChange])
}, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs])
return (
<div className='space-y-4'>
@ -47,8 +51,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.number && (
@ -57,8 +59,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.paragraph && (
@ -66,8 +66,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.select && (
@ -77,7 +75,6 @@ const InputsFormContent = ({ showTip }: Props) => {
items={form.options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(form.variable, item.value as string)}
placeholder={form.label}
readonly={readonly}
/>
)}
{form.type === InputVarType.singleFile && (

View File

@ -38,7 +38,7 @@ const InputsFormNode = ({
<Message3Fill className='h-6 w-6 shrink-0' />
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
{collapsed && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{currentConversationId ? t('common.operation.view') : t('common.operation.edit')}</Button>
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
)}
{!collapsed && currentConversationId && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
@ -46,7 +46,7 @@ const InputsFormNode = ({
</div>
{!collapsed && (
<div className={cn('p-6', isMobile && 'p-4')}>
<InputsFormContent showTip={!!currentConversationId} />
<InputsFormContent />
</div>
)}
{!collapsed && !currentConversationId && (

View File

@ -36,7 +36,7 @@ const ViewFormDropdown = () => {
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
</div>
<div className='p-6'>
<InputsFormContent showTip />
<InputsFormContent />
</div>
</div>
</PortalToFollowElemContent>

View File

@ -32,6 +32,7 @@ const ChatWrapper = () => {
appPrevChatList,
currentConversationId,
currentConversationItem,
currentConversationInputs,
inputsForms,
newConversationInputs,
newConversationInputsRef,
@ -70,7 +71,7 @@ const ChatWrapper = () => {
} = useChat(
appConfig,
{
inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any,
inputs: (currentConversationId ? currentConversationInputs : newConversationInputs) as any,
inputsForm: inputsForms,
},
appPrevChatList,
@ -78,7 +79,7 @@ const ChatWrapper = () => {
clearChatList,
setClearChatList,
)
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current
const inputDisabled = useMemo(() => {
let hasEmptyInput = ''
let fileIsUploading = false
@ -123,7 +124,7 @@ const ChatWrapper = () => {
const data: any = {
query: message,
files,
inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs,
inputs: currentConversationId ? currentConversationInputs : newConversationInputs,
conversation_id: currentConversationId,
parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null,
}
@ -241,7 +242,7 @@ const ChatWrapper = () => {
chatFooterClassName={cn('pb-4', !isMobile && 'rounded-b-2xl')}
chatFooterInnerClassName={cn('mx-auto w-full max-w-full px-4', isMobile && 'px-2')}
onSend={doSend}
inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs}
inputs={currentConversationId ? currentConversationInputs as any : newConversationInputs}
inputsForm={inputsForms}
onRegenerate={doRegenerate}
onStopResponding={handleStop}

View File

@ -46,6 +46,8 @@ export type EmbeddedChatbotContextValue = {
setClearChatList: (state: boolean) => void
isResponding?: boolean
setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
}
export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
@ -70,5 +72,7 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
setClearChatList: () => {},
isResponding: false,
setIsResponding: () => {},
currentConversationInputs: {},
setCurrentConversationInputs: () => {},
})
export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext)

View File

@ -239,6 +239,17 @@ export const useEmbeddedChatbot = () => {
return conversationItem
}, [conversationList, currentConversationId, pinnedConversationList])
const currentConversationLatestInputs = useMemo(() => {
if (!currentConversationId || !appChatListData?.data.length)
return {}
return appChatListData.data.slice().pop().inputs || {}
}, [appChatListData, currentConversationId])
const [currentConversationInputs, setCurrentConversationInputs] = useState<Record<string, any>>(currentConversationLatestInputs || {})
useEffect(() => {
if (currentConversationItem)
setCurrentConversationInputs(currentConversationLatestInputs || {})
}, [currentConversationItem, currentConversationLatestInputs])
const { notify } = useToastContext()
const checkInputsRequired = useCallback((silent?: boolean) => {
let hasEmptyInput = ''
@ -347,5 +358,7 @@ export const useEmbeddedChatbot = () => {
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}
}

View File

@ -160,6 +160,8 @@ const EmbeddedChatbotWrapper = () => {
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} = useEmbeddedChatbot()
return <EmbeddedChatbotContext.Provider value={{
@ -193,6 +195,8 @@ const EmbeddedChatbotWrapper = () => {
setClearChatList,
isResponding,
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}}>
<Chatbot />
</EmbeddedChatbotContext.Provider>

View File

@ -17,20 +17,25 @@ const InputsFormContent = ({ showTip }: Props) => {
appParams,
inputsForms,
currentConversationId,
currentConversationItem,
currentConversationInputs,
setCurrentConversationInputs,
newConversationInputs,
newConversationInputsRef,
handleNewConversationInputsChange,
} = useEmbeddedChatbotContext()
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputs
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputs
const readonly = !!currentConversationId
const handleFormChange = useCallback((variable: string, value: any) => {
setCurrentConversationInputs({
...currentConversationInputs,
[variable]: value,
})
handleNewConversationInputsChange({
...newConversationInputsRef.current,
[variable]: value,
})
}, [newConversationInputsRef, handleNewConversationInputsChange])
}, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs])
return (
<div className='space-y-4'>
@ -47,8 +52,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.number && (
@ -57,8 +60,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.paragraph && (
@ -66,8 +67,6 @@ const InputsFormContent = ({ showTip }: Props) => {
value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/>
)}
{form.type === InputVarType.select && (
@ -77,7 +76,6 @@ const InputsFormContent = ({ showTip }: Props) => {
items={form.options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(form.variable, item.value as string)}
placeholder={form.label}
readonly={readonly}
/>
)}
{form.type === InputVarType.singleFile && (

View File

@ -38,7 +38,7 @@ const InputsFormNode = ({
<Message3Fill className='h-6 w-6 shrink-0' />
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
{collapsed && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{currentConversationId ? t('common.operation.view') : t('common.operation.edit')}</Button>
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
)}
{!collapsed && currentConversationId && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
@ -46,7 +46,7 @@ const InputsFormNode = ({
</div>
{!collapsed && (
<div className={cn('p-6', isMobile && 'p-4')}>
<InputsFormContent showTip={!!currentConversationId} />
<InputsFormContent />
</div>
)}
{!collapsed && !currentConversationId && (

View File

@ -40,7 +40,7 @@ const ViewFormDropdown = ({ iconColor }: Props) => {
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
</div>
<div className='p-6'>
<InputsFormContent showTip />
<InputsFormContent />
</div>
</div>
</PortalToFollowElemContent>