feat: update workflow app publishing

This commit is contained in:
nite-knite 2024-04-02 17:33:14 +08:00
parent e0a152164b
commit 56cb9ccec1
24 changed files with 575 additions and 190 deletions

View File

@ -0,0 +1,173 @@
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import SuggestedAction from './suggested-action'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { PlayCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { CodeBrowser } from '@/app/components/base/icons/src/vender/line/development'
import { LeftIndent02 } from '@/app/components/base/icons/src/vender/line/editor'
import { FileText } from '@/app/components/base/icons/src/vender/line/files'
import { useGetLanguage } from '@/context/i18n'
export type AppPublisherProps = {
disabled?: boolean
publishedAt?: number
/** only needed in workflow / chatflow mode */
draftUpdatedAt?: number
onPublish?: () => Promise<void> | void
onRestore?: () => Promise<void> | void
onToggle?: (state: boolean) => void
}
const AppPublisher = ({
disabled = false,
publishedAt,
draftUpdatedAt,
onPublish,
onRestore,
onToggle,
}: AppPublisherProps) => {
const { t } = useTranslation()
const [published, setPublished] = useState(false)
const [open, setOpen] = useState(false)
const appDetail = useAppStore(state => state.appDetail)
const { app_base_url: appBaseURL, access_token } = appDetail?.site ?? {}
const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode
const appURL = `${appBaseURL}/${appMode}/${access_token}`
const language = useGetLanguage()
const formatTimeFromNow = useCallback((time: number) => {
return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow()
}, [language])
const handlePublish = async () => {
try {
await onPublish?.()
setPublished(true)
}
catch (e) {
setPublished(false)
}
}
const handleRestore = useCallback(async () => {
try {
await onRestore?.()
setOpen(false)
}
catch (e) { }
}, [onRestore])
const handleTrigger = useCallback(() => {
if (disabled) {
setOpen(false)
return
}
onToggle?.(!open)
if (open) {
setOpen(false)
}
else {
setOpen(true)
setPublished(false)
}
}, [disabled, onToggle, open])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 4,
crossAxis: -5,
}}
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<Button
type='primary'
className={`
pl-3 pr-2 py-0 h-8 text-[13px] font-medium
${disabled && 'cursor-not-allowed opacity-50'}
`}
>
{t('workflow.common.publish')}
<ChevronDown className='ml-0.5' />
</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 uppercase'>
{publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')}
</div>
{publishedAt
? (
<div className='flex justify-between items-center h-[18px]'>
<div className='flex items-center mt-[3px] mb-[3px] leading-[18px] text-[13px] font-medium text-gray-700'>
{t('workflow.common.publishedAt')} {formatTimeFromNow(publishedAt)}
</div>
<Button
className={`
ml-2 px-2 py-0 h-6 shadow-xs rounded-md text-xs font-medium text-primary-600 border-[0.5px] bg-white border-gray-200
${published && 'text-primary-300 border-gray-100'}
`}
onClick={handleRestore}
disabled={published}
>
{t('workflow.common.restore')}
</Button>
</div>
)
: (
<div className='flex items-center h-[18px] leading-[18px] text-[13px] font-medium text-gray-700'>
{t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)}
</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
${published && 'border-transparent'}
`}
onClick={handlePublish}
disabled={published}
>
{
published
? t('workflow.common.published')
: publishedAt ? t('workflow.common.update') : t('workflow.common.publish')
}
</Button>
</div>
<div className='p-4 pt-3 border-t-[0.5px] border-t-black/5'>
<SuggestedAction disabled={!publishedAt} link={appURL} icon={<PlayCircle />}>{t('workflow.common.runApp')}</SuggestedAction>
{appMode === 'chat'
? (
<SuggestedAction disabled={!publishedAt} link={appURL} icon={<CodeBrowser className='w-4 h-4' />}>{t('workflow.common.embedIntoSite')}</SuggestedAction>
)
: (
<SuggestedAction disabled={!publishedAt} link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} icon={<LeftIndent02 className='w-4 h-4' />}>{t('workflow.common.batchRunApp')}</SuggestedAction>
)}
<SuggestedAction disabled={!publishedAt} link='./develop' icon={<FileText className='w-4 h-4' />}>{t('workflow.common.accessAPIReference')}</SuggestedAction>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem >
)
}
export default memo(AppPublisher)

View File

@ -0,0 +1,19 @@
import type { PropsWithChildren } from 'react'
import classNames from 'classnames'
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
export type SuggestedActionProps = PropsWithChildren<{
icon?: React.ReactNode
link?: string
disabled?: boolean
}>
const SuggestedAction = ({ icon, link, disabled, children }: SuggestedActionProps) => (
<a href={disabled ? undefined : link} target='_blank' rel='noreferrer' className={classNames('flex justify-start items-center gap-2 h-[34px] px-2.5 bg-gray-100 rounded-lg transition-colors [&:not(:first-child)]:mt-1', disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'hover:bg-primary-50 hover:text-primary-600 cursor-pointer')}>
<div className='relative w-4 h-4'>{icon}</div>
<div className='grow shrink basis-0 text-[13px] font-medium leading-[18px]'>{children}</div>
<ArrowUpRight />
</a>
)
export default SuggestedAction

View File

@ -663,7 +663,7 @@ const Configuration: FC = () => {
}
if (isLoading) {
return <div className='flex h-full items-center justify-center'>
return <div className='flex items-center justify-center h-full'>
<Loading type='area' />
</div>
}
@ -743,10 +743,10 @@ const Configuration: FC = () => {
<div className="flex flex-col h-full">
<div className='relative flex grow h-[200px] pt-14'>
{/* Header */}
<div className='absolute top-0 left-0 w-full h-14 bg-white'>
<div className='flex justify-between items-center px-6 h-14'>
<div className='absolute top-0 left-0 w-full bg-white h-14'>
<div className='flex items-center justify-between px-6 h-14'>
<div className='flex items-center'>
<div className='leading-6 text-base font-semibold text-gray-900'>{t('appDebug.orchestrate')}</div>
<div className='text-base font-semibold leading-6 text-gray-900'>{t('appDebug.orchestrate')}</div>
<div className='flex items-center h-[14px] space-x-1 text-xs'>
{isAdvancedMode && (
<div className='ml-1 flex items-center h-5 px-1.5 border border-gray-100 rounded-md text-[11px] font-medium text-gray-500 uppercase'>{t('appDebug.promptMode.advanced')}</div>
@ -792,7 +792,7 @@ const Configuration: FC = () => {
{isMobile && (
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
<CodeBracketIcon className="w-4 h-4 text-gray-500" />
</Button>
)}
{debugWithMultipleModel
@ -807,14 +807,15 @@ const Configuration: FC = () => {
>
{t('appDebug.operation.applyConfig')}
</Button>)}
{/* <Publish /> */}
</div>
</div>
</div>
<div className={`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`}>
<Config />
</div>
{!isMobile && <div className="grow relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className='flex flex-col grow h-0 rounded-tl-2xl border-t border-l bg-gray-50 '>
{!isMobile && <div className="relative flex flex-col w-1/2 h-full overflow-y-auto grow " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className='flex flex-col h-0 border-t border-l grow rounded-tl-2xl bg-gray-50 '>
<Debug
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="code-browser">
<path id="Icon" d="M22 9H2M14 17.5L16.5 15L14 12.5M10 12.5L7.5 15L10 17.5M2 7.8L2 16.2C2 17.8802 2 18.7202 2.32698 19.362C2.6146 19.9265 3.07354 20.3854 3.63803 20.673C4.27976 21 5.11984 21 6.8 21H17.2C18.8802 21 19.7202 21 20.362 20.673C20.9265 20.3854 21.3854 19.9265 21.673 19.362C22 18.7202 22 17.8802 22 16.2V7.8C22 6.11984 22 5.27977 21.673 4.63803C21.3854 4.07354 20.9265 3.6146 20.362 3.32698C19.7202 3 18.8802 3 17.2 3L6.8 3C5.11984 3 4.27976 3 3.63803 3.32698C3.07354 3.6146 2.6146 4.07354 2.32698 4.63803C2 5.27976 2 6.11984 2 7.8Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 9.24995H12M21 3.99995L12 3.99995M21 14.75H3M21 20H3M4.28 2.95995L8.14667 5.85995C8.43616 6.07707 8.5809 6.18563 8.63266 6.31872C8.678 6.43529 8.678 6.56462 8.63266 6.68119C8.5809 6.81427 8.43616 6.92283 8.14667 7.13995L4.28 10.04C3.86802 10.3489 3.66203 10.5034 3.48961 10.4998C3.33956 10.4967 3.19885 10.4264 3.10632 10.3082C3 10.1724 3 9.91493 3 9.39995V3.59995C3 3.08498 3 2.82749 3.10632 2.6917C3.19885 2.57354 3.33956 2.50318 3.48961 2.50006C3.66203 2.49648 3.86802 2.65097 4.28 2.95995Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="file-text">
<path id="Icon" d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8M14 2L20 8M14 2V8H20M16 13H8M16 17H8M10 9H8" stroke="#101828" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@ -0,0 +1,13 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="play-circle" clip-path="url(#clip0_3607_26538)">
<g id="Icon">
<path d="M7.99992 14.6666C11.6818 14.6666 14.6666 11.6819 14.6666 7.99998C14.6666 4.31808 11.6818 1.33331 7.99992 1.33331C4.31802 1.33331 1.33325 4.31808 1.33325 7.99998C1.33325 11.6819 4.31802 14.6666 7.99992 14.6666Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66659 5.33331L10.6666 7.99998L6.66659 10.6666V5.33331Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
<defs>
<clipPath id="clip0_3607_26538">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 747 B

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "code-browser"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M22 9H2M14 17.5L16.5 15L14 12.5M10 12.5L7.5 15L10 17.5M2 7.8L2 16.2C2 17.8802 2 18.7202 2.32698 19.362C2.6146 19.9265 3.07354 20.3854 3.63803 20.673C4.27976 21 5.11984 21 6.8 21H17.2C18.8802 21 19.7202 21 20.362 20.673C20.9265 20.3854 21.3854 19.9265 21.673 19.362C22 18.7202 22 17.8802 22 16.2V7.8C22 6.11984 22 5.27977 21.673 4.63803C21.3854 4.07354 20.9265 3.6146 20.362 3.32698C19.7202 3 18.8802 3 17.2 3L6.8 3C5.11984 3 4.27976 3 3.63803 3.32698C3.07354 3.6146 2.6146 4.07354 2.32698 4.63803C2 5.27976 2 6.11984 2 7.8Z",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "CodeBrowser"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './CodeBrowser.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 = 'CodeBrowser'
export default Icon

View File

@ -1,6 +1,7 @@
export { default as ArtificialBrain } from './ArtificialBrain'
export { default as BarChartSquare02 } from './BarChartSquare02'
export { default as BracketsX } from './BracketsX'
export { default as CodeBrowser } from './CodeBrowser'
export { default as Container } from './Container'
export { default as Database01 } from './Database01'
export { default as Database03 } from './Database03'

View File

@ -0,0 +1,29 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M21 9.24995H12M21 3.99995L12 3.99995M21 14.75H3M21 20H3M4.28 2.95995L8.14667 5.85995C8.43616 6.07707 8.5809 6.18563 8.63266 6.31872C8.678 6.43529 8.678 6.56462 8.63266 6.68119C8.5809 6.81427 8.43616 6.92283 8.14667 7.13995L4.28 10.04C3.86802 10.3489 3.66203 10.5034 3.48961 10.4998C3.33956 10.4967 3.19885 10.4264 3.10632 10.3082C3 10.1724 3 9.91493 3 9.39995V3.59995C3 3.08498 3 2.82749 3.10632 2.6917C3.19885 2.57354 3.33956 2.50318 3.48961 2.50006C3.66203 2.49648 3.86802 2.65097 4.28 2.95995Z",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
"name": "LeftIndent02"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './LeftIndent02.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 = 'LeftIndent02'
export default Icon

View File

@ -1,5 +1,6 @@
export { default as AlignLeft } from './AlignLeft'
export { default as BezierCurve03 } from './BezierCurve03'
export { default as Colors } from './Colors'
export { default as LeftIndent02 } from './LeftIndent02'
export { default as LetterSpacing01 } from './LetterSpacing01'
export { default as TypeSquare } from './TypeSquare'

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "file-text"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8M14 2L20 8M14 2V8H20M16 13H8M16 17H8M10 9H8",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "FileText"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './FileText.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 = 'FileText'
export default Icon

View File

@ -6,3 +6,4 @@ export { default as FileCheck02 } from './FileCheck02'
export { default as FileDownload02 } from './FileDownload02'
export { default as FilePlus01 } from './FilePlus01'
export { default as FilePlus02 } from './FilePlus02'
export { default as FileText } from './FileText'

View File

@ -0,0 +1,86 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "play-circle",
"clip-path": "url(#clip0_3607_26538)"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M7.99992 14.6666C11.6818 14.6666 14.6666 11.6819 14.6666 7.99998C14.6666 4.31808 11.6818 1.33331 7.99992 1.33331C4.31802 1.33331 1.33325 4.31808 1.33325 7.99998C1.33325 11.6819 4.31802 14.6666 7.99992 14.6666Z",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.66659 5.33331L10.6666 7.99998L6.66659 10.6666V5.33331Z",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_3607_26538"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "PlayCircle"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './PlayCircle.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 = 'PlayCircle'
export default Icon

View File

@ -1,4 +1,5 @@
export { default as Microphone01 } from './Microphone01'
export { default as PlayCircle } from './PlayCircle'
export { default as Play } from './Play'
export { default as SlidersH } from './SlidersH'
export { default as Speaker } from './Speaker'

View File

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useBoolean, useClickAway } from 'ahooks'
import { XMarkIcon } from '@heroicons/react/24/outline'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import TabHeader from '../../base/tab-header'
import Button from '../../base/button'
import { checkOrSetAccessToken } from '../utils'
@ -71,10 +72,22 @@ const TextGeneration: FC<IMainProps> = ({
const isTablet = media === MediaType.tablet
const isMobile = media === MediaType.mobile
const [currTab, setCurrTab] = useState<string>('create')
const searchParams = useSearchParams()
const mode = searchParams.get('mode') || 'create'
const [currentTab, setCurrentTab] = useState<string>(['create', 'batch'].includes(mode) ? mode : 'create')
const router = useRouter()
const pathname = usePathname()
useEffect(() => {
const params = new URLSearchParams(searchParams)
params.delete('mode')
router.replace(`${pathname}?${params.toString()}`)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Notice this situation isCallBatchAPI but not in batch tab
const [isCallBatchAPI, setIsCallBatchAPI] = useState(false)
const isInBatchTab = currTab === 'batch'
const isInBatchTab = currentTab === 'batch'
const [inputs, setInputs] = useState<Record<string, any>>({})
const [appId, setAppId] = useState<string>('')
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
@ -361,7 +374,7 @@ const TextGeneration: FC<IMainProps> = ({
setCanReplaceLogo(can_replace_logo)
changeLanguage(siteInfo.default_language)
const { user_input_form, more_like_this, file_upload, text_to_speech, sensitive_word_avoidance }: any = appParams
const { user_input_form, more_like_this, file_upload, text_to_speech }: any = appParams
setVisionConfig({
...file_upload.image,
image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
@ -447,10 +460,10 @@ const TextGeneration: FC<IMainProps> = ({
}
>
<>
<div className='shrink-0 flex items-center justify-between'>
<div className='flex items-center justify-between shrink-0'>
<div className='flex items-center space-x-3'>
<div className={s.starIcon}></div>
<div className='text-lg text-gray-800 font-semibold'>{t('share.generation.title')}</div>
<div className='text-lg font-semibold text-gray-800'>{t('share.generation.title')}</div>
</div>
<div className='flex items-center space-x-2'>
{allFailedTaskList.length > 0 && (
@ -482,7 +495,7 @@ const TextGeneration: FC<IMainProps> = ({
</div>
</div>
<div className='grow overflow-y-auto'>
<div className='overflow-y-auto grow'>
{!isCallBatchAPI ? renderRes() : renderBatchRes()}
{!noPendingTask && (
<div className='mt-4'>
@ -515,10 +528,10 @@ const TextGeneration: FC<IMainProps> = ({
'shrink-0 relative flex flex-col pb-10 h-full border-r border-gray-100 bg-white',
)}>
<div className='mb-6'>
<div className='flex justify-between items-center'>
<div className='flex items-center justify-between'>
<div className='flex items-center space-x-3'>
<AppIcon size="small" icon={siteInfo.icon} background={siteInfo.icon_background || appDefaultIconBackground} />
<div className='text-lg text-gray-800 font-semibold'>{siteInfo.title}</div>
<div className='text-lg font-semibold text-gray-800'>{siteInfo.title}</div>
</div>
{!isPC && (
<Button
@ -555,11 +568,11 @@ const TextGeneration: FC<IMainProps> = ({
}]
: []),
]}
value={currTab}
onChange={setCurrTab}
value={currentTab}
onChange={setCurrentTab}
/>
<div className='grow h-20 overflow-y-auto'>
<div className={cn(currTab === 'create' ? 'block' : 'hidden')}>
<div className='h-20 overflow-y-auto grow'>
<div className={cn(currentTab === 'create' ? 'block' : 'hidden')}>
<RunOnce
siteInfo={siteInfo}
inputs={inputs}
@ -578,13 +591,13 @@ const TextGeneration: FC<IMainProps> = ({
/>
</div>
{currTab === 'saved' && (
{currentTab === 'saved' && (
<SavedItems
className='mt-4'
isShowTextToSpeech={textToSpeechConfig?.enabled}
list={savedMessages}
onRemove={handleRemoveSavedMessage}
onStartCreateContent={() => setCurrTab('create')}
onStartCreateContent={() => setCurrentTab('create')}
/>
)}
</div>

View File

@ -4,6 +4,7 @@ import {
useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import {
useStore,
useWorkflowStore,
@ -13,32 +14,41 @@ import {
useNodesSyncDraft,
useWorkflowRun,
} from '../hooks'
import AppPublisher from '../../app/app-publisher'
import { ToastContext } from '../../base/toast'
import RunAndHistory from './run-and-history'
import EditingTitle from './editing-title'
import RunningTitle from './running-title'
import RestoringTitle from './restoring-title'
import Publish from './publish'
import Checklist from './checklist'
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 { publishWorkflow } from '@/service/workflow'
const Header: FC = () => {
const { t } = useTranslation()
const workflowStore = useWorkflowStore()
const appDetail = useAppStore(s => s.appDetail)
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
const appID = useAppStore(state => state.appDetail?.id)
const {
nodesReadOnly,
getNodesReadOnly,
} = useNodesReadOnly()
const isRestoring = useStore(s => s.isRestoring)
const publishedAt = useStore(s => s.publishedAt)
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
const {
handleLoadBackupDraft,
handleRunSetting,
handleCheckBeforePublish,
handleBackupDraft,
handleRestoreFromPublishedWorkflow,
} = useWorkflowRun()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { notify } = useContext(ToastContext)
const handleShowFeatures = useCallback(() => {
const {
@ -65,9 +75,31 @@ const Header: FC = () => {
handleSyncWorkflowDraft(true)
}, [handleSyncWorkflowDraft, workflowStore])
const onPublish = useCallback(async () => {
if (handleCheckBeforePublish()) {
const res = await publishWorkflow(`/apps/${appID}/workflows/publish`)
if (res) {
notify({ type: 'success', message: t('common.api.actionSuccess') })
workflowStore.getState().setPublishedAt(res.created_at)
}
}
}, [appID, handleCheckBeforePublish, notify, t, workflowStore])
const onStartRestoring = useCallback(() => {
workflowStore.setState({ isRestoring: true })
handleBackupDraft()
handleRestoreFromPublishedWorkflow()
}, [handleBackupDraft, handleRestoreFromPublishedWorkflow, workflowStore])
const onPublisherToggle = useCallback((state: boolean) => {
if (state)
handleSyncWorkflowDraft(true)
}, [handleSyncWorkflowDraft])
return (
<div
className='absolute top-0 left-0 flex items-center justify-between px-3 w-full h-14 z-10'
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14'
style={{
background: 'linear-gradient(180deg, #F9FAFB 0%, rgba(249, 250, 251, 0.00) 100%)',
}}
@ -100,7 +132,7 @@ const Header: FC = () => {
`}
onClick={handleGoBackToEdit}
>
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
<ArrowNarrowLeft className='w-4 h-4 mr-1' />
{t('workflow.common.goBackToEdit')}
</Button>
)
@ -115,10 +147,19 @@ const Header: FC = () => {
`}
onClick={handleShowFeatures}
>
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
<Grid01 className='w-4 h-4 mr-1 text-gray-500' />
{t('workflow.common.features')}
</Button>
<Publish />
<AppPublisher
{...{
publishedAt,
draftUpdatedAt,
disabled: Boolean(getNodesReadOnly()),
onPublish,
onRestore: onStartRestoring,
onToggle: onPublisherToggle,
}}
/>
{
!nodesReadOnly && (
<>
@ -140,7 +181,7 @@ const Header: FC = () => {
`}
onClick={handleShowFeatures}
>
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
<Grid01 className='w-4 h-4 mr-1 text-gray-500' />
{t('workflow.common.features')}
</Button>
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>

