mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
feat(web): switch warining dialog
This commit is contained in:
parent
79fc352a5a
commit
23291398ec
@ -16,6 +16,7 @@ const mockFetchInstalledAppList = vi.fn()
|
|||||||
const mockFetchAppDetailDirect = vi.fn()
|
const mockFetchAppDetailDirect = vi.fn()
|
||||||
const mockToastError = vi.fn()
|
const mockToastError = vi.fn()
|
||||||
const mockConvertWorkflowType = vi.fn()
|
const mockConvertWorkflowType = vi.fn()
|
||||||
|
const mockRefetchEvaluationWorkflowAssociatedTargets = vi.fn()
|
||||||
|
|
||||||
const sectionProps = vi.hoisted(() => ({
|
const sectionProps = vi.hoisted(() => ({
|
||||||
summary: null as null | Record<string, any>,
|
summary: null as null | Record<string, any>,
|
||||||
@ -27,6 +28,7 @@ const ahooksMocks = vi.hoisted(() => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
let mockAppDetail: Record<string, any> | null = null
|
let mockAppDetail: Record<string, any> | null = null
|
||||||
|
let mockEvaluationWorkflowAssociatedTargets: Record<string, any> | undefined
|
||||||
|
|
||||||
vi.mock('react-i18next', () => ({
|
vi.mock('react-i18next', () => ({
|
||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
@ -96,6 +98,14 @@ vi.mock('@/service/use-apps', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/service/use-evaluation', () => ({
|
||||||
|
useEvaluationWorkflowAssociatedTargets: () => ({
|
||||||
|
data: mockEvaluationWorkflowAssociatedTargets,
|
||||||
|
refetch: mockRefetchEvaluationWorkflowAssociatedTargets,
|
||||||
|
isFetching: false,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||||
toast: {
|
toast: {
|
||||||
error: (...args: unknown[]) => mockToastError(...args),
|
error: (...args: unknown[]) => mockToastError(...args),
|
||||||
@ -198,6 +208,11 @@ describe('AppPublisher', () => {
|
|||||||
access_mode: AccessMode.PUBLIC,
|
access_mode: AccessMode.PUBLIC,
|
||||||
})
|
})
|
||||||
mockConvertWorkflowType.mockResolvedValue({})
|
mockConvertWorkflowType.mockResolvedValue({})
|
||||||
|
mockEvaluationWorkflowAssociatedTargets = { items: [] }
|
||||||
|
mockRefetchEvaluationWorkflowAssociatedTargets.mockResolvedValue({
|
||||||
|
data: { items: [] },
|
||||||
|
isError: false,
|
||||||
|
})
|
||||||
mockOpenAsyncWindow.mockImplementation(async (resolver: () => Promise<string>) => {
|
mockOpenAsyncWindow.mockImplementation(async (resolver: () => Promise<string>) => {
|
||||||
await resolver()
|
await resolver()
|
||||||
})
|
})
|
||||||
@ -518,6 +533,106 @@ describe('AppPublisher', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should confirm before switching an evaluation workflow with associated targets to a standard workflow', async () => {
|
||||||
|
mockAppDetail = {
|
||||||
|
...mockAppDetail,
|
||||||
|
type: AppTypeEnum.EVALUATION,
|
||||||
|
}
|
||||||
|
mockEvaluationWorkflowAssociatedTargets = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
target_type: 'app',
|
||||||
|
target_id: 'dependent-app-1',
|
||||||
|
target_name: 'Dependent App',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target_type: 'knowledge_base',
|
||||||
|
target_id: 'knowledge-1',
|
||||||
|
target_name: 'Knowledge Base',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
mockRefetchEvaluationWorkflowAssociatedTargets.mockResolvedValueOnce({
|
||||||
|
data: mockEvaluationWorkflowAssociatedTargets,
|
||||||
|
isError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AppPublisher
|
||||||
|
publishedAt={Date.now()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('common.publish'))
|
||||||
|
fireEvent.click(screen.getByText('publisher-switch-workflow-type'))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockRefetchEvaluationWorkflowAssociatedTargets).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
expect(mockConvertWorkflowType).not.toHaveBeenCalled()
|
||||||
|
expect(screen.getByText('Dependent App')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Knowledge Base')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'common.switchToStandardWorkflowConfirm.switch' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockConvertWorkflowType).toHaveBeenCalledWith({
|
||||||
|
params: { appId: 'app-1' },
|
||||||
|
query: { target_type: AppTypeEnum.WORKFLOW },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should switch an evaluation workflow directly when there are no associated targets', async () => {
|
||||||
|
mockAppDetail = {
|
||||||
|
...mockAppDetail,
|
||||||
|
type: AppTypeEnum.EVALUATION,
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AppPublisher
|
||||||
|
publishedAt={Date.now()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('common.publish'))
|
||||||
|
fireEvent.click(screen.getByText('publisher-switch-workflow-type'))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockRefetchEvaluationWorkflowAssociatedTargets).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mockConvertWorkflowType).toHaveBeenCalledWith({
|
||||||
|
params: { appId: 'app-1' },
|
||||||
|
query: { target_type: AppTypeEnum.WORKFLOW },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
expect(screen.queryByText('common.switchToStandardWorkflowConfirm.title')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should block switching an evaluation workflow when associated targets fail to load', async () => {
|
||||||
|
mockAppDetail = {
|
||||||
|
...mockAppDetail,
|
||||||
|
type: AppTypeEnum.EVALUATION,
|
||||||
|
}
|
||||||
|
mockRefetchEvaluationWorkflowAssociatedTargets.mockResolvedValueOnce({
|
||||||
|
data: undefined,
|
||||||
|
isError: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AppPublisher
|
||||||
|
publishedAt={Date.now()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('common.publish'))
|
||||||
|
fireEvent.click(screen.getByText('publisher-switch-workflow-type'))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockToastError).toHaveBeenCalledWith('common.switchToStandardWorkflowConfirm.loadFailed')
|
||||||
|
})
|
||||||
|
expect(mockConvertWorkflowType).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('should block switching to evaluation workflow when restricted nodes exist', async () => {
|
it('should block switching to evaluation workflow when restricted nodes exist', async () => {
|
||||||
render(
|
render(
|
||||||
<AppPublisher
|
<AppPublisher
|
||||||
|
|||||||
@ -0,0 +1,158 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { EvaluationWorkflowAssociatedTarget, EvaluationWorkflowAssociatedTargetType } from '@/types/evaluation'
|
||||||
|
import type { I18nKeysWithPrefix } from '@/types/i18n'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/app/components/base/ui/alert-dialog'
|
||||||
|
import Link from '@/next/link'
|
||||||
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
|
type EvaluationWorkflowSwitchConfirmDialogProps = {
|
||||||
|
open: boolean
|
||||||
|
targets: EvaluationWorkflowAssociatedTarget[]
|
||||||
|
loading?: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
onConfirm: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TARGET_TYPE_META: Record<EvaluationWorkflowAssociatedTargetType, {
|
||||||
|
icon: string
|
||||||
|
iconClassName: string
|
||||||
|
labelKey: I18nKeysWithPrefix<'workflow', 'common.switchToStandardWorkflowConfirm.targetTypes.'>
|
||||||
|
href: (targetId: string) => string
|
||||||
|
}> = {
|
||||||
|
app: {
|
||||||
|
icon: 'i-ri-flow-chart',
|
||||||
|
iconClassName: 'bg-components-icon-bg-teal-soft text-util-colors-teal-teal-600',
|
||||||
|
labelKey: 'common.switchToStandardWorkflowConfirm.targetTypes.app',
|
||||||
|
href: targetId => `/app/${targetId}/workflow`,
|
||||||
|
},
|
||||||
|
snippets: {
|
||||||
|
icon: 'i-ri-edit-2-line',
|
||||||
|
iconClassName: 'bg-components-icon-bg-violet-soft text-util-colors-violet-violet-600',
|
||||||
|
labelKey: 'common.switchToStandardWorkflowConfirm.targetTypes.snippets',
|
||||||
|
href: targetId => `/snippets/${targetId}/orchestrate`,
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
icon: 'i-ri-book-2-line',
|
||||||
|
iconClassName: 'bg-components-icon-bg-indigo-soft text-util-colors-blue-blue-600',
|
||||||
|
labelKey: 'common.switchToStandardWorkflowConfirm.targetTypes.knowledge_base',
|
||||||
|
href: targetId => `/datasets/${targetId}/documents`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTargetMeta = (targetType: EvaluationWorkflowAssociatedTargetType) => {
|
||||||
|
return TARGET_TYPE_META[targetType] ?? TARGET_TYPE_META.app
|
||||||
|
}
|
||||||
|
|
||||||
|
const DependentTargetItem = ({
|
||||||
|
target,
|
||||||
|
}: {
|
||||||
|
target: EvaluationWorkflowAssociatedTarget
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const meta = getTargetMeta(target.target_type)
|
||||||
|
const targetName = target.target_name || target.target_id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={meta.href(target.target_id)}
|
||||||
|
className="group flex w-full items-center gap-3 rounded-lg bg-background-section p-2 hover:bg-background-section-burn"
|
||||||
|
title={targetName}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
'flex size-10 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular',
|
||||||
|
meta.iconClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={cn(meta.icon, 'size-5')} />
|
||||||
|
</span>
|
||||||
|
<span className="flex min-w-0 flex-1 flex-col gap-1 py-px">
|
||||||
|
<span className="truncate system-md-semibold text-text-secondary">
|
||||||
|
{targetName}
|
||||||
|
</span>
|
||||||
|
<span className="system-2xs-medium-uppercase text-text-tertiary">
|
||||||
|
{t(meta.labelKey, { ns: 'workflow' })}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="i-ri-arrow-right-up-line size-3.5 shrink-0 text-text-quaternary opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EvaluationWorkflowSwitchConfirmDialog = ({
|
||||||
|
open,
|
||||||
|
targets,
|
||||||
|
loading = false,
|
||||||
|
onOpenChange,
|
||||||
|
onConfirm,
|
||||||
|
}: EvaluationWorkflowSwitchConfirmDialogProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<AlertDialogContent className="w-[480px]">
|
||||||
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
|
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
||||||
|
{t('common.switchToStandardWorkflowConfirm.title', { ns: 'workflow' })}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="w-full system-md-regular text-text-secondary">
|
||||||
|
<span className="block">
|
||||||
|
{t('common.switchToStandardWorkflowConfirm.activeIn', { ns: 'workflow', count: targets.length })}
|
||||||
|
</span>
|
||||||
|
<span className="block">
|
||||||
|
{t('common.switchToStandardWorkflowConfirm.description', { ns: 'workflow' })}
|
||||||
|
</span>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 px-6 py-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="shrink-0 system-xs-medium-uppercase text-text-quaternary">
|
||||||
|
{t('common.switchToStandardWorkflowConfirm.dependentWorkflows', { ns: 'workflow' })}
|
||||||
|
</span>
|
||||||
|
<span className="h-px min-w-0 flex-1 bg-divider-subtle" />
|
||||||
|
</div>
|
||||||
|
<div className="flex max-h-[188px] flex-col gap-1 overflow-y-auto">
|
||||||
|
{targets.map(target => (
|
||||||
|
<DependentTargetItem
|
||||||
|
key={`${target.target_type}:${target.target_id}`}
|
||||||
|
target={target}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AlertDialogActions>
|
||||||
|
<AlertDialogCancelButton disabled={loading}>
|
||||||
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
|
</AlertDialogCancelButton>
|
||||||
|
<AlertDialogConfirmButton
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
{t('common.switchToStandardWorkflowConfirm.switch', { ns: 'workflow' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EvaluationWorkflowSwitchConfirmDialog
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import type { ModelAndParameter } from '../configuration/debug/types'
|
import type { ModelAndParameter } from '../configuration/debug/types'
|
||||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import type { EvaluationWorkflowAssociatedTarget } from '@/types/evaluation'
|
||||||
import type { I18nKeysWithPrefix } from '@/types/i18n'
|
import type { I18nKeysWithPrefix } from '@/types/i18n'
|
||||||
import type { PublishWorkflowParams, WorkflowTypeConversionTarget } from '@/types/workflow'
|
import type { PublishWorkflowParams, WorkflowTypeConversionTarget } from '@/types/workflow'
|
||||||
import { useKeyPress } from 'ahooks'
|
import { useKeyPress } from 'ahooks'
|
||||||
@ -28,11 +29,13 @@ import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/acces
|
|||||||
import { fetchAppDetailDirect } from '@/service/apps'
|
import { fetchAppDetailDirect } from '@/service/apps'
|
||||||
import { fetchInstalledAppList } from '@/service/explore'
|
import { fetchInstalledAppList } from '@/service/explore'
|
||||||
import { useConvertWorkflowTypeMutation } from '@/service/use-apps'
|
import { useConvertWorkflowTypeMutation } from '@/service/use-apps'
|
||||||
|
import { useEvaluationWorkflowAssociatedTargets } from '@/service/use-evaluation'
|
||||||
import { AppModeEnum, AppTypeEnum } from '@/types/app'
|
import { AppModeEnum, AppTypeEnum } from '@/types/app'
|
||||||
import { basePath } from '@/utils/var'
|
import { basePath } from '@/utils/var'
|
||||||
import { toast } from '../../base/ui/toast'
|
import { toast } from '../../base/ui/toast'
|
||||||
import { getKeyboardKeyCodeBySystem } from '../../workflow/utils'
|
import { getKeyboardKeyCodeBySystem } from '../../workflow/utils'
|
||||||
import AccessControl from '../app-access-control'
|
import AccessControl from '../app-access-control'
|
||||||
|
import EvaluationWorkflowSwitchConfirmDialog from './evaluation-workflow-switch-confirm-dialog'
|
||||||
import {
|
import {
|
||||||
PublisherAccessSection,
|
PublisherAccessSection,
|
||||||
PublisherActionsSection,
|
PublisherActionsSection,
|
||||||
@ -122,6 +125,8 @@ const AppPublisher = ({
|
|||||||
const [published, setPublished] = useState(false)
|
const [published, setPublished] = useState(false)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [showAppAccessControl, setShowAppAccessControl] = useState(false)
|
const [showAppAccessControl, setShowAppAccessControl] = useState(false)
|
||||||
|
const [showEvaluationWorkflowSwitchConfirm, setShowEvaluationWorkflowSwitchConfirm] = useState(false)
|
||||||
|
const [evaluationWorkflowSwitchTargets, setEvaluationWorkflowSwitchTargets] = useState<EvaluationWorkflowAssociatedTarget[]>([])
|
||||||
|
|
||||||
const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false)
|
const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false)
|
||||||
|
|
||||||
@ -138,6 +143,10 @@ const AppPublisher = ({
|
|||||||
? WORKFLOW_TYPE_SWITCH_CONFIG[appDetail.type]
|
? WORKFLOW_TYPE_SWITCH_CONFIG[appDetail.type]
|
||||||
: undefined
|
: undefined
|
||||||
const isEvaluationWorkflowType = appDetail?.type === AppTypeEnum.EVALUATION
|
const isEvaluationWorkflowType = appDetail?.type === AppTypeEnum.EVALUATION
|
||||||
|
const {
|
||||||
|
refetch: refetchEvaluationWorkflowAssociatedTargets,
|
||||||
|
isFetching: isFetchingEvaluationWorkflowAssociatedTargets,
|
||||||
|
} = useEvaluationWorkflowAssociatedTargets(appDetail?.id, { enabled: false })
|
||||||
const workflowTypeSwitchDisabledReason = useMemo(() => {
|
const workflowTypeSwitchDisabledReason = useMemo(() => {
|
||||||
if (workflowTypeSwitchConfig?.targetType !== AppTypeEnum.EVALUATION)
|
if (workflowTypeSwitchConfig?.targetType !== AppTypeEnum.EVALUATION)
|
||||||
return undefined
|
return undefined
|
||||||
@ -234,13 +243,9 @@ const AppPublisher = ({
|
|||||||
}
|
}
|
||||||
}, [appDetail, setAppDetail])
|
}, [appDetail, setAppDetail])
|
||||||
|
|
||||||
const handleWorkflowTypeSwitch = useCallback(async () => {
|
const performWorkflowTypeSwitch = useCallback(async () => {
|
||||||
if (!appDetail?.id || !workflowTypeSwitchConfig)
|
if (!appDetail?.id || !workflowTypeSwitchConfig)
|
||||||
return
|
return false
|
||||||
if (workflowTypeSwitchDisabledReason) {
|
|
||||||
toast.error(workflowTypeSwitchDisabledReason)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await convertWorkflowType({
|
await convertWorkflowType({
|
||||||
@ -263,9 +268,57 @@ const AppPublisher = ({
|
|||||||
|
|
||||||
if (publishedAt)
|
if (publishedAt)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
|
||||||
|
setShowEvaluationWorkflowSwitchConfirm(false)
|
||||||
|
setEvaluationWorkflowSwitchTargets([])
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
catch { }
|
catch {
|
||||||
}, [appDetail?.id, convertWorkflowType, handlePublish, publishedAt, setAppDetail, workflowTypeSwitchConfig, workflowTypeSwitchDisabledReason])
|
return false
|
||||||
|
}
|
||||||
|
}, [appDetail?.id, convertWorkflowType, handlePublish, publishedAt, setAppDetail, workflowTypeSwitchConfig])
|
||||||
|
|
||||||
|
const handleWorkflowTypeSwitch = useCallback(async () => {
|
||||||
|
if (!appDetail?.id || !workflowTypeSwitchConfig)
|
||||||
|
return
|
||||||
|
if (workflowTypeSwitchDisabledReason) {
|
||||||
|
toast.error(workflowTypeSwitchDisabledReason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appDetail.type === AppTypeEnum.EVALUATION && workflowTypeSwitchConfig.targetType === AppTypeEnum.WORKFLOW) {
|
||||||
|
const associatedTargetsResult = await refetchEvaluationWorkflowAssociatedTargets()
|
||||||
|
|
||||||
|
if (associatedTargetsResult.isError) {
|
||||||
|
toast.error(t('common.switchToStandardWorkflowConfirm.loadFailed', { ns: 'workflow' }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const associatedTargets = associatedTargetsResult.data?.items ?? []
|
||||||
|
if (associatedTargets.length > 0) {
|
||||||
|
setEvaluationWorkflowSwitchTargets(associatedTargets)
|
||||||
|
setShowEvaluationWorkflowSwitchConfirm(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await performWorkflowTypeSwitch()
|
||||||
|
}, [
|
||||||
|
appDetail?.id,
|
||||||
|
appDetail?.type,
|
||||||
|
performWorkflowTypeSwitch,
|
||||||
|
refetchEvaluationWorkflowAssociatedTargets,
|
||||||
|
t,
|
||||||
|
workflowTypeSwitchConfig,
|
||||||
|
workflowTypeSwitchDisabledReason,
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleEvaluationWorkflowSwitchConfirmOpenChange = useCallback((nextOpen: boolean) => {
|
||||||
|
setShowEvaluationWorkflowSwitchConfirm(nextOpen)
|
||||||
|
|
||||||
|
if (!nextOpen)
|
||||||
|
setEvaluationWorkflowSwitchTargets([])
|
||||||
|
}, [])
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -323,7 +376,7 @@ const AppPublisher = ({
|
|||||||
startNodeLimitExceeded={startNodeLimitExceeded}
|
startNodeLimitExceeded={startNodeLimitExceeded}
|
||||||
upgradeHighlightStyle={upgradeHighlightStyle}
|
upgradeHighlightStyle={upgradeHighlightStyle}
|
||||||
workflowTypeSwitchConfig={workflowTypeSwitchConfig}
|
workflowTypeSwitchConfig={workflowTypeSwitchConfig}
|
||||||
workflowTypeSwitchDisabled={publishDisabled || published || isConvertingWorkflowType || Boolean(workflowTypeSwitchDisabledReason)}
|
workflowTypeSwitchDisabled={publishDisabled || published || isConvertingWorkflowType || isFetchingEvaluationWorkflowAssociatedTargets || Boolean(workflowTypeSwitchDisabledReason)}
|
||||||
workflowTypeSwitchDisabledReason={workflowTypeSwitchDisabledReason}
|
workflowTypeSwitchDisabledReason={workflowTypeSwitchDisabledReason}
|
||||||
onWorkflowTypeSwitch={handleWorkflowTypeSwitch}
|
onWorkflowTypeSwitch={handleWorkflowTypeSwitch}
|
||||||
/>
|
/>
|
||||||
@ -372,6 +425,13 @@ const AppPublisher = ({
|
|||||||
/>
|
/>
|
||||||
{showAppAccessControl && <AccessControl app={appDetail!} onConfirm={handleAccessControlUpdate} onClose={() => { setShowAppAccessControl(false) }} />}
|
{showAppAccessControl && <AccessControl app={appDetail!} onConfirm={handleAccessControlUpdate} onClose={() => { setShowAppAccessControl(false) }} />}
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
|
<EvaluationWorkflowSwitchConfirmDialog
|
||||||
|
open={showEvaluationWorkflowSwitchConfirm}
|
||||||
|
targets={evaluationWorkflowSwitchTargets}
|
||||||
|
loading={isConvertingWorkflowType}
|
||||||
|
onOpenChange={handleEvaluationWorkflowSwitchConfirmOpenChange}
|
||||||
|
onConfirm={() => void performWorkflowTypeSwitch()}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import type {
|
|||||||
EvaluationRunRequest,
|
EvaluationRunRequest,
|
||||||
EvaluationTargetType,
|
EvaluationTargetType,
|
||||||
EvaluationVersionDetailResponse,
|
EvaluationVersionDetailResponse,
|
||||||
|
EvaluationWorkflowAssociatedTargetsResponse,
|
||||||
} from '@/types/evaluation'
|
} from '@/types/evaluation'
|
||||||
import { type } from '@orpc/contract'
|
import { type } from '@orpc/contract'
|
||||||
import { base } from '../base'
|
import { base } from '../base'
|
||||||
@ -290,6 +291,18 @@ export const availableEvaluationWorkflowsContract = base
|
|||||||
}>())
|
}>())
|
||||||
.output(type<AvailableEvaluationWorkflowsResponse>())
|
.output(type<AvailableEvaluationWorkflowsResponse>())
|
||||||
|
|
||||||
|
export const evaluationWorkflowAssociatedTargetsContract = base
|
||||||
|
.route({
|
||||||
|
path: '/workspaces/current/evaluation-workflows/{workflowId}/associated-targets',
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
.input(type<{
|
||||||
|
params: {
|
||||||
|
workflowId: string
|
||||||
|
}
|
||||||
|
}>())
|
||||||
|
.output(type<EvaluationWorkflowAssociatedTargetsResponse>())
|
||||||
|
|
||||||
export const evaluationFileContract = base
|
export const evaluationFileContract = base
|
||||||
.route({
|
.route({
|
||||||
path: '/{targetType}/{targetId}/evaluation/files/{fileId}',
|
path: '/{targetType}/{targetId}/evaluation/files/{fileId}',
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
evaluationRunDetailContract,
|
evaluationRunDetailContract,
|
||||||
evaluationTemplateDownloadContract,
|
evaluationTemplateDownloadContract,
|
||||||
evaluationVersionDetailContract,
|
evaluationVersionDetailContract,
|
||||||
|
evaluationWorkflowAssociatedTargetsContract,
|
||||||
saveDatasetEvaluationConfigContract,
|
saveDatasetEvaluationConfigContract,
|
||||||
saveEvaluationConfigContract,
|
saveEvaluationConfigContract,
|
||||||
startDatasetEvaluationRunContract,
|
startDatasetEvaluationRunContract,
|
||||||
@ -134,6 +135,7 @@ export const consoleRouterContract = {
|
|||||||
nodeInfo: evaluationNodeInfoContract,
|
nodeInfo: evaluationNodeInfoContract,
|
||||||
availableMetrics: availableEvaluationMetricsContract,
|
availableMetrics: availableEvaluationMetricsContract,
|
||||||
availableWorkflows: availableEvaluationWorkflowsContract,
|
availableWorkflows: availableEvaluationWorkflowsContract,
|
||||||
|
associatedTargets: evaluationWorkflowAssociatedTargetsContract,
|
||||||
file: evaluationFileContract,
|
file: evaluationFileContract,
|
||||||
versionDetail: evaluationVersionDetailContract,
|
versionDetail: evaluationVersionDetailContract,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -587,9 +587,6 @@
|
|||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
},
|
},
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 5
|
"count": 5
|
||||||
}
|
}
|
||||||
@ -5556,6 +5553,11 @@
|
|||||||
"count": 4
|
"count": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app/components/evaluation/index.tsx": {
|
||||||
|
"no-restricted-imports": {
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
"app/components/explore/app-card/index.tsx": {
|
"app/components/explore/app-card/index.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 5
|
"count": 5
|
||||||
@ -5566,11 +5568,6 @@
|
|||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/evaluation/index.tsx": {
|
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app/components/explore/banner/banner-item.tsx": {
|
"app/components/explore/banner/banner-item.tsx": {
|
||||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -7665,13 +7662,13 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/splash.tsx": {
|
"app/components/snippets/components/snippet-main.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"ts/no-explicit-any": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/snippets/components/snippet-main.tsx": {
|
"app/components/splash.tsx": {
|
||||||
"ts/no-explicit-any": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -11915,11 +11912,6 @@
|
|||||||
"count": 7
|
"count": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"service/use-evaluation.ts": {
|
|
||||||
"no-barrel-files/no-barrel-files": {
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"service/use-flow.ts": {
|
"service/use-flow.ts": {
|
||||||
"react/no-unnecessary-use-prefix": {
|
"react/no-unnecessary-use-prefix": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|||||||
@ -219,6 +219,16 @@
|
|||||||
"common.switchToEvaluationWorkflowDisabledTip": "Evaluation workflows do not support Human Input nodes or Trigger start nodes.",
|
"common.switchToEvaluationWorkflowDisabledTip": "Evaluation workflows do not support Human Input nodes or Trigger start nodes.",
|
||||||
"common.switchToEvaluationWorkflowTip": "Turns this workflow into a custom evaluator for batch testing. Disables public Web App access.",
|
"common.switchToEvaluationWorkflowTip": "Turns this workflow into a custom evaluator for batch testing. Disables public Web App access.",
|
||||||
"common.switchToStandardWorkflow": "Switch to Standard Workflow",
|
"common.switchToStandardWorkflow": "Switch to Standard Workflow",
|
||||||
|
"common.switchToStandardWorkflowConfirm.activeIn_one": "This evaluator is currently active in {{count}} configuration.",
|
||||||
|
"common.switchToStandardWorkflowConfirm.activeIn_other": "This evaluator is currently active in {{count}} configurations.",
|
||||||
|
"common.switchToStandardWorkflowConfirm.dependentWorkflows": "Dependent workflows",
|
||||||
|
"common.switchToStandardWorkflowConfirm.description": "Switching to a standard workflow will break these dependencies and may cause active batch tests to fail.",
|
||||||
|
"common.switchToStandardWorkflowConfirm.loadFailed": "Failed to load dependent workflows.",
|
||||||
|
"common.switchToStandardWorkflowConfirm.switch": "Switch",
|
||||||
|
"common.switchToStandardWorkflowConfirm.targetTypes.app": "Workflow",
|
||||||
|
"common.switchToStandardWorkflowConfirm.targetTypes.knowledge_base": "Knowledge Base",
|
||||||
|
"common.switchToStandardWorkflowConfirm.targetTypes.snippets": "Snippet",
|
||||||
|
"common.switchToStandardWorkflowConfirm.title": "Switch to Standard Workflow?",
|
||||||
"common.switchToStandardWorkflowTip": "Turns this evaluator back into a standard workflow and restores public Web App access.",
|
"common.switchToStandardWorkflowTip": "Turns this evaluator back into a standard workflow and restores public Web App access.",
|
||||||
"common.syncingData": "Syncing data, just a few seconds.",
|
"common.syncingData": "Syncing data, just a few seconds.",
|
||||||
"common.tagBound": "Number of apps using this tag",
|
"common.tagBound": "Number of apps using this tag",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { EvaluationResourceType } from '@/app/components/evaluation/types'
|
|||||||
import type { AvailableEvaluationWorkflowsResponse, EvaluationConfig } from '@/types/evaluation'
|
import type { AvailableEvaluationWorkflowsResponse, EvaluationConfig } from '@/types/evaluation'
|
||||||
import {
|
import {
|
||||||
keepPreviousData,
|
keepPreviousData,
|
||||||
|
skipToken,
|
||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
@ -59,6 +60,24 @@ export const useAvailableEvaluationMetrics = (enabled = true) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useEvaluationWorkflowAssociatedTargets = (
|
||||||
|
workflowId: string | undefined,
|
||||||
|
options?: { enabled?: boolean },
|
||||||
|
) => {
|
||||||
|
return useQuery(consoleQuery.evaluation.associatedTargets.queryOptions({
|
||||||
|
input: workflowId
|
||||||
|
? {
|
||||||
|
params: {
|
||||||
|
workflowId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: skipToken,
|
||||||
|
enabled: Boolean(workflowId) && (options?.enabled ?? true),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
retry: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export const useEvaluationNodeInfoMutation = () => {
|
export const useEvaluationNodeInfoMutation = () => {
|
||||||
return useMutation(consoleQuery.evaluation.nodeInfo.mutationOptions())
|
return useMutation(consoleQuery.evaluation.nodeInfo.mutationOptions())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,6 +167,18 @@ export type AvailableEvaluationWorkflowsResponse = {
|
|||||||
has_more: boolean
|
has_more: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EvaluationWorkflowAssociatedTargetType = 'app' | 'snippets' | 'knowledge_base'
|
||||||
|
|
||||||
|
export type EvaluationWorkflowAssociatedTarget = {
|
||||||
|
target_type: EvaluationWorkflowAssociatedTargetType
|
||||||
|
target_id: string
|
||||||
|
target_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EvaluationWorkflowAssociatedTargetsResponse = {
|
||||||
|
items: EvaluationWorkflowAssociatedTarget[]
|
||||||
|
}
|
||||||
|
|
||||||
export type EvaluationNodeInfoRequest = {
|
export type EvaluationNodeInfoRequest = {
|
||||||
metrics?: string[]
|
metrics?: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user