diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx index 3b43186196..e4a8db5b07 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx @@ -120,6 +120,7 @@ const AppDetailLayout: FC = (props) => { }).finally(() => { setIsLoadingAppDetail(false) }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [appId, pathname]) useEffect(() => { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index d46563c430..d7a2a615cb 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -166,7 +166,7 @@ const ConfigPopup: FC = ({
-
{t(`${I18N_PREFIX}.tracing`)}
+
{t(`${I18N_PREFIX}.tracing`)}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index e6a520648f..0800602924 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -172,7 +172,7 @@ const ProviderConfigModal: FC = ({
-
{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}
+
{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}
diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 54853cf875..df0f2593bb 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -5,9 +5,17 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import dayjs from 'dayjs' -import { RiArrowDownSLine, RiPlanetLine } from '@remixicon/react' +import { + RiArrowDownSLine, + RiPlanetLine, + RiPlayCircleLine, + RiPlayList2Line, + RiTerminalBoxLine, +} from '@remixicon/react' +import { useKeyPress } from 'ahooks' 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' @@ -20,13 +28,12 @@ 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' +import VersionInfoModal from './version-info-modal' export type AppPublisherProps = { disabled?: boolean @@ -37,8 +44,7 @@ export type AppPublisherProps = { debugWithMultipleModel?: boolean multipleModelConfigs?: ModelAndParameter[] /** modelAndParameter is passed when debugWithMultipleModel is true */ - onPublish?: (modelAndParameter?: ModelAndParameter) => Promise | any - onRestore?: () => Promise | any + onPublish?: (params?: any) => Promise | any onToggle?: (state: boolean) => void crossAxisOffset?: number toolPublished?: boolean @@ -46,6 +52,8 @@ export type AppPublisherProps = { onRefreshData?: () => void } +const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P'] + const AppPublisher = ({ disabled = false, publishDisabled = false, @@ -54,7 +62,6 @@ const AppPublisher = ({ debugWithMultipleModel = false, multipleModelConfigs = [], onPublish, - onRestore, onToggle, crossAxisOffset = 0, toolPublished, @@ -64,6 +71,7 @@ const AppPublisher = ({ const { t } = useTranslation() const [published, setPublished] = useState(false) const [open, setOpen] = useState(false) + const [publishModalOpen, setPublishModalOpen] = useState(false) const appDetail = useAppStore(state => state.appDetail) const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode @@ -74,24 +82,16 @@ const AppPublisher = ({ return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow() }, [language]) - const handlePublish = async (modelAndParameter?: ModelAndParameter) => { + const handlePublish = async (params?: ModelAndParameter | PublishWorkflowParams) => { try { - await onPublish?.(modelAndParameter) + await onPublish?.(params) setPublished(true) } - catch (e) { + catch { setPublished(false) } } - const handleRestore = useCallback(async () => { - try { - await onRestore?.() - setOpen(false) - } - catch (e) { } - }, [onRestore]) - const handleTrigger = useCallback(() => { const state = !open @@ -122,139 +122,178 @@ const AppPublisher = ({ const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) + const openPublishModal = () => { + setOpen(false) + setPublishModalOpen(true) + } + + const closePublishModal = () => { + setPublishModalOpen(false) + } + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { + e.preventDefault() + if (publishDisabled || published) + return + openPublishModal() + } + , { exactMatch: true, useCapture: true }) + return ( - - - - - -
-
-
- {publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')} -
- {publishedAt - ? ( -
-
+ <> + + + + + +
+
+
+ {publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')} +
+ {publishedAt + ? ( +
{t('workflow.common.publishedAt')} {formatTimeFromNow(publishedAt)}
+ ) + : ( +
+ {t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)} +
+ )} + {debugWithMultipleModel + ? ( + handlePublish(item)} + // textGenerationModelList={textGenerationModelList} + /> + ) + : ( -
- ) - : ( -
- {t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)} -
- )} - {debugWithMultipleModel - ? ( - handlePublish(item)} - // textGenerationModelList={textGenerationModelList} - /> - ) - : ( - - ) - } -
-
- }>{t('workflow.common.runApp')} - {appDetail?.mode === 'workflow' - ? ( - } - > - {t('workflow.common.batchRunApp')} - - ) - : ( - { - setEmbeddingModalOpen(true) - handleTrigger() - }} - disabled={!publishedAt} - icon={} - > - {t('workflow.common.embedIntoSite')} - - )} - { - publishedAt && handleOpenInExplore() - }} - disabled={!publishedAt} - icon={} - > - {t('workflow.common.openInExplore')} - - }>{t('workflow.common.accessAPIReference')} - {appDetail?.mode === 'workflow' && ( - +
+ } + > + {t('workflow.common.runApp')} + + {appDetail?.mode === 'workflow' + ? ( + } + > + {t('workflow.common.batchRunApp')} + + ) + : ( + { + setEmbeddingModalOpen(true) + handleTrigger() + }} + disabled={!publishedAt} + icon={} + > + {t('workflow.common.embedIntoSite')} + + )} + { + publishedAt && handleOpenInExplore() }} - name={appDetail?.name} - description={appDetail?.description} - inputs={inputs} - handlePublish={handlePublish} - onRefreshData={onRefreshData} - /> - )} + disabled={!publishedAt} + icon={} + > + {t('workflow.common.openInExplore')} + + } + > + {t('workflow.common.accessAPIReference')} + + {appDetail?.mode === 'workflow' && ( + + )} +
-
- - setEmbeddingModalOpen(false)} - appBaseUrl={appBaseURL} - accessToken={accessToken} - /> - + + setEmbeddingModalOpen(false)} + appBaseUrl={appBaseURL} + accessToken={accessToken} + /> + + {publishModalOpen && ( + + )} + ) } diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx index 6099b2e7bb..97e16bb13a 100644 --- a/web/app/components/app/app-publisher/suggested-action.tsx +++ b/web/app/components/app/app-publisher/suggested-action.tsx @@ -1,6 +1,6 @@ import type { HTMLProps, PropsWithChildren } from 'react' +import { RiArrowRightUpLine } from '@remixicon/react' import classNames from '@/utils/classnames' -import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' export type SuggestedActionProps = PropsWithChildren & { icon?: React.ReactNode @@ -14,15 +14,15 @@ const SuggestedAction = ({ icon, link, disabled, children, className, ...props } target='_blank' rel='noreferrer' className={classNames( - 'flex justify-start items-center gap-2 h-[34px] px-2.5 bg-background-section-burn rounded-lg transition-colors text-text-secondary [&:not(:first-child)]:mt-1', - disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', + 'flex justify-start items-center gap-2 py-2 px-2.5 bg-background-section-burn rounded-lg transition-colors [&:not(:first-child)]:mt-1', + disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'text-text-secondary hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', className, )} {...props} >
{icon}
-
{children}
- +
{children}
+ ) diff --git a/web/app/components/app/app-publisher/version-info-modal.tsx b/web/app/components/app/app-publisher/version-info-modal.tsx new file mode 100644 index 0000000000..c559db2286 --- /dev/null +++ b/web/app/components/app/app-publisher/version-info-modal.tsx @@ -0,0 +1,112 @@ +import React, { type FC, useCallback, useState } from 'react' +import Modal from '@/app/components/base/modal' +import type { VersionHistory } from '@/types/workflow' +import { useTranslation } from 'react-i18next' +import { RiCloseLine } from '@remixicon/react' +import Input from '../../base/input' +import Textarea from '../../base/textarea' +import Button from '../../base/button' +import Toast from '@/app/components/base/toast' + +type VersionInfoModalProps = { + isOpen: boolean + versionInfo?: VersionHistory + onClose: () => void + onPublish: (params: { title: string; releaseNotes: string; id?: string }) => void +} + +const TITLE_MAX_LENGTH = 15 +const RELEASE_NOTES_MAX_LENGTH = 100 + +const VersionInfoModal: FC = ({ + isOpen, + versionInfo, + onClose, + onPublish, +}) => { + const { t } = useTranslation() + const [title, setTitle] = useState(versionInfo?.marked_name || '') + const [releaseNotes, setReleaseNotes] = useState(versionInfo?.marked_comment || '') + const [titleError, setTitleError] = useState(false) + const [releaseNotesError, setReleaseNotesError] = useState(false) + + const handlePublish = () => { + if (title.length > TITLE_MAX_LENGTH) { + setTitleError(true) + Toast.notify({ + type: 'error', + message: t('workflow.versionHistory.editField.titleLengthLimit', { limit: TITLE_MAX_LENGTH }), + }) + return + } + else { + titleError && setTitleError(false) + } + + if (releaseNotes.length > RELEASE_NOTES_MAX_LENGTH) { + setReleaseNotesError(true) + Toast.notify({ + type: 'error', + message: t('workflow.versionHistory.editField.releaseNotesLengthLimit', { limit: RELEASE_NOTES_MAX_LENGTH }), + }) + return + } + else { + releaseNotesError && setReleaseNotesError(false) + } + + onPublish({ title, releaseNotes, id: versionInfo?.id }) + onClose() + } + + const handleTitleChange = useCallback((e: React.ChangeEvent) => { + setTitle(e.target.value) + }, []) + + const handleDescriptionChange = useCallback((e: React.ChangeEvent) => { + setReleaseNotes(e.target.value) + }, []) + + return +
+
+ {versionInfo?.marked_name ? t('workflow.versionHistory.editVersionInfo') : t('workflow.versionHistory.nameThisVersion')} +
+
+ +
+
+
+
+
+ {t('workflow.versionHistory.editField.title')} +
+ +
+
+
+ {t('workflow.versionHistory.editField.releaseNotes')} +
+