mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-15 02:16:00 +08:00
feat: tracing fe (#5487)
This commit is contained in:
parent
4e2de638af
commit
8fa6cb5e03
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ChartView from './chartView'
|
import ChartView from './chartView'
|
||||||
import CardView from './cardView'
|
import CardView from './cardView'
|
||||||
import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
|
import TracingPanel from './tracing/panel'
|
||||||
import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
|
import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
|
||||||
|
|
||||||
export type IDevelopProps = {
|
export type IDevelopProps = {
|
||||||
@ -11,18 +11,10 @@ export type IDevelopProps = {
|
|||||||
const Overview = async ({
|
const Overview = async ({
|
||||||
params: { appId },
|
params: { appId },
|
||||||
}: IDevelopProps) => {
|
}: IDevelopProps) => {
|
||||||
const locale = getLocaleOnServer()
|
|
||||||
/*
|
|
||||||
rename useTranslation to avoid lint error
|
|
||||||
please check: https://github.com/i18next/next-13-app-dir-i18next-example/issues/24
|
|
||||||
*/
|
|
||||||
const { t } = await translate(locale, 'app-overview')
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full px-4 sm:px-16 py-6 overflow-scroll">
|
<div className="h-full px-4 sm:px-16 py-6 overflow-scroll">
|
||||||
<ApikeyInfoPanel />
|
<ApikeyInfoPanel />
|
||||||
<div className='flex flex-row items-center justify-between mb-4 text-xl text-gray-900'>
|
<TracingPanel />
|
||||||
{t('overview.title')}
|
|
||||||
</div>
|
|
||||||
<CardView appId={appId} />
|
<CardView appId={appId} />
|
||||||
<ChartView appId={appId} />
|
<ChartView appId={appId} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import type { PopupProps } from './config-popup'
|
||||||
|
import ConfigPopup from './config-popup'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
|
||||||
|
const I18N_PREFIX = 'app.tracing'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
readOnly: boolean
|
||||||
|
className?: string
|
||||||
|
hasConfigured: boolean
|
||||||
|
controlShowPopup?: number
|
||||||
|
} & PopupProps
|
||||||
|
|
||||||
|
const ConfigBtn: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
hasConfigured,
|
||||||
|
controlShowPopup,
|
||||||
|
...popupProps
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, doSetOpen] = useState(false)
|
||||||
|
const openRef = useRef(open)
|
||||||
|
const setOpen = useCallback((v: boolean) => {
|
||||||
|
doSetOpen(v)
|
||||||
|
openRef.current = v
|
||||||
|
}, [doSetOpen])
|
||||||
|
|
||||||
|
const handleTrigger = useCallback(() => {
|
||||||
|
setOpen(!openRef.current)
|
||||||
|
}, [setOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (controlShowPopup)
|
||||||
|
// setOpen(!openRef.current)
|
||||||
|
setOpen(true)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controlShowPopup])
|
||||||
|
|
||||||
|
if (popupProps.readOnly && !hasConfigured)
|
||||||
|
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 (
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
placement='bottom-end'
|
||||||
|
offset={{
|
||||||
|
mainAxis: 12,
|
||||||
|
crossAxis: hasConfigured ? 8 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||||
|
{triggerContent}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
|
<ConfigPopup {...popupProps} />
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(ConfigBtn)
|
@ -0,0 +1,179 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import TracingIcon from './tracing-icon'
|
||||||
|
import ProviderPanel from './provider-panel'
|
||||||
|
import type { LangFuseConfig, LangSmithConfig } from './type'
|
||||||
|
import { TracingProvider } from './type'
|
||||||
|
import ProviderConfigModal from './provider-config-modal'
|
||||||
|
import Indicator from '@/app/components/header/indicator'
|
||||||
|
import Switch from '@/app/components/base/switch'
|
||||||
|
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
|
|
||||||
|
const I18N_PREFIX = 'app.tracing'
|
||||||
|
|
||||||
|
export type PopupProps = {
|
||||||
|
appId: string
|
||||||
|
readOnly: boolean
|
||||||
|
enabled: boolean
|
||||||
|
onStatusChange: (enabled: boolean) => void
|
||||||
|
chosenProvider: TracingProvider | null
|
||||||
|
onChooseProvider: (provider: TracingProvider) => void
|
||||||
|
langSmithConfig: LangSmithConfig | null
|
||||||
|
langFuseConfig: LangFuseConfig | null
|
||||||
|
onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig) => void
|
||||||
|
onConfigRemoved: (provider: TracingProvider) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigPopup: FC<PopupProps> = ({
|
||||||
|
appId,
|
||||||
|
readOnly,
|
||||||
|
enabled,
|
||||||
|
onStatusChange,
|
||||||
|
chosenProvider,
|
||||||
|
onChooseProvider,
|
||||||
|
langSmithConfig,
|
||||||
|
langFuseConfig,
|
||||||
|
onConfigUpdated,
|
||||||
|
onConfigRemoved,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const [currentProvider, setCurrentProvider] = useState<TracingProvider | null>(TracingProvider.langfuse)
|
||||||
|
const [isShowConfigModal, {
|
||||||
|
setTrue: showConfigModal,
|
||||||
|
setFalse: hideConfigModal,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
const handleOnConfig = useCallback((provider: TracingProvider) => {
|
||||||
|
return () => {
|
||||||
|
setCurrentProvider(provider)
|
||||||
|
showConfigModal()
|
||||||
|
}
|
||||||
|
}, [showConfigModal])
|
||||||
|
|
||||||
|
const handleOnChoose = useCallback((provider: TracingProvider) => {
|
||||||
|
return () => {
|
||||||
|
onChooseProvider(provider)
|
||||||
|
}
|
||||||
|
}, [onChooseProvider])
|
||||||
|
|
||||||
|
const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig) => {
|
||||||
|
onConfigUpdated(currentProvider!, payload)
|
||||||
|
hideConfigModal()
|
||||||
|
}, [currentProvider, hideConfigModal, onConfigUpdated])
|
||||||
|
|
||||||
|
const handleConfigRemoved = useCallback(() => {
|
||||||
|
onConfigRemoved(currentProvider!)
|
||||||
|
hideConfigModal()
|
||||||
|
}, [currentProvider, hideConfigModal, onConfigRemoved])
|
||||||
|
|
||||||
|
const providerAllConfigured = langSmithConfig && langFuseConfig
|
||||||
|
const providerAllNotConfigured = !langSmithConfig && !langFuseConfig
|
||||||
|
|
||||||
|
const switchContent = (
|
||||||
|
<Switch
|
||||||
|
className='ml-3'
|
||||||
|
defaultValue={enabled}
|
||||||
|
onChange={onStatusChange}
|
||||||
|
size='l'
|
||||||
|
disabled={providerAllNotConfigured}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const langSmithPanel = (
|
||||||
|
<ProviderPanel
|
||||||
|
type={TracingProvider.langSmith}
|
||||||
|
readOnly={readOnly}
|
||||||
|
hasConfigured={!!langSmithConfig}
|
||||||
|
onConfig={handleOnConfig(TracingProvider.langSmith)}
|
||||||
|
isChosen={chosenProvider === TracingProvider.langSmith}
|
||||||
|
onChoose={handleOnChoose(TracingProvider.langSmith)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const langfusePanel = (
|
||||||
|
<ProviderPanel
|
||||||
|
type={TracingProvider.langfuse}
|
||||||
|
readOnly={readOnly}
|
||||||
|
hasConfigured={!!langFuseConfig}
|
||||||
|
onConfig={handleOnConfig(TracingProvider.langfuse)}
|
||||||
|
isChosen={chosenProvider === TracingProvider.langfuse}
|
||||||
|
onChoose={handleOnChoose(TracingProvider.langfuse)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-[420px] p-4 rounded-2xl bg-white border-[0.5px] border-black/5 shadow-lg'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<TracingIcon size='md' className='mr-2' />
|
||||||
|
<div className='leading-[120%] text-[18px] font-semibold text-gray-900'>{t(`${I18N_PREFIX}.tracing`)}</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||||
|
<div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'>
|
||||||
|
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
|
||||||
|
</div>
|
||||||
|
{!readOnly && (
|
||||||
|
<>
|
||||||
|
{providerAllNotConfigured
|
||||||
|
? (
|
||||||
|
<TooltipPlus
|
||||||
|
popupContent={t(`${I18N_PREFIX}.disabledTip`)}
|
||||||
|
>
|
||||||
|
{switchContent}
|
||||||
|
|
||||||
|
</TooltipPlus>
|
||||||
|
)
|
||||||
|
: switchContent}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-2 leading-4 text-xs font-normal text-gray-500'>
|
||||||
|
{t(`${I18N_PREFIX}.tracingDescription`)}
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 h-px bg-gray-100'></div>
|
||||||
|
<div className='mt-3'>
|
||||||
|
{(providerAllConfigured || providerAllNotConfigured)
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div>
|
||||||
|
<div className='mt-2 space-y-2'>
|
||||||
|
{langSmithPanel}
|
||||||
|
{langfusePanel}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div>
|
||||||
|
<div className='mt-2'>
|
||||||
|
{langSmithConfig ? langSmithPanel : langfusePanel}
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||||
|
<div className='mt-2'>
|
||||||
|
{!langSmithConfig ? langSmithPanel : langfusePanel}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{isShowConfigModal && (
|
||||||
|
<ProviderConfigModal
|
||||||
|
appId={appId}
|
||||||
|
type={currentProvider!}
|
||||||
|
payload={currentProvider === TracingProvider.langSmith ? langSmithConfig : langFuseConfig}
|
||||||
|
onCancel={hideConfigModal}
|
||||||
|
onSaved={handleConfigUpdated}
|
||||||
|
onChosen={onChooseProvider}
|
||||||
|
onRemoved={handleConfigRemoved}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(ConfigPopup)
|
@ -0,0 +1,6 @@
|
|||||||
|
import { TracingProvider } from './type'
|
||||||
|
|
||||||
|
export const docURL = {
|
||||||
|
[TracingProvider.langSmith]: 'https://docs.smith.langchain.com/',
|
||||||
|
[TracingProvider.langfuse]: 'https://docs.langfuse.com',
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
label: string
|
||||||
|
labelClassName?: string
|
||||||
|
value: string | number
|
||||||
|
onChange: (value: string) => void
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Field: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
labelClassName,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
isRequired = false,
|
||||||
|
placeholder = '',
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn(className)}>
|
||||||
|
<div className='flex py-[7px]'>
|
||||||
|
<div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div>
|
||||||
|
{isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={value}
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
className='flex h-9 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-gray-50 placeholder:text-gray-400'
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Field)
|
@ -0,0 +1,227 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import type { LangFuseConfig, LangSmithConfig } from './type'
|
||||||
|
import { TracingProvider } from './type'
|
||||||
|
import TracingIcon from './tracing-icon'
|
||||||
|
import ToggleExpandBtn from './toggle-fold-btn'
|
||||||
|
import ConfigButton from './config-button'
|
||||||
|
import { LangfuseIcon, LangsmithIcon } 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'
|
||||||
|
|
||||||
|
const I18N_PREFIX = 'app.tracing'
|
||||||
|
|
||||||
|
const Title = ({
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
className?: string
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(className, 'flex items-center text-lg font-semibold text-gray-900')}>
|
||||||
|
{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 : LangfuseIcon
|
||||||
|
|
||||||
|
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
|
||||||
|
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
|
||||||
|
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig)
|
||||||
|
|
||||||
|
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 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
|
||||||
|
setLangFuseConfig(tracing_config as LangFuseConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTracingConfigRemoved = (provider: TracingProvider) => {
|
||||||
|
if (provider === TracingProvider.langSmith)
|
||||||
|
setLangSmithConfig(null)
|
||||||
|
else
|
||||||
|
setLangFuseConfig(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 [isFold, setFold] = useState(false)
|
||||||
|
const [controlShowPopup, setControlShowPopup] = useState<number>(0)
|
||||||
|
const showPopup = useCallback(() => {
|
||||||
|
setControlShowPopup(Date.now())
|
||||||
|
}, [setControlShowPopup])
|
||||||
|
if (!isLoaded) {
|
||||||
|
return (
|
||||||
|
<div className='flex items-center justify-between mb-3'>
|
||||||
|
<Title className='h-[41px]' />
|
||||||
|
<div className='w-[200px]'>
|
||||||
|
<Loading />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFold && !hasConfiguredTracing) {
|
||||||
|
return (
|
||||||
|
<div className={cn('mb-3')}>
|
||||||
|
<Title />
|
||||||
|
<div className='mt-2 flex justify-between p-3 pr-4 items-center bg-white border-[0.5px] border-black/8 rounded-xl shadow-md'>
|
||||||
|
<div className='flex space-x-2'>
|
||||||
|
<TracingIcon size='lg' className='m-1' />
|
||||||
|
<div>
|
||||||
|
<div className='mb-0.5 leading-6 text-base font-semibold text-gray-900'>{t(`${I18N_PREFIX}.title`)}</div>
|
||||||
|
<div className='flex justify-between leading-4 text-xs font-normal text-gray-500'>
|
||||||
|
<span className='mr-2'>{t(`${I18N_PREFIX}.description`)}</span>
|
||||||
|
<div className='flex space-x-3'>
|
||||||
|
<LangsmithIcon className='h-4' />
|
||||||
|
<LangfuseIcon className='h-4' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center space-x-1'>
|
||||||
|
<ConfigButton
|
||||||
|
appId={appId}
|
||||||
|
readOnly={readOnly}
|
||||||
|
hasConfigured={false}
|
||||||
|
enabled={enabled}
|
||||||
|
onStatusChange={handleTracingEnabledChange}
|
||||||
|
chosenProvider={inUseTracingProvider}
|
||||||
|
onChooseProvider={handleChooseProvider}
|
||||||
|
langSmithConfig={langSmithConfig}
|
||||||
|
langFuseConfig={langFuseConfig}
|
||||||
|
onConfigUpdated={handleTracingConfigUpdated}
|
||||||
|
onConfigRemoved={handleTracingConfigRemoved}
|
||||||
|
/>
|
||||||
|
<ToggleExpandBtn isFold={isFold} onFoldChange={setFold} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('mb-3 flex justify-between items-center cursor-pointer')} onClick={showPopup}>
|
||||||
|
<Title className='h-[41px]' />
|
||||||
|
<div className='flex items-center p-2 rounded-xl border-[0.5px] border-gray-200 shadow-xs hover:bg-gray-100'>
|
||||||
|
{!inUseTracingProvider
|
||||||
|
? <>
|
||||||
|
<TracingIcon size='md' className='mr-2' />
|
||||||
|
<div className='leading-5 text-sm font-semibold text-gray-700'>{t(`${I18N_PREFIX}.title`)}</div>
|
||||||
|
</>
|
||||||
|
: <InUseProviderIcon className='ml-1 h-4' />}
|
||||||
|
|
||||||
|
{hasConfiguredTracing && (
|
||||||
|
<div className='ml-4 mr-1 flex items-center'>
|
||||||
|
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||||
|
<div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'>
|
||||||
|
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasConfiguredTracing && (
|
||||||
|
<div className='ml-2 w-px h-3.5 bg-gray-200'></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>
|
||||||
|
{!hasConfiguredTracing && (
|
||||||
|
<div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||||
|
<div className='mx-2 w-px h-3.5 bg-gray-200'></div>
|
||||||
|
<ToggleExpandBtn isFold={isFold} onFoldChange={setFold} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Panel)
|
@ -0,0 +1,292 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import Field from './field'
|
||||||
|
import type { LangFuseConfig, LangSmithConfig } from './type'
|
||||||
|
import { TracingProvider } from './type'
|
||||||
|
import { docURL } from './config'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
import ConfirmUi from '@/app/components/base/confirm'
|
||||||
|
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appId: string
|
||||||
|
type: TracingProvider
|
||||||
|
payload?: LangSmithConfig | LangFuseConfig | null
|
||||||
|
onRemoved: () => void
|
||||||
|
onCancel: () => void
|
||||||
|
onSaved: (payload: LangSmithConfig | LangFuseConfig) => void
|
||||||
|
onChosen: (provider: TracingProvider) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const I18N_PREFIX = 'app.tracing.configProvider'
|
||||||
|
|
||||||
|
const langSmithConfigTemplate = {
|
||||||
|
api_key: '',
|
||||||
|
project: '',
|
||||||
|
endpoint: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const langFuseConfigTemplate = {
|
||||||
|
public_key: '',
|
||||||
|
secret_key: '',
|
||||||
|
host: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderConfigModal: FC<Props> = ({
|
||||||
|
appId,
|
||||||
|
type,
|
||||||
|
payload,
|
||||||
|
onRemoved,
|
||||||
|
onCancel,
|
||||||
|
onSaved,
|
||||||
|
onChosen,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const isEdit = !!payload
|
||||||
|
const isAdd = !isEdit
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig>((() => {
|
||||||
|
if (isEdit)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
if (type === TracingProvider.langSmith)
|
||||||
|
return langSmithConfigTemplate
|
||||||
|
|
||||||
|
return langFuseConfigTemplate
|
||||||
|
})())
|
||||||
|
const [isShowRemoveConfirm, {
|
||||||
|
setTrue: showRemoveConfirm,
|
||||||
|
setFalse: hideRemoveConfirm,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
|
||||||
|
const handleRemove = useCallback(async () => {
|
||||||
|
await removeTracingConfig({
|
||||||
|
appId,
|
||||||
|
provider: type,
|
||||||
|
})
|
||||||
|
Toast.notify({
|
||||||
|
type: 'success',
|
||||||
|
message: t('common.api.remove'),
|
||||||
|
})
|
||||||
|
onRemoved()
|
||||||
|
hideRemoveConfirm()
|
||||||
|
}, [hideRemoveConfirm, appId, type, t, onRemoved])
|
||||||
|
|
||||||
|
const handleConfigChange = useCallback((key: string) => {
|
||||||
|
return (value: string) => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
[key]: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [config])
|
||||||
|
|
||||||
|
const checkValid = useCallback(() => {
|
||||||
|
let errorMessage = ''
|
||||||
|
if (type === TracingProvider.langSmith) {
|
||||||
|
const postData = config as LangSmithConfig
|
||||||
|
if (!postData.api_key)
|
||||||
|
errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' })
|
||||||
|
if (!errorMessage && !postData.project)
|
||||||
|
errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === TracingProvider.langfuse) {
|
||||||
|
const postData = config as LangFuseConfig
|
||||||
|
if (!errorMessage && !postData.secret_key)
|
||||||
|
errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.secretKey`) })
|
||||||
|
if (!errorMessage && !postData.public_key)
|
||||||
|
errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.publicKey`) })
|
||||||
|
if (!errorMessage && !postData.host)
|
||||||
|
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Host' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
}, [config, t, type])
|
||||||
|
const handleSave = useCallback(async () => {
|
||||||
|
if (isSaving)
|
||||||
|
return
|
||||||
|
const errorMessage = checkValid()
|
||||||
|
if (errorMessage) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const action = isEdit ? updateTracingConfig : addTracingConfig
|
||||||
|
try {
|
||||||
|
await action({
|
||||||
|
appId,
|
||||||
|
body: {
|
||||||
|
tracing_provider: type,
|
||||||
|
tracing_config: config,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Toast.notify({
|
||||||
|
type: 'success',
|
||||||
|
message: t('common.api.success'),
|
||||||
|
})
|
||||||
|
onSaved(config)
|
||||||
|
if (isAdd)
|
||||||
|
onChosen(type)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setIsSaving(false)
|
||||||
|
}
|
||||||
|
}, [appId, checkValid, config, isAdd, isEdit, isSaving, onChosen, onSaved, t, type])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isShowRemoveConfirm
|
||||||
|
? (
|
||||||
|
<PortalToFollowElem open>
|
||||||
|
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||||
|
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
|
||||||
|
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
|
||||||
|
<div className='px-8 pt-8'>
|
||||||
|
<div className='flex justify-between items-center mb-4'>
|
||||||
|
<div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-4'>
|
||||||
|
{type === TracingProvider.langSmith && (
|
||||||
|
<>
|
||||||
|
<Field
|
||||||
|
label='API Key'
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
isRequired
|
||||||
|
value={(config as LangSmithConfig).api_key}
|
||||||
|
onChange={handleConfigChange('api_key')}
|
||||||
|
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'API Key' })!}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label={t(`${I18N_PREFIX}.project`)!}
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
isRequired
|
||||||
|
value={(config as LangSmithConfig).project}
|
||||||
|
onChange={handleConfigChange('project')}
|
||||||
|
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.project`) })!}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label='Endpoint'
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
value={(config as LangSmithConfig).endpoint}
|
||||||
|
onChange={handleConfigChange('endpoint')}
|
||||||
|
placeholder={'https://api.smith.langchain.com'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{type === TracingProvider.langfuse && (
|
||||||
|
<>
|
||||||
|
<Field
|
||||||
|
label={t(`${I18N_PREFIX}.secretKey`)!}
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
value={(config as LangFuseConfig).secret_key}
|
||||||
|
isRequired
|
||||||
|
onChange={handleConfigChange('secret_key')}
|
||||||
|
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.secretKey`) })!}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label={t(`${I18N_PREFIX}.publicKey`)!}
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
isRequired
|
||||||
|
value={(config as LangFuseConfig).public_key}
|
||||||
|
onChange={handleConfigChange('public_key')}
|
||||||
|
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.publicKey`) })!}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label='Host'
|
||||||
|
labelClassName='!text-sm'
|
||||||
|
isRequired
|
||||||
|
value={(config as LangFuseConfig).host}
|
||||||
|
onChange={handleConfigChange('host')}
|
||||||
|
placeholder='https://cloud.langfuse.com'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='my-8 flex justify-between items-center h-8'>
|
||||||
|
<a
|
||||||
|
className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]'
|
||||||
|
target='_blank'
|
||||||
|
href={docURL[type]}
|
||||||
|
>
|
||||||
|
<span>{t(`${I18N_PREFIX}.viewDocsLink`, { key: t(`app.tracing.${type}.title`) })}</span>
|
||||||
|
<LinkExternal02 className='w-3 h-3' />
|
||||||
|
</a>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
{isEdit && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className='h-9 text-sm font-medium text-gray-700'
|
||||||
|
onClick={showRemoveConfirm}
|
||||||
|
>
|
||||||
|
<span className='text-[#D92D20]'>{t('common.operation.remove')}</span>
|
||||||
|
</Button>
|
||||||
|
<div className='mx-3 w-px h-[18px] bg-gray-200'></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className='mr-2 h-9 text-sm font-medium text-gray-700'
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
{t('common.operation.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='h-9 text-sm font-medium'
|
||||||
|
variant='primary'
|
||||||
|
onClick={handleSave}
|
||||||
|
loading={isSaving}
|
||||||
|
>
|
||||||
|
{t(`common.operation.${isAdd ? 'saveAndEnable' : 'save'}`)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='border-t-[0.5px] border-t-black/5'>
|
||||||
|
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
|
||||||
|
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||||
|
{t('common.modelProvider.encrypted.front')}
|
||||||
|
<a
|
||||||
|
className='text-primary-600 mx-1'
|
||||||
|
target='_blank' rel='noopener noreferrer'
|
||||||
|
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||||
|
>
|
||||||
|
PKCS1_OAEP
|
||||||
|
</a>
|
||||||
|
{t('common.modelProvider.encrypted.back')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<ConfirmUi
|
||||||
|
isShow
|
||||||
|
onClose={hideRemoveConfirm}
|
||||||
|
type='warning'
|
||||||
|
title={t(`${I18N_PREFIX}.removeConfirmTitle`, { key: t(`app.tracing.${type}.title`) })!}
|
||||||
|
content={t(`${I18N_PREFIX}.removeConfirmContent`)}
|
||||||
|
onConfirm={handleRemove}
|
||||||
|
onCancel={hideRemoveConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(ProviderConfigModal)
|
@ -0,0 +1,77 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { TracingProvider } from './type'
|
||||||
|
import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||||
|
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
|
||||||
|
const I18N_PREFIX = 'app.tracing'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: TracingProvider
|
||||||
|
readOnly: boolean
|
||||||
|
isChosen: boolean
|
||||||
|
onChoose: () => void
|
||||||
|
hasConfigured: boolean
|
||||||
|
onConfig: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = (type: TracingProvider) => {
|
||||||
|
return ({
|
||||||
|
[TracingProvider.langSmith]: LangsmithIconBig,
|
||||||
|
[TracingProvider.langfuse]: LangfuseIconBig,
|
||||||
|
})[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProviderPanel: FC<Props> = ({
|
||||||
|
type,
|
||||||
|
readOnly,
|
||||||
|
isChosen,
|
||||||
|
onChoose,
|
||||||
|
hasConfigured,
|
||||||
|
onConfig,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const Icon = getIcon(type)
|
||||||
|
|
||||||
|
const handleConfigBtnClick = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onConfig()
|
||||||
|
}, [onConfig])
|
||||||
|
|
||||||
|
const handleChosen = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (isChosen || !hasConfigured || readOnly)
|
||||||
|
return
|
||||||
|
onChoose()
|
||||||
|
}, [hasConfigured, isChosen, onChoose, readOnly])
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(isChosen ? 'border-primary-400' : 'border-transparent', !isChosen && hasConfigured && !readOnly && 'cursor-pointer', 'px-4 py-3 rounded-xl border-[1.5px] bg-gray-100')}
|
||||||
|
onClick={handleChosen}
|
||||||
|
>
|
||||||
|
<div className={'flex justify-between items-center space-x-1'}>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Icon className='h-6' />
|
||||||
|
{isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-primary-500 leading-4 text-xs font-medium text-primary-500 uppercase '>{t(`${I18N_PREFIX}.inUse`)}</div>}
|
||||||
|
</div>
|
||||||
|
{!readOnly && (
|
||||||
|
<div
|
||||||
|
className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1'
|
||||||
|
onClick={handleConfigBtnClick}
|
||||||
|
>
|
||||||
|
<Settings04 className='w-3 h-3' />
|
||||||
|
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 leading-4 text-xs font-normal text-gray-500'>
|
||||||
|
{t(`${I18N_PREFIX}.${type}.description`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(ProviderPanel)
|
@ -0,0 +1,46 @@
|
|||||||
|
'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 TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
|
|
||||||
|
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}>
|
||||||
|
<TooltipPlus
|
||||||
|
popupContent={t(`${I18N_PREFIX}.${isFold ? 'expand' : 'collapse'}`)}
|
||||||
|
hideArrow
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</TooltipPlus>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(ToggleFoldBtn)
|
@ -0,0 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { TracingIcon as Icon } from '@/app/components/base/icons/src/public/tracing'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
size: 'lg' | 'md'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClassMap = {
|
||||||
|
lg: 'w-9 h-9 p-2 rounded-[10px]',
|
||||||
|
md: 'w-6 h-6 p-1 rounded-lg',
|
||||||
|
}
|
||||||
|
|
||||||
|
const TracingIcon: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
}) => {
|
||||||
|
const sizeClass = sizeClassMap[size]
|
||||||
|
return (
|
||||||
|
<div className={cn(className, sizeClass, 'bg-primary-500 shadow-md')}>
|
||||||
|
<Icon className='w-full h-full' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(TracingIcon)
|
@ -0,0 +1,16 @@
|
|||||||
|
export enum TracingProvider {
|
||||||
|
langSmith = 'langsmith',
|
||||||
|
langfuse = 'langfuse',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LangSmithConfig = {
|
||||||
|
api_key: string
|
||||||
|
project: string
|
||||||
|
endpoint: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LangFuseConfig = {
|
||||||
|
public_key: string
|
||||||
|
secret_key: string
|
||||||
|
host: string
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="analytics-fill">
|
||||||
|
<path id="Vector" opacity="0.6" d="M5 2.5C3.61929 2.5 2.5 3.61929 2.5 5V9.16667H6.15164C6.78293 9.16667 7.36003 9.52333 7.64235 10.088L8.33333 11.4699L10.9213 6.29399C11.0625 6.01167 11.351 5.83333 11.6667 5.83333C11.9823 5.83333 12.2708 6.01167 12.412 6.29399L13.8483 9.16667H17.5V5C17.5 3.61929 16.3807 2.5 15 2.5H5Z" fill="white"/>
|
||||||
|
<path id="Vector_2" d="M2.5 14.9999C2.5 16.3807 3.61929 17.4999 5 17.4999H15C16.3807 17.4999 17.5 16.3807 17.5 14.9999V10.8333H13.8483C13.2171 10.8333 12.64 10.4766 12.3577 9.91195L11.6667 8.53003L9.07867 13.7059C8.9375 13.9883 8.649 14.1666 8.33333 14.1666C8.01769 14.1666 7.72913 13.9883 7.58798 13.7059L6.15164 10.8333H2.5V14.9999Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 818 B |
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './LangfuseIcon.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'LangfuseIcon'
|
||||||
|
|
||||||
|
export default Icon
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './LangfuseIconBig.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'LangfuseIconBig'
|
||||||
|
|
||||||
|
export default Icon
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './LangsmithIcon.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'LangsmithIcon'
|
||||||
|
|
||||||
|
export default Icon
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './LangsmithIconBig.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'LangsmithIconBig'
|
||||||
|
|
||||||
|
export default Icon
|
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "20",
|
||||||
|
"height": "20",
|
||||||
|
"viewBox": "0 0 20 20",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "analytics-fill"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"opacity": "0.6",
|
||||||
|
"d": "M5 2.5C3.61929 2.5 2.5 3.61929 2.5 5V9.16667H6.15164C6.78293 9.16667 7.36003 9.52333 7.64235 10.088L8.33333 11.4699L10.9213 6.29399C11.0625 6.01167 11.351 5.83333 11.6667 5.83333C11.9823 5.83333 12.2708 6.01167 12.412 6.29399L13.8483 9.16667H17.5V5C17.5 3.61929 16.3807 2.5 15 2.5H5Z",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_2",
|
||||||
|
"d": "M2.5 14.9999C2.5 16.3807 3.61929 17.4999 5 17.4999H15C16.3807 17.4999 17.5 16.3807 17.5 14.9999V10.8333H13.8483C13.2171 10.8333 12.64 10.4766 12.3577 9.91195L11.6667 8.53003L9.07867 13.7059C8.9375 13.9883 8.649 14.1666 8.33333 14.1666C8.01769 14.1666 7.72913 13.9883 7.58798 13.7059L6.15164 10.8333H2.5V14.9999Z",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "TracingIcon"
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './TracingIcon.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'TracingIcon'
|
||||||
|
|
||||||
|
export default Icon
|
@ -0,0 +1,5 @@
|
|||||||
|
export { default as LangfuseIconBig } from './LangfuseIconBig'
|
||||||
|
export { default as LangfuseIcon } from './LangfuseIcon'
|
||||||
|
export { default as LangsmithIconBig } from './LangsmithIconBig'
|
||||||
|
export { default as LangsmithIcon } from './LangsmithIcon'
|
||||||
|
export { default as TracingIcon } from './TracingIcon'
|
@ -160,6 +160,10 @@ const CodeEditor: FC<Props> = ({
|
|||||||
// lineNumbers: (num) => {
|
// lineNumbers: (num) => {
|
||||||
// return <div>{num}</div>
|
// return <div>{num}</div>
|
||||||
// }
|
// }
|
||||||
|
// hide ambiguousCharacters warning
|
||||||
|
unicodeHighlight: {
|
||||||
|
ambiguousCharacters: false,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
/>
|
/>
|
||||||
|
@ -85,6 +85,42 @@ const translation = {
|
|||||||
workflow: 'Workflow',
|
workflow: 'Workflow',
|
||||||
completion: 'Completion',
|
completion: 'Completion',
|
||||||
},
|
},
|
||||||
|
tracing: {
|
||||||
|
title: 'Tracing app performance',
|
||||||
|
description: 'Configuring a Third-Party LLMOps provider and tracing app performance.',
|
||||||
|
config: 'Config',
|
||||||
|
collapse: 'Collapse',
|
||||||
|
expand: 'Expand',
|
||||||
|
tracing: 'Tracing',
|
||||||
|
disabled: 'Disabled',
|
||||||
|
disabledTip: 'Please config provider first',
|
||||||
|
enabled: 'In Service',
|
||||||
|
tracingDescription: 'Capture the full context of app execution, including LLM calls, context, prompts, HTTP requests, and more, to a third-party tracing platform.',
|
||||||
|
configProviderTitle: {
|
||||||
|
configured: 'Configured',
|
||||||
|
notConfigured: 'Config provider to enable tracing',
|
||||||
|
moreProvider: 'More Provider',
|
||||||
|
},
|
||||||
|
langsmith: {
|
||||||
|
title: 'LangSmith',
|
||||||
|
description: 'An all-in-one developer platform for every step of the LLM-powered application lifecycle.',
|
||||||
|
},
|
||||||
|
langfuse: {
|
||||||
|
title: 'Langfuse',
|
||||||
|
description: 'Traces, evals, prompt management and metrics to debug and improve your LLM application.',
|
||||||
|
},
|
||||||
|
inUse: 'In use',
|
||||||
|
configProvider: {
|
||||||
|
title: 'Config ',
|
||||||
|
placeholder: 'Enter your {{key}}',
|
||||||
|
project: 'Project',
|
||||||
|
publicKey: 'Public Key',
|
||||||
|
secretKey: 'Secret Key',
|
||||||
|
viewDocsLink: 'View {{key}} docs',
|
||||||
|
removeConfirmTitle: 'Remove {{key}} configuration?',
|
||||||
|
removeConfirmContent: 'The current configuration is in use, removing it will turn off the Tracing feature.',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -12,6 +12,7 @@ const translation = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
saveAndEnable: 'Save & Enable',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
added: 'Added',
|
added: 'Added',
|
||||||
@ -439,7 +440,7 @@ const translation = {
|
|||||||
latestAvailable: 'Dify {{version}} is the latest version available.',
|
latestAvailable: 'Dify {{version}} is the latest version available.',
|
||||||
},
|
},
|
||||||
appMenus: {
|
appMenus: {
|
||||||
overview: 'Overview',
|
overview: 'Monitoring',
|
||||||
promptEng: 'Orchestrate',
|
promptEng: 'Orchestrate',
|
||||||
apiAccess: 'API Access',
|
apiAccess: 'API Access',
|
||||||
logAndAnn: 'Logs & Ann.',
|
logAndAnn: 'Logs & Ann.',
|
||||||
|
@ -84,6 +84,42 @@ const translation = {
|
|||||||
workflow: '工作流',
|
workflow: '工作流',
|
||||||
completion: '文本生成',
|
completion: '文本生成',
|
||||||
},
|
},
|
||||||
|
tracing: {
|
||||||
|
title: '追踪应用性能',
|
||||||
|
description: '配置第三方 LLMOps 提供商并跟踪应用程序性能。',
|
||||||
|
config: '配置',
|
||||||
|
collapse: '折叠',
|
||||||
|
expand: '展开',
|
||||||
|
tracing: '追踪',
|
||||||
|
disabled: '已禁用',
|
||||||
|
disabledTip: '请先配置提供商',
|
||||||
|
enabled: '已启用',
|
||||||
|
tracingDescription: '捕获应用程序执行的完整上下文,包括 LLM 调用、上下文、提示、HTTP 请求等,发送到第三方跟踪平台。',
|
||||||
|
configProviderTitle: {
|
||||||
|
configured: '已配置',
|
||||||
|
notConfigured: '配置提供商以启用追踪',
|
||||||
|
moreProvider: '更多提供商',
|
||||||
|
},
|
||||||
|
langsmith: {
|
||||||
|
title: 'LangSmith',
|
||||||
|
description: '一个全方位的开发者平台,适用于 LLM 驱动应用程序生命周期的每个步骤。',
|
||||||
|
},
|
||||||
|
langfuse: {
|
||||||
|
title: 'Langfuse',
|
||||||
|
description: '跟踪、评估、提示管理和指标,以调试和改进您的 LLM 应用程序。',
|
||||||
|
},
|
||||||
|
inUse: '使用中',
|
||||||
|
configProvider: {
|
||||||
|
title: '配置 ',
|
||||||
|
placeholder: '输入你的{{key}}',
|
||||||
|
project: '项目',
|
||||||
|
publicKey: '公钥',
|
||||||
|
secretKey: '密钥',
|
||||||
|
viewDocsLink: '查看 {{key}} 的文档',
|
||||||
|
removeConfirmTitle: '删除 {{key}} 配置?',
|
||||||
|
removeConfirmContent: '当前配置正在使用中,删除它将关闭追踪功能。',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -12,6 +12,7 @@ const translation = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
clear: '清空',
|
clear: '清空',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
|
saveAndEnable: '保存并启用',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
add: '添加',
|
add: '添加',
|
||||||
added: '已添加',
|
added: '已添加',
|
||||||
@ -435,7 +436,7 @@ const translation = {
|
|||||||
latestAvailable: 'Dify {{version}} 已是最新版本。',
|
latestAvailable: 'Dify {{version}} 已是最新版本。',
|
||||||
},
|
},
|
||||||
appMenus: {
|
appMenus: {
|
||||||
overview: '概览',
|
overview: '监测',
|
||||||
promptEng: '编排',
|
promptEng: '编排',
|
||||||
apiAccess: '访问 API',
|
apiAccess: '访问 API',
|
||||||
logAndAnn: '日志与标注',
|
logAndAnn: '日志与标注',
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { LangFuseConfig, LangSmithConfig, TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
||||||
import type { App, AppTemplate, SiteConfig } from '@/types/app'
|
import type { App, AppTemplate, SiteConfig } from '@/types/app'
|
||||||
|
|
||||||
/* export type App = {
|
/* export type App = {
|
||||||
@ -129,3 +130,13 @@ export type AppVoicesListResponse = [{
|
|||||||
name: string
|
name: string
|
||||||
value: string
|
value: string
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
export type TracingStatus = {
|
||||||
|
enabled: boolean
|
||||||
|
tracing_provider: TracingProvider | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TracingConfig = {
|
||||||
|
tracing_provider: TracingProvider
|
||||||
|
tracing_config: LangSmithConfig | LangFuseConfig
|
||||||
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import type { Fetcher } from 'swr'
|
import type { Fetcher } from 'swr'
|
||||||
import { del, get, post, put } from './base'
|
import { del, get, patch, post, put } from './base'
|
||||||
import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||||
import type { CommonResponse } from '@/models/common'
|
import type { CommonResponse } from '@/models/common'
|
||||||
import type { AppMode, ModelConfig } from '@/types/app'
|
import type { AppMode, ModelConfig } from '@/types/app'
|
||||||
|
import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
||||||
|
|
||||||
export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
|
export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
|
||||||
return get<AppListResponse>(url, { params })
|
return get<AppListResponse>(url, { params })
|
||||||
@ -121,3 +122,32 @@ export const generationIntroduction: Fetcher<GenerationIntroductionResponse, { u
|
|||||||
export const fetchAppVoices: Fetcher<AppVoicesListResponse, { appId: string; language?: string }> = ({ appId, language }) => {
|
export const fetchAppVoices: Fetcher<AppVoicesListResponse, { appId: string; language?: string }> = ({ appId, language }) => {
|
||||||
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tracing
|
||||||
|
export const fetchTracingStatus: Fetcher<TracingStatus, { appId: string }> = ({ appId }) => {
|
||||||
|
return get(`/apps/${appId}/trace`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTracingStatus: Fetcher<CommonResponse, { appId: string; body: Record<string, any> }> = ({ appId, body }) => {
|
||||||
|
return post(`/apps/${appId}/trace`, { body })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchTracingConfig: Fetcher<TracingConfig & { has_not_configured: true }, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||||
|
return get(`/apps/${appId}/trace-config`, {
|
||||||
|
params: {
|
||||||
|
tracing_provider: provider,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||||
|
return post(`/apps/${appId}/trace-config`, { body })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||||
|
return patch(`/apps/${appId}/trace-config`, { body })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeTracingConfig: Fetcher<CommonResponse, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||||
|
return del(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user