This commit is contained in:
StyleZhang
2024-03-06 17:45:55 +08:00
parent 4edaa95cbf
commit 0164dec438
28 changed files with 404 additions and 336 deletions

View File

@ -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 (
<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>

View File

@ -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 (
<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)

View File

@ -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>
</>
)
}
</div>
</div>
{
mode === Mode.Editing && !runTaskId && <EditingTitle />
}
{
(mode === Mode.Running || runTaskId) && <RunningTitle />
}
</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>
)
}

View File

@ -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')}
</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>

View File

@ -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 (
<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'
? (
<>
<Loading02 className='mr-1 w-4 h-4 animate-spin' />
Running
{
appDetail?.mode === 'workflow' && (
<Loading02 className='mr-1 w-4 h-4 animate-spin' />
)
}
{
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>

View File

@ -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 (
<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)

View File

@ -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}

View File

@ -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'>
{

View File

@ -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 />
)
}

View File

@ -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 (
<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' })}
>
<AlertCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#F79009]' />
{
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)

View File

@ -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

View File

@ -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 })),
}))

View File

@ -139,3 +139,8 @@ export type NodeDefault<T> = {
}
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
export enum Mode {
Editing = 'editing',
Running = 'running',
}

View File

@ -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 (
<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)