mirror of
https://gitee.com/dify_ai/dify.git
synced 2024-12-01 02:38:12 +08:00
feat: tracing fe (#5487)
This commit is contained in:
parent
4e2de638af
commit
8fa6cb5e03
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import ChartView from './chartView'
|
||||
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'
|
||||
|
||||
export type IDevelopProps = {
|
||||
@ -11,18 +11,10 @@ export type IDevelopProps = {
|
||||
const Overview = async ({
|
||||
params: { appId },
|
||||
}: 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 (
|
||||
<div className="h-full px-4 sm:px-16 py-6 overflow-scroll">
|
||||
<ApikeyInfoPanel />
|
||||
<div className='flex flex-row items-center justify-between mb-4 text-xl text-gray-900'>
|
||||
{t('overview.title')}
|
||||
</div>
|
||||
<TracingPanel />
|
||||
<CardView appId={appId} />
|
||||
<ChartView appId={appId} />
|
||||
</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) => {
|
||||
// return <div>{num}</div>
|
||||
// }
|
||||
// hide ambiguousCharacters warning
|
||||
unicodeHighlight: {
|
||||
ambiguousCharacters: false,
|
||||
},
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
|
@ -85,6 +85,42 @@ const translation = {
|
||||
workflow: 'Workflow',
|
||||
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
|
||||
|
@ -12,6 +12,7 @@ const translation = {
|
||||
cancel: 'Cancel',
|
||||
clear: 'Clear',
|
||||
save: 'Save',
|
||||
saveAndEnable: 'Save & Enable',
|
||||
edit: 'Edit',
|
||||
add: 'Add',
|
||||
added: 'Added',
|
||||
@ -439,7 +440,7 @@ const translation = {
|
||||
latestAvailable: 'Dify {{version}} is the latest version available.',
|
||||
},
|
||||
appMenus: {
|
||||
overview: 'Overview',
|
||||
overview: 'Monitoring',
|
||||
promptEng: 'Orchestrate',
|
||||
apiAccess: 'API Access',
|
||||
logAndAnn: 'Logs & Ann.',
|
||||
|
@ -84,6 +84,42 @@ const translation = {
|
||||
workflow: '工作流',
|
||||
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
|
||||
|
@ -12,6 +12,7 @@ const translation = {
|
||||
cancel: '取消',
|
||||
clear: '清空',
|
||||
save: '保存',
|
||||
saveAndEnable: '保存并启用',
|
||||
edit: '编辑',
|
||||
add: '添加',
|
||||
added: '已添加',
|
||||
@ -435,7 +436,7 @@ const translation = {
|
||||
latestAvailable: 'Dify {{version}} 已是最新版本。',
|
||||
},
|
||||
appMenus: {
|
||||
overview: '概览',
|
||||
overview: '监测',
|
||||
promptEng: '编排',
|
||||
apiAccess: '访问 API',
|
||||
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'
|
||||
|
||||
/* export type App = {
|
||||
@ -129,3 +130,13 @@ export type AppVoicesListResponse = [{
|
||||
name: 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 { del, get, 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 { del, get, patch, post, put } from './base'
|
||||
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 { 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 }) => {
|
||||
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 }) => {
|
||||
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…
Reference in New Issue
Block a user