View File

@ -1,163 +0,0 @@
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
useStore,
useWorkflowStore,
} from '../store'
import {
useNodesReadOnly,
useNodesSyncDraft,
useWorkflow,
useWorkflowRun,
} from '../hooks'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { publishWorkflow } from '@/service/workflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useToastContext } from '@/app/components/base/toast'
const Publish = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const [published, setPublished] = useState(false)
const workflowStore = useWorkflowStore()
const { formatTimeFromNow } = useWorkflow()
const {
handleBackupDraft,
handleCheckBeforePublish,
handleRestoreFromPublishedWorkflow,
} = useWorkflowRun()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const {
nodesReadOnly,
getNodesReadOnly,
} = useNodesReadOnly()
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
const publishedAt = useStore(s => s.publishedAt)
const [open, setOpen] = useState(false)
const handlePublish = async () => {
const appId = useAppStore.getState().appDetail?.id
if (handleCheckBeforePublish()) {
try {
const res = await publishWorkflow(`/apps/${appId}/workflows/publish`)
if (res) {
notify({ type: 'success', message: t('common.api.actionSuccess') })
setPublished(true)
workflowStore.getState().setPublishedAt(res.created_at)
}
}
catch (e) {
setPublished(false)
}
}
}
const handleRestore = useCallback(() => {
workflowStore.getState().setIsRestoring(true)
handleBackupDraft()
handleRestoreFromPublishedWorkflow()
setOpen(false)
}, [workflowStore, handleBackupDraft, handleRestoreFromPublishedWorkflow])
const handleTrigger = useCallback(() => {
if (getNodesReadOnly())
return
if (open)
setOpen(false)
if (!open) {
handleSyncWorkflowDraft(true)
setOpen(true)
setPublished(false)
}
}, [getNodesReadOnly, open, handleSyncWorkflowDraft])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 4,
crossAxis: -5,
}}
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<Button
type='primary'
className={`
px-3 py-0 h-8 text-[13px] font-medium
${nodesReadOnly && 'cursor-not-allowed opacity-50'}
`}
>
{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'>
{t('workflow.common.currentDraft').toLocaleUpperCase()}
</div>
<div className='flex items-center h-[18px] text-[13px] font-medium text-gray-700'>
{t('workflow.common.autoSaved')} {formatTimeFromNow(draftUpdatedAt)}
</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
${published && 'border-transparent'}
`}
onClick={handlePublish}
disabled={published}
>
{
published
? t('workflow.common.published')
: t('workflow.common.publish')
}
</Button>
</div>
{
!!publishedAt && (
<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'>
{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'>
{t('workflow.common.autoSaved')} {formatTimeFromNow(publishedAt)}
</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
${published && 'opacity-50 border-transparent shadow-none bg-transparent'}
`}
onClick={handleRestore}
disabled={published}
>
{t('workflow.common.restore')}
</Button>
</div>
</div>
)
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(Publish)

