feat: add runtime upgrade handling and UI components for LLM nodes

This commit is contained in:
Novice
2026-03-24 14:26:38 +08:00
parent 2cbc8da9cb
commit dd6fde26d0
5 changed files with 127 additions and 40 deletions

View File

@ -3,12 +3,14 @@ import { useHover, useKeyPress } from 'ahooks'
import { usePathname } from 'next/navigation'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { cn } from '@/utils/classnames'
import Divider from '../base/divider'
import Tooltip from '../base/tooltip'
import { getKeyboardKeyCodeBySystem } from '../workflow/utils'
import AppInfo from './app-info'
import AppSidebarDropdown from './app-sidebar-dropdown'
@ -34,9 +36,11 @@ const AppDetailNav = ({
extraInfo,
iconType = 'app',
}: IAppDetailNavProps) => {
const { appSidebarExpand, setAppSidebarExpand } = useAppStore(useShallow(state => ({
const { t } = useTranslation()
const { appSidebarExpand, setAppSidebarExpand, needsRuntimeUpgrade } = useAppStore(useShallow(state => ({
appSidebarExpand: state.appSidebarExpand,
setAppSidebarExpand: state.setAppSidebarExpand,
needsRuntimeUpgrade: state.needsRuntimeUpgrade,
})))
const sidebarRef = React.useRef<HTMLDivElement>(null)
const media = useBreakpoints()
@ -49,6 +53,8 @@ const AppDetailNav = ({
const isHoveringSidebar = useHover(sidebarRef)
const showUpgradeButton = iconType === 'app' && needsRuntimeUpgrade
// Check if the current path is a workflow canvas & fullscreen
const pathname = usePathname()
const inWorkflowCanvas = pathname.endsWith('/workflow')
@ -57,9 +63,9 @@ const AppDetailNav = ({
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
eventEmitter?.useSubscription((v: { type: string; payload?: boolean }) => {
if (v?.type === 'workflow-canvas-maximize')
setHideHeader(v.payload)
setHideHeader(v.payload ?? false)
})
useEffect(() => {
@ -136,10 +142,10 @@ const AppDetailNav = ({
expand ? 'px-3 py-2' : 'p-3',
)}
>
{navigation.map((item, index) => {
{navigation.map((item) => {
return (
<NavLink
key={index}
key={item.href}
mode={appSidebarExpand}
iconMap={{ selected: item.selectedIcon, normal: item.icon }}
name={item.name}
@ -150,6 +156,28 @@ const AppDetailNav = ({
})}
</nav>
{iconType !== 'app' && extraInfo && extraInfo(appSidebarExpand)}
{iconType === 'app' && showUpgradeButton && (
<div className={cn('shrink-0 border-t border-divider-subtle', expand ? 'p-3' : 'p-2')}>
<Tooltip popupContent={!expand ? t('sandboxMigrationModal.title', { ns: 'workflow' }) : undefined}>
<button
type="button"
className={cn(
'flex w-full cursor-pointer items-center gap-2 rounded-lg text-components-menu-item-text',
'hover:bg-components-menu-item-bg-hover hover:text-components-menu-item-text-hover',
expand ? 'px-2 py-1.5' : 'justify-center p-2',
)}
onClick={() => eventEmitter?.emit({ type: 'upgrade-runtime-click' })}
>
<div className="flex shrink-0 items-center justify-center rounded-xl bg-[#296dff] p-1.5 shadow-sm">
<span className="i-custom-vender-workflow-thinking h-4 w-4 text-white/90" />
</div>
{expand && (
<span className="system-xs-medium">{t('sandboxMigrationModal.title', { ns: 'workflow' })}</span>
)}
</button>
</Tooltip>
</div>
)}
</div>
)
}

View File

@ -11,6 +11,7 @@ type State = {
showAgentLogModal: boolean
showMessageLogModal: boolean
showAppConfigureFeaturesModal: boolean
needsRuntimeUpgrade: boolean
}
type Action = {
@ -22,6 +23,7 @@ type Action = {
setShowAgentLogModal: (showAgentLogModal: boolean) => void
setShowMessageLogModal: (showMessageLogModal: boolean) => void
setShowAppConfigureFeaturesModal: (showAppConfigureFeaturesModal: boolean) => void
setNeedsRuntimeUpgrade: (needsRuntimeUpgrade: boolean) => void
}
export const useStore = create<State & Action>(set => ({
@ -51,4 +53,6 @@ export const useStore = create<State & Action>(set => ({
}),
showAppConfigureFeaturesModal: false,
setShowAppConfigureFeaturesModal: showAppConfigureFeaturesModal => set(() => ({ showAppConfigureFeaturesModal })),
needsRuntimeUpgrade: false,
setNeedsRuntimeUpgrade: needsRuntimeUpgrade => set(() => ({ needsRuntimeUpgrade })),
}))

View File

@ -22,6 +22,7 @@ import {
} from '@/service/workflow'
import { AppModeEnum } from '@/types/app'
import { storage } from '@/utils/storage'
import { setSandboxMigrationDismissed } from '../utils/sandbox-migration-storage'
import { useWorkflowTemplate } from './use-workflow-template'
const hasConnectedUserInput = (nodes: Node[] = [], edges: Edge[] = []): boolean => {
@ -46,7 +47,7 @@ export const useWorkflowInit = () => {
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
workflowStore.setState({ appId: appDetail.id, appName: appDetail.name })
}, [appDetail.id, workflowStore])
}, [appDetail.id, appDetail.name, workflowStore])
const handleUpdateWorkflowFileUploadConfig = useCallback((config: FileUploadConfigResponse) => {
const { setFileUploadConfig } = workflowStore.getState()
@ -92,6 +93,9 @@ export const useWorkflowInit = () => {
if (enableSandboxRuntime)
storage.remove(runtimeStorageKey)
if (!enableSandboxRuntime)
setSandboxMigrationDismissed(appDetail.id)
syncWorkflowDraft({
url: `/apps/${appDetail.id}/workflows/draft`,
params: {

View File

@ -37,6 +37,7 @@ import {
initialNodes,
} from '@/app/components/workflow/utils'
import { useAppContext } from '@/context/app-context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { upgradeAppRuntime } from '@/service/apps'
import { fetchRunDetail } from '@/service/log'
import { useAppTriggers } from '@/service/use-tools'
@ -44,7 +45,6 @@ import { AppModeEnum } from '@/types/app'
import { useFeatures } from '../base/features/hooks'
import ViewPicker from '../workflow/view-picker'
import SandboxMigrationModal from './components/sandbox-migration-modal'
import UpgradeRuntimeBanner from './components/upgrade-runtime-banner'
import UpgradedFromBanner from './components/upgraded-from-banner'
import WorkflowAppMain from './components/workflow-main'
import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url'
@ -193,9 +193,11 @@ const WorkflowAppWithAdditionalContext = () => {
setShowMigrationModal(false)
}, [appId])
const handleOpenMigrationModal = useCallback(() => {
setShowMigrationModal(true)
}, [])
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: { type: string }) => {
if (v?.type === 'upgrade-runtime-click')
setShowMigrationModal(true)
})
const showUpgradeRuntimeModal = useStore(s => s.showUpgradeRuntimeModal)
const setShowUpgradeRuntimeModal = useStore(s => s.setShowUpgradeRuntimeModal)
@ -255,7 +257,7 @@ const WorkflowAppWithAdditionalContext = () => {
const { debouncedSyncWorkflowDraft } = workflowStore.getState()
// The debounced function from lodash has a cancel method
if (debouncedSyncWorkflowDraft && 'cancel' in debouncedSyncWorkflowDraft)
(debouncedSyncWorkflowDraft as any).cancel()
(debouncedSyncWorkflowDraft as { cancel: () => void }).cancel()
}
}, [workflowStore])
@ -364,16 +366,18 @@ const WorkflowAppWithAdditionalContext = () => {
const isDataReady = !(!data || isLoading || isLoadingCurrentWorkspace || !currentWorkspace.id)
const sandboxEnabled = isSandboxFeatureEnabled
const setNeedsRuntimeUpgrade = useAppStore(s => s.setNeedsRuntimeUpgrade)
useEffect(() => {
if (!isDataReady || !appId)
return
setNeedsRuntimeUpgrade(!sandboxEnabled)
if (lastCheckedAppIdRef.current !== appId) {
lastCheckedAppIdRef.current = appId
const dismissed = getSandboxMigrationDismissed(appId)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setShowMigrationModal(!sandboxEnabled && !dismissed)
}
}, [appId, isDataReady, sandboxEnabled])
}, [appId, isDataReady, sandboxEnabled, setNeedsRuntimeUpgrade])
const renderGraph = useCallback((headerLeftSlot: ReactNode) => {
if (!isDataReady)
return null
@ -432,9 +436,6 @@ const WorkflowAppWithAdditionalContext = () => {
onClose={handleCloseMigrationModal}
onUpgrade={handleUpgradeRuntime}
/>
{!sandboxEnabled && !upgradedFromId && (
<UpgradeRuntimeBanner onUpgrade={handleOpenMigrationModal} />
)}
{upgradedFromId && (
<UpgradedFromBanner
fromAppId={upgradedFromId}