From 565a83594748b8684d2cd6d6f5e79a0e4600baec Mon Sep 17 00:00:00 2001 From: JzoNg Date: Sun, 1 Sep 2024 15:00:23 +0800 Subject: [PATCH] conversation opener --- .../config/automatic/get-automatic-res.tsx | 20 +- .../config/feature/use-feature.tsx | 96 ------ .../chat-group/opening-statement/index.tsx | 300 ---------------- .../experience-enchance-group/index.tsx | 43 --- .../more-like-this/index.tsx | 51 --- .../components/app/configuration/index.tsx | 8 + .../app/configuration/toolbox/index.tsx | 45 --- .../toolbox/moderation/index.tsx | 80 ----- .../features/feature-panel/citation/index.tsx | 25 -- .../feature-panel/file-upload/index.tsx | 63 ---- .../file-upload/param-config-content.tsx | 119 ------- .../file-upload/param-config.tsx | 49 --- .../file-upload/radio-group/index.tsx | 40 --- .../file-upload/radio-group/style.module.css | 24 -- .../base/features/feature-panel/index.tsx | 115 ------- .../feature-panel/moderation/index.tsx | 108 ------ .../feature-panel/opening-statement/index.tsx | 321 ------------------ .../score-slider/base-slider/index.tsx | 38 --- .../score-slider/base-slider/style.module.css | 20 -- .../feature-panel/score-slider/index.tsx | 46 --- .../feature-panel/speech-to-text/index.tsx | 22 -- .../index.tsx | 25 -- .../feature-panel/text-to-speech/index.tsx | 62 ---- .../text-to-speech/param-config-content.tsx | 241 ------------- .../text-to-speech/params-config.tsx | 48 --- .../conversation-opener/index.tsx | 15 +- .../conversation-opener/modal.tsx | 60 +++- .../new-feature-panel/feature-bar.tsx | 2 +- .../base/features/new-feature-panel/index.tsx | 14 +- web/context/modal-context.tsx | 17 +- 30 files changed, 116 insertions(+), 2001 deletions(-) delete mode 100644 web/app/components/app/configuration/config/feature/use-feature.tsx delete mode 100644 web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx delete mode 100644 web/app/components/app/configuration/features/experience-enchance-group/index.tsx delete mode 100644 web/app/components/app/configuration/features/experience-enchance-group/more-like-this/index.tsx delete mode 100644 web/app/components/app/configuration/toolbox/index.tsx delete mode 100644 web/app/components/app/configuration/toolbox/moderation/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/citation/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/file-upload/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx delete mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config.tsx delete mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css delete mode 100644 web/app/components/base/features/feature-panel/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/moderation/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/opening-statement/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css delete mode 100644 web/app/components/base/features/feature-panel/score-slider/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/speech-to-text/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/text-to-speech/index.tsx delete mode 100644 web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx delete mode 100644 web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index df1b61432a..05339c7216 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -25,10 +25,10 @@ import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import type { Model } from '@/types/app' import { AppType } from '@/types/app' import ConfigVar from '@/app/components/app/configuration/config-var' -import OpeningStatement from '@/app/components/app/configuration/features/chat-group/opening-statement' import GroupName from '@/app/components/app/configuration/base/group-name' import Loading from '@/app/components/base/loading' import Confirm from '@/app/components/base/confirm' +import { LoveMessage } from '@/app/components/base/icons/src/vender/features' // type import type { AutomaticRes } from '@/service/debug' @@ -262,11 +262,19 @@ const GetAutomaticRes: FC = ({ {(mode !== AppType.completion && res?.opening_statement) && (
- {/* ##TODO## use new style of opening */} - +
+
+
+ +
+
+ {t('appDebug.feature.conversationOpener.title')} +
+
+
{res.opening_statement}
+
)} diff --git a/web/app/components/app/configuration/config/feature/use-feature.tsx b/web/app/components/app/configuration/config/feature/use-feature.tsx deleted file mode 100644 index 190c50eab5..0000000000 --- a/web/app/components/app/configuration/config/feature/use-feature.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useEffect } from 'react' - -function useFeature({ - introduction, - setIntroduction, - moreLikeThis, - setMoreLikeThis, - suggestedQuestionsAfterAnswer, - setSuggestedQuestionsAfterAnswer, - speechToText, - setSpeechToText, - textToSpeech, - setTextToSpeech, - citation, - setCitation, - annotation, - setAnnotation, - moderation, - setModeration, -}: { - introduction: string - setIntroduction: (introduction: string) => void - moreLikeThis: boolean - setMoreLikeThis: (moreLikeThis: boolean) => void - suggestedQuestionsAfterAnswer: boolean - setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: boolean) => void - speechToText: boolean - setSpeechToText: (speechToText: boolean) => void - textToSpeech: boolean - setTextToSpeech: (textToSpeech: boolean) => void - citation: boolean - setCitation: (citation: boolean) => void - annotation: boolean - setAnnotation: (annotation: boolean) => void - moderation: boolean - setModeration: (moderation: boolean) => void -}) { - const [tempshowOpeningStatement, setTempShowOpeningStatement] = React.useState(!!introduction) - useEffect(() => { - // wait to api data back - if (introduction) - setTempShowOpeningStatement(true) - }, [introduction]) - - // const [tempMoreLikeThis, setTempMoreLikeThis] = React.useState(moreLikeThis) - // useEffect(() => { - // setTempMoreLikeThis(moreLikeThis) - // }, [moreLikeThis]) - - const featureConfig = { - openingStatement: tempshowOpeningStatement, - moreLikeThis, - suggestedQuestionsAfterAnswer, - speechToText, - textToSpeech, - citation, - annotation, - moderation, - } - const handleFeatureChange = (key: string, value: boolean) => { - switch (key) { - case 'openingStatement': - if (!value) - setIntroduction('') - - setTempShowOpeningStatement(value) - break - case 'moreLikeThis': - setMoreLikeThis(value) - break - case 'suggestedQuestionsAfterAnswer': - setSuggestedQuestionsAfterAnswer(value) - break - case 'speechToText': - setSpeechToText(value) - break - case 'textToSpeech': - setTextToSpeech(value) - break - case 'citation': - setCitation(value) - break - case 'annotation': - setAnnotation(value) - break - case 'moderation': - setModeration(value) - } - } - return { - featureConfig, - handleFeatureChange, - } -} - -export default useFeature diff --git a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx b/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx deleted file mode 100644 index d007225bda..0000000000 --- a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx +++ /dev/null @@ -1,300 +0,0 @@ -/* eslint-disable multiline-ternary */ -'use client' -import type { FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' -import { - RiAddLine, - RiDeleteBinLine, -} from '@remixicon/react' -import { useContext } from 'use-context-selector' -import produce from 'immer' -import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import { ReactSortable } from 'react-sortablejs' -import cn from '@/utils/classnames' -import ConfigContext from '@/context/debug-configuration' -import Panel from '@/app/components/app/configuration/base/feature-panel' -import Button from '@/app/components/base/button' -import OperationBtn from '@/app/components/app/configuration/base/operation-btn' -import { getInputKeys } from '@/app/components/base/block-input' -import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' -import { getNewVar } from '@/utils/var' -import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' -import Toast from '@/app/components/base/toast' - -const MAX_QUESTION_NUM = 5 - -export type IOpeningStatementProps = { - value: string - readonly?: boolean - onChange?: (value: string) => void - suggestedQuestions?: string[] - onSuggestedQuestionsChange?: (value: string[]) => void -} - -// regex to match the {{}} and replace it with a span -const regex = /\{\{([^}]+)\}\}/g - -const OpeningStatement: FC = ({ - value = '', - readonly, - onChange, - suggestedQuestions = [], - onSuggestedQuestionsChange = () => { }, -}) => { - const { t } = useTranslation() - const { - modelConfig, - setModelConfig, - } = useContext(ConfigContext) - const promptVariables = modelConfig.configs.prompt_variables - const [notIncludeKeys, setNotIncludeKeys] = useState([]) - - const hasValue = !!(value || '').trim() - const inputRef = useRef(null) - - const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false) - - const setFocus = () => { - didSetFocus() - setTimeout(() => { - const input = inputRef.current - if (input) { - input.focus() - input.setSelectionRange(input.value.length, input.value.length) - } - }, 0) - } - - const [tempValue, setTempValue] = useState(value) - useEffect(() => { - setTempValue(value || '') - }, [value]) - - const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(suggestedQuestions || []) - const notEmptyQuestions = tempSuggestedQuestions.filter(question => !!question && question.trim()) - const coloredContent = (tempValue || '') - .replace(//g, '>') - .replace(regex, varHighlightHTML({ name: '$1' })) // `{{$1}}` - .replace(/\n/g, '
') - - const handleEdit = () => { - if (readonly) - return - setFocus() - } - - const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) - - const handleCancel = () => { - setBlur() - setTempValue(value) - setTempSuggestedQuestions(suggestedQuestions) - } - - const handleConfirm = () => { - if (!(tempValue || '').trim()) { - Toast.notify({ - type: 'error', - message: t('common.errorMsg.fieldRequired', { - field: t('appDebug.openingStatement.title'), - }), - }) - return - } - const keys = getInputKeys(tempValue) - const promptKeys = promptVariables.map(item => item.key) - let notIncludeKeys: string[] = [] - - if (promptKeys.length === 0) { - if (keys.length > 0) - notIncludeKeys = keys - } - else { - notIncludeKeys = keys.filter(key => !promptKeys.includes(key)) - } - - if (notIncludeKeys.length > 0) { - setNotIncludeKeys(notIncludeKeys) - showConfirmAddVar() - return - } - setBlur() - onChange?.(tempValue) - onSuggestedQuestionsChange(tempSuggestedQuestions) - } - - const cancelAutoAddVar = () => { - onChange?.(tempValue) - hideConfirmAddVar() - setBlur() - } - - const autoAddVar = () => { - const newModelConfig = produce(modelConfig, (draft) => { - draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key, 'string'))] - }) - onChange?.(tempValue) - setModelConfig(newModelConfig) - hideConfirmAddVar() - setBlur() - } - - const headerRight = !readonly ? ( - isFocus ? ( -
- - -
- ) : ( - - ) - ) : null - - const renderQuestions = () => { - return isFocus ? ( -
-
-
-
{t('appDebug.openingStatement.openingQuestion')}
-
·
-
{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}
-
-
-
- { - return { - id: index, - name, - } - })} - setList={list => setTempSuggestedQuestions(list.map(item => item.name))} - handle='.handle' - ghostClass="opacity-50" - animation={150} - > - {tempSuggestedQuestions.map((question, index) => { - return ( -
-
- - - -
- { - const value = e.target.value - setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => { - if (index === i) - return value - - return item - })) - }} - className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'} - /> - -
{ - setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i)) - }} - > - -
-
- ) - })}
- {tempSuggestedQuestions.length < MAX_QUESTION_NUM && ( -
{ setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }} - className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'> - -
{t('appDebug.variableConig.addOption')}
-
- )} -
- ) : ( -
- {notEmptyQuestions.map((question, index) => { - return ( -
- {question} -
- ) - })} -
- ) - } - - return ( - - - - } - headerRight={headerRight} - hasHeaderBottomBorder={!hasValue} - isFocus={isFocus} - > -
- {(hasValue || (!hasValue && isFocus)) ? ( - <> - {isFocus - ? ( -
- -
- ) - : ( -
- )} - {renderQuestions()} - ) : ( -
{t('appDebug.openingStatement.noDataPlaceHolder')}
- )} - - {isShowConfirmAddVar && ( - - )} - -
-
- ) -} -export default React.memo(OpeningStatement) diff --git a/web/app/components/app/configuration/features/experience-enchance-group/index.tsx b/web/app/components/app/configuration/features/experience-enchance-group/index.tsx deleted file mode 100644 index 6902a17468..0000000000 --- a/web/app/components/app/configuration/features/experience-enchance-group/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import GroupName from '../../base/group-name' -import TextToSpeech from '../chat-group/text-to-speech' -import MoreLikeThis from './more-like-this' - -/* -* Include -* 1. More like this -*/ - -type ExperienceGroupProps = { - isShowTextToSpeech: boolean - isShowMoreLike: boolean -} - -const ExperienceEnchanceGroup: FC = ({ - isShowTextToSpeech, - isShowMoreLike, -}) => { - const { t } = useTranslation() - - return ( -
- -
- { - isShowMoreLike && ( - - ) - } - { - isShowTextToSpeech && ( - - ) - } -
-
- ) -} -export default React.memo(ExperienceEnchanceGroup) diff --git a/web/app/components/app/configuration/features/experience-enchance-group/more-like-this/index.tsx b/web/app/components/app/configuration/features/experience-enchance-group/more-like-this/index.tsx deleted file mode 100644 index f63ed1c25a..0000000000 --- a/web/app/components/app/configuration/features/experience-enchance-group/more-like-this/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { XMarkIcon } from '@heroicons/react/24/outline' -import { useLocalStorageState } from 'ahooks' -import MoreLikeThisIcon from '../../../base/icons/more-like-this-icon' -import Panel from '@/app/components/app/configuration/base/feature-panel' - -const GENERATE_NUM = 1 - -const warningIcon = ( - - - - -) -const MoreLikeThis: FC = () => { - const { t } = useTranslation() - - const [isHideTip, setIsHideTip] = useLocalStorageState('isHideMoreLikeThisTip', { - defaultValue: false, - }) - - const headerRight = ( -
{t('appDebug.feature.moreLikeThis.generateNumTip')} {GENERATE_NUM}
- ) - return ( - } - headerRight={headerRight} - noBodySpacing - > - {!isHideTip && ( -
-
-
{warningIcon}
-
{t('appDebug.feature.moreLikeThis.tip')}
-
-
setIsHideTip(true)}> - -
-
- )} - -
- ) -} -export default React.memo(MoreLikeThis) diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 2ec0c0b58d..7a5aa26a56 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -466,6 +466,12 @@ const Configuration: FC = () => { if (flag) formattingChangedDispatcher() }, [formattingChangedDispatcher, setShowAppConfigureFeaturesModal]) + const handleAddPromptVariable = useCallback((variable: PromptVariable[]) => { + const newModelConfig = produce(modelConfig, (draft: ModelConfig) => { + draft.configs.prompt_variables = variable + }) + setModelConfig(newModelConfig) + }, [modelConfig]) useEffect(() => { (async () => { @@ -994,6 +1000,8 @@ const Configuration: FC = () => { disabled={false} onChange={handleFeaturesChange} onClose={() => setShowAppConfigureFeaturesModal(false)} + promptVariables={modelConfig.configs.prompt_variables} + onAutoAddPromptVariable={handleAddPromptVariable} /> )} diff --git a/web/app/components/app/configuration/toolbox/index.tsx b/web/app/components/app/configuration/toolbox/index.tsx deleted file mode 100644 index 488ca86b90..0000000000 --- a/web/app/components/app/configuration/toolbox/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -'use client' - -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import GroupName from '../base/group-name' -import Moderation from './moderation' -import Annotation from './annotation/config-param' -import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type' - -export type ToolboxProps = { - showModerationSettings: boolean - showAnnotation: boolean - onEmbeddingChange: (embeddingModel: EmbeddingModelConfig) => void - onScoreChange: (score: number, embeddingModel?: EmbeddingModelConfig) => void -} - -const Toolbox: FC = ({ - showModerationSettings, - showAnnotation, - onEmbeddingChange, - onScoreChange, -}) => { - const { t } = useTranslation() - - return ( -
- - { - showModerationSettings && ( - - ) - } - { - (showAnnotation || true) && ( - - ) - } -
- ) -} -export default React.memo(Toolbox) diff --git a/web/app/components/app/configuration/toolbox/moderation/index.tsx b/web/app/components/app/configuration/toolbox/moderation/index.tsx deleted file mode 100644 index 9eb14e98d2..0000000000 --- a/web/app/components/app/configuration/toolbox/moderation/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useTranslation } from 'react-i18next' -import useSWR from 'swr' -import { useContext } from 'use-context-selector' -import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import { useModalContext } from '@/context/modal-context' -import ConfigContext from '@/context/debug-configuration' -import { fetchCodeBasedExtensionList } from '@/service/common' -import I18n from '@/context/i18n' -const Moderation = () => { - const { t } = useTranslation() - const { setShowModerationSettingModal } = useModalContext() - const { locale } = useContext(I18n) - const { - moderationConfig, - setModerationConfig, - } = useContext(ConfigContext) - const { data: codeBasedExtensionList } = useSWR( - '/code-based-extension?module=moderation', - fetchCodeBasedExtensionList, - ) - - const handleOpenModerationSettingModal = () => { - setShowModerationSettingModal({ - payload: moderationConfig, - onSaveCallback: setModerationConfig, - }) - } - - const renderInfo = () => { - let prefix = '' - let suffix = '' - if (moderationConfig.type === 'openai_moderation') - prefix = t('appDebug.feature.moderation.modal.provider.openai') - else if (moderationConfig.type === 'keywords') - prefix = t('appDebug.feature.moderation.modal.provider.keywords') - else if (moderationConfig.type === 'api') - prefix = t('common.apiBasedExtension.selector.title') - else - prefix = codeBasedExtensionList?.data.find(item => item.name === moderationConfig.type)?.label[locale] || '' - - if (moderationConfig.config?.inputs_config?.enabled && moderationConfig.config?.outputs_config?.enabled) - suffix = t('appDebug.feature.moderation.allEnabled') - else if (moderationConfig.config?.inputs_config?.enabled) - suffix = t('appDebug.feature.moderation.inputEnabled') - else if (moderationConfig.config?.outputs_config?.enabled) - suffix = t('appDebug.feature.moderation.outputEnabled') - - return `${prefix} · ${suffix}` - } - - return ( -
-
- -
-
- {t('appDebug.feature.moderation.title')} -
-
- {renderInfo()} -
-
-
- - {t('common.operation.settings')} -
-
- ) -} - -export default Moderation diff --git a/web/app/components/base/features/feature-panel/citation/index.tsx b/web/app/components/base/features/feature-panel/citation/index.tsx deleted file mode 100644 index 4003b68cd3..0000000000 --- a/web/app/components/base/features/feature-panel/citation/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client' -import React, { type FC } from 'react' -import { useTranslation } from 'react-i18next' -import Panel from '@/app/components/app/configuration/base/feature-panel' -import { Citations } from '@/app/components/base/icons/src/vender/solid/editor' - -const Citation: FC = () => { - const { t } = useTranslation() - - return ( - -
{t('appDebug.feature.citation.title')}
- - } - headerIcon={} - headerRight={ -
{t('appDebug.feature.citation.resDes')}
- } - noBodySpacing - /> - ) -} -export default React.memo(Citation) diff --git a/web/app/components/base/features/feature-panel/file-upload/index.tsx b/web/app/components/base/features/feature-panel/file-upload/index.tsx deleted file mode 100644 index 3ea940ca16..0000000000 --- a/web/app/components/base/features/feature-panel/file-upload/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -'use client' -import produce from 'immer' -import React, { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import type { OnFeaturesChange } from '../../types' -import { - useFeatures, - useFeaturesStore, -} from '../../hooks' -import ParamConfig from './param-config' -import Switch from '@/app/components/base/switch' -import { File05 } from '@/app/components/base/icons/src/vender/solid/files' - -type FileUploadProps = { - onChange?: OnFeaturesChange - disabled?: boolean -} -const FileUpload = ({ - onChange, - disabled, -}: FileUploadProps) => { - const { t } = useTranslation() - const featuresStore = useFeaturesStore() - const file = useFeatures(s => s.features.file) - - const handleSwitch = useCallback((value: boolean) => { - const { - features, - setFeatures, - } = featuresStore!.getState() - const newFeatures = produce(features, (draft) => { - if (draft.file?.image) - draft.file.image.enabled = value - }) - setFeatures(newFeatures) - - if (onChange) - onChange(newFeatures) - }, [featuresStore, onChange]) - - return ( -
-
- -
-
- {t('common.imageUploader.imageUpload')} -
-
-
- -
- -
-
- ) -} -export default React.memo(FileUpload) diff --git a/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx b/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx deleted file mode 100644 index b481420aaa..0000000000 --- a/web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx +++ /dev/null @@ -1,119 +0,0 @@ -'use client' - -import produce from 'immer' -import React, { useCallback, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import type { OnFeaturesChange } from '../../types' -import { - useFeatures, - useFeaturesStore, -} from '../../hooks' -import RadioGroup from './radio-group' -import { TransferMethod } from '@/types/app' -import ParamItem from '@/app/components/base/param-item' - -const MIN = 1 -const MAX = 6 -type ParamConfigContentProps = { - onChange?: OnFeaturesChange -} -const ParamConfigContent = ({ - onChange, -}: ParamConfigContentProps) => { - const { t } = useTranslation() - const featuresStore = useFeaturesStore() - const file = useFeatures(s => s.features.file) - - const transferMethod = useMemo(() => { - if (!file?.image?.transfer_methods || file?.image.transfer_methods.length === 2) - return TransferMethod.all - - return file.image.transfer_methods[0] - }, [file?.image?.transfer_methods]) - - const handleTransferMethodsChange = useCallback((value: TransferMethod) => { - const { - features, - setFeatures, - } = featuresStore!.getState() - const newFeatures = produce(features, (draft) => { - if (draft.file?.image) { - if (value === TransferMethod.all) - draft.file.image.transfer_methods = [TransferMethod.remote_url, TransferMethod.local_file] - else - draft.file.image.transfer_methods = [value] - } - }) - setFeatures(newFeatures) - if (onChange) - onChange(newFeatures) - }, [featuresStore, onChange]) - - const handleLimitsChange = useCallback((_key: string, value: number) => { - if (!value) - return - - const { - features, - setFeatures, - } = featuresStore!.getState() - const newFeatures = produce(features, (draft) => { - if (draft.file?.image) - draft.file.image.number_limits = value - }) - setFeatures(newFeatures) - if (onChange) - onChange(newFeatures) - }, [featuresStore, onChange]) - - return ( -
-
-
{t('common.operation.settings')}
-
-
-
{t('appDebug.vision.visionSettings.uploadMethod')}
- -
-
- -
-
-
-
- ) -} - -export default React.memo(ParamConfigContent) diff --git a/web/app/components/base/features/feature-panel/file-upload/param-config.tsx b/web/app/components/base/features/feature-panel/file-upload/param-config.tsx deleted file mode 100644 index 805fe8fb3e..0000000000 --- a/web/app/components/base/features/feature-panel/file-upload/param-config.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import { memo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import type { OnFeaturesChange } from '../../types' -import ParamConfigContent from './param-config-content' -import cn from '@/utils/classnames' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' - -type ParamsConfigProps = { - onChange?: OnFeaturesChange - disabled?: boolean -} -const ParamsConfig = ({ - onChange, - disabled, -}: ParamsConfigProps) => { - const { t } = useTranslation() - const [open, setOpen] = useState(false) - - return ( - - !disabled && setOpen(v => !v)}> -
- -
{t('appDebug.voice.settings')}
-
-
- -
- -
-
-
- ) -} -export default memo(ParamsConfig) diff --git a/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx b/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx deleted file mode 100644 index a1cfb06e6a..0000000000 --- a/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import s from './style.module.css' -import cn from '@/utils/classnames' - -type OPTION = { - label: string - value: any -} - -type Props = { - className?: string - options: OPTION[] - value: any - onChange: (value: any) => void -} - -const RadioGroup: FC = ({ - className = '', - options, - value, - onChange, -}) => { - return ( -
- {options.map(item => ( -
onChange(item.value)} - > -
-
{item.label}
-
- ))} -
- ) -} -export default React.memo(RadioGroup) diff --git a/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css b/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css deleted file mode 100644 index 22c29c6a42..0000000000 --- a/web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.item { - @apply grow flex items-center h-8 px-2.5 rounded-lg bg-gray-25 border border-gray-100 cursor-pointer space-x-2; -} - -.item:hover { - background-color: #ffffff; - border-color: #B2CCFF; - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} - -.item.checked { - background-color: #ffffff; - border-color: #528BFF; - box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.10); -} - -.radio { - @apply w-4 h-4 border-[2px] border-gray-200 rounded-full; -} - -.item.checked .radio { - border-width: 5px; - border-color: #155eef; -} \ No newline at end of file diff --git a/web/app/components/base/features/feature-panel/index.tsx b/web/app/components/base/features/feature-panel/index.tsx deleted file mode 100644 index e979391c92..0000000000 --- a/web/app/components/base/features/feature-panel/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { - memo, - useMemo, -} from 'react' -import { useTranslation } from 'react-i18next' -import type { OnFeaturesChange } from '../types' -import { useFeatures } from '../hooks' -import FileUpload from './file-upload' -import OpeningStatement from './opening-statement' -import type { OpeningStatementProps } from './opening-statement' -import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer' -import TextToSpeech from './text-to-speech' -import SpeechToText from './speech-to-text' -import Citation from './citation' -import Moderation from './moderation' - -export type FeaturePanelProps = { - onChange?: OnFeaturesChange - openingStatementProps: OpeningStatementProps - disabled?: boolean -} -const FeaturePanel = ({ - onChange, - openingStatementProps, - disabled, -}: FeaturePanelProps) => { - const { t } = useTranslation() - const features = useFeatures(s => s.features) - - const showAdvanceFeature = useMemo(() => { - return features.opening?.enabled || features.suggested?.enabled || features.speech2text?.enabled || features.text2speech?.enabled || features.citation?.enabled - }, [features]) - - const showToolFeature = useMemo(() => { - return features.moderation?.enabled - }, [features]) - - return ( -
- - { - showAdvanceFeature && ( -
-
-
- {t('appDebug.feature.groupChat.title')} -
-
-
-
- { - features.opening?.enabled && ( - - ) - } - { - features.suggested?.enabled && ( - - ) - } - { - features.text2speech?.enabled && ( - - ) - } - { - features.speech2text?.enabled && ( - - ) - } - { - features.citation?.enabled && ( - - ) - } -
-
- ) - } - { - showToolFeature && ( -
-
-
- {t('appDebug.feature.groupChat.title')} -
-
-
-
- { - features.moderation?.enabled && ( - - ) - } -
-
- ) - } -
- ) -} -export default memo(FeaturePanel) diff --git a/web/app/components/base/features/feature-panel/moderation/index.tsx b/web/app/components/base/features/feature-panel/moderation/index.tsx deleted file mode 100644 index 0a473ccd06..0000000000 --- a/web/app/components/base/features/feature-panel/moderation/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' -import useSWR from 'swr' -import produce from 'immer' -import { useContext } from 'use-context-selector' -import { - useFeatures, - useFeaturesStore, -} from '../../hooks' -import type { OnFeaturesChange } from '../../types' -import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import { useModalContext } from '@/context/modal-context' -import { fetchCodeBasedExtensionList } from '@/service/common' -import I18n from '@/context/i18n' - -type ModerationProps = { - onChange?: OnFeaturesChange - disabled?: boolean -} -const Moderation = ({ - onChange, - disabled, -}: ModerationProps) => { - const { t } = useTranslation() - const { setShowModerationSettingModal } = useModalContext() - const { locale } = useContext(I18n) - const featuresStore = useFeaturesStore() - const moderation = useFeatures(s => s.features.moderation) - - const { data: codeBasedExtensionList } = useSWR( - '/code-based-extension?module=moderation', - fetchCodeBasedExtensionList, - ) - - const handleOpenModerationSettingModal = () => { - if (disabled) - return - - const { - features, - setFeatures, - } = featuresStore!.getState() - setShowModerationSettingModal({ - payload: moderation as any, - onSaveCallback: (newModeration) => { - const newFeatures = produce(features, (draft) => { - draft.moderation = newModeration - }) - setFeatures(newFeatures) - if (onChange) - onChange(newFeatures) - }, - }) - } - - const renderInfo = () => { - let prefix = '' - let suffix = '' - if (moderation?.type === 'openai_moderation') - prefix = t('appDebug.feature.moderation.modal.provider.openai') - else if (moderation?.type === 'keywords') - prefix = t('appDebug.feature.moderation.modal.provider.keywords') - else if (moderation?.type === 'api') - prefix = t('common.apiBasedExtension.selector.title') - else - prefix = codeBasedExtensionList?.data.find(item => item.name === moderation?.type)?.label[locale] || '' - - if (moderation?.config?.inputs_config?.enabled && moderation.config?.outputs_config?.enabled) - suffix = t('appDebug.feature.moderation.allEnabled') - else if (moderation?.config?.inputs_config?.enabled) - suffix = t('appDebug.feature.moderation.inputEnabled') - else if (moderation?.config?.outputs_config?.enabled) - suffix = t('appDebug.feature.moderation.outputEnabled') - - return `${prefix} · ${suffix}` - } - - return ( -
-
- -
-
- {t('appDebug.feature.moderation.title')} -
-
- {renderInfo()} -
-
-
- - {t('common.operation.settings')} -
-
- ) -} - -export default memo(Moderation) diff --git a/web/app/components/base/features/feature-panel/opening-statement/index.tsx b/web/app/components/base/features/feature-panel/opening-statement/index.tsx deleted file mode 100644 index 54bf8bd937..0000000000 --- a/web/app/components/base/features/feature-panel/opening-statement/index.tsx +++ /dev/null @@ -1,321 +0,0 @@ -/* eslint-disable multiline-ternary */ -'use client' -import type { FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' -import produce from 'immer' -import { - RiAddLine, - RiDeleteBinLine, -} from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import { ReactSortable } from 'react-sortablejs' -import { - useFeatures, - useFeaturesStore, -} from '../../hooks' -import type { OnFeaturesChange } from '../../types' -import cn from '@/utils/classnames' -import Panel from '@/app/components/app/configuration/base/feature-panel' -import Button from '@/app/components/base/button' -import OperationBtn from '@/app/components/app/configuration/base/operation-btn' -import { getInputKeys } from '@/app/components/base/block-input' -import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' -import { getNewVar } from '@/utils/var' -import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' -import type { PromptVariable } from '@/models/debug' - -const MAX_QUESTION_NUM = 5 - -export type OpeningStatementProps = { - onChange?: OnFeaturesChange - readonly?: boolean - promptVariables?: PromptVariable[] - onAutoAddPromptVariable: (variable: PromptVariable[]) => void -} - -// regex to match the {{}} and replace it with a span -const regex = /\{\{([^}]+)\}\}/g - -const OpeningStatement: FC = ({ - onChange, - readonly, - promptVariables = [], - onAutoAddPromptVariable, -}) => { - const { t } = useTranslation() - const featureStore = useFeaturesStore() - const openingStatement = useFeatures(s => s.features.opening) - const value = openingStatement?.opening_statement || '' - const suggestedQuestions = openingStatement?.suggested_questions || [] - const [notIncludeKeys, setNotIncludeKeys] = useState([]) - - const hasValue = !!(value || '').trim() - const inputRef = useRef(null) - - const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false) - - const setFocus = () => { - didSetFocus() - setTimeout(() => { - const input = inputRef.current - if (input) { - input.focus() - input.setSelectionRange(input.value.length, input.value.length) - } - }, 0) - } - - const [tempValue, setTempValue] = useState(value) - useEffect(() => { - setTempValue(value || '') - }, [value]) - - const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(suggestedQuestions || []) - const notEmptyQuestions = tempSuggestedQuestions.filter(question => !!question && question.trim()) - const coloredContent = (tempValue || '') - .replace(//g, '>') - .replace(regex, varHighlightHTML({ name: '$1' })) // `{{$1}}` - .replace(/\n/g, '
') - - const handleEdit = () => { - if (readonly) - return - setFocus() - } - - const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) - - const handleCancel = () => { - setBlur() - setTempValue(value) - setTempSuggestedQuestions(suggestedQuestions) - } - - const handleConfirm = () => { - const keys = getInputKeys(tempValue) - const promptKeys = promptVariables.map(item => item.key) - let notIncludeKeys: string[] = [] - - if (promptKeys.length === 0) { - if (keys.length > 0) - notIncludeKeys = keys - } - else { - notIncludeKeys = keys.filter(key => !promptKeys.includes(key)) - } - - if (notIncludeKeys.length > 0) { - setNotIncludeKeys(notIncludeKeys) - showConfirmAddVar() - return - } - setBlur() - const { getState } = featureStore! - const { - features, - setFeatures, - } = getState() - - const newFeatures = produce(features, (draft) => { - if (draft.opening) { - draft.opening.opening_statement = tempValue - draft.opening.suggested_questions = tempSuggestedQuestions - } - }) - setFeatures(newFeatures) - - if (onChange) - onChange(newFeatures) - } - - const cancelAutoAddVar = () => { - const { getState } = featureStore! - const { - features, - setFeatures, - } = getState() - - const newFeatures = produce(features, (draft) => { - if (draft.opening) - draft.opening.opening_statement = tempValue - }) - setFeatures(newFeatures) - - if (onChange) - onChange(newFeatures) - hideConfirmAddVar() - setBlur() - } - - const autoAddVar = () => { - const { getState } = featureStore! - const { - features, - setFeatures, - } = getState() - - const newFeatures = produce(features, (draft) => { - if (draft.opening) - draft.opening.opening_statement = tempValue - }) - setFeatures(newFeatures) - if (onChange) - onChange(newFeatures) - onAutoAddPromptVariable([...notIncludeKeys.map(key => getNewVar(key, 'string'))]) - hideConfirmAddVar() - setBlur() - } - - const headerRight = !readonly ? ( - isFocus ? ( -
- - -
- ) : ( - - ) - ) : null - - const renderQuestions = () => { - return isFocus ? ( -
-
-
-
{t('appDebug.openingStatement.openingQuestion')}
-
·
-
{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}
-
-
-
- { - return { - id: index, - name, - } - })} - setList={list => setTempSuggestedQuestions(list.map(item => item.name))} - handle='.handle' - ghostClass="opacity-50" - animation={150} - > - {tempSuggestedQuestions.map((question, index) => { - return ( -
-
- - - -
- { - const value = e.target.value - setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => { - if (index === i) - return value - - return item - })) - }} - className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'} - /> - -
{ - setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i)) - }} - > - -
-
- ) - })}
- {tempSuggestedQuestions.length < MAX_QUESTION_NUM && ( -
{ setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }} - className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'> - -
{t('appDebug.variableConig.addOption')}
-
- )} -
- ) : ( -
- {notEmptyQuestions.map((question, index) => { - return ( -
- {question} -
- ) - })} -
- ) - } - - return ( - - - - } - headerRight={headerRight} - hasHeaderBottomBorder={!hasValue} - isFocus={isFocus} - > -
- {(hasValue || (!hasValue && isFocus)) ? ( - <> - {isFocus - ? ( -
- -
- ) - : ( -
- )} - {renderQuestions()} - ) : ( -
{t('appDebug.openingStatement.noDataPlaceHolder')}
- )} - - {isShowConfirmAddVar && ( - - )} - -
-
- ) -} -export default React.memo(OpeningStatement) diff --git a/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx b/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx deleted file mode 100644 index 2e08a99122..0000000000 --- a/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import ReactSlider from 'react-slider' -import s from './style.module.css' -import cn from '@/utils/classnames' - -type ISliderProps = { - className?: string - value: number - max?: number - min?: number - step?: number - disabled?: boolean - onChange: (value: number) => void -} - -const Slider: React.FC = ({ className, max, min, step, value, disabled, onChange }) => { - return ( -
-
-
- {(state.valueNow / 100).toFixed(2)} -
-
-
- )} - /> -} - -export default Slider diff --git a/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css b/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css deleted file mode 100644 index 4e93b39563..0000000000 --- a/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.slider { - position: relative; -} - -.slider.disabled { - opacity: 0.6; -} - -.slider-thumb:focus { - outline: none; -} - -.slider-track { - background-color: #528BFF; - height: 2px; -} - -.slider-track-1 { - background-color: #E5E7EB; -} \ No newline at end of file diff --git a/web/app/components/base/features/feature-panel/score-slider/index.tsx b/web/app/components/base/features/feature-panel/score-slider/index.tsx deleted file mode 100644 index 9826cbadcf..0000000000 --- a/web/app/components/base/features/feature-panel/score-slider/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import Slider from '@/app/components/app/configuration/toolbox/score-slider/base-slider' - -type Props = { - className?: string - value: number - onChange: (value: number) => void -} - -const ScoreSlider: FC = ({ - className, - value, - onChange, -}) => { - const { t } = useTranslation() - - return ( -
-
- -
-
-
-
0.8
-
·
-
{t('appDebug.feature.annotation.scoreThreshold.easyMatch')}
-
-
-
1.0
-
·
-
{t('appDebug.feature.annotation.scoreThreshold.accurateMatch')}
-
-
-
- ) -} -export default React.memo(ScoreSlider) diff --git a/web/app/components/base/features/feature-panel/speech-to-text/index.tsx b/web/app/components/base/features/feature-panel/speech-to-text/index.tsx deleted file mode 100644 index 2e5e3de439..0000000000 --- a/web/app/components/base/features/feature-panel/speech-to-text/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client' -import React, { type FC } from 'react' -import { useTranslation } from 'react-i18next' -import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' - -const SpeechToTextConfig: FC = () => { - const { t } = useTranslation() - - return ( -
-
- -
-
-
{t('appDebug.feature.speechToText.title')}
-
-
-
{t('appDebug.feature.speechToText.resDes')}
-
- ) -} -export default React.memo(SpeechToTextConfig) diff --git a/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx b/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx deleted file mode 100644 index e6d0b6e7e0..0000000000 --- a/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { MessageSmileSquare } from '@/app/components/base/icons/src/vender/solid/communication' -import Tooltip from '@/app/components/base/tooltip' - -const SuggestedQuestionsAfterAnswer: FC = () => { - const { t } = useTranslation() - - return ( -
-
- -
-
-
{t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
- -
-
-
{t('appDebug.feature.suggestedQuestionsAfterAnswer.resDes')}
-
- ) -} -export default React.memo(SuggestedQuestionsAfterAnswer) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/index.tsx b/web/app/components/base/features/feature-panel/text-to-speech/index.tsx deleted file mode 100644 index 2480a19077..0000000000 --- a/web/app/components/base/features/feature-panel/text-to-speech/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client' -import useSWR from 'swr' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { usePathname } from 'next/navigation' -import { useFeatures } from '../../hooks' -import type { OnFeaturesChange } from '../../types' -import ParamsConfig from './params-config' -import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' -import { languages } from '@/i18n/language' -import { fetchAppVoices } from '@/service/apps' -import AudioBtn from '@/app/components/base/audio-btn' - -type TextToSpeechProps = { - onChange?: OnFeaturesChange - disabled?: boolean -} -const TextToSpeech = ({ - onChange, - disabled, -}: TextToSpeechProps) => { - const { t } = useTranslation() - const textToSpeech = useFeatures(s => s.features.text2speech) - - const pathname = usePathname() - const matched = pathname.match(/\/app\/([^/]+)/) - const appId = (matched?.length && matched[1]) ? matched[1] : '' - const language = textToSpeech?.language - const languageInfo = languages.find(i => i.value === textToSpeech?.language) - - const voiceItems = useSWR({ appId, language }, fetchAppVoices).data - const voiceItem = voiceItems?.find(item => item.value === textToSpeech?.voice) - - return ( -
-
- -
-
- {t('appDebug.feature.textToSpeech.title')} -
-
-
-
- {languageInfo && (`${languageInfo?.name} - `)}{voiceItem?.name ?? t('appDebug.voice.defaultDisplay')} - { languageInfo?.example && ( - - )} -
-
- -
-
- ) -} -export default React.memo(TextToSpeech) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx b/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx deleted file mode 100644 index e923d9a333..0000000000 --- a/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx +++ /dev/null @@ -1,241 +0,0 @@ -'use client' -import useSWR from 'swr' -import produce from 'immer' -import React, { Fragment } from 'react' -import { usePathname } from 'next/navigation' -import { useTranslation } from 'react-i18next' -import { Listbox, Transition } from '@headlessui/react' -import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' -import { - useFeatures, - useFeaturesStore, -} from '../../hooks' -import type { OnFeaturesChange } from '../../types' -import classNames from '@/utils/classnames' -import type { Item } from '@/app/components/base/select' -import { fetchAppVoices } from '@/service/apps' -import Tooltip from '@/app/components/base/tooltip' -import { languages } from '@/i18n/language' -import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group' -import { TtsAutoPlay } from '@/types/app' - -type VoiceParamConfigProps = { - onChange?: OnFeaturesChange -} -const VoiceParamConfig = ({ - onChange, -}: VoiceParamConfigProps) => { - const { t } = useTranslation() - const pathname = usePathname() - const matched = pathname.match(/\/app\/([^/]+)/) - const appId = (matched?.length && matched[1]) ? matched[1] : '' - const text2speech = useFeatures(state => state.features.text2speech) - const featuresStore = useFeaturesStore() - - let languageItem = languages.find(item => item.value === text2speech?.language) - if (languages && !languageItem) - languageItem = languages[0] - const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') - - const language = languageItem?.value - const voiceItems = useSWR({ appId, language }, fetchAppVoices).data - let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice) - if (voiceItems && !voiceItem) - voiceItem = voiceItems[0] - const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') - - const handleChange = (value: Record) => { - const { - features, - setFeatures, - } = featuresStore!.getState() - - const newFeatures = produce(features, (draft) => { - draft.text2speech = { - ...draft.text2speech, - ...value, - } - }) - - setFeatures(newFeatures) - if (onChange) - onChange(newFeatures) - } - - return ( -
-
-
{t('appDebug.voice.voiceSettings.title')}
-
-
-
-
{t('appDebug.voice.voiceSettings.language')}
- - {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => ( -
{item} -
- ))} -
- } - /> -
- { - handleChange({ - language: String(value.value), - }) - }} - > -
- - - {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} - - - - - - - - {languages.map((item: Item) => ( - - `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' - }` - } - value={item} - disabled={false} - > - {({ /* active, */ selected }) => ( - <> - {t(`common.voice.language.${(item.value).toString().replace('-', '')}`)} - {(selected || item.value === text2speech?.language) && ( - - - )} - - )} - - ))} - - -
-
-
- -
-
{t('appDebug.voice.voiceSettings.voice')}
- { - handleChange({ - voice: String(value.value), - }) - }} - > -
- - {voiceItem?.name ?? localVoicePlaceholder} - - - - - - - {voiceItems?.map((item: Item) => ( - - `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' - }` - } - value={item} - disabled={false} - > - {({ /* active, */ selected }) => ( - <> - {item.name} - {(selected || item.value === text2speech?.voice) && ( - - - )} - - )} - - ))} - - -
-
-
-
-
{t('appDebug.voice.voiceSettings.autoPlay')}
- { - handleChange({ - autoPlay: value, - }) - }} - /> -
-
-
-
- ) -} - -export default React.memo(VoiceParamConfig) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx b/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx deleted file mode 100644 index 095fd6cce8..0000000000 --- a/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client' -import { memo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import type { OnFeaturesChange } from '../../types' -import ParamConfigContent from './param-config-content' -import cn from '@/utils/classnames' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' - -type ParamsConfigProps = { - onChange?: OnFeaturesChange - disabled?: boolean -} -const ParamsConfig = ({ - onChange, - disabled, -}: ParamsConfigProps) => { - const { t } = useTranslation() - const [open, setOpen] = useState(false) - - return ( - - !disabled && setOpen(v => !v)}> -
- -
{t('appDebug.voice.settings')}
-
-
- -
- -
-
-
- ) -} -export default memo(ParamsConfig) diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx index db7bad4d18..1e7c141059 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx @@ -6,18 +6,23 @@ import { LoveMessage } 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, OpeningStatement } from '@/app/components/base/features/types' +import type { OnFeaturesChange } from '@/app/components/base/features/types' import { FeatureEnum } from '@/app/components/base/features/types' import { useModalContext } from '@/context/modal-context' +import type { PromptVariable } from '@/models/debug' type Props = { disabled?: boolean onChange?: OnFeaturesChange + promptVariables?: PromptVariable[] + onAutoAddPromptVariable?: (variable: PromptVariable[]) => void } const ConversationOpener = ({ disabled, onChange, + promptVariables, + onAutoAddPromptVariable, }: Props) => { const { t } = useTranslation() const { setShowOpeningModal } = useModalContext() @@ -32,7 +37,11 @@ const ConversationOpener = ({ setFeatures, } = featuresStore!.getState() setShowOpeningModal({ - payload: opening as OpeningStatement, + payload: { + ...opening, + promptVariables, + onAutoAddPromptVariable, + }, onSaveCallback: (newOpening) => { const newFeatures = produce(features, (draft) => { draft.opening = newOpening @@ -46,7 +55,7 @@ const ConversationOpener = ({ onChange() }, }) - }, [disabled, featuresStore, onChange, opening, setShowOpeningModal]) + }, [disabled, featuresStore, onAutoAddPromptVariable, onChange, opening, promptVariables, setShowOpeningModal]) const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { const { diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx index 6706b3699b..9f158695c7 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -1,16 +1,23 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' import produce from 'immer' import { ReactSortable } from 'react-sortablejs' import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' +import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' import type { OpeningStatement } from '@/app/components/base/features/types' +import { getInputKeys } from '@/app/components/base/block-input' +import type { PromptVariable } from '@/models/debug' +import { getNewVar } from '@/utils/var' type OpeningSettingModalProps = { data: OpeningStatement onSave: (newState: OpeningStatement) => void onCancel: () => void + promptVariables?: PromptVariable[] + onAutoAddPromptVariable?: (variable: PromptVariable[]) => void } const MAX_QUESTION_NUM = 5 @@ -19,6 +26,8 @@ const OpeningSettingModal = ({ data, onSave, onCancel, + promptVariables = [], + onAutoAddPromptVariable, }: OpeningSettingModalProps) => { const { t } = useTranslation() const [tempValue, setTempValue] = useState(data?.opening_statement || '') @@ -26,8 +35,29 @@ const OpeningSettingModal = ({ setTempValue(data.opening_statement || '') }, [data.opening_statement]) const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(data.suggested_questions || []) + const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) + const [notIncludeKeys, setNotIncludeKeys] = useState([]) - const handleSave = () => { + const handleSave = useCallback((ignoreVariablesCheck?: boolean) => { + if (!ignoreVariablesCheck) { + const keys = getInputKeys(tempValue) + const promptKeys = promptVariables.map(item => item.key) + let notIncludeKeys: string[] = [] + + if (promptKeys.length === 0) { + if (keys.length > 0) + notIncludeKeys = keys + } + else { + notIncludeKeys = keys.filter(key => !promptKeys.includes(key)) + } + + if (notIncludeKeys.length > 0) { + setNotIncludeKeys(notIncludeKeys) + showConfirmAddVar() + return + } + } const newOpening = produce(data, (draft) => { if (draft) { draft.opening_statement = tempValue @@ -35,7 +65,21 @@ const OpeningSettingModal = ({ } }) onSave(newOpening) - } + }, [data, onSave, promptVariables, showConfirmAddVar, tempSuggestedQuestions, tempValue]) + + const cancelAutoAddVar = useCallback(() => { + hideConfirmAddVar() + handleSave(true) + }, [handleSave, hideConfirmAddVar]) + + const autoAddVar = useCallback(() => { + onAutoAddPromptVariable?.([ + ...promptVariables, + ...notIncludeKeys.map(key => getNewVar(key, 'string')), + ]) + hideConfirmAddVar() + handleSave(true) + }, [handleSave, hideConfirmAddVar, notIncludeKeys, onAutoAddPromptVariable, promptVariables]) const renderQuestions = () => { return ( @@ -137,11 +181,19 @@ const OpeningSettingModal = ({ + {isShowConfirmAddVar && ( + + )} ) } diff --git a/web/app/components/base/features/new-feature-panel/feature-bar.tsx b/web/app/components/base/features/new-feature-panel/feature-bar.tsx index 4fd79f9144..3c2d748975 100644 --- a/web/app/components/base/features/new-feature-panel/feature-bar.tsx +++ b/web/app/components/base/features/new-feature-panel/feature-bar.tsx @@ -118,7 +118,7 @@ const FeatureBar = ({ )} - {/* annotation reply ##TODO## */} + {/* ##TODO## annotation_reply */}
{t('appDebug.feature.bar.enableText')}