diff --git a/web/app/components/base/features/feature-choose/feature-item/index.tsx b/web/app/components/base/features/feature-choose/feature-item/index.tsx index 0d7ab4e02c..f485e6851d 100644 --- a/web/app/components/base/features/feature-choose/feature-item/index.tsx +++ b/web/app/components/base/features/feature-choose/feature-item/index.tsx @@ -1,9 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useCallback } from 'react' import cn from 'classnames' import s from './style.module.css' import Switch from '@/app/components/base/switch' +import type { FeatureEnum } from '@/app/components/base/features/types' export type IFeatureItemProps = { icon: React.ReactNode @@ -11,7 +12,8 @@ export type IFeatureItemProps = { title: string description: string value: boolean - onChange: (value: boolean) => void + onChange: (type: FeatureEnum, value: boolean) => void + type: FeatureEnum } const FeatureItem: FC = ({ @@ -21,7 +23,12 @@ const FeatureItem: FC = ({ description, value, onChange, + type, }) => { + const handleChange = useCallback((newValue: boolean) => { + onChange(type, newValue) + }, [type, onChange]) + return (
@@ -40,7 +47,7 @@ const FeatureItem: FC = ({
- + { previewImgClassName && (
diff --git a/web/app/components/base/features/feature-choose/feature-modal.tsx b/web/app/components/base/features/feature-choose/feature-modal.tsx index 755bb5b788..2f3bdebb3f 100644 --- a/web/app/components/base/features/feature-choose/feature-modal.tsx +++ b/web/app/components/base/features/feature-choose/feature-modal.tsx @@ -3,7 +3,10 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import produce from 'immer' import { useTranslation } from 'react-i18next' -import { useFeatures } from '../hooks' +import { + useFeatures, + useFeaturesStore, +} from '../hooks' import FeatureGroup from './feature-group' import FeatureItem from './feature-item' import Modal from '@/app/components/base/modal' @@ -15,37 +18,43 @@ import { MessageFast, MessageHeartCircle, } from '@/app/components/base/icons/src/vender/solid/communication' +import { FeatureEnum } from '@/app/components/base/features/types' +import type { Features } from '@/app/components/base/features/types' -export type ChooseFeatureProps = { +export type FeatureModalProps = { + onChange?: (features: Features) => void showTextToSpeechItem?: boolean showSpeechToTextItem?: boolean } -const ChooseFeature: FC = ({ +const FeatureModal: FC = ({ + onChange, showTextToSpeechItem, showSpeechToTextItem, }) => { const { t } = useTranslation() + const featuresStore = useFeaturesStore() const setShowFeaturesModal = useFeatures(s => s.setShowFeaturesModal) - const openingStatement = useFeatures(s => s.openingStatement) - const setOpeningStatement = useFeatures(s => s.setOpeningStatement) - const suggestedQuestionsAfterAnswer = useFeatures(s => s.suggestedQuestionsAfterAnswer) - const setSuggestedQuestionsAfterAnswer = useFeatures(s => s.setSuggestedQuestionsAfterAnswer) - const textToSpeech = useFeatures(s => s.textToSpeech) - const setTextToSpeech = useFeatures(s => s.setTextToSpeech) - const speechToText = useFeatures(s => s.speechToText) - const setSpeechToText = useFeatures(s => s.setSpeechToText) - const citation = useFeatures(s => s.citation) - const setCitation = useFeatures(s => s.setCitation) - const moderation = useFeatures(s => s.moderation) - const setModeration = useFeatures(s => s.setModeration) - const annotation = useFeatures(s => s.annotation) - const setAnnotation = useFeatures(s => s.setAnnotation) + const features = useFeatures(s => s.features) const handleCancelModal = useCallback(() => { setShowFeaturesModal(false) }, [setShowFeaturesModal]) + const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft[type].enabled = enabled + }) + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + }, [featuresStore, onChange]) + return ( = ({ previewImgClassName='openingStatementPreview' title={t('appDebug.feature.conversationOpener.title')} description={t('appDebug.feature.conversationOpener.description')} - value={openingStatement.enabled} - onChange={value => setOpeningStatement(produce(openingStatement, (draft) => { - draft.enabled = value - }))} + value={!!features.opening.enabled} + onChange={handleChange} + type={FeatureEnum.opening} /> } previewImgClassName='suggestedQuestionsAfterAnswerPreview' title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')} description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')} - value={suggestedQuestionsAfterAnswer.enabled} - onChange={value => setSuggestedQuestionsAfterAnswer(produce(suggestedQuestionsAfterAnswer, (draft) => { - draft.enabled = value - }))} + value={!!features.suggested.enabled} + onChange={handleChange} + type={FeatureEnum.suggested} /> { showTextToSpeechItem && ( @@ -89,10 +96,9 @@ const ChooseFeature: FC = ({ previewImgClassName='textToSpeechPreview' title={t('appDebug.feature.textToSpeech.title')} description={t('appDebug.feature.textToSpeech.description')} - value={textToSpeech.enabled} - onChange={value => setTextToSpeech(produce(textToSpeech, (draft) => { - draft.enabled = value - }))} + value={!!features.text2speech.enabled} + onChange={handleChange} + type={FeatureEnum.text2speech} /> ) } @@ -103,10 +109,9 @@ const ChooseFeature: FC = ({ previewImgClassName='speechToTextPreview' title={t('appDebug.feature.speechToText.title')} description={t('appDebug.feature.speechToText.description')} - value={speechToText.enabled} - onChange={value => setSpeechToText(produce(speechToText, (draft) => { - draft.enabled = value - }))} + value={!!features.speech2text.enabled} + onChange={handleChange} + type={FeatureEnum.speech2text} /> ) } @@ -115,10 +120,9 @@ const ChooseFeature: FC = ({ previewImgClassName='citationPreview' title={t('appDebug.feature.citation.title')} description={t('appDebug.feature.citation.description')} - value={citation.enabled} - onChange={value => setCitation(produce(citation, (draft) => { - draft.enabled = value - }))} + value={!!features.citation.enabled} + onChange={handleChange} + type={FeatureEnum.citation} /> @@ -130,19 +134,17 @@ const ChooseFeature: FC = ({ previewImgClassName='' title={t('appDebug.feature.moderation.title')} description={t('appDebug.feature.moderation.description')} - value={moderation.enabled} - onChange={value => setModeration(produce(moderation, (draft) => { - draft.enabled = value - }))} + value={!!features.moderation.enabled} + onChange={handleChange} + type={FeatureEnum.moderation} /> } title={t('appDebug.feature.annotation.title')} description={t('appDebug.feature.annotation.description')} - value={annotation.enabled} - onChange={value => setAnnotation(produce(annotation, (draft) => { - draft.enabled = value - }))} + value={!!features.annotation.enabled} + onChange={handleChange} + type={FeatureEnum.annotation} /> @@ -150,4 +152,4 @@ const ChooseFeature: FC = ({ ) } -export default React.memo(ChooseFeature) +export default React.memo(FeatureModal) diff --git a/web/app/components/base/features/feature-panel/index.tsx b/web/app/components/base/features/feature-panel/index.tsx index f789a3828d..9f4e328d47 100644 --- a/web/app/components/base/features/feature-panel/index.tsx +++ b/web/app/components/base/features/feature-panel/index.tsx @@ -23,21 +23,15 @@ const FeaturePanel = ({ annotationProps, }: FeaturePanelProps) => { const { t } = useTranslation() - const openingStatement = useFeatures(s => s.openingStatement) - const suggestedQuestionsAfterAnswer = useFeatures(s => s.suggestedQuestionsAfterAnswer) - const textToSpeech = useFeatures(s => s.textToSpeech) - const speechToText = useFeatures(s => s.speechToText) - const citation = useFeatures(s => s.citation) - const moderation = useFeatures(s => s.moderation) - const annotation = useFeatures(s => s.annotation) + const features = useFeatures(s => s.features) const showAdvanceFeature = useMemo(() => { - return openingStatement.enabled || suggestedQuestionsAfterAnswer.enabled || textToSpeech.enabled || speechToText.enabled || citation.enabled - }, [openingStatement, suggestedQuestionsAfterAnswer, textToSpeech, speechToText, citation]) + return features.opening.enabled || features.suggested.enabled || features.speech2text.enabled || features.text2speech.enabled || features.citation.enabled + }, [features]) const showToolFeature = useMemo(() => { - return moderation.enabled || annotation.enabled - }, [moderation, annotation]) + return features.moderation.enabled || features.annotation.enabled + }, [features]) return (
@@ -55,27 +49,27 @@ const FeaturePanel = ({
{ - openingStatement.enabled && ( + features.opening.enabled && ( ) } { - suggestedQuestionsAfterAnswer.enabled && ( + features.suggested.enabled && ( ) } { - textToSpeech.enabled && ( + features.text2speech.enabled && ( ) } { - speechToText.enabled && ( + features.speech2text.enabled && ( ) } { - citation.enabled && ( + features.citation.enabled && ( ) } @@ -97,12 +91,12 @@ const FeaturePanel = ({
{ - moderation.enabled && ( + features.moderation.enabled && ( ) } { - annotation.enabled && ( + features.annotation.enabled && ( ) } diff --git a/web/app/components/base/features/feature-panel/moderation/index.tsx b/web/app/components/base/features/feature-panel/moderation/index.tsx index d0f079a0c5..b3364e538c 100644 --- a/web/app/components/base/features/feature-panel/moderation/index.tsx +++ b/web/app/components/base/features/feature-panel/moderation/index.tsx @@ -1,8 +1,12 @@ 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 } from '../../hooks' +import { + useFeatures, + useFeaturesStore, +} from '../../hooks' 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' @@ -13,8 +17,8 @@ const Moderation = () => { const { t } = useTranslation() const { setShowModerationSettingModal } = useModalContext() const { locale } = useContext(I18n) - const moderation = useFeatures(s => s.moderation) - const setModeration = useFeatures(s => s.setModeration) + const featuresStore = useFeaturesStore() + const moderation = useFeatures(s => s.features.moderation) const { data: codeBasedExtensionList } = useSWR( '/code-based-extension?module=moderation', @@ -22,9 +26,17 @@ const Moderation = () => { ) const handleOpenModerationSettingModal = () => { + const { + features, + setFeatures, + } = featuresStore!.getState() setShowModerationSettingModal({ - payload: moderation, - onSaveCallback: setModeration, + payload: moderation as any, + onSaveCallback: (newModeration) => { + setFeatures(produce(features, (draft) => { + draft.moderation = newModeration + })) + }, }) } 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 index 84c7e84f35..069cd6f090 100644 --- a/web/app/components/base/features/feature-panel/opening-statement/index.tsx +++ b/web/app/components/base/features/feature-panel/opening-statement/index.tsx @@ -7,7 +7,10 @@ import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { ReactSortable } from 'react-sortablejs' -import { useFeatures } from '../../hooks' +import { + useFeatures, + useFeaturesStore, +} from '../../hooks' 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' @@ -35,8 +38,8 @@ const OpeningStatement: FC = ({ onAutoAddPromptVariable, }) => { const { t } = useTranslation() - const openingStatement = useFeatures(s => s.openingStatement) - const setOpeningStatement = useFeatures(s => s.setOpeningStatement) + const featureStore = useFeaturesStore() + const openingStatement = useFeatures(s => s.features.opening) const value = openingStatement.opening_statement || '' const suggestedQuestions = openingStatement.suggested_questions || [] const [notIncludeKeys, setNotIncludeKeys] = useState([]) @@ -103,23 +106,41 @@ const OpeningStatement: FC = ({ return } setBlur() - setOpeningStatement(produce(openingStatement, (draft) => { - draft.opening_statement = tempValue - draft.suggested_questions = tempSuggestedQuestions + const { getState } = featureStore! + const { + features, + setFeatures, + } = getState() + + setFeatures(produce(features, (draft) => { + draft.opening.opening_statement = tempValue + draft.opening.suggested_questions = tempSuggestedQuestions })) } const cancelAutoAddVar = () => { - setOpeningStatement(produce(openingStatement, (draft) => { - draft.opening_statement = tempValue + const { getState } = featureStore! + const { + features, + setFeatures, + } = getState() + + setFeatures(produce(features, (draft) => { + draft.opening.opening_statement = tempValue })) hideConfirmAddVar() setBlur() } const autoAddVar = () => { - setOpeningStatement(produce(openingStatement, (draft) => { - draft.opening_statement = tempValue + const { getState } = featureStore! + const { + features, + setFeatures, + } = getState() + + setFeatures(produce(features, (draft) => { + draft.opening.opening_statement = tempValue })) onAutoAddPromptVariable([...notIncludeKeys.map(key => getNewVar(key, 'string'))]) hideConfirmAddVar() 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 index f0a3648894..5cb9aaf513 100644 --- 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 @@ -12,7 +12,7 @@ import AudioBtn from '@/app/components/base/audio-btn' const TextToSpeech: FC = () => { const { t } = useTranslation() - const textToSpeech = useFeatures(s => s.textToSpeech) + const textToSpeech = useFeatures(s => s.features.text2speech) const pathname = usePathname() const matched = pathname.match(/\/app\/([^/]+)/) diff --git a/web/app/components/base/features/store.ts b/web/app/components/base/features/store.ts index edda945fd8..0a17fb0f55 100644 --- a/web/app/components/base/features/store.ts +++ b/web/app/components/base/features/store.ts @@ -1,13 +1,5 @@ import { createStore } from 'zustand' -import type { - AnnotationReply, - OpeningStatement, - RetrieverResource, - SensitiveWordAvoidance, - SpeechToText, - SuggestedQuestionsAfterAnswer, - TextToSpeech, -} from './types' +import type { Features } from './types' export type FeaturesModal = { showFeaturesModal: boolean @@ -15,23 +7,11 @@ export type FeaturesModal = { } export type FeaturesState = { - openingStatement: OpeningStatement - suggestedQuestionsAfterAnswer: SuggestedQuestionsAfterAnswer - textToSpeech: TextToSpeech - speechToText: SpeechToText - citation: RetrieverResource - moderation: SensitiveWordAvoidance - annotation: AnnotationReply + features: Features } export type FeaturesAction = { - setOpeningStatement: (openingStatement: OpeningStatement) => void - setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: SuggestedQuestionsAfterAnswer) => void - setTextToSpeech: (textToSpeech: TextToSpeech) => void - setSpeechToText: (speechToText: SpeechToText) => void - setCitation: (citation: RetrieverResource) => void - setModeration: (moderation: SensitiveWordAvoidance) => void - setAnnotation: (annotation: AnnotationReply) => void + setFeatures: (features: Features) => void } export type FeatureStoreState = FeaturesState & FeaturesAction & FeaturesModal @@ -40,39 +20,34 @@ export type FeaturesStore = ReturnType export const createFeaturesStore = (initProps?: Partial) => { const DEFAULT_PROPS: FeaturesState = { - openingStatement: { - enabled: false, - }, - suggestedQuestionsAfterAnswer: { - enabled: false, - }, - textToSpeech: { - enabled: false, - }, - speechToText: { - enabled: false, - }, - citation: { - enabled: false, - }, - moderation: { - enabled: false, - }, - annotation: { - enabled: false, + features: { + opening: { + enabled: false, + }, + suggested: { + enabled: false, + }, + text2speech: { + enabled: false, + }, + speech2text: { + enabled: false, + }, + citation: { + enabled: false, + }, + moderation: { + enabled: false, + }, + annotation: { + enabled: false, + }, }, } return createStore()(set => ({ ...DEFAULT_PROPS, ...initProps, - setOpeningStatement: openingStatement => set(() => ({ openingStatement })), - setSuggestedQuestionsAfterAnswer: suggestedQuestionsAfterAnswer => set(() => ({ suggestedQuestionsAfterAnswer })), - setSpeechToText: speechToText => set(() => ({ speechToText })), - setTextToSpeech: textToSpeech => set(() => ({ textToSpeech })), - setCitation: citation => set(() => ({ citation })), - setModeration: moderation => set(() => ({ moderation })), - setAnnotation: annotation => set(() => ({ annotation })), - + setFeatures: features => set(() => ({ features })), showFeaturesModal: false, setShowFeaturesModal: showFeaturesModal => set(() => ({ showFeaturesModal })), })) diff --git a/web/app/components/base/features/types.ts b/web/app/components/base/features/types.ts index 1ec90454d1..c20ed36986 100644 --- a/web/app/components/base/features/types.ts +++ b/web/app/components/base/features/types.ts @@ -31,3 +31,23 @@ export type AnnotationReply = EnabledOrDisabled & { embedding_provider_name: string } } + +export enum FeatureEnum { + opening = 'opening', + suggested = 'suggested', + text2speech = 'text2speech', + speech2text = 'speech2text', + citation = 'citation', + moderation = 'moderation', + annotation = 'annotation', +} + +export type Features = { + [FeatureEnum.opening]: OpeningStatement + [FeatureEnum.suggested]: SuggestedQuestionsAfterAnswer + [FeatureEnum.text2speech]: TextToSpeech + [FeatureEnum.speech2text]: SpeechToText + [FeatureEnum.citation]: RetrieverResource + [FeatureEnum.moderation]: SensitiveWordAvoidance + [FeatureEnum.annotation]: AnnotationReply +} diff --git a/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/play.svg b/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/play.svg new file mode 100644 index 0000000000..545e407f5f --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/mediaAndDevices/play.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.json b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.json new file mode 100644 index 0000000000..b32d786e4e --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.json @@ -0,0 +1,38 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "play" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Solid", + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M4.00312 1.40109C4.0091 1.40508 4.0151 1.40907 4.02111 1.41309L9.29548 4.92933C9.44809 5.03105 9.58959 5.12537 9.69827 5.21301C9.81168 5.30448 9.94538 5.43132 10.0223 5.61687C10.124 5.86212 10.124 6.13775 10.0223 6.38301C9.94538 6.56856 9.81168 6.6954 9.69827 6.78686C9.5896 6.8745 9.44811 6.96881 9.2955 7.07053L4.00314 10.5988C3.8166 10.7232 3.64886 10.835 3.50652 10.9121C3.36409 10.9893 3.16859 11.0775 2.9404 11.0639C2.64852 11.0465 2.3789 10.9022 2.20249 10.669C2.06458 10.4867 2.02952 10.2751 2.01474 10.1138C1.99997 9.95254 1.99999 9.75094 2 9.52674L2 2.49475C2 2.48752 2 2.48031 2 2.47313C1.99999 2.24893 1.99997 2.04733 2.01474 1.88612C2.02952 1.72479 2.06458 1.5132 2.20249 1.33089C2.3789 1.0977 2.64852 0.953401 2.9404 0.935973C3.16859 0.922349 3.36409 1.01055 3.50652 1.08774C3.64885 1.16488 3.81659 1.27672 4.00312 1.40109Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "Play" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.tsx b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.tsx new file mode 100644 index 0000000000..f182f7a565 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/Play.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Play.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Play' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts index 37d5b3a2d9..ab43a47ea1 100644 --- a/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts +++ b/web/app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts @@ -2,6 +2,7 @@ export { default as MagicBox } from './MagicBox' export { default as MagicEyes } from './MagicEyes' export { default as MagicWand } from './MagicWand' export { default as Microphone01 } from './Microphone01' +export { default as Play } from './Play' export { default as Robot } from './Robot' export { default as Sliders02 } from './Sliders02' export { default as Speaker } from './Speaker' diff --git a/web/app/components/workflow/features.tsx b/web/app/components/workflow/features.tsx index 918bf543ee..6881fdc102 100644 --- a/web/app/components/workflow/features.tsx +++ b/web/app/components/workflow/features.tsx @@ -1,4 +1,5 @@ import { memo } from 'react' +import { useTranslation } from 'react-i18next' import { useStore } from './store' import { XClose } from '@/app/components/base/icons/src/vender/line/general' import { @@ -7,12 +8,13 @@ import { } from '@/app/components/base/features' const Features = () => { + const { t } = useTranslation() const setShowFeaturesPanel = useStore(state => state.setShowFeaturesPanel) return (
- Features + {t('workflow.common.features')}
diff --git a/web/app/components/workflow/header/editing-title.tsx b/web/app/components/workflow/header/editing-title.tsx new file mode 100644 index 0000000000..538dd0b7c1 --- /dev/null +++ b/web/app/components/workflow/header/editing-title.tsx @@ -0,0 +1,34 @@ +import { memo } from 'react' +import dayjs from 'dayjs' +import { useTranslation } from 'react-i18next' +import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general' +import { useStore } from '@/app/components/workflow/store' + +const EditingTitle = () => { + const { t } = useTranslation() + const draftUpdatedAt = useStore(state => state.draftUpdatedAt) + const publishedAt = useStore(state => state.publishedAt) + + return ( +
+ + {t('workflow.common.editing')} + { + draftUpdatedAt && ( + <> + · + {t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')} + + ) + } + · + { + publishedAt + ? `${t('workflow.common.published')} ${dayjs(publishedAt).fromNow()}` + : t('workflow.common.unpublished') + } +
+ ) +} + +export default memo(EditingTitle) diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index 618f21f25b..301b526aed 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -3,26 +3,27 @@ import { memo, useCallback, } from 'react' -import dayjs from 'dayjs' +import { useTranslation } from 'react-i18next' import { useStore } from '../store' import RunAndHistory from './run-and-history' +import EditingTitle from './editing-title' +import RunningTitle from './running-title' import Publish from './publish' -import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general' import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout' import Button from '@/app/components/base/button' import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' import { useStore as useAppStore } from '@/app/components/app/store' +import { Mode } from '@/app/components/workflow/types' const Header: FC = () => { + const { t } = useTranslation() const appDetail = useAppStore(state => state.appDetail) - const setShowFeaturesPanel = useStore(state => state.setShowFeaturesPanel) - const runStaus = useStore(state => state.runStaus) - const setRunStaus = useStore(state => state.setRunStaus) - const draftUpdatedAt = useStore(state => state.draftUpdatedAt) + const mode = useStore(state => state.mode) + const runTaskId = useStore(state => state.runTaskId) const handleShowFeatures = useCallback(() => { - setShowFeaturesPanel(true) - }, [setShowFeaturesPanel]) + useStore.setState({ showFeaturesPanel: true }) + }, []) return (
{ >
{appDetail?.name}
-
-
- - Editing - { - draftUpdatedAt && ( - <> - · - - Auto-Saved {dayjs(draftUpdatedAt).format('HH:mm:ss')} - - - ) - } -
-
+ { + mode === Mode.Editing && !runTaskId && + } + { + (mode === Mode.Running || runTaskId) && + }
{ - runStaus && ( + (mode === Mode.Running || runTaskId) && ( ) } @@ -77,7 +68,7 @@ const Header: FC = () => { onClick={handleShowFeatures} > - Features + {t('workflow.common.features')} ) } diff --git a/web/app/components/workflow/header/publish.tsx b/web/app/components/workflow/header/publish.tsx index 2580befe06..884b3046a2 100644 --- a/web/app/components/workflow/header/publish.tsx +++ b/web/app/components/workflow/header/publish.tsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -7,6 +8,7 @@ import { } from '@/app/components/base/portal-to-follow-elem' const Publish = () => { + const { t } = useTranslation() const [open, setOpen] = useState(false) return ( @@ -24,35 +26,35 @@ const Publish = () => { type='primary' className='px-3 py-0 h-8 text-[13px] font-medium' > - publish + {t('workflow.common.publish')}
- Current Draft + {t('workflow.common.currentDraft').toLocaleUpperCase()}
- Auto-Saved 3 min ago · Evan + {t('workflow.common.autoSaved')} 3 min ago · Evan
- Latest Published + {t('workflow.common.latestPublished').toLocaleUpperCase()}
- Auto-Saved 3 min ago · Evan + {t('workflow.common.autoSaved')} 3 min ago · Evan
diff --git a/web/app/components/workflow/header/run-and-history.tsx b/web/app/components/workflow/header/run-and-history.tsx index 6121a272f2..69f135e14c 100644 --- a/web/app/components/workflow/header/run-and-history.tsx +++ b/web/app/components/workflow/header/run-and-history.tsx @@ -1,16 +1,19 @@ import type { FC } from 'react' import { memo } from 'react' +import { useTranslation } from 'react-i18next' import { useStore } from '../store' import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' import TooltipPlus from '@/app/components/base/tooltip-plus' import { Loading02 } from '@/app/components/base/icons/src/vender/line/general' +import { Mode } from '@/app/components/workflow/types' +import { useStore as useAppStore } from '@/app/components/app/store' const RunAndHistory: FC = () => { + const { t } = useTranslation() + const appDetail = useAppStore(state => state.appDetail) + const mode = useStore(state => state.mode) const showRunHistory = useStore(state => state.showRunHistory) - const setShowRunHistory = useStore(state => state.setShowRunHistory) - const runStaus = useStore(state => state.runStaus) - const setRunStaus = useStore(state => state.setRunStaus) return (
@@ -18,38 +21,51 @@ const RunAndHistory: FC = () => { className={` flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600 hover:bg-primary-50 cursor-pointer - ${runStaus === 'running' && 'bg-primary-50 !cursor-not-allowed'} + ${mode === 'running' && 'bg-primary-50 !cursor-not-allowed'} + ${mode === 'running' && appDetail?.mode !== 'workflow' && 'opacity-50'} `} - onClick={() => runStaus !== 'running' && setRunStaus('running')} + onClick={() => mode !== 'running' && useStore.setState({ mode: Mode.Running })} > { - runStaus === 'running' + mode === 'running' ? ( <> - - Running + { + appDetail?.mode === 'workflow' && ( + + ) + } + { + appDetail?.mode === 'workflow' + ? t('workflow.common.running') + : t('workflow.common.inPreview') + } ) : ( <> - Run + { + appDetail?.mode === 'workflow' + ? t('workflow.common.run') + : t('workflow.common.preview') + } ) }
setShowRunHistory(true)} + onClick={() => useStore.setState({ showRunHistory: true })} > - +
diff --git a/web/app/components/workflow/header/running-title.tsx b/web/app/components/workflow/header/running-title.tsx new file mode 100644 index 0000000000..c558cfcd09 --- /dev/null +++ b/web/app/components/workflow/header/running-title.tsx @@ -0,0 +1,24 @@ +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import { useStore as useAppStore } from '@/app/components/app/store' +import { Play } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' + +const RunningTitle = () => { + const { t } = useTranslation() + const appDetail = useAppStore(state => state.appDetail) + + return ( +
+ + { + appDetail?.mode === 'advanced-chat' + ? t('workflow.common.inPreviewMode') + : t('workflow.common.inRunMode') + } + · + Test Run#2 +
+ ) +} + +export default memo(RunningTitle) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index fe925abaf2..c63ab2ae0d 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -40,6 +40,7 @@ import { import { useStore as useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' import { FeaturesProvider } from '@/app/components/base/features' +import type { Features as FeaturesData } from '@/app/components/base/features/types' import { fetchCollectionList } from '@/service/tools' const nodeTypes = { @@ -205,9 +206,24 @@ const WorkflowWrap: FC = ({ ) } + const features = data?.features || {} + const initialFeatures: FeaturesData = { + opening: { + enabled: !!features.opening_statement, + opening_statement: features.opening_statement, + suggested_questions: features.suggested_questions, + }, + suggested: features.suggested_questions_after_answer || { enabled: false }, + speech2text: features.speech_to_text || { enabled: false }, + text2speech: features.text_to_speech || { enabled: false }, + citation: features.retriever_resource || { enabled: false }, + moderation: features.sensitive_word_avoidance || { enabled: false }, + annotation: features.annotation_reply || { enabled: false }, + } + return ( - + = ({ }, [handleNodeDataUpdate, id, data]) return ( -
+
{ diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index d7b50f7efd..0bfa74bcd2 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -15,7 +15,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' const Panel: FC = () => { const appDetail = useAppStore(state => state.appDetail) - const runStaus = useStore(state => state.runStaus) + const runTaskId = useStore(state => state.runTaskId) const nodes = useNodes() const selectedNode = nodes.find(node => node.data._selected) const showRunHistory = useStore(state => state.showRunHistory) @@ -25,16 +25,16 @@ const Panel: FC = () => { showDebugAndPreviewPanel, } = useMemo(() => { return { - showWorkflowInfoPanel: appDetail?.mode === 'workflow' && !selectedNode, - showNodePanel: !!selectedNode, - showDebugAndPreviewPanel: appDetail?.mode === 'advanced-chat' && !selectedNode, + showWorkflowInfoPanel: appDetail?.mode === 'workflow' && !selectedNode && !runTaskId, + showNodePanel: !!selectedNode && !runTaskId, + showDebugAndPreviewPanel: appDetail?.mode === 'advanced-chat' && !selectedNode && !runTaskId, } - }, [selectedNode, appDetail]) + }, [selectedNode, appDetail, runTaskId]) return (
{ - runStaus && ( + runTaskId && ( ) } diff --git a/web/app/components/workflow/panel/run-history.tsx b/web/app/components/workflow/panel/run-history.tsx index 8b3f848d8b..22bc4e0e59 100644 --- a/web/app/components/workflow/panel/run-history.tsx +++ b/web/app/components/workflow/panel/run-history.tsx @@ -1,45 +1,33 @@ -import { useStore } from '../store' -import { - CheckCircle, - XClose, -} from '@/app/components/base/icons/src/vender/line/general' +import { memo } from 'react' +import { XClose } from '@/app/components/base/icons/src/vender/line/general' import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' +import { useStore } from '@/app/components/workflow/store' +import { useStore as useAppStore } from '@/app/components/app/store' const RunHistory = () => { - const mode = useStore(state => state.mode) - const setShowRunHistory = useStore(state => state.setShowRunHistory) - const setRunStaus = useStore(state => state.setRunStaus) + const appDetail = useAppStore(state => state.appDetail) return ( -
+
Run History
setShowRunHistory(false)} + onClick={() => useStore.setState({ showRunHistory: false })} >
- { - mode === 'workflow' && ( -
- -
-
Test Run#7
-
- Evan · 2 min ago -
-
-
- ) - }
setRunStaus('finished')} + className='flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer' + onClick={() => useStore.setState({ runTaskId: '1' })} > - + { + appDetail?.mode === 'advanced-chat' && ( + + ) + }
Test Run#6
@@ -52,4 +40,4 @@ const RunHistory = () => { ) } -export default RunHistory +export default memo(RunHistory) diff --git a/web/app/components/workflow/panel/workflow-info.tsx b/web/app/components/workflow/panel/workflow-info.tsx index 5b60365669..c3328063d7 100644 --- a/web/app/components/workflow/panel/workflow-info.tsx +++ b/web/app/components/workflow/panel/workflow-info.tsx @@ -14,7 +14,7 @@ const WorkflowInfo: FC = () => { return null return ( -
+
void + setRunTaskId: (runTaskId: string) => void setShowRunHistory: (showRunHistory: boolean) => void setShowFeaturesPanel: (showFeaturesPanel: boolean) => void - setRunStaus: (runStaus: string) => void setIsDragging: (isDragging: boolean) => void setHelpLine: (helpLine?: HelpLinePosition) => void setToolsets: (toolsets: CollectionWithExpanded[]) => void setToolsMap: (toolsMap: Record) => void setDraftUpdatedAt: (draftUpdatedAt: number) => void + setPublishedAt: (publishedAt: number) => void } export const useStore = create(set => ({ - mode: 'workflow', + mode: Mode.Editing, + runTaskId: '', + setRunTaskId: runTaskId => set(() => ({ runTaskId })), + setMode: mode => set(() => ({ mode })), showRunHistory: false, setShowRunHistory: showRunHistory => set(() => ({ showRunHistory })), showFeaturesPanel: false, setShowFeaturesPanel: showFeaturesPanel => set(() => ({ showFeaturesPanel })), - runStaus: '', - setRunStaus: runStaus => set(() => ({ runStaus })), isDragging: false, setIsDragging: isDragging => set(() => ({ isDragging })), helpLine: undefined, @@ -47,4 +52,6 @@ export const useStore = create(set => ({ setToolsMap: toolsMap => set(() => ({ toolsMap })), draftUpdatedAt: 0, setDraftUpdatedAt: draftUpdatedAt => set(() => ({ draftUpdatedAt })), + publishedAt: 0, + setPublishedAt: publishedAt => set(() => ({ publishedAt })), })) diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 62b601887a..963b39d8d3 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -139,3 +139,8 @@ export type NodeDefault = { } export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void + +export enum Mode { + Editing = 'editing', + Running = 'running', +} diff --git a/web/app/components/workflow/zoom-in-out.tsx b/web/app/components/workflow/zoom-in-out.tsx deleted file mode 100644 index f0bf5d0d24..0000000000 --- a/web/app/components/workflow/zoom-in-out.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import type { FC } from 'react' -import { - Fragment, - memo, - useState, -} from 'react' -import { useReactFlow } from 'reactflow' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import { SearchLg } from '@/app/components/base/icons/src/vender/line/general' -import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' - -const ZOOM_IN_OUT_OPTIONS = [ - [ - { - key: 'in', - text: 'Zoom In', - }, - { - key: 'out', - text: 'Zoom Out', - }, - ], - [ - { - key: 'to50', - text: 'Zoom to 50%', - }, - { - key: 'to100', - text: 'Zoom to 100%', - }, - ], - [ - { - key: 'fit', - text: 'Zoom to Fit', - }, - ], -] - -const ZoomInOut: FC = () => { - const reactFlow = useReactFlow() - const [open, setOpen] = useState(false) - - const handleZoom = (type: string) => { - if (type === 'in') - reactFlow.zoomIn() - - if (type === 'out') - reactFlow.zoomOut() - - if (type === 'fit') - reactFlow.fitView() - } - - return ( - - setOpen(v => !v)}> -
- - 100% - -
-
- -
- { - ZOOM_IN_OUT_OPTIONS.map((options, i) => ( - - { - i !== 0 && ( -
- ) - } -
- { - options.map(option => ( -
handleZoom(option.key)} - > - {option.text} -
- )) - } -
- - )) - } -
- - - ) -} - -export default memo(ZoomInOut) diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 7c76f47f1f..27bf73e4ef 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -6,6 +6,7 @@ const translation = { published: 'Published', publish: 'Publish', run: 'Run', + running: 'Running', inRunMode: 'In Run Mode', inPreview: 'In Preview', inPreviewMode: 'In Preview Mode', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index bdb99f7f68..9c405dc538 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -6,7 +6,8 @@ const translation = { published: '已发布', publish: '发布', run: '运行', - inRunMode: '运行中', + running: '运行中', + inRunMode: '在运行模式中', inPreview: '预览中', inPreviewMode: '预览中', preview: '预览',