Merge branch 'jzh' into deploy/dev

This commit is contained in:
JzoNg 2026-04-29 22:33:54 +08:00
commit f62cad931b
26 changed files with 428 additions and 255 deletions

View File

@ -1,7 +1,6 @@
import type { AppPublisherProps } from '@/app/components/app/app-publisher'
import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
import type { FileUpload } from '@/app/components/base/features/types'
import type { PublishWorkflowParams } from '@/types/workflow'
import type { AppPublisherProps, AppPublisherPublishParams } from '@/app/components/app/app-publisher'
import type { Features, FileUpload } from '@/app/components/base/features/types'
import type { ModelConfig } from '@/models/debug'
import {
AlertDialog,
AlertDialogActions,
@ -21,9 +20,15 @@ import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { Resolution } from '@/types/app'
type PublishedModelConfig = ModelConfig & {
resetAppConfig?: () => void
}
type Props = Omit<AppPublisherProps, 'onPublish'> & {
onPublish?: (params?: ModelAndParameter | PublishWorkflowParams, features?: any) => Promise<any> | any
publishedConfig?: any
onPublish?: (params?: AppPublisherPublishParams, features?: Features) => Promise<unknown> | unknown
publishedConfig: {
modelConfig: PublishedModelConfig
}
resetAppConfig?: () => void
}
@ -71,7 +76,7 @@ const FeaturesWrappedAppPublisher = (props: Props) => {
setRestoreConfirmOpen(false)
}, [featuresStore, props])
const handlePublish = useCallback((params?: ModelAndParameter | PublishWorkflowParams) => {
const handlePublish = useCallback((params?: AppPublisherPublishParams) => {
return props.onPublish?.(params, features)
}, [features, props])

View File

@ -3,9 +3,7 @@ import type { ModelAndParameter } from '../configuration/debug/types'
import type { WorkflowHiddenStartVariable, WorkflowLaunchInputValue } from '@/app/components/app/overview/app-card-utils'
import type { CollaborationUpdate } from '@/app/components/workflow/collaboration/types/collaboration'
import type { InputVar, Variable } from '@/app/components/workflow/types'
import type { EvaluationWorkflowAssociatedTarget } from '@/types/evaluation'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import type { PublishWorkflowParams, WorkflowTypeConversionTarget } from '@/types/workflow'
import type { PublishWorkflowParams } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
@ -42,11 +40,9 @@ import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/acces
import { fetchAppDetailDirect, publishToCreatorsPlatform } from '@/service/apps'
import { fetchInstalledAppList } from '@/service/explore'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useConvertWorkflowTypeMutation } from '@/service/use-apps'
import { useEvaluationWorkflowAssociatedTargets } from '@/service/use-evaluation'
import { useInvalidateAppWorkflow } from '@/service/use-workflow'
import { fetchPublishedWorkflow } from '@/service/workflow'
import { AppModeEnum, AppTypeEnum } from '@/types/app'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
import { getKeyboardKeyCodeBySystem } from '../../workflow/utils'
import AccessControl from '../app-access-control'
@ -57,12 +53,20 @@ import {
PublisherSummarySection,
} from './sections'
import SuggestedAction from './suggested-action'
import { useWorkflowTypeSwitch } from './use-workflow-type-switch'
import {
getDisabledFunctionTooltip,
getPublisherAppUrl,
isPublisherAccessConfigured,
} from './utils'
export type AppPublisherPublishParams
= | ModelAndParameter
| (Pick<PublishWorkflowParams, 'title' | 'releaseNotes'> & {
url?: string
id?: string
})
export type AppPublisherProps = {
disabled?: boolean
publishDisabled?: boolean
@ -72,8 +76,8 @@ export type AppPublisherProps = {
debugWithMultipleModel?: boolean
multipleModelConfigs?: ModelAndParameter[]
/** modelAndParameter is passed when debugWithMultipleModel is true */
onPublish?: (params?: any) => Promise<any> | any
onRestore?: () => Promise<any> | any
onPublish?: (params?: AppPublisherPublishParams) => Promise<unknown> | unknown
onRestore?: () => Promise<unknown> | unknown
onToggle?: (state: boolean) => void
crossAxisOffset?: number
toolPublished?: boolean
@ -89,32 +93,6 @@ export type AppPublisherProps = {
const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P']
type WorkflowTypeSwitchLabelKey = I18nKeysWithPrefix<'workflow', 'common.'>
const WORKFLOW_TYPE_SWITCH_CONFIG: Record<WorkflowTypeConversionTarget, {
targetType: WorkflowTypeConversionTarget
publishLabelKey: WorkflowTypeSwitchLabelKey
switchLabelKey: WorkflowTypeSwitchLabelKey
tipKey: WorkflowTypeSwitchLabelKey
}> = {
workflow: {
targetType: 'evaluation',
publishLabelKey: 'common.publishAsEvaluationWorkflow',
switchLabelKey: 'common.switchToEvaluationWorkflow',
tipKey: 'common.switchToEvaluationWorkflowTip',
},
evaluation: {
targetType: 'workflow',
publishLabelKey: 'common.publishAsStandardWorkflow',
switchLabelKey: 'common.switchToStandardWorkflow',
tipKey: 'common.switchToStandardWorkflowTip',
},
} as const
const isWorkflowTypeConversionTarget = (type?: AppTypeEnum): type is WorkflowTypeConversionTarget => {
return type === 'workflow' || type === 'evaluation'
}
const AppPublisher = ({
disabled = false,
publishDisabled = false,
@ -141,8 +119,6 @@ const AppPublisher = ({
const [published, setPublished] = useState(false)
const [open, setOpen] = useState(false)
const [showAppAccessControl, setShowAppAccessControl] = useState(false)
const [showEvaluationWorkflowSwitchConfirm, setShowEvaluationWorkflowSwitchConfirm] = useState(false)
const [evaluationWorkflowSwitchTargets, setEvaluationWorkflowSwitchTargets] = useState<EvaluationWorkflowAssociatedTarget[]>([])
const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false)
const [workflowLaunchDialogOpen, setWorkflowLaunchDialogOpen] = useState(false)
@ -157,36 +133,10 @@ const AppPublisher = ({
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const { formatTimeFromNow } = useFormatTimeFromNow()
const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {}
const { mutateAsync: convertWorkflowType, isPending: isConvertingWorkflowType } = useConvertWorkflowTypeMutation()
const appURL = getPublisherAppUrl({ appBaseUrl: appBaseURL, accessToken, mode: appDetail?.mode })
const isChatApp = [AppModeEnum.CHAT, AppModeEnum.AGENT_CHAT, AppModeEnum.COMPLETION].includes(appDetail?.mode || AppModeEnum.CHAT)
const workflowTypeSwitchConfig = useMemo(() => {
if (!appDetail?.workflow_kind)
return WORKFLOW_TYPE_SWITCH_CONFIG.workflow
if (!isWorkflowTypeConversionTarget(appDetail?.workflow_kind))
return undefined
return WORKFLOW_TYPE_SWITCH_CONFIG[appDetail.workflow_kind]
}, [appDetail?.workflow_kind])
const isEvaluationWorkflowType = appDetail?.workflow_kind === AppTypeEnum.EVALUATION
const {
refetch: refetchEvaluationWorkflowAssociatedTargets,
isFetching: isFetchingEvaluationWorkflowAssociatedTargets,
} = useEvaluationWorkflowAssociatedTargets(appDetail?.id, { enabled: false })
const workflowTypeSwitchDisabledReason = useMemo(() => {
if (workflowTypeSwitchConfig?.targetType !== AppTypeEnum.EVALUATION)
return undefined
if (!canAccessSnippetsAndEvaluation)
return t('compliance.sandboxUpgradeTooltip', { ns: 'common' })
if (!hasHumanInputNode && !hasTriggerNode)
return undefined
return t('common.switchToEvaluationWorkflowDisabledTip', { ns: 'workflow' })
}, [canAccessSnippetsAndEvaluation, hasHumanInputNode, hasTriggerNode, t, workflowTypeSwitchConfig?.targetType])
const hiddenLaunchVariables = useMemo<WorkflowHiddenStartVariable[]>(
() => (inputs ?? []).filter(input => input.hide === true),
[inputs],
@ -230,7 +180,7 @@ const AppPublisher = ({
refetch()
}, [open, appDetail, refetch, systemFeatures])
const handlePublish = useCallback(async (params?: ModelAndParameter | PublishWorkflowParams) => {
const handlePublish = useCallback(async (params?: AppPublisherPublishParams) => {
try {
await onPublish?.(params)
setPublished(true)
@ -312,109 +262,8 @@ const AppPublisher = ({
}
}, [appDetail, setAppDetail])
const getWorkflowTypeSwitchPublishUrl = useCallback(() => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return undefined
if (workflowTypeSwitchConfig.targetType === AppTypeEnum.EVALUATION)
return `/apps/${appDetail.id}/workflows/publish/evaluation`
return `/apps/${appDetail.id}/workflows/publish`
}, [appDetail?.id, workflowTypeSwitchConfig])
const performWorkflowTypeSwitch = useCallback(async () => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return false
try {
if (!publishedAt) {
const publishUrl = getWorkflowTypeSwitchPublishUrl()
if (!publishUrl)
return false
await handlePublish({
url: publishUrl,
title: '',
releaseNotes: '',
})
const latestAppDetail = await fetchAppDetailDirect({
url: '/apps',
id: appDetail.id,
})
setAppDetail(latestAppDetail)
setShowEvaluationWorkflowSwitchConfirm(false)
setEvaluationWorkflowSwitchTargets([])
return true
}
await convertWorkflowType({
params: {
appId: appDetail.id,
},
query: {
target_type: workflowTypeSwitchConfig.targetType,
},
})
const latestAppDetail = await fetchAppDetailDirect({
url: '/apps',
id: appDetail.id,
})
setAppDetail(latestAppDetail)
if (publishedAt)
setOpen(false)
setShowEvaluationWorkflowSwitchConfirm(false)
setEvaluationWorkflowSwitchTargets([])
return true
}
catch {
return false
}
}, [appDetail?.id, convertWorkflowType, getWorkflowTypeSwitchPublishUrl, handlePublish, publishedAt, setAppDetail, workflowTypeSwitchConfig])
const handleWorkflowTypeSwitch = useCallback(async () => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return
if (workflowTypeSwitchDisabledReason) {
toast.error(workflowTypeSwitchDisabledReason)
return
}
if (appDetail.workflow_kind === 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?.workflow_kind,
performWorkflowTypeSwitch,
refetchEvaluationWorkflowAssociatedTargets,
t,
workflowTypeSwitchConfig,
workflowTypeSwitchDisabledReason,
])
const handleEvaluationWorkflowSwitchConfirmOpenChange = useCallback((nextOpen: boolean) => {
setShowEvaluationWorkflowSwitchConfirm(nextOpen)
if (!nextOpen)
setEvaluationWorkflowSwitchTargets([])
const handlePublishedWorkflowTypeSwitch = useCallback(() => {
setOpen(false)
}, [])
const handleOpenWorkflowLaunchDialog = useCallback((targetUrl: string) => {
@ -442,6 +291,29 @@ const AppPublisher = ({
window.open(targetUrl, '_blank')
setWorkflowLaunchDialogOpen(false)
}, [supportedWorkflowLaunchVariables, workflowLaunchTargetUrl, workflowLaunchValues])
const {
evaluationWorkflowSwitchTargets,
handleEvaluationWorkflowSwitchConfirmOpenChange,
handleWorkflowTypeSwitch,
isConvertingWorkflowType,
isEvaluationWorkflowType,
performWorkflowTypeSwitch,
showEvaluationWorkflowSwitchConfirm,
workflowTypeSwitchConfig,
workflowTypeSwitchDisabled,
workflowTypeSwitchDisabledReason,
} = useWorkflowTypeSwitch({
appDetail,
canAccessSnippetsAndEvaluation,
hasHumanInputNode,
hasTriggerNode,
onPublish: handlePublish,
onPublishedSwitch: handlePublishedWorkflowTypeSwitch,
published,
publishedAt,
publishDisabled,
setAppDetail,
})
const handlePublishToMarketplace = useCallback(async () => {
if (!appDetail?.id || publishingToMarketplace)
@ -541,7 +413,7 @@ const AppPublisher = ({
startNodeLimitExceeded={startNodeLimitExceeded}
upgradeHighlightStyle={upgradeHighlightStyle}
workflowTypeSwitchConfig={workflowTypeSwitchConfig}
workflowTypeSwitchDisabled={publishDisabled || published || isConvertingWorkflowType || isFetchingEvaluationWorkflowAssociatedTargets || Boolean(workflowTypeSwitchDisabledReason)}
workflowTypeSwitchDisabled={workflowTypeSwitchDisabled}
workflowTypeSwitchDisabledReason={workflowTypeSwitchDisabledReason}
onWorkflowTypeSwitch={handleWorkflowTypeSwitch}
/>

View File

@ -1,8 +1,8 @@
import type { CSSProperties, ReactNode } from 'react'
import type { ModelAndParameter } from '../configuration/debug/types'
import type { AppPublisherProps } from './index'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import type { PublishWorkflowParams, WorkflowTypeConversionTarget } from '@/types/workflow'
import type { WorkflowTypeSwitchConfig } from './use-workflow-type-switch'
import type { PublishWorkflowParams } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import {
Tooltip,
@ -23,8 +23,6 @@ import PublishWithMultipleModel from './publish-with-multiple-model'
import SuggestedAction from './suggested-action'
import { ACCESS_MODE_MAP } from './utils'
type WorkflowTypeSwitchLabelKey = I18nKeysWithPrefix<'workflow', 'common.'>
type SummarySectionProps = Pick<AppPublisherProps, | 'debugWithMultipleModel'
| 'draftUpdatedAt'
| 'multipleModelConfigs'
@ -39,12 +37,7 @@ type SummarySectionProps = Pick<AppPublisherProps, | 'debugWithMultipleModel'
published: boolean
publishShortcut: string[]
upgradeHighlightStyle: CSSProperties
workflowTypeSwitchConfig?: {
targetType: WorkflowTypeConversionTarget
publishLabelKey: WorkflowTypeSwitchLabelKey
switchLabelKey: WorkflowTypeSwitchLabelKey
tipKey: WorkflowTypeSwitchLabelKey
}
workflowTypeSwitchConfig?: WorkflowTypeSwitchConfig
workflowTypeSwitchDisabled: boolean
workflowTypeSwitchDisabledReason?: string
}

View File

@ -0,0 +1,229 @@
import type { ModelAndParameter } from '../configuration/debug/types'
import type { App, AppSSO } from '@/types/app'
import type { EvaluationWorkflowAssociatedTarget } from '@/types/evaluation'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import type { PublishWorkflowParams, WorkflowKind, WorkflowTypeConversionTarget } from '@/types/workflow'
import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { fetchAppDetailDirect } from '@/service/apps'
import { useConvertWorkflowTypeMutation } from '@/service/use-apps'
import { useEvaluationWorkflowAssociatedTargets } from '@/service/use-evaluation'
import { AppTypeEnum } from '@/types/app'
type WorkflowTypeSwitchLabelKey = I18nKeysWithPrefix<'workflow', 'common.'>
export type WorkflowTypeSwitchConfig = {
targetType: WorkflowTypeConversionTarget
publishLabelKey: WorkflowTypeSwitchLabelKey
switchLabelKey: WorkflowTypeSwitchLabelKey
tipKey: WorkflowTypeSwitchLabelKey
}
const WORKFLOW_TYPE_SWITCH_CONFIG: Record<WorkflowTypeConversionTarget, WorkflowTypeSwitchConfig> = {
workflow: {
targetType: 'evaluation',
publishLabelKey: 'common.publishAsEvaluationWorkflow',
switchLabelKey: 'common.switchToEvaluationWorkflow',
tipKey: 'common.switchToEvaluationWorkflowTip',
},
evaluation: {
targetType: 'workflow',
publishLabelKey: 'common.publishAsStandardWorkflow',
switchLabelKey: 'common.switchToStandardWorkflow',
tipKey: 'common.switchToStandardWorkflowTip',
},
} as const
const getWorkflowTypeSwitchConfig = (workflowKind?: WorkflowKind | null) => {
if (!workflowKind || workflowKind === 'standard')
return WORKFLOW_TYPE_SWITCH_CONFIG.workflow
if (workflowKind === 'evaluation')
return WORKFLOW_TYPE_SWITCH_CONFIG.evaluation
}
type UseWorkflowTypeSwitchParams = {
appDetail?: App & Partial<AppSSO>
canAccessSnippetsAndEvaluation: boolean
hasHumanInputNode: boolean
hasTriggerNode: boolean
onPublish: (params?: ModelAndParameter | PublishWorkflowParams) => Promise<void>
onPublishedSwitch: () => void
published: boolean
publishedAt?: number
publishDisabled: boolean
setAppDetail: (appDetail?: App & Partial<AppSSO>) => void
}
export const useWorkflowTypeSwitch = ({
appDetail,
canAccessSnippetsAndEvaluation,
hasHumanInputNode,
hasTriggerNode,
onPublish,
onPublishedSwitch,
published,
publishedAt,
publishDisabled,
setAppDetail,
}: UseWorkflowTypeSwitchParams) => {
const { t } = useTranslation()
const [showEvaluationWorkflowSwitchConfirm, setShowEvaluationWorkflowSwitchConfirm] = useState(false)
const [evaluationWorkflowSwitchTargets, setEvaluationWorkflowSwitchTargets] = useState<EvaluationWorkflowAssociatedTarget[]>([])
const { mutateAsync: convertWorkflowType, isPending: isConvertingWorkflowType } = useConvertWorkflowTypeMutation()
const {
refetch: refetchEvaluationWorkflowAssociatedTargets,
isFetching: isFetchingEvaluationWorkflowAssociatedTargets,
} = useEvaluationWorkflowAssociatedTargets(appDetail?.id, { enabled: false })
const workflowTypeSwitchConfig = useMemo(() => {
return getWorkflowTypeSwitchConfig(appDetail?.workflow_kind)
}, [appDetail?.workflow_kind])
const workflowTypeSwitchDisabledReason = useMemo(() => {
if (workflowTypeSwitchConfig?.targetType !== AppTypeEnum.EVALUATION)
return undefined
if (!canAccessSnippetsAndEvaluation)
return t('compliance.sandboxUpgradeTooltip', { ns: 'common' })
if (!hasHumanInputNode && !hasTriggerNode)
return undefined
return t('common.switchToEvaluationWorkflowDisabledTip', { ns: 'workflow' })
}, [canAccessSnippetsAndEvaluation, hasHumanInputNode, hasTriggerNode, t, workflowTypeSwitchConfig?.targetType])
const getWorkflowTypeSwitchPublishUrl = useCallback(() => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return undefined
if (workflowTypeSwitchConfig.targetType === AppTypeEnum.EVALUATION)
return `/apps/${appDetail.id}/workflows/publish/evaluation`
return `/apps/${appDetail.id}/workflows/publish`
}, [appDetail?.id, workflowTypeSwitchConfig])
const resetEvaluationWorkflowSwitchConfirm = useCallback(() => {
setShowEvaluationWorkflowSwitchConfirm(false)
setEvaluationWorkflowSwitchTargets([])
}, [])
const performWorkflowTypeSwitch = useCallback(async () => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return false
try {
if (!publishedAt) {
const publishUrl = getWorkflowTypeSwitchPublishUrl()
if (!publishUrl)
return false
await onPublish({
url: publishUrl,
title: '',
releaseNotes: '',
})
const latestAppDetail = await fetchAppDetailDirect({
url: '/apps',
id: appDetail.id,
})
setAppDetail(latestAppDetail)
resetEvaluationWorkflowSwitchConfirm()
return true
}
await convertWorkflowType({
params: {
appId: appDetail.id,
},
query: {
target_type: workflowTypeSwitchConfig.targetType,
},
})
const latestAppDetail = await fetchAppDetailDirect({
url: '/apps',
id: appDetail.id,
})
setAppDetail(latestAppDetail)
onPublishedSwitch()
resetEvaluationWorkflowSwitchConfirm()
return true
}
catch {
return false
}
}, [
appDetail?.id,
convertWorkflowType,
getWorkflowTypeSwitchPublishUrl,
onPublish,
onPublishedSwitch,
publishedAt,
resetEvaluationWorkflowSwitchConfirm,
setAppDetail,
workflowTypeSwitchConfig,
])
const handleWorkflowTypeSwitch = useCallback(async () => {
if (!appDetail?.id || !workflowTypeSwitchConfig)
return
if (workflowTypeSwitchDisabledReason) {
toast.error(workflowTypeSwitchDisabledReason)
return
}
if (appDetail.workflow_kind === 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?.workflow_kind,
performWorkflowTypeSwitch,
refetchEvaluationWorkflowAssociatedTargets,
t,
workflowTypeSwitchConfig,
workflowTypeSwitchDisabledReason,
])
const handleEvaluationWorkflowSwitchConfirmOpenChange = useCallback((nextOpen: boolean) => {
setShowEvaluationWorkflowSwitchConfirm(nextOpen)
if (!nextOpen)
setEvaluationWorkflowSwitchTargets([])
}, [])
return {
evaluationWorkflowSwitchTargets,
handleEvaluationWorkflowSwitchConfirmOpenChange,
handleWorkflowTypeSwitch,
isConvertingWorkflowType,
isEvaluationWorkflowType: appDetail?.workflow_kind === AppTypeEnum.EVALUATION,
performWorkflowTypeSwitch,
showEvaluationWorkflowSwitchConfirm,
workflowTypeSwitchConfig,
workflowTypeSwitchDisabled: publishDisabled
|| published
|| isConvertingWorkflowType
|| isFetchingEvaluationWorkflowAssociatedTargets
|| Boolean(workflowTypeSwitchDisabledReason),
workflowTypeSwitchDisabledReason,
}
}

View File

@ -1,5 +1,6 @@
'use client'
import type { ComponentProps } from 'react'
import type { AppPublisherPublishParams } from '@/app/components/app/app-publisher'
import type AppPublisher from '@/app/components/app/app-publisher/features-wrapper'
import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
import type { Features as FeaturesData, OnFeaturesChange } from '@/app/components/base/features/types'
@ -21,7 +22,6 @@ import type {
TextToSpeechConfig,
} from '@/models/debug'
import type { VisionSettings } from '@/types/app'
import type { PublishWorkflowParams } from '@/types/workflow'
import { useBoolean, useGetState } from 'ahooks'
import { clone } from 'es-toolkit/object'
import { produce } from 'immer'
@ -481,7 +481,7 @@ export const useConfiguration = (): ConfigurationViewModel => {
resolvedModelModeType,
])
const onPublish = useCallback(async (params?: ModelAndParameter | PublishWorkflowParams, features?: FeaturesData) => {
const onPublish = useCallback(async (params?: AppPublisherPublishParams, features?: FeaturesData) => {
const modelAndParameter = params && 'model' in params && 'provider' in params && 'parameters' in params
? params
: undefined

View File

@ -6,7 +6,7 @@ import ConditionsSection from '../components/conditions-section'
import { useEvaluationStore } from '../store'
const mockUpload = vi.hoisted(() => vi.fn())
const mockUseAvailableEvaluationMetrics = vi.hoisted(() => vi.fn())
const mockUseDatasetEvaluationMetrics = vi.hoisted(() => vi.fn())
const mockUseDefaultEvaluationMetrics = vi.hoisted(() => vi.fn())
const mockUseEvaluationConfig = vi.hoisted(() => vi.fn())
const mockUseSaveEvaluationConfigMutation = vi.hoisted(() => vi.fn())
@ -51,7 +51,7 @@ vi.mock('@/service/base', () => ({
vi.mock('@/service/use-evaluation', () => ({
useEvaluationConfig: (...args: unknown[]) => mockUseEvaluationConfig(...args),
useAvailableEvaluationMetrics: (...args: unknown[]) => mockUseAvailableEvaluationMetrics(...args),
useDatasetEvaluationMetrics: (...args: unknown[]) => mockUseDatasetEvaluationMetrics(...args),
useDefaultEvaluationMetrics: (...args: unknown[]) => mockUseDefaultEvaluationMetrics(...args),
useSaveEvaluationConfigMutation: (...args: unknown[]) => mockUseSaveEvaluationConfigMutation(...args),
useStartEvaluationRunMutation: (...args: unknown[]) => mockUseStartEvaluationRunMutation(...args),
@ -119,7 +119,7 @@ describe('Evaluation', () => {
data: null,
})
mockUseAvailableEvaluationMetrics.mockReturnValue({
mockUseDatasetEvaluationMetrics.mockReturnValue({
data: {
metrics: ['answer-correctness', 'faithfulness', 'context-precision', 'context-recall', 'context-relevance'],
},
@ -582,6 +582,7 @@ describe('Evaluation', () => {
it('should render the pipeline-specific layout without auto-selecting a judge model', () => {
renderWithQueryClient(<Evaluation resourceType="datasets" resourceId="dataset-1" />)
expect(mockUseDatasetEvaluationMetrics).toHaveBeenCalledWith('dataset-1')
expect(screen.getByTestId('evaluation-model-selector')).toHaveTextContent('empty')
expect(screen.getByText('evaluation.history.columns.time')).toBeInTheDocument()
expect(screen.getByText('Context Precision')).toBeInTheDocument()
@ -621,6 +622,33 @@ describe('Evaluation', () => {
expect(screen.getByRole('button', { name: 'evaluation.pipeline.uploadAndRun' })).toBeEnabled()
})
it('should download the fixed pipeline template columns', () => {
const createElement = document.createElement.bind(document)
let downloadLink: HTMLAnchorElement | undefined
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName, options) => {
const element = createElement(tagName, options)
if (tagName === 'a') {
downloadLink = element as HTMLAnchorElement
vi.spyOn(downloadLink, 'click').mockImplementation(() => {})
}
return element
})
renderWithQueryClient(<Evaluation resourceType="datasets" resourceId="dataset-template" />)
fireEvent.click(screen.getByRole('button', { name: 'select-model' }))
fireEvent.click(screen.getByRole('button', { name: /Context Precision/i }))
fireEvent.click(screen.getByRole('button', { name: 'evaluation.batch.downloadTemplate' }))
expect(downloadLink?.download).toBe('pipeline-evaluation-template.csv')
expect(decodeURIComponent(downloadLink?.href ?? '')).toContain('query,expect_results\n')
expect(decodeURIComponent(downloadLink?.href ?? '')).not.toContain('expected_output')
createElementSpy.mockRestore()
})
it('should upload and start a pipeline evaluation run', async () => {
const startRun = vi.fn()
mockUseStartEvaluationRunMutation.mockReturnValue({
@ -639,14 +667,14 @@ describe('Evaluation', () => {
fireEvent.click(screen.getByRole('button', { name: 'evaluation.pipeline.uploadAndRun' }))
expect(screen.getAllByText('query').length).toBeGreaterThan(0)
expect(screen.getAllByText('Expect Results').length).toBeGreaterThan(0)
expect(screen.getAllByText('expect_results').length).toBeGreaterThan(0)
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"][accept=".csv"]')
expect(fileInput).toBeInTheDocument()
fireEvent.change(fileInput!, {
target: {
files: [new File(['query,Expect Results'], 'pipeline-evaluation.csv', { type: 'text/csv' })],
files: [new File(['query,expect_results'], 'pipeline-evaluation.csv', { type: 'text/csv' })],
},
})

View File

@ -21,6 +21,7 @@ type UseInputFieldsActionsParams = EvaluationResourceProps & {
isInputFieldsLoading: boolean
isPanelReady: boolean
isRunnable: boolean
templateContent?: string
templateFileName: string
}
@ -31,6 +32,7 @@ export const useInputFieldsActions = ({
isInputFieldsLoading,
isPanelReady,
isRunnable,
templateContent,
templateFileName,
}: UseInputFieldsActionsParams) => {
const { t } = useTranslation('evaluation')
@ -79,7 +81,7 @@ export const useInputFieldsActions = ({
return
}
const content = buildTemplateCsvContent(inputFields)
const content = templateContent ?? buildTemplateCsvContent(inputFields)
const link = document.createElement('a')
link.href = `data:text/csv;charset=utf-8,${encodeURIComponent(content)}`
link.download = templateFileName

View File

@ -1,39 +1,21 @@
'use client'
import type { EvaluationResourceProps } from '../types'
import { useEffect } from 'react'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useEvaluationResource, useEvaluationStore } from '../store'
import { decodeModelSelection, encodeModelSelection } from '../utils'
type JudgeModelSelectorProps = EvaluationResourceProps & {
autoSelectFirst?: boolean
}
const JudgeModelSelector = ({
resourceType,
resourceId,
autoSelectFirst = true,
}: JudgeModelSelectorProps) => {
}: EvaluationResourceProps) => {
const { data: modelList } = useModelList(ModelTypeEnum.textGeneration)
const resource = useEvaluationResource(resourceType, resourceId)
const setJudgeModel = useEvaluationStore(state => state.setJudgeModel)
const selectedModel = decodeModelSelection(resource.judgeModelId)
useEffect(() => {
if (!autoSelectFirst || resource.judgeModelId || !modelList.length)
return
const firstProvider = modelList[0]
const firstModel = firstProvider.models[0]
if (!firstProvider || !firstModel)
return
setJudgeModel(resourceType, resourceId, encodeModelSelection(firstProvider.provider, firstModel.model))
}, [autoSelectFirst, modelList, resource.judgeModelId, resourceId, resourceType, setJudgeModel])
return (
<ModelSelector
defaultModel={selectedModel}

View File

@ -27,8 +27,8 @@ const PipelineEvaluation = ({
}, [ensureResource, resourceId, resourceType])
return (
<div className="flex h-full min-h-0 flex-col bg-background-default xl:flex-row">
<div className="flex min-h-0 flex-col border-b border-divider-subtle bg-background-default xl:w-[450px] xl:shrink-0 xl:border-r xl:border-b-0">
<div className="flex h-full min-h-0 flex-col overflow-y-auto bg-background-default xl:flex-row xl:overflow-hidden">
<div className="flex shrink-0 flex-col border-b border-divider-subtle bg-background-default xl:min-h-0 xl:w-[450px] xl:border-r xl:border-b-0">
<div className="px-6 pt-4 pb-2">
<SectionHeader
title={t('title')}
@ -58,7 +58,6 @@ const PipelineEvaluation = ({
<JudgeModelSelector
resourceType={resourceType}
resourceId={resourceId}
autoSelectFirst={false}
/>
</div>
</section>
@ -77,7 +76,7 @@ const PipelineEvaluation = ({
<div className="border-t border-divider-subtle" />
<div className="min-h-0 flex-1 px-6 py-4">
<div className="px-6 py-4 xl:min-h-0 xl:flex-1">
<HistoryTab
resourceType={resourceType}
resourceId={resourceId}
@ -85,7 +84,7 @@ const PipelineEvaluation = ({
</div>
</div>
<div className="min-h-0 flex-1 bg-background-default">
<div className="shrink-0 bg-background-default xl:min-h-0 xl:flex-1">
<PipelineResultsPanel
resourceType={resourceType}
resourceId={resourceId}

View File

@ -11,8 +11,9 @@ import { useInputFieldsActions } from '../batch-test-panel/input-fields/use-inpu
const PIPELINE_INPUT_FIELDS: InputField[] = [
{ name: 'query', type: 'string' },
{ name: 'Expect Results', type: 'string' },
{ name: 'expect_results', type: 'string' },
]
const PIPELINE_TEMPLATE_CONTENT = 'query,expect_results\n'
const PipelineBatchActions = ({
resourceType,
@ -29,6 +30,7 @@ const PipelineBatchActions = ({
isInputFieldsLoading: false,
isPanelReady: isConfigReady,
isRunnable,
templateContent: PIPELINE_TEMPLATE_CONTENT,
templateFileName: EVALUATION_TEMPLATE_FILE_NAMES[resourceType],
})

View File

@ -30,7 +30,7 @@ const PipelineMetricItem = ({
const metricDescription = getTranslatedMetricDescription(t, metric.id, metric.description)
return (
<div className="flex items-center justify-between gap-3 px-1 py-1">
<div className="flex h-8 items-center justify-between gap-3 px-1 py-1">
<button
type="button"
className="flex min-w-0 items-center gap-2 text-left"
@ -56,7 +56,7 @@ const PipelineMetricItem = ({
? (
<div className="flex items-center gap-2">
<span className="system-xs-medium text-text-accent">{t('pipeline.passIf')}</span>
<div className="w-[52px]">
<div className="w-[64px]">
<Input
value={String(threshold)}
type="number"
@ -77,11 +77,12 @@ const PipelineMetricItem = ({
type="button"
disabled={disabledCondition}
className={cn(
'system-xs-medium text-text-tertiary',
'flex items-center gap-0.5 system-xs-medium text-text-tertiary',
disabledCondition && 'cursor-not-allowed text-components-button-secondary-accent-text-disabled',
)}
>
+ Condition
<span aria-hidden="true" className="i-ri-add-line h-3.5 w-3.5" />
{t('conditions.addCondition')}
</button>
)}
</div>

View File

@ -7,7 +7,7 @@ import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { BlockEnum } from '@/app/components/workflow/types'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useAvailableEvaluationMetrics } from '@/service/use-evaluation'
import { useDatasetEvaluationMetrics } from '@/service/use-evaluation'
import { usePublishedPipelineInfo } from '@/service/use-pipeline'
import { useEvaluationResource, useEvaluationStore } from '../../store'
import { buildMetricOption } from '../metric-selector/utils'
@ -49,7 +49,7 @@ const PipelineMetricsSection = ({
const addBuiltinMetric = useEvaluationStore(state => state.addBuiltinMetric)
const removeMetric = useEvaluationStore(state => state.removeMetric)
const updateMetricThreshold = useEvaluationStore(state => state.updateMetricThreshold)
const { data: availableMetricsData } = useAvailableEvaluationMetrics()
const { data: datasetMetricsData } = useDatasetEvaluationMetrics(resourceId)
const { data: publishedPipeline } = usePublishedPipelineInfo(pipelineId || '')
const resource = useEvaluationResource(resourceType, resourceId)
const knowledgeIndexNodeInfoList = useMemo(
@ -63,12 +63,12 @@ const PipelineMetricsSection = ({
), [resource.metrics])
const availableBuiltinMetrics = useMemo(() => {
const metricIds = new Set([
...(availableMetricsData?.metrics ?? []),
...(datasetMetricsData?.metrics ?? []),
...builtinMetricMap.keys(),
])
return Array.from(metricIds).map(metricId => buildMetricOption(metricId))
}, [availableMetricsData?.metrics, builtinMetricMap])
}, [datasetMetricsData?.metrics, builtinMetricMap])
useEffect(() => {
if (!knowledgeIndexNodeInfoList.length)

View File

@ -57,7 +57,7 @@ const PipelineResultsPanel = ({
if (isEmpty) {
return (
<div className="flex min-h-[360px] flex-1 items-center justify-center xl:min-h-0">
<div className="flex min-h-[360px] w-full items-center justify-center xl:h-full xl:min-h-0">
<div className="flex flex-col items-center gap-4 px-4 text-center">
<span aria-hidden="true" className="i-ri-file-list-3-line h-12 w-12 text-text-quaternary" />
<div className="system-md-medium text-text-quaternary">{t('results.empty')}</div>
@ -67,7 +67,7 @@ const PipelineResultsPanel = ({
}
return (
<div className="flex h-full min-h-0 flex-col border-l border-divider-subtle bg-background-default">
<div className="flex min-h-[360px] flex-col border-l border-divider-subtle bg-background-default xl:h-full xl:min-h-0">
<div className="shrink-0 px-6 pt-4 pb-2">
<h2 className="system-xl-semibold text-text-primary">{t('results.title')}</h2>
</div>
@ -75,7 +75,7 @@ const PipelineResultsPanel = ({
<div className="px-6 py-4 system-sm-regular text-text-destructive">{t('results.loadFailed')}</div>
)}
{!runDetailQuery.isError && (
<div className="flex min-h-0 flex-1 flex-col px-6 py-1">
<div className="flex flex-col px-6 py-1 xl:min-h-0 xl:flex-1">
<div className="flex shrink-0 flex-wrap items-center justify-between gap-3 py-1">
<div className="flex min-w-0 flex-wrap items-center gap-2 system-xs-regular text-text-secondary">
<span>{getRunDate(runDetail?.run.started_at ?? runDetail?.run.created_at ?? null)}</span>

View File

@ -26,7 +26,7 @@ const PipelineResultsTable = ({
const { t } = useTranslation('evaluation')
return (
<div className="min-h-0 flex-1 overflow-auto py-2">
<div className="overflow-x-auto py-2 xl:min-h-0 xl:flex-1 xl:overflow-auto">
<table className="min-w-full table-fixed border-collapse overflow-hidden rounded-lg">
<colgroup>
<col className="w-10" />
@ -38,14 +38,14 @@ const PipelineResultsTable = ({
<thead>
<tr className="bg-background-section">
<th className="h-7 rounded-l-lg" />
<th className="system-xs-medium-uppercase h-7 px-3 text-left text-text-tertiary">{t('results.columns.query')}</th>
<th className="system-xs-medium-uppercase h-7 px-3 text-left text-text-tertiary">{t('results.columns.expected')}</th>
<th className="system-xs-medium-uppercase h-7 px-3 text-left text-text-tertiary">{t('results.columns.actual')}</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('results.columns.query')}</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('results.columns.expected')}</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('results.columns.actual')}</th>
{metricColumns.map((column, index) => (
<th
key={column.id}
className={cn(
'system-xs-medium-uppercase h-7 px-3 text-left text-text-tertiary',
'h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary',
index === metricColumns.length - 1 && 'rounded-r-lg',
)}
>
@ -79,14 +79,14 @@ const PipelineResultsTable = ({
)}
/>
</td>
<td className="system-sm-regular h-10 px-3 py-3 align-top text-text-secondary">
<td className="h-10 px-3 py-3 align-top system-sm-regular text-text-secondary">
<div className="line-clamp-2 break-words">{getQueryContent(item)}</div>
</td>
<td className="system-sm-regular h-10 px-3 py-3 align-top text-text-secondary">
<td className="h-10 px-3 py-3 align-top system-sm-regular text-text-secondary">
<div className="line-clamp-2 break-words">{formatValue(item.expected_output)}</div>
</td>
<td className={cn(
'system-sm-regular h-10 px-3 py-3 align-top',
'h-10 px-3 py-3 align-top system-sm-regular',
actualOutput ? 'text-text-secondary' : 'text-text-destructive',
)}
>
@ -100,7 +100,7 @@ const PipelineResultsTable = ({
return (
<td
key={column.id}
className={cn('system-sm-regular h-10 px-3 py-3 align-top', getMetricTextClassName(metricValue, column))}
className={cn('h-10 px-3 py-3 align-top system-sm-regular', getMetricTextClassName(metricValue, column))}
>
{formatValue(metricValue)}
</td>

View File

@ -316,5 +316,16 @@ describe('SnippetMain', () => {
expect(capturedHooksStore?.handleStartWorkflowRun).toBe(mockHandleStartWorkflowRun)
expect(capturedHooksStore?.handleWorkflowStartRunInWorkflow).toBe(mockHandleWorkflowStartRunInWorkflow)
})
it('should pass snippet workflow run detail urls to WorkflowWithInnerContext', () => {
renderSnippetMain()
const getWorkflowRunAndTraceUrl = capturedHooksStore?.getWorkflowRunAndTraceUrl as ((runId?: string) => { runUrl: string, traceUrl: string }) | undefined
expect(getWorkflowRunAndTraceUrl?.('run-1')).toEqual({
runUrl: '/snippets/snippet-1/workflow-runs/run-1',
traceUrl: '/snippets/snippet-1/workflow-runs/run-1/node-executions',
})
})
})
})

View File

@ -13,6 +13,7 @@ import { useAvailableNodesMetaData } from '@/app/components/workflow-app/hooks'
import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars'
import { BlockEnum } from '@/app/components/workflow/types'
import { useConfigsMap } from '../hooks/use-configs-map'
import { useGetRunAndTraceUrl } from '../hooks/use-get-run-and-trace-url'
import { useInspectVarsCrud } from '../hooks/use-inspect-vars-crud'
import { useNodesSyncDraft } from '../hooks/use-nodes-sync-draft'
import { useSnippetRefreshDraft } from '../hooks/use-snippet-refresh-draft'
@ -179,6 +180,7 @@ const SnippetMain = ({
handleRun,
inputFields: fields,
})
const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl(snippetId)
useEffect(() => {
reset()
@ -200,6 +202,7 @@ const SnippetMain = ({
handleStopRun,
handleStartWorkflowRun,
handleWorkflowStartRunInWorkflow,
getWorkflowRunAndTraceUrl,
availableNodesMetaData,
fetchInspectVars,
hasNodeInspectVars,
@ -237,6 +240,7 @@ const SnippetMain = ({
handleStartWorkflowRun,
handleStopRun,
handleWorkflowStartRunInWorkflow,
getWorkflowRunAndTraceUrl,
hasNodeInspectVars,
hasSetInspectVar,
invalidateConversationVarValues,

View File

@ -263,7 +263,7 @@ const SnippetRunPanel = ({
elapsed_time={workflowRunningData.result?.elapsed_time}
total_tokens={workflowRunningData.result?.total_tokens}
created_at={workflowRunningData.result?.created_at}
created_by={workflowRunningData.result?.created_by}
created_by={(workflowRunningData.result?.created_by as unknown as { name: string })?.name}
steps={workflowRunningData.result?.total_steps}
exceptionCounts={workflowRunningData.result?.exceptions_count}
/>

View File

@ -0,0 +1,22 @@
import { renderHook } from '@testing-library/react'
import { useGetRunAndTraceUrl } from '../use-get-run-and-trace-url'
describe('useGetRunAndTraceUrl', () => {
it('should build snippet workflow run and trace urls from the snippet id', () => {
const { result } = renderHook(() => useGetRunAndTraceUrl('snippet-1'))
expect(result.current.getWorkflowRunAndTraceUrl('run-1')).toEqual({
runUrl: '/snippets/snippet-1/workflow-runs/run-1',
traceUrl: '/snippets/snippet-1/workflow-runs/run-1/node-executions',
})
})
it('should return empty urls when no run id is provided', () => {
const { result } = renderHook(() => useGetRunAndTraceUrl('snippet-1'))
expect(result.current.getWorkflowRunAndTraceUrl()).toEqual({
runUrl: '',
traceUrl: '',
})
})
})

View File

@ -0,0 +1,21 @@
import { useCallback } from 'react'
export const useGetRunAndTraceUrl = (snippetId: string) => {
const getWorkflowRunAndTraceUrl = useCallback((runId?: string) => {
if (!runId) {
return {
runUrl: '',
traceUrl: '',
}
}
return {
runUrl: `/snippets/${snippetId}/workflow-runs/${runId}`,
traceUrl: `/snippets/${snippetId}/workflow-runs/${runId}/node-executions`,
}
}, [snippetId])
return {
getWorkflowRunAndTraceUrl,
}
}

View File

@ -1,11 +1,10 @@
import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
import type { AppPublisherPublishParams } from '@/app/components/app/app-publisher'
import type { EndNodeType } from '@/app/components/workflow/nodes/end/types'
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
import type {
CommonEdgeType,
Node,
} from '@/app/components/workflow/types'
import type { PublishWorkflowParams } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
@ -144,7 +143,7 @@ const FeaturesTrigger = () => {
const needWarningNodes = useChecklist(nodes, edges)
const updatePublishedWorkflow = useInvalidateAppWorkflow()
const onPublish = useCallback(async (params?: ModelAndParameter | PublishWorkflowParams) => {
const onPublish = useCallback(async (params?: AppPublisherPublishParams) => {
const publishParams = params && 'title' in params ? params : undefined
// First check if there are any items in the checklist
// if (!validateBeforeRun())

View File

@ -1,3 +1,4 @@
import type { WorkflowKind } from '@/types/workflow'
import { AppTypeEnum } from '@/types/app'
import { BlockEnum, TRIGGER_NODE_TYPES } from '../types'
@ -6,7 +7,7 @@ const EVALUATION_WORKFLOW_RESTRICTED_NODE_TYPES = new Set<string>([
...TRIGGER_NODE_TYPES,
])
export const isEvaluationWorkflow = (appType?: string) => appType === AppTypeEnum.EVALUATION
export const isEvaluationWorkflow = (appType?: WorkflowKind | null) => appType === AppTypeEnum.EVALUATION
export const isEvaluationWorkflowRestrictedNodeType = (nodeType?: string) => {
if (!nodeType)

View File

@ -284,13 +284,6 @@ export const evaluationNodeInfoContract = base
}>())
.output(type<EvaluationNodeInfoResponse>())
export const availableEvaluationMetricsContract = base
.route({
path: '/evaluation/available-metrics',
method: 'GET',
})
.output(type<EvaluationMetricsListResponse>())
export const availableEvaluationWorkflowsContract = base
.route({
path: '/workspaces/current/available-evaluation-workflows',

View File

@ -3,7 +3,6 @@ import { accountAvatarContract } from './console/account'
import { appDeleteContract, appWorkflowTypeConvertContract, workflowOnlineUsersContract } from './console/apps'
import { bindPartnerStackContract, invoicesContract } from './console/billing'
import {
availableEvaluationMetricsContract,
availableEvaluationWorkflowsContract,
cancelDatasetEvaluationRunContract,
cancelEvaluationRunContract,
@ -148,7 +147,6 @@ export const consoleRouterContract = {
metrics: evaluationMetricsContract,
defaultMetrics: evaluationDefaultMetricsContract,
nodeInfo: evaluationNodeInfoContract,
availableMetrics: availableEvaluationMetricsContract,
availableWorkflows: availableEvaluationWorkflowsContract,
associatedTargets: evaluationWorkflowAssociatedTargetsContract,
file: evaluationFileContract,

View File

@ -54,9 +54,17 @@ export const useEvaluationConfig = (
return useQuery<EvaluationConfig>(getEvaluationConfigQueryOptions(resourceType, resourceId))
}
export const useAvailableEvaluationMetrics = (enabled = true) => {
return useQuery(consoleQuery.evaluation.availableMetrics.queryOptions({
enabled,
export const useDatasetEvaluationMetrics = (datasetId: string, enabled = true) => {
return useQuery(consoleQuery.datasetEvaluation.metrics.queryOptions({
input: datasetId
? {
params: {
datasetId,
},
}
: skipToken,
enabled: !!datasetId && enabled,
refetchOnWindowFocus: false,
}))
}

View File

@ -9,6 +9,7 @@ import type {
WeightedScoreEnum,
} from '@/models/datasets'
import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug'
import type { WorkflowKind } from '@/types/workflow'
export type Theme = 'light' | 'dark' | 'system'
export const Theme = {
@ -392,7 +393,7 @@ export type App = {
/** whether workflow trigger has un-published draft */
has_draft_trigger?: boolean
/** Type */
workflow_kind?: AppTypeEnum
workflow_kind?: WorkflowKind | null
}
export type AppSSO = {

View File

@ -429,6 +429,8 @@ export type PublishWorkflowParams = {
export type WorkflowTypeConversionTarget = 'workflow' | 'evaluation'
export type WorkflowKind = 'standard' | 'evaluation'
export type UpdateWorkflowParams = {
url: string
title: string