You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gcgj-dify-1.7.0/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx

225 lines
8.5 KiB
TypeScript

'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import {
RiArrowDownDoubleLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { usePathname } from 'next/navigation'
import { useBoolean } from 'ahooks'
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
import { TracingProvider } from './type'
import TracingIcon from './tracing-icon'
import ConfigButton from './config-button'
import cn from '@/utils/classnames'
import { LangfuseIcon, LangsmithIcon, OpikIcon } from '@/app/components/base/icons/src/public/tracing'
import Indicator from '@/app/components/header/indicator'
import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps'
import type { TracingStatus } from '@/models/app'
import Toast from '@/app/components/base/toast'
import { useAppContext } from '@/context/app-context'
import Loading from '@/app/components/base/loading'
import Divider from '@/app/components/base/divider'
const I18N_PREFIX = 'app.tracing'
const Title = ({
className,
}: {
className?: string
}) => {
const { t } = useTranslation()
return (
<div className={cn('system-xl-semibold text-text-primary flex items-center', className)}>
{t('common.appMenus.overview')}
</div>
)
}
const Panel: FC = () => {
const { t } = useTranslation()
const pathname = usePathname()
const matched = pathname.match(/\/app\/([^/]+)/)
const appId = (matched?.length && matched[1]) ? matched[1] : ''
const { isCurrentWorkspaceEditor } = useAppContext()
const readOnly = !isCurrentWorkspaceEditor
const [isLoaded, {
setTrue: setLoaded,
}] = useBoolean(false)
const [tracingStatus, setTracingStatus] = useState<TracingStatus | null>(null)
const enabled = tracingStatus?.enabled || false
const handleTracingStatusChange = async (tracingStatus: TracingStatus, noToast?: boolean) => {
await updateTracingStatus({ appId, body: tracingStatus })
setTracingStatus(tracingStatus)
if (!noToast) {
Toast.notify({
type: 'success',
message: t('common.api.success'),
})
}
}
const handleTracingEnabledChange = (enabled: boolean) => {
handleTracingStatusChange({
tracing_provider: tracingStatus?.tracing_provider || null,
enabled,
})
}
const handleChooseProvider = (provider: TracingProvider) => {
handleTracingStatusChange({
tracing_provider: provider,
enabled: true,
})
}
const inUseTracingProvider: TracingProvider | null = tracingStatus?.tracing_provider || null
const InUseProviderIcon
= inUseTracingProvider === TracingProvider.langSmith
? LangsmithIcon
: inUseTracingProvider === TracingProvider.langfuse
? LangfuseIcon
: OpikIcon
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null)
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig)
const fetchTracingConfig = async () => {
const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith })
if (!langSmithHasNotConfig)
setLangSmithConfig(langSmithConfig as LangSmithConfig)
const { tracing_config: langFuseConfig, has_not_configured: langFuseHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langfuse })
if (!langFuseHasNotConfig)
setLangFuseConfig(langFuseConfig as LangFuseConfig)
const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik })
if (!OpikHasNotConfig)
setOpikConfig(opikConfig as OpikConfig)
}
const handleTracingConfigUpdated = async (provider: TracingProvider) => {
// call api to hide secret key value
const { tracing_config } = await doFetchTracingConfig({ appId, provider })
if (provider === TracingProvider.langSmith)
setLangSmithConfig(tracing_config as LangSmithConfig)
else if (provider === TracingProvider.langfuse)
setLangFuseConfig(tracing_config as LangFuseConfig)
else if (provider === TracingProvider.opik)
setOpikConfig(tracing_config as OpikConfig)
}
const handleTracingConfigRemoved = (provider: TracingProvider) => {
if (provider === TracingProvider.langSmith)
setLangSmithConfig(null)
else if (provider === TracingProvider.langfuse)
setLangFuseConfig(null)
else if (provider === TracingProvider.opik)
setOpikConfig(null)
if (provider === inUseTracingProvider) {
handleTracingStatusChange({
enabled: false,
tracing_provider: null,
}, true)
}
}
useEffect(() => {
(async () => {
const tracingStatus = await fetchTracingStatus({ appId })
setTracingStatus(tracingStatus)
await fetchTracingConfig()
setLoaded()
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const [controlShowPopup, setControlShowPopup] = useState<number>(0)
const showPopup = useCallback(() => {
setControlShowPopup(Date.now())
}, [setControlShowPopup])
if (!isLoaded) {
return (
<div className='mb-3 flex items-center justify-between'>
<Title className='h-[41px]' />
<div className='w-[200px]'>
<Loading />
</div>
</div>
)
}
return (
<div className={cn('mb-3 flex items-center justify-between')}>
<Title className='h-[41px]' />
<div
className={cn(
'bg-background-default-dodge border-effects-highlight shadow-xs hover:bg-background-default-lighter hover:border-effects-highlight-lightmode-off flex cursor-pointer items-center rounded-xl border-l-[0.5px] border-t p-2',
controlShowPopup && 'bg-background-default-lighter border-effects-highlight-lightmode-off',
)}
onClick={showPopup}
>
{!inUseTracingProvider && (
<>
<TracingIcon size='md' />
<div className='system-sm-semibold text-text-secondary mx-2'>{t(`${I18N_PREFIX}.title`)}</div>
<div className='flex items-center' onClick={e => e.stopPropagation()}>
<ConfigButton
appId={appId}
readOnly={readOnly}
hasConfigured={false}
enabled={enabled}
onStatusChange={handleTracingEnabledChange}
chosenProvider={inUseTracingProvider}
onChooseProvider={handleChooseProvider}
langSmithConfig={langSmithConfig}
langFuseConfig={langFuseConfig}
opikConfig={opikConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
controlShowPopup={controlShowPopup}
/>
</div>
<Divider type='vertical' className='h-3.5' />
<div className='rounded-md p-1'>
<RiArrowDownDoubleLine className='text-text-tertiary h-4 w-4' />
</div>
</>
)}
{hasConfiguredTracing && (
<>
<div className='ml-4 mr-1 flex items-center'>
<Indicator color={enabled ? 'green' : 'gray'} />
<div className='system-xs-semibold-uppercase text-text-tertiary ml-1.5'>
{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}
opikConfig={opikConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
controlShowPopup={controlShowPopup}
/>
</div>
</>
)}
</div>
</div>
)
}
export default React.memo(Panel)