View File

@ -5,6 +5,7 @@ const translation = {
unpublished: 'Unpublished',
published: 'Published',
publish: 'Publish',
update: 'Update',
run: 'Run',
running: 'Running',
inRunMode: 'In Run Mode',
@ -19,8 +20,14 @@ const translation = {
debugAndPreview: 'Debug and Preview',
restart: 'Restart',
currentDraft: 'Current Draft',
currentDraftUnpublished: 'Current Draft Unpublished',
latestPublished: 'Latest Published',
publishedAt: 'Published',
restore: 'Restore',
runApp: 'Run App',
batchRunApp: 'Batch Run App',
accessAPIReference: 'Access API Reference',
embedIntoSite: 'Embed Into Site',
addTitle: 'Add title...',
addDescription: 'Add description...',
noVar: 'No variable',

View File

@ -5,6 +5,7 @@ const translation = {
unpublished: '未发布',
published: '已发布',
publish: '发布',
update: '更新',
run: '运行',
running: '运行中',
inRunMode: '在运行模式中',
@ -19,8 +20,14 @@ const translation = {
debugAndPreview: '调试和预览',
restart: '重新开始',
currentDraft: '当前草稿',
currentDraftUnpublished: '当前草稿未发布',
latestPublished: '最新发布',
publishedAt: '发布于',
restore: '恢复',
runApp: '运行',
batchRunApp: '批量运行',
accessAPIReference: '访问 API',
embedIntoSite: '嵌入网站',
addTitle: '添加标题...',
addDescription: '添加描述...',
noVar: '没有变量',