瀏覽代碼

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

tags/1.2.0
KVOJJJin 7 月之前
父節點
當前提交
c23135c9e8
沒有連結到貢獻者的電子郵件帳戶。

+ 6
- 4
web/app/components/base/chat/chat-with-history/chat-wrapper.tsx 查看文件

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

+ 4
- 0
web/app/components/base/chat/chat-with-history/context.tsx 查看文件

setClearChatList: (state: boolean) => void setClearChatList: (state: boolean) => void
isResponding?: boolean isResponding?: boolean
setIsResponding: (state: boolean) => void, setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
} }


export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
setClearChatList: () => {}, setClearChatList: () => {},
isResponding: false, isResponding: false,
setIsResponding: () => {}, setIsResponding: () => {},
currentConversationInputs: {},
setCurrentConversationInputs: () => {},
}) })
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)

+ 1
- 1
web/app/components/base/chat/chat-with-history/header-in-mobile.tsx 查看文件

<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div> <div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
</div> </div>
<div className='p-4'> <div className='p-4'>
<InputsFormContent showTip />
<InputsFormContent />
</div> </div>
</div> </div>
</div> </div>

+ 13
- 0
web/app/components/base/chat/chat-with-history/hooks.tsx 查看文件

return conversationItem return conversationItem
}, [conversationList, currentConversationId, pinnedConversationList]) }, [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 { notify } = useToastContext()
const checkInputsRequired = useCallback((silent?: boolean) => { const checkInputsRequired = useCallback((silent?: boolean) => {
let hasEmptyInput = '' let hasEmptyInput = ''
setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} }
} }

+ 4
- 0
web/app/components/base/chat/chat-with-history/index.tsx 查看文件

setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} = useChatWithHistory(installedAppInfo) } = useChatWithHistory(installedAppInfo)


return ( return (
setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}}> }}>
<ChatWithHistory className={className} /> <ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider> </ChatWithHistoryContext.Provider>

+ 8
- 11
web/app/components/base/chat/chat-with-history/inputs-form/content.tsx 查看文件

appParams, appParams,
inputsForms, inputsForms,
currentConversationId, currentConversationId,
currentConversationItem,
currentConversationInputs,
setCurrentConversationInputs,
newConversationInputs, newConversationInputs,
newConversationInputsRef, newConversationInputsRef,
handleNewConversationInputsChange, handleNewConversationInputsChange,
} = useChatWithHistoryContext() } = useChatWithHistoryContext()
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputs
const readonly = !!currentConversationId
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputs


const handleFormChange = useCallback((variable: string, value: any) => { const handleFormChange = useCallback((variable: string, value: any) => {
setCurrentConversationInputs({
...currentConversationInputs,
[variable]: value,
})
handleNewConversationInputsChange({ handleNewConversationInputsChange({
...newConversationInputsRef.current, ...newConversationInputsRef.current,
[variable]: value, [variable]: value,
}) })
}, [newConversationInputsRef, handleNewConversationInputsChange])
}, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs])


