Commit 0164dec4 authored by StyleZhang's avatar StyleZhang

features

parent 4edaa95c
'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<IFeatureItemProps> = ({
......@@ -21,7 +23,12 @@ const FeatureItem: FC<IFeatureItemProps> = ({
description,
value,
onChange,
type,
}) => {
const handleChange = useCallback((newValue: boolean) => {
onChange(type, newValue)
}, [type, onChange])
return (
<div className={cn(s.wrap, 'relative flex justify-between p-3 rounded-xl border border-transparent bg-gray-50 hover:border-gray-200 cursor-pointer')}>
<div className='flex space-x-3 mr-2'>
......@@ -40,7 +47,7 @@ const FeatureItem: FC<IFeatureItemProps> = ({
</div>
</div>
<Switch onChange={onChange} defaultValue={value} />
<Switch onChange={handleChange} defaultValue={value} />
{
previewImgClassName && (
<div className={cn(s.preview, s[previewImgClassName])}>
......
......@@ -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<ChooseFeatureProps> = ({
const FeatureModal: FC<FeatureModalProps> = ({
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 (
<Modal
isShow
......@@ -67,20 +76,18 @@ const ChooseFeature: FC<ChooseFeatureProps> = ({
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}
/>
<FeatureItem
icon={<SuggestedQuestionsAfterAnswerIcon />}
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<ChooseFeatureProps> = ({
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<ChooseFeatureProps> = ({
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<ChooseFeatureProps> = ({
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}
/>
</>
</FeatureGroup>
......@@ -130,19 +134,17 @@ const ChooseFeature: FC<ChooseFeatureProps> = ({
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}
/>
<FeatureItem
icon={<MessageFast className='w-4 h-4 text-[#444CE7]' />}
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}
/>
</>
</FeatureGroup>
......@@ -150,4 +152,4 @@ const ChooseFeature: FC<ChooseFeatureProps> = ({
</Modal>
)
}
export default React.memo(ChooseFeature)
export default React.memo(FeatureModal)
......@@ -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 (
<div className='space-y-3'>
......@@ -55,27 +49,27 @@ const FeaturePanel = ({
</div>
<div className='py-2 space-y-2'>
{
openingStatement.enabled && (
features.opening.enabled && (
<OpeningStatement {...openingStatementProps} />
)
}
{
suggestedQuestionsAfterAnswer.enabled && (
features.suggested.enabled && (
<SuggestedQuestionsAfterAnswer />
)
}
{
textToSpeech.enabled && (
features.text2speech.enabled && (
<TextToSpeech />
)
}
{
speechToText.enabled && (
features.speech2text.enabled && (
<SpeechToText />
)
}
{
citation.enabled && (
features.citation.enabled && (
<Citation />
)
}
......@@ -97,12 +91,12 @@ const FeaturePanel = ({
</div>
<div className='py-2 space-y-2'>
{
moderation.enabled && (
features.moderation.enabled && (
<Moderation />
)
}
{
annotation.enabled && (
features.annotation.enabled && (
<Annotation {...annotationProps} />
)
}
......
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
}))
},
})
}
......
......@@ -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<OpeningStatementProps> = ({
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<string[]>([])
......@@ -103,23 +106,41 @@ const OpeningStatement: FC<OpeningStatementProps> = ({
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()
......
......@@ -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\/([^/]+)/)
......
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,16 +20,17 @@ export type FeaturesStore = ReturnType<typeof createFeaturesStore>
export const createFeaturesStore = (initProps?: Partial<FeaturesState>) => {
const DEFAULT_PROPS: FeaturesState = {
openingStatement: {
features: {
opening: {
enabled: false,
},
suggestedQuestionsAfterAnswer: {
suggested: {
enabled: false,
},
textToSpeech: {
text2speech: {
enabled: false,
},
speechToText: {
speech2text: {
enabled: false,
},
citation: {
......@@ -61,18 +42,12 @@ export const createFeaturesStore = (initProps?: Partial<FeaturesState>) => {
annotation: {
enabled: false,
},
},
}
return createStore<FeatureStoreState>()(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 })),
}))
......
......@@ -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
}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="play">
<path 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="#155EEF"/>
</g>
</svg>
{
"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
// 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<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Play'
export default Icon
......@@ -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'
......
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 (
<div className='fixed top-16 left-2 bottom-2 w-[600px] rounded-2xl border-[0.5px] border-gray-200 bg-white shadow-xl z-10'>
<div className='flex items-center justify-between px-4 pt-3'>
Features
{t('workflow.common.features')}
<div className='flex items-center'>
<FeaturesChoose />
<div className='mx-3 w-[1px] h-[14px] bg-gray-200'></div>
......
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 (
<div className='flex items-center h-[18px] text-xs text-gray-500'>
<Edit03 className='mr-1 w-3 h-3 text-gray-400' />
{t('workflow.common.editing')}
{
draftUpdatedAt && (
<>
<span className='flex items-center mx-1'>·</span>
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
</>
)
}
<span className='flex items-center mx-1'>·</span>
{
publishedAt
? `${t('workflow.common.published')} ${dayjs(publishedAt).fromNow()}`
: t('workflow.common.unpublished')
}
</div>
)
}
export default memo(EditingTitle)
......@@ -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 (
<div
......@@ -33,35 +34,25 @@ const Header: FC = () => {
>
<div>
<div className='text-xs font-medium text-gray-700'>{appDetail?.name}</div>
<div className='flex items-center'>
<div className='flex items-center text-xs text-gray-500'>
<Edit03 className='mr-1 w-3 h-3 text-gray-400' />
Editing
{
draftUpdatedAt && (
<>
<span className='flex items-center mx-1'>·</span>
<span>
Auto-Saved {dayjs(draftUpdatedAt).format('HH:mm:ss')}
</span>
</>
)
mode === Mode.Editing && !runTaskId && <EditingTitle />
}
{
(mode === Mode.Running || runTaskId) && <RunningTitle />
}
</div>
</div>
</div>
<div className='flex items-center'>
{
runStaus && (
(mode === Mode.Running || runTaskId) && (
<Button
className={`
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
border-[0.5px] border-gray-200 shadow-xs
`}
onClick={() => setRunStaus('')}
onClick={() => useStore.setState({ mode: Mode.Editing, runTaskId: '' })}
>
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
Go back to editor
{t('workflow.common.goBackToEdit')}
</Button>
)
}
......@@ -77,7 +68,7 @@ const Header: FC = () => {
onClick={handleShowFeatures}
>
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
Features
{t('workflow.common.features')}
</Button>
)
}
......
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')}
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[11]'>
<div className='w-[320px] bg-white rounded-2xl border-[0.5px] border-gray-200 shadow-xl'>
<div className='p-4 pt-3'>
<div className='flex items-center h-6 text-xs font-medium text-gray-500'>
Current Draft
{t('workflow.common.currentDraft').toLocaleUpperCase()}
</div>
<div className='flex items-center h-[18px] text-[13px] font-medium text-gray-700'>
Auto-Saved 3 min ago · Evan
{t('workflow.common.autoSaved')} 3 min ago · Evan
</div>
<Button
type='primary'
className='mt-3 px-3 py-0 w-full h-8 border-[0.5px] border-primary-700 rounded-lg text-[13px] font-medium'
>
Publish
{t('workflow.common.publish')}
</Button>
</div>
<div className='p-4 pt-3 border-t-[0.5px] border-t-black/5'>
<div className='flex items-center h-6 text-xs font-medium text-gray-500'>
Latest Published
{t('workflow.common.latestPublished').toLocaleUpperCase()}
</div>
<div className='flex justify-between'>
<div className='flex items-center mt-[3px] mb-[3px] leading-[18px] text-[13px] font-medium text-gray-700'>
Auto-Saved 3 min ago · Evan
{t('workflow.common.autoSaved')} 3 min ago · Evan
</div>
<Button className='ml-2 px-2 py-0 h-6 shadow-xs rounded-md text-xs font-medium text-gray-700 border-[0.5px] border-gray-200'>
Restore
{t('workflow.common.restore')}
</Button>
</div>
</div>
......
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 (
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
......@@ -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'
? (
<>
{
appDetail?.mode === 'workflow' && (
<Loading02 className='mr-1 w-4 h-4 animate-spin' />
Running
)
}
{
appDetail?.mode === 'workflow'
? t('workflow.common.running')
: t('workflow.common.inPreview')
}
</>
)
: (
<>
<Play className='mr-1 w-4 h-4' />
Run
{
appDetail?.mode === 'workflow'
? t('workflow.common.run')
: t('workflow.common.preview')
}
</>
)
}
</div>
<div className='mx-0.5 w-[0.5px] h-8 bg-gray-200'></div>
<TooltipPlus
popupContent='View run history'
popupContent={t('workflow.common.viewRunHistory')}
>
<div
className={`
flex items-center justify-center w-7 h-7 rounded-md hover:bg-black/5 cursor-pointer
${showRunHistory && 'bg-black/5'}
${showRunHistory && 'bg-primary-50'}
`}
onClick={() => setShowRunHistory(true)}
onClick={() => useStore.setState({ showRunHistory: true })}
>
<ClockPlay className='w-4 h-4 text-gray-500' />
<ClockPlay className={`w-4 h-4 ${showRunHistory ? 'text-primary-600' : 'text-gray-500'}`} />
</div>
</TooltipPlus>
</div>
......
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 (
<div className='flex items-center h-[18px] text-xs text-primary-600'>
<Play className='mr-1 w-3 h-3' />
{
appDetail?.mode === 'advanced-chat'
? t('workflow.common.inPreviewMode')
: t('workflow.common.inRunMode')
}
<span className='mx-1'>·</span>
<span className='text-gray-500'>Test Run#2</span>
</div>
)
}
export default memo(RunningTitle)
......@@ -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<WorkflowProps> = ({
)
}
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 (
<ReactFlowProvider>
<FeaturesProvider>
<FeaturesProvider features={initialFeatures}>
<Workflow
nodes={nodesData}
edges={edgesData}
......
......@@ -48,7 +48,7 @@ const BasePanel: FC<BasePanelProps> = ({
}, [handleNodeDataUpdate, id, data])
return (
<div className='mr-2 w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
<div className='w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5 z-10'>
<div className='flex items-center px-4 pt-4 pb-1'>
{
......
......@@ -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<CommonNodeType>()
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 (
<div className='absolute top-14 right-0 bottom-2 flex z-10'>
{
runStaus && (
runTaskId && (
<Record />
)
}
......
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 (
<div className='w-[200px] h-full bg-white border-[0.5px] border-gray-200 shadow-xl rounded-l-2xl'>
<div className='ml-2 w-[200px] h-full bg-white border-[0.5px] border-gray-200 shadow-xl rounded-l-2xl'>
<div className='flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'>
Run History
<div
className='flex items-center justify-center w-6 h-6 cursor-pointer'
onClick={() => setShowRunHistory(false)}
onClick={() => useStore.setState({ showRunHistory: false })}
>
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div>
<div className='p-2'>
{
mode === 'workflow' && (
<div className='flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'>
<CheckCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#12B76A]' />
<div>
<div className='flex items-center text-[13px] font-medium text-primary-600 leading-[18px]'>Test Run#7</div>
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
Evan · 2 min ago
</div>
</div>
</div>
)
}
<div
className='flex px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'
onClick={() => 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' && (
<AlertCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#F79009]' />
)
}
<div>
<div className='flex items-center text-[13px] font-medium text-primary-600 leading-[18px]'>Test Run#6</div>
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
......@@ -52,4 +40,4 @@ const RunHistory = () => {
)
}
export default RunHistory
export default memo(RunHistory)
......@@ -14,7 +14,7 @@ const WorkflowInfo: FC = () => {
return null
return (
<div className='mr-2 w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
<div className='w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5'>
<div className='flex pt-4 px-4 pb-1'>
<AppIcon
......
......@@ -5,38 +5,43 @@ import type {
ToolInWorkflow,
ToolsMap,
} from './block-selector/types'
import { Mode } from './types'
type State = {
mode: string
mode: Mode
runTaskId: string
showRunHistory: boolean
showFeaturesPanel: boolean
runStaus: string
isDragging: boolean
helpLine?: HelpLinePosition
toolsets: CollectionWithExpanded[]
toolsMap: ToolsMap
draftUpdatedAt: number
publishedAt: number
}
type Action = {
setMode: (mode: Mode) => 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<string, ToolInWorkflow[]>) => void
setDraftUpdatedAt: (draftUpdatedAt: number) => void
setPublishedAt: (publishedAt: number) => void
}
export const useStore = create<State & Action>(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<State & Action>(set => ({
setToolsMap: toolsMap => set(() => ({ toolsMap })),
draftUpdatedAt: 0,
setDraftUpdatedAt: draftUpdatedAt => set(() => ({ draftUpdatedAt })),
publishedAt: 0,
setPublishedAt: publishedAt => set(() => ({ publishedAt })),
}))
......@@ -139,3 +139,8 @@ export type NodeDefault<T> = {
}
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
export enum Mode {
Editing = 'editing',
Running = 'running',
}
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 (
<PortalToFollowElem
placement='top-start'
open={open}
onOpenChange={setOpen}
offset={4}
>
<PortalToFollowElemTrigger asChild onClick={() => setOpen(v => !v)}>
<div className={`
absolute left-6 bottom-6
flex items-center px-2.5 h-9 cursor-pointer rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg
text-[13px] text-gray-500 z-10
`}>
<SearchLg className='mr-1 w-4 h-4' />
100%
<ChevronDown className='ml-1 w-4 h-4' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='w-[168px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
{
ZOOM_IN_OUT_OPTIONS.map((options, i) => (
<Fragment key={i}>
{
i !== 0 && (
<div className='h-[1px] bg-gray-100' />
)
}
<div className='p-1'>
{
options.map(option => (
<div
key={option.key}
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700'
onClick={() => handleZoom(option.key)}
>
{option.text}
</div>
))
}
</div>
</Fragment>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(ZoomInOut)
......@@ -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',
......
......@@ -6,7 +6,8 @@ const translation = {
published: '已发布',
publish: '发布',
run: '运行',
inRunMode: '运行中',
running: '运行中',
inRunMode: '在运行模式中',
inPreview: '预览中',
inPreviewMode: '预览中',
preview: '预览',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment