From d69b4537292e33a6992a9a018f456e5492f325e3 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 28 Aug 2024 18:37:02 +0800 Subject: [PATCH] conversation opener --- .../conversation-opener/index.tsx | 105 ++++++++++++ .../conversation-opener/modal.tsx | 149 ++++++++++++++++++ .../base/features/new-feature-panel/index.tsx | 4 + .../new-feature-panel/moderation/index.tsx | 23 +-- web/app/components/workflow/features.tsx | 3 +- web/context/modal-context.tsx | 24 +++ web/i18n/en-US/app-debug.ts | 2 +- 7 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx create mode 100644 web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx 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 new file mode 100644 index 0000000000..b84836cf55 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx @@ -0,0 +1,105 @@ +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { RiEditLine } from '@remixicon/react' +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 { FeatureEnum } from '@/app/components/base/features/types' +import { useModalContext } from '@/context/modal-context' + +type Props = { + disabled?: boolean + onChange?: OnFeaturesChange +} + +const ConversationOpener = ({ + disabled, + onChange, +}: Props) => { + const { t } = useTranslation() + const { setShowOpeningModal } = useModalContext() + const opening = useFeatures(s => s.features.opening) + const featuresStore = useFeaturesStore() + const [isHovering, setIsHovering] = useState(false) + const handleOpenOpeningModal = useCallback(() => { + if (disabled) + return + const { + features, + setFeatures, + } = featuresStore!.getState() + setShowOpeningModal({ + payload: opening as OpeningStatement, + onSaveCallback: (newOpening) => { + const newFeatures = produce(features, (draft) => { + draft.opening = newOpening + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, + onCancelCallback: () => { + if (onChange) + onChange(features) + }, + }) + }, [disabled, featuresStore, onChange, opening, setShowOpeningModal]) + + 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.conversationOpener.title')} + value={!!opening?.enabled} + onChange={state => handleChange(FeatureEnum.opening, state)} + onMouseEnter={() => setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > + <> + {!opening?.enabled && ( +
{t('appDebug.feature.conversationOpener.description')}
+ )} + {!!opening?.enabled && ( + <> + {!isHovering && ( +
+ {opening.opening_statement || t('appDebug.openingStatement.placeholder')} +
+ )} + {isHovering && ( + + )} + + )} + +
+ ) +} + +export default ConversationOpener 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 new file mode 100644 index 0000000000..6706b3699b --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -0,0 +1,149 @@ +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +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 type { OpeningStatement } from '@/app/components/base/features/types' + +type OpeningSettingModalProps = { + data: OpeningStatement + onSave: (newState: OpeningStatement) => void + onCancel: () => void +} + +const MAX_QUESTION_NUM = 5 + +const OpeningSettingModal = ({ + data, + onSave, + onCancel, +}: OpeningSettingModalProps) => { + const { t } = useTranslation() + const [tempValue, setTempValue] = useState(data?.opening_statement || '') + useEffect(() => { + setTempValue(data.opening_statement || '') + }, [data.opening_statement]) + const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(data.suggested_questions || []) + + const handleSave = () => { + const newOpening = produce(data, (draft) => { + if (draft) { + draft.opening_statement = tempValue + draft.suggested_questions = tempSuggestedQuestions + } + }) + onSave(newOpening) + } + + const renderQuestions = () => { + return ( +
+
+
+
{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')}
+
+ )} +
+ ) + } + + return ( + { }} + className='!p-6 !mt-14 !max-w-none !w-[640px] !bg-components-panel-bg-blur' + > +
+
{t('appDebug.feature.conversationOpener.title')}
+
+
+
+
+ +
+
+