mirror of https://github.com/langgenius/dify.git
feat: update workflow app publishing
This commit is contained in:
parent
e0a152164b
commit
56cb9ccec1
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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' })}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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: '没有变量',
|
||||
|
|
|
|||
Loading…
Reference in New Issue