| | |
| | | } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import dayjs from 'dayjs' |
| | | import { |
| | | RiArrowDownSLine, |
| | | RiPlanetLine, |
| | | RiPlayCircleLine, |
| | | RiPlayList2Line, |
| | | RiTerminalBoxLine, |
| | | } from '@remixicon/react' |
| | | import { useKeyPress } from 'ahooks' |
| | | import { RiArrowDownSLine, RiPlanetLine } from '@remixicon/react' |
| | | import Toast from '../../base/toast' |
| | | import type { ModelAndParameter } from '../configuration/debug/types' |
| | | import { getKeyboardKeyCodeBySystem } from '../../workflow/utils' |
| | | import SuggestedAction from './suggested-action' |
| | | import PublishWithMultipleModel from './publish-with-multiple-model' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | PortalToFollowElemContent, |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | import { WEB_PREFIX } from '@/config' |
| | | import { fetchInstalledAppList } from '@/service/explore' |
| | | import EmbeddedModal from '@/app/components/app/overview/embedded' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { useGetLanguage } from '@/context/i18n' |
| | | 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 WorkflowToolConfigureButton from '@/app/components/tools/workflow-tool/configure-button' |
| | | import type { InputVar } from '@/app/components/workflow/types' |
| | | import { appDefaultIconBackground } from '@/config' |
| | | import type { PublishWorkflowParams } from '@/types/workflow' |
| | | |
| | | export type AppPublisherProps = { |
| | | disabled?: boolean |
| | |
| | | debugWithMultipleModel?: boolean |
| | | multipleModelConfigs?: ModelAndParameter[] |
| | | /** modelAndParameter is passed when debugWithMultipleModel is true */ |
| | | onPublish?: (params?: any) => Promise<any> | any |
| | | onPublish?: (modelAndParameter?: ModelAndParameter) => Promise<any> | any |
| | | onRestore?: () => Promise<any> | any |
| | | onToggle?: (state: boolean) => void |
| | | crossAxisOffset?: number |
| | |
| | | inputs?: InputVar[] |
| | | onRefreshData?: () => void |
| | | } |
| | | |
| | | const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P'] |
| | | |
| | | const AppPublisher = ({ |
| | | disabled = false, |
| | |
| | | const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} |
| | | const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode |
| | | const appURL = `${appBaseURL}/${appMode}/${accessToken}` |
| | | const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') |
| | | |
| | | const language = useGetLanguage() |
| | | const formatTimeFromNow = useCallback((time: number) => { |
| | | return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow() |
| | | }, [language]) |
| | | |
| | | const handlePublish = useCallback(async (params?: ModelAndParameter | PublishWorkflowParams) => { |
| | | const handlePublish = async (modelAndParameter?: ModelAndParameter) => { |
| | | try { |
| | | await onPublish?.(params) |
| | | await onPublish?.(modelAndParameter) |
| | | setPublished(true) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | setPublished(false) |
| | | } |
| | | }, [onPublish]) |
| | | } |
| | | |
| | | const handleRestore = useCallback(async () => { |
| | | try { |
| | | await onRestore?.() |
| | | setOpen(false) |
| | | } |
| | | catch {} |
| | | catch (e) { } |
| | | }, [onRestore]) |
| | | |
| | | const handleTrigger = useCallback(() => { |
| | |
| | | try { |
| | | const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {} |
| | | if (installed_apps?.length > 0) |
| | | window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | else |
| | | throw new Error('No app found in Explore') |
| | | } |
| | |
| | | |
| | | const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) |
| | | |
| | | useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { |
| | | e.preventDefault() |
| | | if (publishDisabled || published) |
| | | return |
| | | handlePublish() |
| | | }, |
| | | { exactMatch: true, useCapture: true }) |
| | | |
| | | return ( |
| | | <> |
| | | <PortalToFollowElem |
| | | open={open} |
| | | onOpenChange={setOpen} |
| | | placement='bottom-end' |
| | | offset={{ |
| | | mainAxis: 4, |
| | | crossAxis: crossAxisOffset, |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={handleTrigger}> |
| | | <Button |
| | | variant='primary' |
| | | className='p-2' |
| | | disabled={disabled} |
| | | > |
| | | {t('workflow.common.publish')} |
| | | <RiArrowDownSLine className='h-4 w-4 text-components-button-primary-text' /> |
| | | </Button> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[11]'> |
| | | <div className='w-[320px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'> |
| | | <div className='p-4 pt-3'> |
| | | <div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'> |
| | | {publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')} |
| | | </div> |
| | | {publishedAt |
| | | ? ( |
| | | <div className='flex items-center justify-between'> |
| | | <div className='system-sm-medium flex items-center text-text-secondary'> |
| | | {t('workflow.common.publishedAt')} {formatTimeFromNow(publishedAt)} |
| | | </div> |
| | | {isChatApp && <Button |
| | | variant='secondary-accent' |
| | | size='small' |
| | | onClick={handleRestore} |
| | | disabled={published} |
| | | > |
| | | {t('workflow.common.restore')} |
| | | </Button>} |
| | | <PortalToFollowElem |
| | | open={open} |
| | | onOpenChange={setOpen} |
| | | placement='bottom-end' |
| | | offset={{ |
| | | mainAxis: 4, |
| | | crossAxis: crossAxisOffset, |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={handleTrigger}> |
| | | <Button |
| | | variant='primary' |
| | | className='pl-3 pr-2' |
| | | disabled={disabled} |
| | | > |
| | | {t('workflow.common.publish')} |
| | | <RiArrowDownSLine className='w-4 h-4 ml-0.5' /> |
| | | </Button> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[11]'> |
| | | <div className='w-[336px] 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> |
| | | ) |
| | | : ( |
| | | <div className='system-sm-medium flex items-center text-text-secondary'> |
| | | {t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)} |
| | | </div> |
| | | )} |
| | | {debugWithMultipleModel |
| | | ? ( |
| | | <PublishWithMultipleModel |
| | | multipleModelConfigs={multipleModelConfigs} |
| | | onSelect={item => handlePublish(item)} |
| | | // textGenerationModelList={textGenerationModelList} |
| | | /> |
| | | ) |
| | | : ( |
| | | <Button |
| | | variant='primary' |
| | | className='mt-3 w-full' |
| | | onClick={() => handlePublish()} |
| | | disabled={publishDisabled || published} |
| | | className={` |
| | | ml-2 px-2 text-primary-600 |
| | | ${published && 'text-primary-300 border-gray-100'} |
| | | `} |
| | | size='small' |
| | | onClick={handleRestore} |
| | | disabled={published} |
| | | > |
| | | { |
| | | published |
| | | ? t('workflow.common.published') |
| | | : ( |
| | | <div className='flex gap-1'> |
| | | <span>{t('workflow.common.publishUpdate')}</span> |
| | | <div className='flex gap-0.5'> |
| | | {PUBLISH_SHORTCUT.map(key => ( |
| | | <span key={key} className='system-kbd h-4 w-4 rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface'> |
| | | {key} |
| | | </span> |
| | | ))} |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | {t('workflow.common.restore')} |
| | | </Button> |
| | | ) |
| | | } |
| | | </div> |
| | | <div className='border-t-[0.5px] border-t-divider-regular p-4 pt-3'> |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link={appURL} |
| | | icon={<RiPlayCircleLine className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.runApp')} |
| | | </SuggestedAction> |
| | | {appDetail?.mode === 'workflow' || appDetail?.mode === 'completion' |
| | | ? ( |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} |
| | | icon={<RiPlayList2Line className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.batchRunApp')} |
| | | </SuggestedAction> |
| | | ) |
| | | : ( |
| | | <SuggestedAction |
| | | onClick={() => { |
| | | setEmbeddingModalOpen(true) |
| | | handleTrigger() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<CodeBrowser className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.embedIntoSite')} |
| | | </SuggestedAction> |
| | | )} |
| | | <SuggestedAction |
| | | onClick={() => { |
| | | publishedAt && handleOpenInExplore() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<RiPlanetLine className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.openInExplore')} |
| | | </SuggestedAction> |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link='./develop' |
| | | icon={<RiTerminalBoxLine className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.accessAPIReference')} |
| | | </SuggestedAction> |
| | | {appDetail?.mode === 'workflow' && ( |
| | | <WorkflowToolConfigureButton |
| | | disabled={!publishedAt} |
| | | published={!!toolPublished} |
| | | detailNeedUpdate={!!toolPublished && published} |
| | | workflowAppId={appDetail?.id} |
| | | icon={{ |
| | | content: (appDetail.icon_type === 'image' ? '🤖' : appDetail?.icon) || '🤖', |
| | | background: (appDetail.icon_type === 'image' ? appDefaultIconBackground : appDetail?.icon_background) || appDefaultIconBackground, |
| | | }} |
| | | name={appDetail?.name} |
| | | description={appDetail?.description} |
| | | inputs={inputs} |
| | | handlePublish={handlePublish} |
| | | onRefreshData={onRefreshData} |
| | | /> |
| | | </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> |
| | | )} |
| | | </div> |
| | | {debugWithMultipleModel |
| | | ? ( |
| | | <PublishWithMultipleModel |
| | | multipleModelConfigs={multipleModelConfigs} |
| | | onSelect={item => handlePublish(item)} |
| | | // textGenerationModelList={textGenerationModelList} |
| | | /> |
| | | ) |
| | | : ( |
| | | <Button |
| | | variant='primary' |
| | | className='w-full mt-3' |
| | | onClick={() => handlePublish()} |
| | | disabled={publishDisabled || published} |
| | | > |
| | | { |
| | | published |
| | | ? t('workflow.common.published') |
| | | : publishedAt ? t('workflow.common.update') : t('workflow.common.publish') |
| | | } |
| | | </Button> |
| | | ) |
| | | } |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | | <EmbeddedModal |
| | | siteInfo={appDetail?.site} |
| | | isShow={embeddingModalOpen} |
| | | onClose={() => setEmbeddingModalOpen(false)} |
| | | appBaseUrl={appBaseURL} |
| | | accessToken={accessToken} |
| | | /> |
| | | </PortalToFollowElem > |
| | | </> |
| | | <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> |
| | | {appDetail?.mode === 'workflow' |
| | | ? ( |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} |
| | | icon={<LeftIndent02 className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.batchRunApp')} |
| | | </SuggestedAction> |
| | | ) |
| | | : ( |
| | | <SuggestedAction |
| | | onClick={() => { |
| | | setEmbeddingModalOpen(true) |
| | | handleTrigger() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<CodeBrowser className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.embedIntoSite')} |
| | | </SuggestedAction> |
| | | )} |
| | | <SuggestedAction |
| | | onClick={() => { |
| | | handleOpenInExplore() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<RiPlanetLine className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.openInExplore')} |
| | | </SuggestedAction> |
| | | <SuggestedAction disabled={!publishedAt} link='./develop' icon={<FileText className='w-4 h-4' />}>{t('workflow.common.accessAPIReference')}</SuggestedAction> |
| | | {appDetail?.mode === 'workflow' && ( |
| | | <WorkflowToolConfigureButton |
| | | disabled={!publishedAt} |
| | | published={!!toolPublished} |
| | | detailNeedUpdate={!!toolPublished && published} |
| | | workflowAppId={appDetail?.id} |
| | | icon={{ |
| | | content: (appDetail.icon_type === 'image' ? '🤖' : appDetail?.icon) || '🤖', |
| | | background: (appDetail.icon_type === 'image' ? appDefaultIconBackground : appDetail?.icon_background) || appDefaultIconBackground, |
| | | }} |
| | | name={appDetail?.name} |
| | | description={appDetail?.description} |
| | | inputs={inputs} |
| | | handlePublish={handlePublish} |
| | | onRefreshData={onRefreshData} |
| | | /> |
| | | )} |
| | | </div> |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | | <EmbeddedModal |
| | | siteInfo={appDetail?.site} |
| | | isShow={embeddingModalOpen} |
| | | onClose={() => setEmbeddingModalOpen(false)} |
| | | appBaseUrl={appBaseURL} |
| | | accessToken={accessToken} |
| | | /> |
| | | </PortalToFollowElem > |
| | | ) |
| | | } |
| | | |