return ( return (
<div className='space-y-4'> <div className='space-y-4'>
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.number && ( {form.type === InputVarType.number && (
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.paragraph && ( {form.type === InputVarType.paragraph && (
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.select && ( {form.type === InputVarType.select && (
items={form.options.map((option: string) => ({ value: option, name: option }))} items={form.options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(form.variable, item.value as string)} onSelect={item => handleFormChange(form.variable, item.value as string)}
placeholder={form.label} placeholder={form.label}
readonly={readonly}
/> />
)} )}
{form.type === InputVarType.singleFile && ( {form.type === InputVarType.singleFile && (

+ 2
- 2
web/app/components/base/chat/chat-with-history/inputs-form/index.tsx 查看文件

<Message3Fill className='h-6 w-6 shrink-0' /> <Message3Fill className='h-6 w-6 shrink-0' />
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div> <div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
{collapsed && ( {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 && ( {!collapsed && currentConversationId && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button> <Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
</div> </div>
{!collapsed && ( {!collapsed && (
<div className={cn('p-6', isMobile && 'p-4')}> <div className={cn('p-6', isMobile && 'p-4')}>
<InputsFormContent showTip={!!currentConversationId} />
<InputsFormContent />
</div> </div>
)} )}
{!collapsed && !currentConversationId && ( {!collapsed && !currentConversationId && (

+ 1
- 1
web/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx 查看文件

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

+ 5
- 4
web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx 查看文件

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

+ 4
- 0
web/app/components/base/chat/embedded-chatbot/context.tsx 查看文件

setClearChatList: (state: boolean) => void setClearChatList: (state: boolean) => void
isResponding?: boolean isResponding?: boolean
setIsResponding: (state: boolean) => void, setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
} }


export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
setClearChatList: () => {}, setClearChatList: () => {},
isResponding: false, isResponding: false,
setIsResponding: () => {}, setIsResponding: () => {},
currentConversationInputs: {},
setCurrentConversationInputs: () => {},
}) })
export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext)

+ 13
- 0
web/app/components/base/chat/embedded-chatbot/hooks.tsx 查看文件

return conversationItem return conversationItem
}, [conversationList, currentConversationId, pinnedConversationList]) }, [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 { notify } = useToastContext()
const checkInputsRequired = useCallback((silent?: boolean) => { const checkInputsRequired = useCallback((silent?: boolean) => {
let hasEmptyInput = '' let hasEmptyInput = ''
setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} }
} }

+ 4
- 0
web/app/components/base/chat/embedded-chatbot/index.tsx 查看文件

setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
} = useEmbeddedChatbot() } = useEmbeddedChatbot()


return <EmbeddedChatbotContext.Provider value={{ return <EmbeddedChatbotContext.Provider value={{
setClearChatList, setClearChatList,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
}}> }}>
<Chatbot /> <Chatbot />
</EmbeddedChatbotContext.Provider> </EmbeddedChatbotContext.Provider>

+ 8
- 10
web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx 查看文件

appParams, appParams,
inputsForms, inputsForms,
currentConversationId, currentConversationId,
currentConversationItem,
currentConversationInputs,
setCurrentConversationInputs,
newConversationInputs, newConversationInputs,
newConversationInputsRef, newConversationInputsRef,
handleNewConversationInputsChange, handleNewConversationInputsChange,
} = useEmbeddedChatbotContext() } = useEmbeddedChatbotContext()
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputs
const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputs
const readonly = !!currentConversationId const readonly = !!currentConversationId


const handleFormChange = useCallback((variable: string, value: any) => { const handleFormChange = useCallback((variable: string, value: any) => {
setCurrentConversationInputs({
...currentConversationInputs,
[variable]: value,
})
handleNewConversationInputsChange({ handleNewConversationInputsChange({
...newConversationInputsRef.current, ...newConversationInputsRef.current,
[variable]: value, [variable]: value,
}) })
}, [newConversationInputsRef, handleNewConversationInputsChange])
}, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs])


return ( return (
<div className='space-y-4'> <div className='space-y-4'>
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.number && ( {form.type === InputVarType.number && (
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.paragraph && ( {form.type === InputVarType.paragraph && (
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
onChange={e => handleFormChange(form.variable, e.target.value)} onChange={e => handleFormChange(form.variable, e.target.value)}
placeholder={form.label} placeholder={form.label}
readOnly={readonly}
disabled={readonly}
/> />
)} )}
{form.type === InputVarType.select && ( {form.type === InputVarType.select && (
items={form.options.map((option: string) => ({ value: option, name: option }))} items={form.options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(form.variable, item.value as string)} onSelect={item => handleFormChange(form.variable, item.value as string)}
placeholder={form.label} placeholder={form.label}
readonly={readonly}
/> />
)} )}
{form.type === InputVarType.singleFile && ( {form.type === InputVarType.singleFile && (

+ 2
- 2
web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx 查看文件

<Message3Fill className='h-6 w-6 shrink-0' /> <Message3Fill className='h-6 w-6 shrink-0' />
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div> <div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
{collapsed && ( {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 && ( {!collapsed && currentConversationId && (
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button> <Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
</div> </div>
{!collapsed && ( {!collapsed && (
<div className={cn('p-6', isMobile && 'p-4')}> <div className={cn('p-6', isMobile && 'p-4')}>
<InputsFormContent showTip={!!currentConversationId} />
<InputsFormContent />
</div> </div>
)} )}
{!collapsed && !currentConversationId && ( {!collapsed && !currentConversationId && (

+ 1
- 1
web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx 查看文件

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

Loading…
取消
儲存