diff --git a/web/app/components/base/features/new-feature-panel/citation.tsx b/web/app/components/base/features/new-feature-panel/citation.tsx new file mode 100644 index 0000000000..e0ab288e0c --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/citation.tsx @@ -0,0 +1,53 @@ +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { Citations } from '@/app/components/base/icons/src/vender/features' +import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { FeatureEnum } from '@/app/components/base/features/types' + +type Props = { + onChange?: OnFeaturesChange +} + +const Citation = ({ + onChange, +}: Props) => { + const { t } = useTranslation() + const features = useFeatures(s => s.features) + const featuresStore = useFeaturesStore() + + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type] = { + ...draft[type], + enabled, + } + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( + + + + } + title={t('appDebug.feature.citation.title')} + value={!!features.citation?.enabled} + description={t('appDebug.feature.citation.description')} + onChange={state => handleChange(FeatureEnum.citation, state)} + /> + ) +} + +export default Citation diff --git a/web/app/components/base/features/new-feature-panel/feature-card.tsx b/web/app/components/base/features/new-feature-panel/feature-card.tsx new file mode 100644 index 0000000000..bc53af75b9 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/feature-card.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { + RiQuestionLine, +} from '@remixicon/react' +import Switch from '@/app/components/base/switch' +import Tooltip from '@/app/components/base/tooltip' + +type Props = { + icon: any + title: any + tooltip?: any + value: any + description?: string + children?: React.ReactNode + disabled?: boolean + onChange?: (state: any) => void + onMouseEnter?: () => void + onMouseLeave?: () => void +} + +const FeatureCard = ({ + icon, + title, + tooltip, + value, + description, + children, + // disabled, + onChange, + onMouseEnter, + onMouseLeave, +}: Props) => { + return ( +
+
+ {icon} +
+ {title} + {tooltip && ( + +
+
+ )} +
+ onChange?.(state)} defaultValue={value} /> +
+ {description && ( +
{description}
+ )} + {children} +
+ ) +} + +export default FeatureCard diff --git a/web/app/components/base/features/new-feature-panel/follow-up.tsx b/web/app/components/base/features/new-feature-panel/follow-up.tsx new file mode 100644 index 0000000000..d658d4d1f4 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/follow-up.tsx @@ -0,0 +1,53 @@ +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { VirtualAssistant } from '@/app/components/base/icons/src/vender/features' +import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { FeatureEnum } from '@/app/components/base/features/types' + +type Props = { + onChange?: OnFeaturesChange +} + +const FollowUp = ({ + onChange, +}: Props) => { + const { t } = useTranslation() + const features = useFeatures(s => s.features) + const featuresStore = useFeaturesStore() + + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type] = { + ...draft[type], + enabled, + } + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( + + + + } + title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')} + value={!!features.suggested?.enabled} + description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')} + onChange={state => handleChange(FeatureEnum.suggested, state)} + /> + ) +} + +export default FollowUp diff --git a/web/app/components/base/features/new-feature-panel/index.tsx b/web/app/components/base/features/new-feature-panel/index.tsx index 7323cc25e2..199e1a12b4 100644 --- a/web/app/components/base/features/new-feature-panel/index.tsx +++ b/web/app/components/base/features/new-feature-panel/index.tsx @@ -1,17 +1,15 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import { usePathname, useRouter } from 'next/navigation' import produce from 'immer' import { RiCloseLine, RiEqualizer2Line, - RiQuestionLine, - RiSparklingFill, + RiExternalLinkLine, } from '@remixicon/react' import { - Citations, - Microphone01, - TextToAudio, - VirtualAssistant, + FolderUpload, + MessageFast, } from '@/app/components/base/icons/src/vender/features' import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' @@ -19,26 +17,38 @@ import { useDefaultModel } from '@/app/components/header/account-setting/model-p import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { OnFeaturesChange } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types' -import { TtsAutoPlay } from '@/types/app' -import { languages } from '@/i18n/language' import Switch from '@/app/components/base/switch' -import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import MoreLikeThis from '@/app/components/base/features/new-feature-panel/more-like-this' +import SpeechToText from '@/app/components/base/features/new-feature-panel/speech-to-text' +import TextToSpeech from '@/app/components/base/features/new-feature-panel/text-to-speech' +import FollowUp from '@/app/components/base/features/new-feature-panel/follow-up' +import Citation from '@/app/components/base/features/new-feature-panel/citation' + type Props = { show: boolean + showAnnotation?: boolean isChatMode: boolean disabled: boolean onChange?: OnFeaturesChange onClose: () => void } -const NewFeaturePanel = ({ show, isChatMode, onChange, onClose }: Props) => { +const NewFeaturePanel = ({ + show, + showAnnotation = false, + isChatMode, + onChange, + onClose, +}: Props) => { const { t } = useTranslation() + const router = useRouter() + const pathname = usePathname() + const matched = pathname.match(/\/app\/([^/]+)/) + const appId = (matched?.length && matched[1]) ? matched[1] : '' const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text) const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts) - const textToSpeech = useFeatures(s => s.features.text2speech) // .language .voice .autoPlay - const languageInfo = languages.find(i => i.value === textToSpeech?.language) const features = useFeatures(s => s.features) const featuresStore = useFeaturesStore() @@ -75,117 +85,69 @@ const NewFeaturePanel = ({ show, isChatMode, onChange, onClose }: Props) => { {/* list */}
- {/* more like this */} {!isChatMode && ( -
-
-
- -
-
- {t('appDebug.feature.moreLikeThis.title')} - - {t('appDebug.feature.moreLikeThis.tip')} -
- } - selector='feature-more-like-this' - > -
- -
- handleChange(FeatureEnum.moreLikeThis, value)} defaultValue={!!features.moreLikeThis?.enabled} /> -
-
{t('appDebug.feature.moreLikeThis.description')}
-
+ )} - {/* speech to text */} {isChatMode && speech2textDefaultModel && ( -
-
-
- -
-
- {t('appDebug.feature.speechToText.title')} -
- handleChange(FeatureEnum.speech2text, value)} defaultValue={!!features.speech2text?.enabled} /> -
-
{t('appDebug.feature.speechToText.description')}
-
+ )} - {/* text to speech */} {text2speechDefaultModel && ( + + )} + {/* file upload ##TODO## */} +
+
+
+ +
+
+ File upload +
+ handleChange(FeatureEnum.text2speech, value)} defaultValue={!!features.text2speech?.enabled} /> +
+
+ {isChatMode && ( + + )} + {isChatMode && ( + + )} + {/* annotation reply ##TODO## */} + {showAnnotation && (
-
- +
+
- {t('appDebug.feature.textToSpeech.title')} + {t('appDebug.feature.annotation.title')}
handleChange(FeatureEnum.text2speech, value)} defaultValue={!!features.text2speech?.enabled} />
- {!features.text2speech?.enabled && ( -
{t('appDebug.feature.textToSpeech.description')}
- )} - {!!features.text2speech?.enabled && ( - <> -
-
-
{t('appDebug.voice.voiceSettings.language')}
-
{languageInfo?.name || '-'}
-
-
-
-
{t('appDebug.voice.voiceSettings.voice')}
-
{features.text2speech?.voice || t('appDebug.voice.defaultDisplay')}
-
-
-
-
{t('appDebug.voice.voiceSettings.autoPlay')}
-
{features.text2speech?.autoPlay === TtsAutoPlay.enabled ? t('appDebug.voice.voiceSettings.autoPlayEnabled') : t('appDebug.voice.voiceSettings.autoPlayDisabled')}
-
-
-
- -
- - )} -
- )} - {/* follow up */} - {isChatMode && ( -
-
-
- +
{t('appDebug.feature.annotation.description')}
+
+
+
{t('appDebug.feature.annotation.scoreThreshold.title')}
+ {/*
{languageInfo?.name || '-'}
*/}
-
- {t('appDebug.feature.suggestedQuestionsAfterAnswer.title')} +
+
+
{t('common.modelProvider.embeddingModel.key')}
+ {/*
{features.text2speech?.voice || '-'}
*/}
- handleChange(FeatureEnum.suggested, value)} defaultValue={!!features.suggested?.enabled} />
-
{t('appDebug.feature.suggestedQuestionsAfterAnswer.description')}
-
- )} - {/* citations & attributions */} - {isChatMode && ( -
-
-
- -
-
- {t('appDebug.feature.citation.title')} -
- handleChange(FeatureEnum.citation, value)} defaultValue={!!features.citation?.enabled} /> +
+ +
-
{t('appDebug.feature.citation.description')}
)}
diff --git a/web/app/components/base/features/new-feature-panel/more-like-this.tsx b/web/app/components/base/features/new-feature-panel/more-like-this.tsx new file mode 100644 index 0000000000..9694ca989b --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/more-like-this.tsx @@ -0,0 +1,54 @@ +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { RiSparklingFill } from '@remixicon/react' +import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { FeatureEnum } from '@/app/components/base/features/types' + +type Props = { + onChange?: OnFeaturesChange +} + +const MoreLikeThis = ({ + onChange, +}: Props) => { + const { t } = useTranslation() + const features = useFeatures(s => s.features) + const featuresStore = useFeaturesStore() + + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type] = { + ...draft[type], + enabled, + } + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( + + +
+ } + title={t('appDebug.feature.moreLikeThis.title')} + tooltip={t('appDebug.feature.moreLikeThis.tip')} + value={!!features.moreLikeThis?.enabled} + description={t('appDebug.feature.moreLikeThis.description')} + onChange={state => handleChange(FeatureEnum.moreLikeThis, state)} + /> + ) +} + +export default MoreLikeThis diff --git a/web/app/components/base/features/new-feature-panel/speech-to-text.tsx b/web/app/components/base/features/new-feature-panel/speech-to-text.tsx new file mode 100644 index 0000000000..ee2a065243 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/speech-to-text.tsx @@ -0,0 +1,53 @@ +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { Microphone01 } from '@/app/components/base/icons/src/vender/features' +import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { FeatureEnum } from '@/app/components/base/features/types' + +type Props = { + onChange?: OnFeaturesChange +} + +const SpeechToText = ({ + onChange, +}: Props) => { + const { t } = useTranslation() + const features = useFeatures(s => s.features) + const featuresStore = useFeaturesStore() + + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type] = { + ...draft[type], + enabled, + } + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( + + +
+ } + title={t('appDebug.feature.speechToText.title')} + value={!!features.speech2text?.enabled} + description={t('appDebug.feature.speechToText.description')!} + onChange={state => handleChange(FeatureEnum.speech2text, state)} + /> + ) +} + +export default SpeechToText diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech.tsx new file mode 100644 index 0000000000..5924bb769e --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/text-to-speech.tsx @@ -0,0 +1,95 @@ +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { RiEqualizer2Line } from '@remixicon/react' +import { TextToAudio } from '@/app/components/base/icons/src/vender/features' +import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' +import Button from '@/app/components/base/button' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { FeatureEnum } from '@/app/components/base/features/types' +import { languages } from '@/i18n/language' +import { TtsAutoPlay } from '@/types/app' + +type Props = { + onChange?: OnFeaturesChange +} + +const TextToSpeech = ({ + onChange, +}: Props) => { + const { t } = useTranslation() + const textToSpeech = useFeatures(s => s.features.text2speech) // .language .voice .autoPlay + const languageInfo = languages.find(i => i.value === textToSpeech?.language) + const [isHovering, setIsHovering] = useState(false) + const features = useFeatures(s => s.features) + const featuresStore = useFeaturesStore() + + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type] = { + ...draft[type], + enabled, + } + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + + return ( + + +
+ } + title={t('appDebug.feature.textToSpeech.title')} + value={!!features.text2speech?.enabled} + onChange={state => handleChange(FeatureEnum.text2speech, state)} + onMouseEnter={() => setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > + <> + {!features.text2speech?.enabled && ( +
{t('appDebug.feature.textToSpeech.description')}
+ )} + {!!features.text2speech?.enabled && ( + <> + {!isHovering && ( +
+
+
{t('appDebug.voice.voiceSettings.language')}
+
{languageInfo?.name || '-'}
+
+
+
+
{t('appDebug.voice.voiceSettings.voice')}
+
{features.text2speech?.voice || t('appDebug.voice.defaultDisplay')}
+
+
+
+
{t('appDebug.voice.voiceSettings.autoPlay')}
+
{features.text2speech?.autoPlay === TtsAutoPlay.enabled ? t('appDebug.voice.voiceSettings.autoPlayEnabled') : t('appDebug.voice.voiceSettings.autoPlayDisabled')}
+
+
+ )} + {isHovering && ( + + )} + + )} + + + ) +} + +export default TextToSpeech