features in workflow

This commit is contained in:
JzoNg 2024-08-29 22:54:36 +08:00
parent b3529d3ccc
commit 0e2f78b3a6
19 changed files with 250 additions and 91 deletions

View File

@ -10,7 +10,6 @@ import Recorder from 'js-audio-recorder'
import type { import type {
EnableType, EnableType,
OnSend, OnSend,
VisionConfig,
} from '../../types' } from '../../types'
import type { Theme } from '../../embedded-chatbot/theme/theme-context' import type { Theme } from '../../embedded-chatbot/theme/theme-context'
import { useTextAreaHeight } from './hooks' import { useTextAreaHeight } from './hooks'
@ -20,14 +19,22 @@ import { FileListFlexOperation } from '@/app/components/base/file-uploader'
import { FileContextProvider } from '@/app/components/base/file-uploader/store' import { FileContextProvider } from '@/app/components/base/file-uploader/store'
import VoiceInput from '@/app/components/base/voice-input' import VoiceInput from '@/app/components/base/voice-input'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import FeatureBar from '@/app/components/base/features/new-feature-panel/feature-bar'
import type { FileUpload } from '@/app/components/base/features/types'
type ChatInputAreaProps = { type ChatInputAreaProps = {
visionConfig?: VisionConfig showFeatureBar?: boolean
featureBarDisabled?: boolean
onFeatureBarClick?: (state: boolean) => void
visionConfig?: FileUpload
speechToTextConfig?: EnableType speechToTextConfig?: EnableType
onSend?: OnSend onSend?: OnSend
theme?: Theme | null theme?: Theme | null
} }
const ChatInputArea = ({ const ChatInputArea = ({
showFeatureBar,
featureBarDisabled,
onFeatureBarClick,
visionConfig, visionConfig,
speechToTextConfig = { enabled: true }, speechToTextConfig = { enabled: true },
onSend, onSend,
@ -86,6 +93,7 @@ const ChatInputArea = ({
const operation = ( const operation = (
<Operation <Operation
ref={holdSpaceRef} ref={holdSpaceRef}
visionConfig={visionConfig}
speechToTextConfig={speechToTextConfig} speechToTextConfig={speechToTextConfig}
onShowVoiceInput={handleShowVoiceInput} onShowVoiceInput={handleShowVoiceInput}
onSend={handleSend} onSend={handleSend}
@ -94,9 +102,10 @@ const ChatInputArea = ({
return ( return (
<FileContextProvider> <FileContextProvider>
<>
<div <div
className={cn( className={cn(
'py-[9px] bg-components-panel-bg-blur border border-components-chat-input-border rounded-xl shadow-md', 'relative py-[9px] bg-components-panel-bg-blur border border-components-chat-input-border rounded-xl shadow-md z-10',
)} )}
> >
<div className='relative px-[9px] max-h-[158px] overflow-x-hidden overflow-y-auto'> <div className='relative px-[9px] max-h-[158px] overflow-x-hidden overflow-y-auto'>
@ -146,6 +155,8 @@ const ChatInputArea = ({
) )
} }
</div> </div>
{showFeatureBar && <FeatureBar disabled={featureBarDisabled} onFeatureBarClick={onFeatureBarClick} />}
</>
</FileContextProvider> </FileContextProvider>
) )
} }

View File

@ -12,14 +12,17 @@ import type {
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import ActionButton from '@/app/components/base/action-button' import ActionButton from '@/app/components/base/action-button'
import { FileUploaderInChatInput } from '@/app/components/base/file-uploader' import { FileUploaderInChatInput } from '@/app/components/base/file-uploader'
import type { FileUpload } from '@/app/components/base/features/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type OperationProps = { type OperationProps = {
visionConfig?: FileUpload
speechToTextConfig?: EnableType speechToTextConfig?: EnableType
onShowVoiceInput?: () => void onShowVoiceInput?: () => void
onSend: () => void onSend: () => void
} }
const Operation = forwardRef<HTMLDivElement, OperationProps>(({ const Operation = forwardRef<HTMLDivElement, OperationProps>(({
visionConfig,
speechToTextConfig, speechToTextConfig,
onShowVoiceInput, onShowVoiceInput,
onSend, onSend,
@ -35,7 +38,7 @@ const Operation = forwardRef<HTMLDivElement, OperationProps>(({
ref={ref} ref={ref}
> >
<div className='flex items-center space-x-1'> <div className='flex items-center space-x-1'>
<FileUploaderInChatInput /> {visionConfig?.enabled && <FileUploaderInChatInput />}
{ {
speechToTextConfig?.enabled && ( speechToTextConfig?.enabled && (
<ActionButton <ActionButton

View File

@ -61,6 +61,8 @@ export type ChatProps = {
hideProcessDetail?: boolean hideProcessDetail?: boolean
hideLogModal?: boolean hideLogModal?: boolean
themeBuilder?: ThemeBuilder themeBuilder?: ThemeBuilder
showFeatureBar?: boolean
onFeatureBarClick?: (state: boolean) => void
} }
const Chat: FC<ChatProps> = ({ const Chat: FC<ChatProps> = ({
@ -89,6 +91,8 @@ const Chat: FC<ChatProps> = ({
hideProcessDetail, hideProcessDetail,
hideLogModal, hideLogModal,
themeBuilder, themeBuilder,
showFeatureBar,
onFeatureBarClick,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
@ -272,7 +276,10 @@ const Chat: FC<ChatProps> = ({
{ {
!noChatInput && ( !noChatInput && (
<ChatInputArea <ChatInputArea
visionConfig={config?.file_upload?.image} showFeatureBar={showFeatureBar}
featureBarDisabled={isResponding}
onFeatureBarClick={onFeatureBarClick}
visionConfig={config?.file_upload}
speechToTextConfig={config?.speech_to_text} speechToTextConfig={config?.speech_to_text}
onSend={onSend} onSend={onSend}
theme={themeBuilder?.theme} theme={themeBuilder?.theme}

View File

@ -8,10 +8,12 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types'
type Props = { type Props = {
disabled?: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const Citation = ({ const Citation = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -46,6 +48,7 @@ const Citation = ({
value={!!features.citation?.enabled} value={!!features.citation?.enabled}
description={t('appDebug.feature.citation.description')!} description={t('appDebug.feature.citation.description')!}
onChange={state => handleChange(FeatureEnum.citation, state)} onChange={state => handleChange(FeatureEnum.citation, state)}
disabled={disabled}
/> />
) )
} }

View File

@ -77,6 +77,7 @@ const ConversationOpener = ({
onChange={state => handleChange(FeatureEnum.opening, state)} onChange={state => handleChange(FeatureEnum.opening, state)}
onMouseEnter={() => setIsHovering(true)} onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)} onMouseLeave={() => setIsHovering(false)}
disabled={disabled}
> >
<> <>
{!opening?.enabled && ( {!opening?.enabled && (
@ -90,7 +91,7 @@ const ConversationOpener = ({
</div> </div>
)} )}
{isHovering && ( {isHovering && (
<Button className='w-full' onClick={handleOpenOpeningModal}> <Button className='w-full' onClick={handleOpenOpeningModal} disabled={disabled}>
<RiEditLine className='mr-1 w-4 h-4' /> <RiEditLine className='mr-1 w-4 h-4' />
{t('appDebug.openingStatement.writeOpener')} {t('appDebug.openingStatement.writeOpener')}
</Button> </Button>

View File

@ -0,0 +1,127 @@
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiApps2AddLine, RiArrowRightLine, RiSparklingFill } from '@remixicon/react'
import { Citations, ContentModeration, FolderUpload, LoveMessage, Microphone01, TextToAudio, VirtualAssistant } from '@/app/components/base/icons/src/vender/features'
import Button from '@/app/components/base/button'
import Tooltip from '@/app/components/base/tooltip'
import VoiceSettings from '@/app/components/base/features/new-feature-panel/text-to-speech/voice-settings'
import { useFeatures } from '@/app/components/base/features/hooks'
import cn from '@/utils/classnames'
type Props = {
disabled?: boolean
onFeatureBarClick?: (state: boolean) => void
}
const FeatureBar = ({
disabled,
onFeatureBarClick,
}: Props) => {
const { t } = useTranslation()
const features = useFeatures(s => s.features)
const [modalOpen, setModalOpen] = useState(false)
const noFeatureEnabled = useMemo(() => {
return !Object.values(features).some(f => f.enabled)
}, [features])
return (
<div className='-translate-y-2 m-1 mt-0 px-2.5 py-2 pt-4 bg-util-colors-indigo-indigo-50 rounded-b-[10px] border-l border-b border-r border-components-panel-border-subtle'>
{noFeatureEnabled && (
<div className='flex items-end gap-1 cursor-pointer' onClick={() => onFeatureBarClick?.(true)}>
<RiApps2AddLine className='w-3.5 h-3.5 text-text-accent' />
<div className='text-text-accent body-xs-medium'>{t('appDebug.feature.bar.empty')}</div>
<RiArrowRightLine className='w-3.5 h-3.5 text-text-accent' />
</div>
)}
{!noFeatureEnabled && (
<div className='flex items-center gap-2'>
<div className='shrink-0 flex items-center gap-0.5'>
{!!features.moreLikeThis?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.moreLikeThis.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-blue-light-blue-light-500'>
<RiSparklingFill className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.opening?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.conversationOpener.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-blue-light-blue-light-500'>
<LoveMessage className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.moderation?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.moderation.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-text-success'>
<ContentModeration className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.speech2text?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.speechToText.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-violet-violet-600'>
<Microphone01 className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.text2speech?.enabled && (
<VoiceSettings placementLeft={false} open={modalOpen && !disabled} onOpen={setModalOpen}>
<Tooltip
popupContent={t('appDebug.feature.textToSpeech.title')}
>
<div className={cn('shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-violet-violet-600', !disabled && 'cursor-pointer')}>
<TextToAudio className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
</VoiceSettings>
)}
{!!features.file?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.fileUpload.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-blue-blue-600'>
<FolderUpload className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.suggested?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-blue-light-blue-light-500'>
<VirtualAssistant className='w-3.5 h-3.5 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{!!features.citation?.enabled && (
<Tooltip
popupContent={t('appDebug.feature.citation.title')}
>
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-warning-warning-500'>
<Citations className='w-4 h-4 text-text-primary-on-surface' />
</div>
</Tooltip>
)}
{/* annotation reply ##TODO## */}
</div>
<div className='grow text-text-tertiary body-xs-regular'>{t('appDebug.feature.bar.enableText')}</div>
<Button className='shrink-0' variant='ghost-accent' size='small' onClick={() => onFeatureBarClick?.(true)}>
<div className='mx-1'>{t('appDebug.feature.bar.manage')}</div>
<RiArrowRightLine className='w-3.5 h-3.5 text-text-accent' />
</Button>
</div>
)}
</div>
)
}
export default FeatureBar

View File

@ -25,7 +25,7 @@ const FeatureCard = ({
value, value,
description, description,
children, children,
// disabled, disabled,
onChange, onChange,
onMouseEnter, onMouseEnter,
onMouseLeave, onMouseLeave,
@ -48,7 +48,7 @@ const FeatureCard = ({
</Tooltip> </Tooltip>
)} )}
</div> </div>
<Switch className='shrink-0' onChange={state => onChange?.(state)} defaultValue={value} /> <Switch disabled={disabled} className='shrink-0' onChange={state => onChange?.(state)} defaultValue={value} />
</div> </div>
{description && ( {description && (
<div className='min-h-8 text-text-tertiary system-xs-regular line-clamp-2'>{description}</div> <div className='min-h-8 text-text-tertiary system-xs-regular line-clamp-2'>{description}</div>

View File

@ -11,10 +11,12 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types'
type Props = { type Props = {
disabled: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const FileUpload = ({ const FileUpload = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -57,6 +59,7 @@ const FileUpload = ({
onChange={state => handleChange(FeatureEnum.file, state)} onChange={state => handleChange(FeatureEnum.file, state)}
onMouseEnter={() => setIsHovering(true)} onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)} onMouseLeave={() => setIsHovering(false)}
disabled={disabled}
> >
<> <>
{!file?.enabled && ( {!file?.enabled && (
@ -79,14 +82,14 @@ const FileUpload = ({
)} )}
{(isHovering || modalOpen) && ( {(isHovering || modalOpen) && (
<SettingModal <SettingModal
open={modalOpen} open={modalOpen && !disabled}
onOpen={(v) => { onOpen={(v) => {
setModalOpen(v) setModalOpen(v)
setIsHovering(v) setIsHovering(v)
}} }}
onChange={onChange} onChange={onChange}
> >
<Button className='w-full'> <Button className='w-full' disabled={disabled}>
<RiEqualizer2Line className='mr-1 w-4 h-4' /> <RiEqualizer2Line className='mr-1 w-4 h-4' />
{t('common.operation.settings')} {t('common.operation.settings')}
</Button> </Button>

View File

@ -8,10 +8,12 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types'
type Props = { type Props = {
disabled?: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const FollowUp = ({ const FollowUp = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -46,6 +48,7 @@ const FollowUp = ({
value={!!features.suggested?.enabled} value={!!features.suggested?.enabled}
description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')!} description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')!}
onChange={state => handleChange(FeatureEnum.suggested, state)} onChange={state => handleChange(FeatureEnum.suggested, state)}
disabled={disabled}
/> />
) )
} }

View File

@ -39,6 +39,7 @@ const NewFeaturePanel = ({
show, show,
showAnnotation = false, showAnnotation = false,
isChatMode, isChatMode,
disabled,
onChange, onChange,
onClose, onClose,
}: Props) => { }: Props) => {
@ -86,24 +87,24 @@ const NewFeaturePanel = ({
{/* list */} {/* list */}
<div className='grow overflow-y-auto px-4 pb-4'> <div className='grow overflow-y-auto px-4 pb-4'>
{!isChatMode && ( {!isChatMode && (
<MoreLikeThis onChange={onChange} /> <MoreLikeThis disabled={disabled} onChange={onChange} />
)} )}
{isChatMode && ( {isChatMode && (
<ConversationOpener onChange={onChange} /> <ConversationOpener disabled={disabled} onChange={onChange} />
)} )}
<Moderation onChange={onChange} /> <Moderation disabled={disabled} onChange={onChange} />
{isChatMode && speech2textDefaultModel && ( {isChatMode && speech2textDefaultModel && (
<SpeechToText onChange={onChange} /> <SpeechToText disabled={disabled} onChange={onChange} />
)} )}
{text2speechDefaultModel && ( {text2speechDefaultModel && (
<TextToSpeech onChange={onChange} /> <TextToSpeech disabled={disabled} onChange={onChange} />
)} )}
<FileUpload onChange={onChange} /> <FileUpload disabled={disabled} onChange={onChange} />
{isChatMode && ( {isChatMode && (
<FollowUp onChange={onChange} /> <FollowUp disabled={disabled} onChange={onChange} />
)} )}
{isChatMode && ( {isChatMode && (
<Citation onChange={onChange} /> <Citation disabled={disabled} onChange={onChange} />
)} )}
{/* annotation reply ##TODO## */} {/* annotation reply ##TODO## */}
{showAnnotation && ( {showAnnotation && (

View File

@ -139,6 +139,7 @@ const Moderation = ({
onChange={state => handleChange(FeatureEnum.moderation, state)} onChange={state => handleChange(FeatureEnum.moderation, state)}
onMouseEnter={() => setIsHovering(true)} onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)} onMouseLeave={() => setIsHovering(false)}
disabled={disabled}
> >
<> <>
{!moderation?.enabled && ( {!moderation?.enabled && (
@ -160,7 +161,7 @@ const Moderation = ({
</div> </div>
)} )}
{isHovering && ( {isHovering && (
<Button className='w-full' onClick={handleOpenModerationSettingModal}> <Button className='w-full' onClick={handleOpenModerationSettingModal} disabled={disabled}>
<RiEqualizer2Line className='mr-1 w-4 h-4' /> <RiEqualizer2Line className='mr-1 w-4 h-4' />
{t('common.operation.settings')} {t('common.operation.settings')}
</Button> </Button>

View File

@ -8,10 +8,12 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types'
type Props = { type Props = {
disabled?: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const MoreLikeThis = ({ const MoreLikeThis = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -47,6 +49,7 @@ const MoreLikeThis = ({
value={!!features.moreLikeThis?.enabled} value={!!features.moreLikeThis?.enabled}
description={t('appDebug.feature.moreLikeThis.description')!} description={t('appDebug.feature.moreLikeThis.description')!}
onChange={state => handleChange(FeatureEnum.moreLikeThis, state)} onChange={state => handleChange(FeatureEnum.moreLikeThis, state)}
disabled={disabled}
/> />
) )
} }

View File

@ -8,10 +8,12 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types'
type Props = { type Props = {
disabled: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const SpeechToText = ({ const SpeechToText = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -46,6 +48,7 @@ const SpeechToText = ({
value={!!features.speech2text?.enabled} value={!!features.speech2text?.enabled}
description={t('appDebug.feature.speechToText.description')!} description={t('appDebug.feature.speechToText.description')!}
onChange={state => handleChange(FeatureEnum.speech2text, state)} onChange={state => handleChange(FeatureEnum.speech2text, state)}
disabled={disabled}
/> />
) )
} }

View File

@ -13,10 +13,12 @@ import { languages } from '@/i18n/language'
import { TtsAutoPlay } from '@/types/app' import { TtsAutoPlay } from '@/types/app'
type Props = { type Props = {
disabled: boolean
onChange?: OnFeaturesChange onChange?: OnFeaturesChange
} }
const TextToSpeech = ({ const TextToSpeech = ({
disabled,
onChange, onChange,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -56,6 +58,7 @@ const TextToSpeech = ({
onChange={state => handleChange(FeatureEnum.text2speech, state)} onChange={state => handleChange(FeatureEnum.text2speech, state)}
onMouseEnter={() => setIsHovering(true)} onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)} onMouseLeave={() => setIsHovering(false)}
disabled={disabled}
> >
<> <>
{!features.text2speech?.enabled && ( {!features.text2speech?.enabled && (
@ -82,8 +85,8 @@ const TextToSpeech = ({
</div> </div>
)} )}
{(isHovering || modalOpen) && ( {(isHovering || modalOpen) && (
<VoiceSettings open={modalOpen} onOpen={setModalOpen} onChange={onChange}> <VoiceSettings open={modalOpen && !disabled} onOpen={setModalOpen} onChange={onChange}>
<Button className='w-full'> <Button className='w-full' disabled={disabled}>
<RiEqualizer2Line className='mr-1 w-4 h-4' /> <RiEqualizer2Line className='mr-1 w-4 h-4' />
{t('appDebug.voice.voiceSettings.title')} {t('appDebug.voice.voiceSettings.title')}
</Button> </Button>

View File

@ -65,7 +65,7 @@ const VoiceParamConfig = ({
return ( return (
<> <>
<div className='mb-4 flex items-center'> <div className='mb-4 flex items-center justify-between'>
<div className='text-text-primary system-xl-semibold'>{t('appDebug.voice.voiceSettings.title')}</div> <div className='text-text-primary system-xl-semibold'>{t('appDebug.voice.voiceSettings.title')}</div>
<div className='p-1 cursor-pointer' onClick={onClose}><RiCloseLine className='w-4 h-4 text-text-tertiary'/></div> <div className='p-1 cursor-pointer' onClick={onClose}><RiCloseLine className='w-4 h-4 text-text-tertiary'/></div>
</div> </div>

View File

@ -4,7 +4,6 @@ import {
useCallback, useCallback,
useMemo, useMemo,
} from 'react' } from 'react'
import { RiApps2AddLine } from '@remixicon/react'
import { useNodes } from 'reactflow' import { useNodes } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
@ -47,7 +46,7 @@ const Header: FC = () => {
const appSidebarExpand = useAppStore(s => s.appSidebarExpand) const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
const appID = appDetail?.id const appID = appDetail?.id
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly() const { nodesReadOnly } = useNodesReadOnly()
const publishedAt = useStore(s => s.publishedAt) const publishedAt = useStore(s => s.publishedAt)
const draftUpdatedAt = useStore(s => s.draftUpdatedAt) const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
const toolPublished = useStore(s => s.toolPublished) const toolPublished = useStore(s => s.toolPublished)
@ -86,17 +85,6 @@ const Header: FC = () => {
viewHistory, viewHistory,
} = useWorkflowMode() } = useWorkflowMode()
const handleShowFeatures = useCallback(() => {
const {
showFeaturesPanel,
isRestoring,
setShowFeaturesPanel,
} = workflowStore.getState()
if (getNodesReadOnly() && !isRestoring)
return
setShowFeaturesPanel(!showFeaturesPanel)
}, [workflowStore, getNodesReadOnly])
const handleCancelRestore = useCallback(() => { const handleCancelRestore = useCallback(() => {
handleLoadBackupDraft() handleLoadBackupDraft()
workflowStore.setState({ isRestoring: false }) workflowStore.setState({ isRestoring: false })
@ -173,10 +161,6 @@ const Header: FC = () => {
<EnvButton disabled={nodesReadOnly} /> <EnvButton disabled={nodesReadOnly} />
<div className='w-[1px] h-3.5 bg-gray-200'></div> <div className='w-[1px] h-3.5 bg-gray-200'></div>
<RunAndHistory /> <RunAndHistory />
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
{t('workflow.common.features')}
</Button>
<AppPublisher <AppPublisher
{...{ {...{
publishedAt, publishedAt,
@ -213,11 +197,6 @@ const Header: FC = () => {
{ {
restoring && ( restoring && (
<div className='flex items-center'> <div className='flex items-center'>
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
{t('workflow.common.features')}
</Button>
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
<Button <Button
className='mr-2' className='mr-2'
onClick={handleCancelRestore} onClick={handleCancelRestore}

View File

@ -19,7 +19,7 @@ import { useChat } from './hooks'
import type { ChatWrapperRefType } from './index' import type { ChatWrapperRefType } from './index'
import Chat from '@/app/components/base/chat/chat' import Chat from '@/app/components/base/chat/chat'
import type { OnSend } from '@/app/components/base/chat/types' import type { OnSend } from '@/app/components/base/chat/types'
import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useFeatures } from '@/app/components/base/features/hooks'
import { import {
fetchSuggestedQuestions, fetchSuggestedQuestions,
stopChatMessageResponding, stopChatMessageResponding,
@ -38,10 +38,8 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
const startVariables = startNode?.data.variables const startVariables = startNode?.data.variables
const appDetail = useAppStore(s => s.appDetail) const appDetail = useAppStore(s => s.appDetail)
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const featuresStore = useFeaturesStore()
const inputs = useStore(s => s.inputs) const inputs = useStore(s => s.inputs)
const features = featuresStore!.getState().features const features = useFeatures(s => s.features)
const config = useMemo(() => { const config = useMemo(() => {
return { return {
opening_statement: features.opening?.opening_statement || '', opening_statement: features.opening?.opening_statement || '',
@ -54,6 +52,7 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
file_upload: features.file, file_upload: features.file,
} }
}, [features]) }, [features])
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
const { const {
conversationId, conversationId,
@ -105,7 +104,9 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
chatContainerClassName='px-3' chatContainerClassName='px-3'
chatContainerInnerClassName='pt-6' chatContainerInnerClassName='pt-6'
chatFooterClassName='px-4 rounded-bl-2xl' chatFooterClassName='px-4 rounded-bl-2xl'
chatFooterInnerClassName='pb-4' chatFooterInnerClassName='pb-2'
showFeatureBar
onFeatureBarClick={setShowFeaturesPanel}
onSend={doSend} onSend={doSend}
onStopResponding={handleStop} onStopResponding={handleStop}
chatNode={( chatNode={(

View File

@ -206,6 +206,11 @@ const translation = {
numberLimit: 'Max uploads', numberLimit: 'Max uploads',
modalTitle: 'File Upload Setting', modalTitle: 'File Upload Setting',
}, },
bar: {
empty: 'Enable feature to enhance web app user experience',
enableText: 'Features Enabled',
manage: 'Manage',
},
}, },
generate: { generate: {
title: 'Prompt Generator', title: 'Prompt Generator',

View File

@ -206,6 +206,11 @@ const translation = {
numberLimit: '最大上传数', numberLimit: '最大上传数',
modalTitle: '文件上传设置', modalTitle: '文件上传设置',
}, },
bar: {
empty: '开启功能增强 webapp 用户体验',
enableText: '功能已开启',
manage: '管理',
},
}, },
generate: { generate: {
title: '提示词生成器', title: '提示词生成器',