Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins

This commit is contained in:
WTW0313 2025-01-08 16:41:22 +08:00
commit b656b7a0dd
34 changed files with 367 additions and 284 deletions

View File

@ -1,20 +1,18 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import {
RiEqualizer2Line,
} from '@remixicon/react'
import type { PopupProps } from './config-popup' import type { PopupProps } from './config-popup'
import ConfigPopup from './config-popup' import ConfigPopup from './config-popup'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Button from '@/app/components/base/button'
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
import { import {
PortalToFollowElem, PortalToFollowElem,
PortalToFollowElemContent, PortalToFollowElemContent,
PortalToFollowElemTrigger, PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
const I18N_PREFIX = 'app.tracing'
type Props = { type Props = {
readOnly: boolean readOnly: boolean
className?: string className?: string
@ -28,7 +26,6 @@ const ConfigBtn: FC<Props> = ({
controlShowPopup, controlShowPopup,
...popupProps ...popupProps
}) => { }) => {
const { t } = useTranslation()
const [open, doSetOpen] = useState(false) const [open, doSetOpen] = useState(false)
const openRef = useRef(open) const openRef = useRef(open)
const setOpen = useCallback((v: boolean) => { const setOpen = useCallback((v: boolean) => {
@ -50,21 +47,6 @@ const ConfigBtn: FC<Props> = ({
if (popupProps.readOnly && !hasConfigured) if (popupProps.readOnly && !hasConfigured)
return null return null
const triggerContent = hasConfigured
? (
<div className={cn(className, 'p-1 rounded-md hover:bg-black/5 cursor-pointer')}>
<Settings04 className='w-4 h-4 text-gray-500' />
</div>
)
: (
<Button variant='primary'
className={cn(className, '!h-8 !px-3 select-none')}
>
<Settings04 className='mr-1 w-4 h-4' />
<span className='text-[13px]'>{t(`${I18N_PREFIX}.config`)}</span>
</Button>
)
return ( return (
<PortalToFollowElem <PortalToFollowElem
open={open} open={open}
@ -72,11 +54,13 @@ const ConfigBtn: FC<Props> = ({
placement='bottom-end' placement='bottom-end'
offset={{ offset={{
mainAxis: 12, mainAxis: 12,
crossAxis: hasConfigured ? 8 : 0, crossAxis: hasConfigured ? 8 : 49,
}} }}
> >
<PortalToFollowElemTrigger onClick={handleTrigger}> <PortalToFollowElemTrigger onClick={handleTrigger}>
{triggerContent} <div className={cn(className, 'p-1 rounded-md')}>
<RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
</div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[11]'> <PortalToFollowElemContent className='z-[11]'>
<ConfigPopup {...popupProps} /> <ConfigPopup {...popupProps} />

View File

@ -1,6 +1,9 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import {
RiArrowDownDoubleLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
@ -8,7 +11,6 @@ import type { LangFuseConfig, LangSmithConfig } from './type'
import { TracingProvider } from './type' import { TracingProvider } from './type'
import TracingIcon from './tracing-icon' import TracingIcon from './tracing-icon'
import ConfigButton from './config-button' import ConfigButton from './config-button'
import cn from '@/utils/classnames'
import { LangfuseIcon, LangsmithIcon } from '@/app/components/base/icons/src/public/tracing' import { LangfuseIcon, LangsmithIcon } from '@/app/components/base/icons/src/public/tracing'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps'
@ -16,6 +18,8 @@ import type { TracingStatus } from '@/models/app'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Divider from '@/app/components/base/divider'
import cn from '@/utils/classnames'
const I18N_PREFIX = 'app.tracing' const I18N_PREFIX = 'app.tracing'
@ -27,7 +31,7 @@ const Title = ({
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className={cn(className, 'flex items-center text-lg font-semibold text-gray-900')}> <div className={cn('flex items-center system-xl-semibold text-text-primary', className)}>
{t('common.appMenus.overview')} {t('common.appMenus.overview')}
</div> </div>
) )
@ -135,43 +139,68 @@ const Panel: FC = () => {
return ( return (
<div className={cn('mb-3 flex justify-between items-center')}> <div className={cn('mb-3 flex justify-between items-center')}>
<Title className='h-[41px]' /> <Title className='h-[41px]' />
<div className='flex items-center p-2 rounded-xl border-[0.5px] border-gray-200 shadow-xs cursor-pointer hover:bg-gray-100' onClick={showPopup}> <div
{!inUseTracingProvider className={cn(
? <> 'flex items-center p-2 rounded-xl bg-background-default-dodge border-t border-l-[0.5px] border-effects-highlight shadow-xs cursor-pointer hover:bg-background-default-lighter hover:border-effects-highlight-lightmode-off',
<TracingIcon size='md' className='mr-2' /> controlShowPopup && 'bg-background-default-lighter border-effects-highlight-lightmode-off',
<div className='leading-5 text-sm font-semibold text-gray-700'>{t(`${I18N_PREFIX}.title`)}</div> )}
</> onClick={showPopup}
: <InUseProviderIcon className='ml-1 h-4' />} >
{!inUseTracingProvider && (
{hasConfiguredTracing && ( <>
<div className='ml-4 mr-1 flex items-center'> <TracingIcon size='md' />
<Indicator color={enabled ? 'green' : 'gray'} /> <div className='mx-2 system-sm-semibold text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
<div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'> <div className='flex items-center' onClick={e => e.stopPropagation()}>
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)} <ConfigButton
appId={appId}
readOnly={readOnly}
hasConfigured={false}
enabled={enabled}
onStatusChange={handleTracingEnabledChange}
chosenProvider={inUseTracingProvider}
onChooseProvider={handleChooseProvider}
langSmithConfig={langSmithConfig}
langFuseConfig={langFuseConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
controlShowPopup={controlShowPopup}
/>
</div> </div>
</div> <Divider type='vertical' className='h-3.5' />
<div className='p-1 rounded-md'>
<RiArrowDownDoubleLine className='w-4 h-4 text-text-tertiary' />
</div>
</>
)} )}
{hasConfiguredTracing && ( {hasConfiguredTracing && (
<div className='ml-2 w-px h-3.5 bg-gray-200'></div> <>
<div className='ml-4 mr-1 flex items-center'>
<Indicator color={enabled ? 'green' : 'gray'} />
<div className='ml-1.5 system-xs-semibold-uppercase text-text-tertiary'>
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
</div>
</div>
<InUseProviderIcon className='ml-1 h-4' />
<Divider type='vertical' className='h-3.5' />
<div className='flex items-center' onClick={e => e.stopPropagation()}>
<ConfigButton
appId={appId}
readOnly={readOnly}
hasConfigured
className='ml-2'
enabled={enabled}
onStatusChange={handleTracingEnabledChange}
chosenProvider={inUseTracingProvider}
onChooseProvider={handleChooseProvider}
langSmithConfig={langSmithConfig}
langFuseConfig={langFuseConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
controlShowPopup={controlShowPopup}
/>
</div>
</>
)} )}
<div className='flex items-center' onClick={e => e.stopPropagation()}>
<ConfigButton
appId={appId}
readOnly={readOnly}
hasConfigured
className='ml-2'
enabled={enabled}
onStatusChange={handleTracingEnabledChange}
chosenProvider={inUseTracingProvider}
onChooseProvider={handleChooseProvider}
langSmithConfig={langSmithConfig}
langFuseConfig={langFuseConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
controlShowPopup={controlShowPopup}
/>
</div>
</div> </div>
</div> </div>
) )

View File

@ -1,45 +0,0 @@
'use client'
import { ChevronDoubleDownIcon } from '@heroicons/react/20/solid'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import React, { useCallback } from 'react'
import Tooltip from '@/app/components/base/tooltip'
const I18N_PREFIX = 'app.tracing'
type Props = {
isFold: boolean
onFoldChange: (isFold: boolean) => void
}
const ToggleFoldBtn: FC<Props> = ({
isFold,
onFoldChange,
}) => {
const { t } = useTranslation()
const handleFoldChange = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
onFoldChange(!isFold)
}, [isFold, onFoldChange])
return (
// text-[0px] to hide spacing between tooltip elements
<div className='shrink-0 cursor-pointer text-[0px]' onClick={handleFoldChange}>
<Tooltip
popupContent={t(`${I18N_PREFIX}.${isFold ? 'expand' : 'collapse'}`)}
>
{isFold && (
<div className='p-1 rounded-md text-gray-500 hover:text-gray-800 hover:bg-black/5'>
<ChevronDoubleDownIcon className='w-4 h-4' />
</div>
)}
{!isFold && (
<div className='p-2 rounded-lg text-gray-500 border-[0.5px] border-gray-200 hover:text-gray-800 hover:bg-black/5'>
<ChevronDoubleDownIcon className='w-4 h-4 transform rotate-180' />
</div>
)}
</Tooltip>
</div>
)
}
export default React.memo(ToggleFoldBtn)

View File

@ -1,5 +1,4 @@
import { import {
useCallback,
useEffect, useEffect,
useMemo, useMemo,
useState, useState,
@ -15,9 +14,8 @@ import TracingPanel from '@/app/components/workflow/run/tracing-panel'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { useStore as useAppStore } from '@/app/components/app/store'
interface WorkflowProcessProps { type WorkflowProcessProps = {
data: WorkflowProcess data: WorkflowProcess
item?: ChatItem item?: ChatItem
expand?: boolean expand?: boolean
@ -26,7 +24,6 @@ interface WorkflowProcessProps {
} }
const WorkflowProcessItem = ({ const WorkflowProcessItem = ({
data, data,
item,
expand = false, expand = false,
hideInfo = false, hideInfo = false,
hideProcessDetail = false, hideProcessDetail = false,
@ -54,22 +51,6 @@ const WorkflowProcessItem = ({
setCollapse(!expand) setCollapse(!expand)
}, [expand]) }, [expand])
const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem)
const setShowMessageLogModal = useAppStore(s => s.setShowMessageLogModal)
const setCurrentLogModalActiveTab = useAppStore(s => s.setCurrentLogModalActiveTab)
const showIterationDetail = useCallback(() => {
setCurrentLogItem(item)
setCurrentLogModalActiveTab('TRACING')
setShowMessageLogModal(true)
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
const showRetryDetail = useCallback(() => {
setCurrentLogItem(item)
setCurrentLogModalActiveTab('TRACING')
setShowMessageLogModal(true)
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
return ( return (
<div <div
className={cn( className={cn(
@ -110,8 +91,6 @@ const WorkflowProcessItem = ({
{ {
<TracingPanel <TracingPanel
list={data.tracing} list={data.tracing}
onShowIterationDetail={showIterationDetail}
onShowRetryDetail={showRetryDetail}
hideNodeInfo={hideInfo} hideNodeInfo={hideInfo}
hideNodeProcessDetail={hideProcessDetail} hideNodeProcessDetail={hideProcessDetail}
/> />

View File

@ -40,7 +40,7 @@ const ModelIcon: FC<ModelIconProps> = ({
return ( return (
<div className={cn( <div className={cn(
'flex items-center justify-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle', 'flex items-center justify-center rounded-md border-[0.5px] w-5 h-5 border-components-panel-border-subtle bg-background-default-subtle',
className, className,
)}> )}>
<div className='flex w-5 h-5 items-center justify-center opacity-35'> <div className='flex w-5 h-5 items-center justify-center opacity-35'>

View File

@ -26,6 +26,7 @@ import cn from '@/utils/classnames'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { RiEqualizer2Line } from '@remixicon/react' import { RiEqualizer2Line } from '@remixicon/react'
import { fetchPluginInfoFromMarketPlace } from '@/service/plugins' import { fetchPluginInfoFromMarketPlace } from '@/service/plugins'
import { fetchModelProviderModelList } from '@/service/common'
export type AgentModelTriggerProps = { export type AgentModelTriggerProps = {
open?: boolean open?: boolean
@ -67,11 +68,22 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
const [pluginInfo, setPluginInfo] = useState<PluginInfoFromMarketPlace | null>(null) const [pluginInfo, setPluginInfo] = useState<PluginInfoFromMarketPlace | null>(null)
const [isPluginChecked, setIsPluginChecked] = useState(false) const [isPluginChecked, setIsPluginChecked] = useState(false)
const [installed, setInstalled] = useState(false) const [installed, setInstalled] = useState(false)
const [inModelList, setInModelList] = useState(false)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const handleOpenModal = useModelModalHandler() const handleOpenModal = useModelModalHandler()
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (providerName && !modelProvider) { if (modelId && currentProvider) {
try {
const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${currentProvider?.provider}/models`)
if (modelId && modelsData.data.find(item => item.model === modelId))
setInModelList(true)
}
catch (error) {
// pass
}
}
if (providerName) {
const parts = providerName.split('/') const parts = providerName.split('/')
const org = parts[0] const org = parts[0]
const name = parts[1] const name = parts[1]
@ -89,7 +101,7 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
setIsPluginChecked(true) setIsPluginChecked(true)
} }
})() })()
}, [providerName, modelProvider]) }, [providerName, modelId, currentProvider])
if (modelId && !isPluginChecked) if (modelId && !isPluginChecked)
return null return null
@ -121,6 +133,7 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
<StatusIndicators <StatusIndicators
needsConfiguration={needsConfiguration} needsConfiguration={needsConfiguration}
modelProvider={!!modelProvider} modelProvider={!!modelProvider}
inModelList={inModelList}
disabled={!!disabled} disabled={!!disabled}
pluginInfo={pluginInfo} pluginInfo={pluginInfo}
t={t} t={t}

View File

@ -1,46 +1,73 @@
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import Link from 'next/link' import Link from 'next/link'
import { SwitchPluginVersion } from '@/app/components/workflow/nodes/_base/components/switch-plugin-version'
import { useInstalledPluginList } from '@/service/use-plugins'
import { RiErrorWarningFill } from '@remixicon/react' import { RiErrorWarningFill } from '@remixicon/react'
type StatusIndicatorsProps = { type StatusIndicatorsProps = {
needsConfiguration: boolean needsConfiguration: boolean
modelProvider: boolean modelProvider: boolean
inModelList: boolean
disabled: boolean disabled: boolean
pluginInfo: any pluginInfo: any
t: any t: any
} }
const StatusIndicators = ({ needsConfiguration, modelProvider, disabled, pluginInfo, t }: StatusIndicatorsProps) => { const StatusIndicators = ({ needsConfiguration, modelProvider, inModelList, disabled, pluginInfo, t }: StatusIndicatorsProps) => {
const { data: pluginList } = useInstalledPluginList()
const renderTooltipContent = (title: string, description?: string, linkText?: string, linkHref?: string) => {
return (
<div className='flex w-[240px] max-w-[240px] gap-1 flex-col px-1 py-1.5' onClick={e => e.stopPropagation()}>
<div className='text-text-primary title-xs-semi-bold'>{title}</div>
{description && (
<div className='min-w-[200px] text-text-secondary body-xs-regular'>
{description}
</div>
)}
{linkText && linkHref && (
<div className='text-text-accent body-xs-regular cursor-pointer z-[100]'>
<Link
href={linkHref}
onClick={(e) => {
e.stopPropagation()
}}
>
{linkText}
</Link>
</div>
)}
</div>
)
}
// const installedPluginUniqueIdentifier = pluginList?.plugins.find(plugin => plugin.name === pluginInfo.name)?.plugin_unique_identifier
return ( return (
<> <>
{/* plugin installed and model is in model list but disabled */}
{/* plugin installed from github/local and model is not in model list */}
{!needsConfiguration && modelProvider && disabled && ( {!needsConfiguration && modelProvider && disabled && (
<Tooltip <Tooltip
popupContent={t('workflow.nodes.agent.modelSelectorTooltips.deprecated')} popupContent={inModelList ? t('workflow.nodes.agent.modelSelectorTooltips.deprecated')
: renderTooltipContent(
t('workflow.nodes.agent.modelNotSupport.title'),
!pluginInfo ? t('workflow.nodes.agent.modelNotSupport.desc') : t('workflow.nodes.agent.modelNotSupport.descForVersionSwitch'),
!pluginInfo ? t('workflow.nodes.agent.linkToPlugin') : '',
!pluginInfo ? '/plugins' : '',
)
}
asChild={false} asChild={false}
needsDelay={!inModelList}
> >
<RiErrorWarningFill className='w-4 h-4 text-text-destructive' /> {!pluginInfo ? <RiErrorWarningFill className='w-4 h-4 text-text-destructive' /> : <SwitchPluginVersion uniqueIdentifier={pluginList?.plugins.find(plugin => plugin.name === pluginInfo.name)?.plugin_unique_identifier ?? ''} />}
</Tooltip> </Tooltip>
)} )}
{!modelProvider && !pluginInfo && ( {!modelProvider && !pluginInfo && (
<Tooltip <Tooltip
popupContent={ popupContent={renderTooltipContent(
<div className='flex w-[240px] max-w-[240px] gap-1 flex-col px-1 py-1.5'> t('workflow.nodes.agent.modelNotInMarketplace.title'),
<div className='text-text-primary title-xs-semi-bold'>{t('workflow.nodes.agent.modelNotInMarketplace.title')}</div> t('workflow.nodes.agent.modelNotInMarketplace.desc'),
<div className='min-w-[200px] text-text-secondary body-xs-regular'> t('workflow.nodes.agent.linkToPlugin'),
{t('workflow.nodes.agent.modelNotInMarketplace.desc')} '/plugins',
</div> )}
<div className='text-text-accent body-xs-regular cursor-pointer z-[100]'>
<Link
href={'/plugins'}
onClick={(e) => {
e.stopPropagation()
}}
>
{t('workflow.nodes.agent.linkToPlugin')}
</Link>
</div>
</div>
}
asChild={false} asChild={false}
needsDelay needsDelay
> >

View File

@ -22,11 +22,11 @@ const ModelTrigger: FC<ModelTriggerProps> = ({
return ( return (
<div <div
className={cn('group flex flex-grow items-center p-[3px] pl-1 h-6 gap-1 rounded-lg bg-components-input-bg-disabled cursor-pointer', className)} className={cn('group flex flex-grow items-center p-[3px] pl-1 h-8 gap-1 rounded-lg bg-components-input-bg-disabled cursor-pointer', className)}
> >
<div className='flex items-center py-[1px] gap-1 grow'> <div className='flex items-center py-[1px] gap-1 grow'>
<ModelIcon <ModelIcon
className="m-0.5 w-4 h-4" className="w-4 h-4"
provider={currentProvider} provider={currentProvider}
modelName={modelName} modelName={modelName}
/> />

View File

@ -11,7 +11,7 @@ import Placeholder from './base/placeholder'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import { getLanguage } from '@/i18n/language' import { getLanguage } from '@/i18n/language'
import { useCategories } from '../hooks' import { useSingleCategories } from '../hooks'
import { renderI18nObject } from '@/hooks/use-i18n' import { renderI18nObject } from '@/hooks/use-i18n'
export type Props = { export type Props = {
@ -43,7 +43,7 @@ const Card = ({
}: Props) => { }: Props) => {
const defaultLocale = useGetLanguage() const defaultLocale = useGetLanguage()
const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
const { categoriesMap } = useCategories() const { categoriesMap } = useSingleCategories()
const { category, type, name, org, label, brief, icon, verified } = payload const { category, type, name, org, label, brief, icon, verified } = payload
const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent_strategy'].includes(type) const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent_strategy'].includes(type)
const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label

View File

@ -64,3 +64,31 @@ export const useCategories = (translateFromOut?: TFunction) => {
categoriesMap, categoriesMap,
} }
} }
export const useSingleCategories = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
const categories = categoryKeys.map((category) => {
if (category === 'agent') {
return {
name: 'agent_strategy',
label: t(`plugin.categorySingle.${category}`),
}
}
return {
name: category,
label: t(`plugin.categorySingle.${category}`),
}
})
const categoriesMap = categories.reduce((acc, category) => {
acc[category.name] = category
return acc
}, {} as Record<string, Category>)
return {
categories,
categoriesMap,
}
}

View File

@ -12,6 +12,7 @@ import { useInstallPackageFromMarketPlace, useUpdatePackageFromMarketPlace } fro
import checkTaskStatus from '../../base/check-task-status' import checkTaskStatus from '../../base/check-task-status'
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
import Version from '../../base/version' import Version from '../../base/version'
import { usePluginTaskList } from '@/service/use-plugins'
const i18nPrefix = 'plugin.installModal' const i18nPrefix = 'plugin.installModal'
@ -50,6 +51,7 @@ const Installed: FC<Props> = ({
check, check,
stop, stop,
} = checkTaskStatus() } = checkTaskStatus()
const { handleRefetch } = usePluginTaskList()
useEffect(() => { useEffect(() => {
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
@ -93,6 +95,9 @@ const Installed: FC<Props> = ({
onInstalled() onInstalled()
return return
} }
handleRefetch()
const { status, error } = await check({ const { status, error } = await check({
taskId, taskId,
pluginUniqueIdentifier: uniqueIdentifier, pluginUniqueIdentifier: uniqueIdentifier,

View File

@ -22,7 +22,7 @@ import cn from '@/utils/classnames'
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools' import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
import { useCategories } from '../hooks' import { useSingleCategories } from '../hooks'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { useRenderI18nObject } from '@/hooks/use-i18n' import { useRenderI18nObject } from '@/hooks/use-i18n'
@ -36,7 +36,7 @@ const PluginItem: FC<Props> = ({
plugin, plugin,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { categoriesMap } = useCategories() const { categoriesMap } = useSingleCategories()
const currentPluginID = usePluginPageContext(v => v.currentPluginID) const currentPluginID = usePluginPageContext(v => v.currentPluginID)
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList()

View File

@ -8,14 +8,15 @@ import type { UseMutationResult } from '@tanstack/react-query'
type Props = { type Props = {
plugin: Plugin plugin: Plugin
onSave: () => void
onCancel: () => void onCancel: () => void
mutation: UseMutationResult mutation: Pick<UseMutationResult, 'isSuccess' | 'isPending'>
mutate: () => void
confirmButtonText: ReactNode confirmButtonText: ReactNode
cancelButtonText: ReactNode cancelButtonText: ReactNode
modelTitle: ReactNode modelTitle: ReactNode
description: ReactNode description: ReactNode
cardTitleLeft: ReactNode cardTitleLeft: ReactNode
modalBottomLeft?: ReactNode
} }
const PluginMutationModal: FC<Props> = ({ const PluginMutationModal: FC<Props> = ({
@ -27,6 +28,8 @@ const PluginMutationModal: FC<Props> = ({
modelTitle, modelTitle,
description, description,
cardTitleLeft, cardTitleLeft,
mutate,
modalBottomLeft,
}: Props) => { }: Props) => {
return ( return (
<Modal <Modal
@ -47,20 +50,25 @@ const PluginMutationModal: FC<Props> = ({
titleLeft={cardTitleLeft} titleLeft={cardTitleLeft}
/> />
</div> </div>
<div className='flex pt-5 justify-end items-center gap-2 self-stretch'> <div className='flex pt-5 items-center gap-2 self-stretch'>
{mutation.isPending && ( <div>
<Button onClick={onCancel}> {modalBottomLeft}
{cancelButtonText} </div>
<div className='ml-auto flex gap-2'>
{!mutation.isPending && (
<Button onClick={onCancel}>
{cancelButtonText}
</Button>
)}
<Button
variant='primary'
loading={mutation.isPending}
onClick={mutate}
disabled={mutation.isPending}
>
{confirmButtonText}
</Button> </Button>
)} </div>
<Button
variant='primary'
loading={mutation.isPending}
onClick={mutation.mutate}
disabled={mutation.isPending}
>
{confirmButtonText}
</Button>
</div> </div>
</Modal> </Modal>
) )

View File

@ -404,6 +404,7 @@ export const SUPPORT_OUTPUT_VARS_NODE = [
BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier, BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier,
BlockEnum.ParameterExtractor, BlockEnum.Iteration, BlockEnum.ParameterExtractor, BlockEnum.Iteration,
BlockEnum.DocExtractor, BlockEnum.ListFilter, BlockEnum.DocExtractor, BlockEnum.ListFilter,
BlockEnum.Agent,
] ]
export const LLM_OUTPUT_STRUCT: Var[] = [ export const LLM_OUTPUT_STRUCT: Var[] = [

View File

@ -3,13 +3,19 @@
import Badge from '@/app/components/base/badge' import Badge from '@/app/components/base/badge'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
import { RiArrowLeftRightLine } from '@remixicon/react' import { RiArrowLeftRightLine, RiExternalLinkLine } from '@remixicon/react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { type FC, useCallback, useState } from 'react' import { type FC, useCallback, useState } from 'react'
import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-market-place'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { useCheckInstalled } from '@/service/use-plugins' import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils'
import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import { marketplaceUrlPrefix } from '@/config'
export type SwitchPluginVersionProps = { export type SwitchPluginVersionProps = {
uniqueIdentifier: string uniqueIdentifier: string
@ -23,7 +29,10 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
const [pluginId] = uniqueIdentifier.split(':') const [pluginId] = uniqueIdentifier.split(':')
const [isShow, setIsShow] = useState(false) const [isShow, setIsShow] = useState(false)
const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false) const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false)
const [targetVersion, setTargetVersion] = useState<string>() const [target, setTarget] = useState<{
version: string,
pluginUniqueIden: string;
}>()
const pluginDetails = useCheckInstalled({ const pluginDetails = useCheckInstalled({
pluginIds: [pluginId], pluginIds: [pluginId],
enabled: true, enabled: true,
@ -33,28 +42,55 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
const handleUpdatedFromMarketplace = useCallback(() => { const handleUpdatedFromMarketplace = useCallback(() => {
hideUpdateModal() hideUpdateModal()
pluginDetails.refetch() pluginDetails.refetch()
onChange?.(targetVersion!) onChange?.(target!.version)
}, [hideUpdateModal, onChange, pluginDetails, targetVersion]) }, [hideUpdateModal, onChange, pluginDetails, target])
const { getIconUrl } = useGetIcon()
const targetUniqueIdentifier = (() => { const icon = pluginDetail?.declaration.icon ? getIconUrl(pluginDetail.declaration.icon) : undefined
if (!targetVersion || !pluginDetail) return uniqueIdentifier const mutation = useUpdatePackageFromMarketPlace()
return uniqueIdentifier.replaceAll(pluginDetail.version, targetVersion) const install = () => {
})() mutation.mutate(
{
new_plugin_unique_identifier: target!.pluginUniqueIden,
original_plugin_unique_identifier: uniqueIdentifier,
},
{
onSuccess() {
handleUpdatedFromMarketplace()
},
})
}
const { t } = useTranslation()
return <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod='hover'> return <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod='hover'>
<div className={cn('w-fit', className)}> <div className={cn('w-fit flex items-center justify-center', className)}>
{isShowUpdateModal && pluginDetail && <UpdateFromMarketplace {isShowUpdateModal && pluginDetail && <PluginMutationModel
payload={{
originalPackageInfo: {
id: uniqueIdentifier,
payload: pluginDetail.declaration,
},
targetPackageInfo: {
id: targetUniqueIdentifier,
version: targetVersion!,
},
}}
onCancel={hideUpdateModal} onCancel={hideUpdateModal}
onSave={handleUpdatedFromMarketplace} plugin={pluginManifestToCardPluginProps({
...pluginDetail.declaration,
icon: icon!,
})}
mutation={mutation}
mutate={install}
confirmButtonText={t('workflow.nodes.agent.installPlugin.install')}
cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')}
modelTitle={t('workflow.nodes.agent.installPlugin.title')}
description={t('workflow.nodes.agent.installPlugin.desc')}
cardTitleLeft={<>
<Badge2 className='mx-1' size="s" state={BadgeState.Warning}>
{`${pluginDetail.version} -> ${target!.version}`}
</Badge2>
</>}
modalBottomLeft={
<Link
className='flex justify-center items-center gap-1'
href={`${marketplaceUrlPrefix}/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`}
target='_blank'
>
<span className='text-text-accent system-xs-regular text-xs'>
{t('workflow.nodes.agent.installPlugin.changelog')}
</span>
<RiExternalLinkLine className='text-text-accent size-3' />
</Link>
}
/>} />}
{pluginDetail && <PluginVersionPicker {pluginDetail && <PluginVersionPicker
isShow={isShow} isShow={isShow}
@ -62,7 +98,10 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
pluginID={pluginId} pluginID={pluginId}
currentVersion={pluginDetail.version} currentVersion={pluginDetail.version}
onSelect={(state) => { onSelect={(state) => {
setTargetVersion(state.version) setTarget({
pluginUniqueIden: state.unique_identifier,
version: state.version,
})
showUpdateModal() showUpdateModal()
}} }}
trigger={ trigger={

View File

@ -319,16 +319,13 @@ const formatItem = (
case BlockEnum.Agent: { case BlockEnum.Agent: {
const payload = data as AgentNodeType const payload = data as AgentNodeType
const outputs: Var[] = [] const outputs: Var[] = []
Object.keys(payload.output_schema.properties).forEach((outputKey) => { Object.keys(payload.output_schema?.properties || {}).forEach((outputKey) => {
const output = payload.output_schema.properties[outputKey] const output = payload.output_schema.properties[outputKey]
outputs.push({ outputs.push({
variable: outputKey, variable: outputKey,
type: output.type === 'array' type: output.type === 'array'
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]` as VarType ? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]` as VarType
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}` as VarType, : `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}` as VarType,
// TODO: is this required?
// @ts-expect-error todo added
description: output.description,
}) })
}) })
res.vars = [ res.vars = [

View File

@ -26,7 +26,7 @@ const nodeDefault: NodeDefault<AgentNodeType> = {
if (!strategy) { if (!strategy) {
return { return {
isValid: false, isValid: false,
errorMessage: t('workflow.checkList.strategyNotSelected'), errorMessage: t('workflow.nodes.agent.checkList.strategyNotSelected'),
} }
} }
for (const param of strategy.parameters) { for (const param of strategy.parameters) {

View File

@ -28,7 +28,7 @@ import {
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import { getLastAnswer } from '@/app/components/base/chat/utils' import { getLastAnswer } from '@/app/components/base/chat/utils'
interface ChatWrapperProps { type ChatWrapperProps = {
showConversationVariableModal: boolean showConversationVariableModal: boolean
onConversationModalHide: () => void onConversationModalHide: () => void
showInputsFieldsPanel: boolean showInputsFieldsPanel: boolean

View File

@ -27,10 +27,9 @@ import {
getProcessedFilesFromResponse, getProcessedFilesFromResponse,
} from '@/app/components/base/file-uploader/utils' } from '@/app/components/base/file-uploader/utils'
import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { NodeTracing } from '@/types/workflow'
type GetAbortController = (abortController: AbortController) => void type GetAbortController = (abortController: AbortController) => void
interface SendCallback { type SendCallback = {
onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any> onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any>
} }
export const useChat = ( export const useChat = (
@ -276,6 +275,7 @@ export const useChat = (
) )
setSuggestQuestions(data) setSuggestQuestions(data)
} }
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) { catch (error) {
setSuggestQuestions([]) setSuggestQuestions([])
} }
@ -331,8 +331,7 @@ export const useChat = (
responseItem.workflowProcess!.tracing!.push({ responseItem.workflowProcess!.tracing!.push({
...data, ...data,
status: NodeRunningStatus.Running, status: NodeRunningStatus.Running,
details: [], })
} as any)
handleUpdateChatList(produce(chatListRef.current, (draft) => { handleUpdateChatList(produce(chatListRef.current, (draft) => {
const currentIndex = draft.findIndex(item => item.id === responseItem.id) const currentIndex = draft.findIndex(item => item.id === responseItem.id)
draft[currentIndex] = { draft[currentIndex] = {
@ -341,30 +340,21 @@ export const useChat = (
} }
})) }))
}, },
onIterationNext: ({ data }) => {
const tracing = responseItem.workflowProcess!.tracing!
const iterations = tracing.find(item => item.node_id === data.node_id
&& (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id))!
iterations.details!.push([])
handleUpdateChatList(produce(chatListRef.current, (draft) => {
const currentIndex = draft.length - 1
draft[currentIndex] = responseItem
}))
},
onIterationFinish: ({ data }) => { onIterationFinish: ({ data }) => {
const tracing = responseItem.workflowProcess!.tracing! const currentTracingIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.id === data.id)
const iterationsIndex = tracing.findIndex(item => item.node_id === data.node_id if (currentTracingIndex > -1) {
&& (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id))! responseItem.workflowProcess!.tracing[currentTracingIndex] = {
tracing[iterationsIndex] = { ...responseItem.workflowProcess!.tracing[currentTracingIndex],
...tracing[iterationsIndex], ...data,
...data, }
status: NodeRunningStatus.Succeeded, handleUpdateChatList(produce(chatListRef.current, (draft) => {
} as any const currentIndex = draft.findIndex(item => item.id === responseItem.id)
handleUpdateChatList(produce(chatListRef.current, (draft) => { draft[currentIndex] = {
const currentIndex = draft.length - 1 ...draft[currentIndex],
draft[currentIndex] = responseItem ...responseItem,
})) }
}))
}
}, },
onNodeStarted: ({ data }) => { onNodeStarted: ({ data }) => {
if (data.iteration_id) if (data.iteration_id)
@ -386,16 +376,7 @@ export const useChat = (
if (data.iteration_id) if (data.iteration_id)
return return
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => { responseItem.workflowProcess!.tracing!.push(data)
if (!item.execution_metadata?.parallel_id)
return item.node_id === data.node_id
return item.node_id === data.node_id && (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id)
})
if (responseItem.workflowProcess!.tracing[currentIndex].retryDetail)
responseItem.workflowProcess!.tracing[currentIndex].retryDetail?.push(data as NodeTracing)
else
responseItem.workflowProcess!.tracing[currentIndex].retryDetail = [data as NodeTracing]
handleUpdateChatList(produce(chatListRef.current, (draft) => { handleUpdateChatList(produce(chatListRef.current, (draft) => {
const currentIndex = draft.findIndex(item => item.id === responseItem.id) const currentIndex = draft.findIndex(item => item.id === responseItem.id)
draft[currentIndex] = { draft[currentIndex] = {
@ -408,27 +389,20 @@ export const useChat = (
if (data.iteration_id) if (data.iteration_id)
return return
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => { const currentTracingIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.id === data.id)
if (!item.execution_metadata?.parallel_id) if (currentTracingIndex > -1) {
return item.node_id === data.node_id responseItem.workflowProcess!.tracing[currentTracingIndex] = {
return item.node_id === data.node_id && (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id) ...responseItem.workflowProcess!.tracing[currentTracingIndex],
}) ...data,
responseItem.workflowProcess!.tracing[currentIndex] = {
...(responseItem.workflowProcess!.tracing[currentIndex]?.extras
? { extras: responseItem.workflowProcess!.tracing[currentIndex].extras }
: {}),
...(responseItem.workflowProcess!.tracing[currentIndex]?.retryDetail
? { retryDetail: responseItem.workflowProcess!.tracing[currentIndex].retryDetail }
: {}),
...data,
} as any
handleUpdateChatList(produce(chatListRef.current, (draft) => {
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
draft[currentIndex] = {
...draft[currentIndex],
...responseItem,
} }
})) handleUpdateChatList(produce(chatListRef.current, (draft) => {
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
draft[currentIndex] = {
...draft[currentIndex],
...responseItem,
}
}))
}
}, },
}, },
) )

View File

@ -22,7 +22,7 @@ import Tooltip from '@/app/components/base/tooltip'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import { useStore } from '@/app/components/workflow/store' import { useStore } from '@/app/components/workflow/store'
export interface ChatWrapperRefType { export type ChatWrapperRefType = {
handleRestart: () => void handleRestart: () => void
} }
const DebugAndPreview = () => { const DebugAndPreview = () => {

View File

@ -24,7 +24,6 @@ import Toast from '../../base/toast'
import InputsPanel from './inputs-panel' import InputsPanel from './inputs-panel'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import formatNodeList from '@/app/components/workflow/run/utils/format-log'
const WorkflowPreview = () => { const WorkflowPreview = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -161,7 +160,7 @@ const WorkflowPreview = () => {
{currentTab === 'TRACING' && ( {currentTab === 'TRACING' && (
<TracingPanel <TracingPanel
className='bg-background-section-burn' className='bg-background-section-burn'
list={formatNodeList(workflowRunningData?.tracing || [], t)} list={workflowRunningData?.tracing || []}
/> />
)} )}
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && ( {currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (

View File

@ -1,9 +1,9 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { useStore as usePluginDependenciesStore } from './store' import { useStore as usePluginDependenciesStore } from './store'
import { useMutationCheckDependecies } from '@/service/use-plugins' import { useMutationCheckDependencies } from '@/service/use-plugins'
export const usePluginDependencies = () => { export const usePluginDependencies = () => {
const { mutateAsync } = useMutationCheckDependecies() const { mutateAsync } = useMutationCheckDependencies()
const handleCheckPluginDependencies = useCallback(async (appId: string) => { const handleCheckPluginDependencies = useCallback(async (appId: string) => {
const { leaked_dependencies } = await mutateAsync(appId) const { leaked_dependencies } = await mutateAsync(appId)

View File

@ -30,26 +30,26 @@ const AgentLogItem = ({
<div className='border-[0.5px] border-components-panel-border rounded-[10px]'> <div className='border-[0.5px] border-components-panel-border rounded-[10px]'>
<div <div
className={cn( className={cn(
'flex items-center pl-1.5 pt-2 pr-3 pb-2', 'flex items-center pl-1.5 pt-2 pr-3 pb-2 cursor-pointer',
expanded && 'pb-1', expanded && 'pb-1',
)} )}
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
> >
{ {
expanded expanded
? <RiArrowRightSLine className='shrink-0 w-4 h-4 rotate-90' /> ? <RiArrowRightSLine className='shrink-0 w-4 h-4 rotate-90 text-text-quaternary' />
: <RiArrowRightSLine className='shrink-0 w-4 h-4' /> : <RiArrowRightSLine className='shrink-0 w-4 h-4 text-text-quaternary' />
} }
<div className='shrink-0 mr-1.5 w-5 h-5'></div> <div className='shrink-0 mr-1.5 w-5 h-5'></div>
<div className='grow system-sm-semibold-uppercase text-text-secondary truncate'>{label}</div> <div className='grow system-sm-semibold-uppercase text-text-secondary truncate'>{label}</div>
<div className='shrink-0 mr-2 system-xs-regular text-text-tertiary'>0.02s</div> {/* <div className='shrink-0 mr-2 system-xs-regular text-text-tertiary'>0.02s</div> */}
<NodeStatusIcon status={status} /> <NodeStatusIcon status={status} />
</div> </div>
{ {
expanded && ( expanded && (
<div className='p-1 pt-0'> <div className='p-1 pt-0'>
{ {
!!children.length && ( !!children?.length && (
<Button <Button
className='flex items-center justify-between mb-1 w-full' className='flex items-center justify-between mb-1 w-full'
variant='tertiary' variant='tertiary'

View File

@ -19,7 +19,9 @@ const AgentLogNav = ({
className='shrink-0 px-[5px]' className='shrink-0 px-[5px]'
size='small' size='small'
variant='ghost-accent' variant='ghost-accent'
onClick={() => onShowAgentOrToolLog()} onClick={() => {
onShowAgentOrToolLog()
}}
> >
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' /> <RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Agent Agent
@ -31,7 +33,6 @@ const AgentLogNav = ({
variant='ghost-accent' variant='ghost-accent'
onClick={() => {}} onClick={() => {}}
> >
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Agent strategy Agent strategy
</Button> </Button>
{ {

View File

@ -24,7 +24,9 @@ const AgentLogTrigger = ({
<div className='grow mx-0.5 px-1 system-xs-medium text-text-secondary'></div> <div className='grow mx-0.5 px-1 system-xs-medium text-text-secondary'></div>
<div <div
className='shrink-0 flex items-center px-[1px] system-xs-regular-uppercase text-text-tertiary cursor-pointer' className='shrink-0 flex items-center px-[1px] system-xs-regular-uppercase text-text-tertiary cursor-pointer'
onClick={() => onShowAgentOrToolLog({ id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren)} onClick={() => {
onShowAgentOrToolLog({ id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren)
}}
> >
Detail Detail
<RiArrowRightLine className='ml-0.5 w-3.5 h-3.5' /> <RiArrowRightLine className='ml-0.5 w-3.5 h-3.5' />

View File

@ -13,7 +13,6 @@ import { fetchRunDetail, fetchTracingList } from '@/service/log'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
import type { WorkflowRunDetailResponse } from '@/models/log' import type { WorkflowRunDetailResponse } from '@/models/log'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import formatNodeList from './utils/format-log'
export type RunProps = { export type RunProps = {
hideResult?: boolean hideResult?: boolean
activeTab?: 'RESULT' | 'DETAIL' | 'TRACING' activeTab?: 'RESULT' | 'DETAIL' | 'TRACING'
@ -61,7 +60,7 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
const { data: nodeList } = await fetchTracingList({ const { data: nodeList } = await fetchTracingList({
url: `/apps/${appID}/workflow-runs/${runID}/node-executions`, url: `/apps/${appID}/workflow-runs/${runID}/node-executions`,
}) })
setList(formatNodeList(nodeList, t)) setList(nodeList)
} }
catch (err) { catch (err) {
notify({ notify({

View File

@ -36,7 +36,10 @@ const SpecialResultPanel = ({
handleShowAgentOrToolLog, handleShowAgentOrToolLog,
}: SpecialResultPanelProps) => { }: SpecialResultPanelProps) => {
return ( return (
<> <div onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}}>
{ {
!!showRetryDetail && !!retryResultList?.length && setShowRetryDetailFalse && ( !!showRetryDetail && !!retryResultList?.length && setShowRetryDetailFalse && (
<RetryResultPanel <RetryResultPanel
@ -63,7 +66,7 @@ const SpecialResultPanel = ({
/> />
) )
} }
</> </div>
) )
} }

View File

@ -11,10 +11,12 @@ import {
RiArrowDownSLine, RiArrowDownSLine,
RiMenu4Line, RiMenu4Line,
} from '@remixicon/react' } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useLogs } from './hooks' import { useLogs } from './hooks'
import NodePanel from './node' import NodePanel from './node'
import SpecialResultPanel from './special-result-panel' import SpecialResultPanel from './special-result-panel'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
import formatNodeList from '@/app/components/workflow/run/utils/format-log'
type TracingPanelProps = { type TracingPanelProps = {
list: NodeTracing[] list: NodeTracing[]
@ -29,7 +31,8 @@ const TracingPanel: FC<TracingPanelProps> = ({
hideNodeInfo = false, hideNodeInfo = false,
hideNodeProcessDetail = false, hideNodeProcessDetail = false,
}) => { }) => {
const treeNodes = list const { t } = useTranslation()
const treeNodes = formatNodeList(list, t)
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set()) const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set())
const [hoveredParallel, setHoveredParallel] = useState<string | null>(null) const [hoveredParallel, setHoveredParallel] = useState<string | null>(null)
@ -166,7 +169,13 @@ const TracingPanel: FC<TracingPanelProps> = ({
} }
return ( return (
<div className={cn(className || 'bg-components-panel-bg', 'py-2')}> <div
className={cn(className || 'bg-components-panel-bg', 'py-2')}
onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}}
>
{treeNodes.map(renderNode)} {treeNodes.map(renderNode)}
</div> </div>
) )

View File

@ -3,10 +3,17 @@ const translation = {
all: 'All', all: 'All',
models: 'Models', models: 'Models',
tools: 'Tools', tools: 'Tools',
agents: 'Agent Strategy', agents: 'Agent Strategies',
extensions: 'Extensions', extensions: 'Extensions',
bundles: 'Bundles', bundles: 'Bundles',
}, },
categorySingle: {
model: 'Model',
tool: 'Tool',
agent: 'Agent Strategy',
extension: 'Extension',
bundle: 'Bundle',
},
search: 'Search', search: 'Search',
allCategories: 'All Categories', allCategories: 'All Categories',
searchCategories: 'Search Categories', searchCategories: 'Search Categories',

View File

@ -725,6 +725,7 @@ const translation = {
modelNotSupport: { modelNotSupport: {
title: 'Unsupported Model', title: 'Unsupported Model',
desc: 'The installed plugin version does not provide this model.', desc: 'The installed plugin version does not provide this model.',
descForVersionSwitch: 'The installed plugin version does not provide this model. Click to switch version.',
}, },
configureModel: 'Configure Model', configureModel: 'Configure Model',
notAuthorized: 'Not Authorized', notAuthorized: 'Not Authorized',
@ -755,9 +756,16 @@ const translation = {
}, },
json: 'agent generated json', json: 'agent generated json',
}, },
}, checkList: {
checkList: { strategyNotSelected: 'Strategy not selected',
strategyNotSelected: 'Strategy not selected', },
installPlugin: {
title: 'Install Plugin',
desc: 'About to install the following plugin',
changelog: 'Change log',
install: 'Install',
cancel: 'Cancel',
},
}, },
}, },
tracing: { tracing: {

View File

@ -7,6 +7,13 @@ const translation = {
extensions: '扩展', extensions: '扩展',
bundles: '插件集', bundles: '插件集',
}, },
categorySingle: {
model: '模型',
tool: '工具',
agent: 'Agent 策略',
extension: '扩展',
bundle: '插件集',
},
search: '搜索', search: '搜索',
allCategories: '所有类别', allCategories: '所有类别',
searchCategories: '搜索类别', searchCategories: '搜索类别',

View File

@ -725,6 +725,7 @@ const translation = {
modelNotSupport: { modelNotSupport: {
title: '不支持的模型', title: '不支持的模型',
desc: '已安装的插件版本不提供此模型。', desc: '已安装的插件版本不提供此模型。',
descForVersionSwitch: '已安装的插件版本不提供此模型。点击切换版本。',
}, },
model: '模型', model: '模型',
toolbox: '工具箱', toolbox: '工具箱',
@ -758,6 +759,13 @@ const translation = {
checkList: { checkList: {
strategyNotSelected: '未选择策略', strategyNotSelected: '未选择策略',
}, },
installPlugin: {
title: '安装插件',
desc: '即将安装以下插件',
changelog: '更新日志',
install: '安装',
cancel: '取消',
},
}, },
}, },
tracing: { tracing: {

View File

@ -365,7 +365,7 @@ export const usePluginTaskList = () => {
queryKey: usePluginTaskListKey, queryKey: usePluginTaskListKey,
queryFn: async () => { queryFn: async () => {
const currentData = await get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100') const currentData = await get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100')
const taskDone = currentData.tasks.every(task => task.status === TaskStatus.success) const taskDone = currentData.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
if (taskDone) if (taskDone)
setEnabled(false) setEnabled(false)
@ -423,10 +423,10 @@ export const useDownloadPlugin = (info: { organization: string; pluginName: stri
}) })
} }
export const useMutationCheckDependecies = () => { export const useMutationCheckDependencies = () => {
return useMutation({ return useMutation({
mutationFn: (appId: string) => { mutationFn: (appId: string) => {
return get<{ leaked_dependencies: Dependency[] }>(`/apps/import/${appId}/check-dependencies`) return get<{ leaked_dependencies: Dependency[] }>(`/apps/imports/${appId}/check-dependencies`)
}, },
}) })
} }

View File

@ -23,6 +23,7 @@ export type NodeTracing = {
index: number index: number
predecessor_node_id: string predecessor_node_id: string
node_id: string node_id: string
iteration_id?: string
node_type: BlockEnum node_type: BlockEnum
title: string title: string
inputs: any inputs: any