| 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 = { | ||||
| 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'> | |||||
| {t('overview.title')} | |||||
| </div> | |||||
| <TracingPanel /> | |||||
| <CardView appId={appId} /> | <CardView appId={appId} /> | ||||
| <ChartView appId={appId} /> | <ChartView appId={appId} /> | ||||
| </div> | </div> |
| '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) |
| '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) |
| import { TracingProvider } from './type' | |||||
| export const docURL = { | |||||
| [TracingProvider.langSmith]: 'https://docs.smith.langchain.com/', | |||||
| [TracingProvider.langfuse]: 'https://docs.langfuse.com', | |||||
| } |
| '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) |
| '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) |
| '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) |
| '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) |
| '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) |
| '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) |
| 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 | |||||
| } |
| <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> |
| // 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 |
| // 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 |
| // 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 |
| // 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 |
| { | |||||
| "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" | |||||
| } |
| // 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 |
| 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' |
| // lineNumbers: (num) => { | // lineNumbers: (num) => { | ||||
| // return <div>{num}</div> | // return <div>{num}</div> | ||||
| // } | // } | ||||
| // hide ambiguousCharacters warning | |||||
| unicodeHighlight: { | |||||
| ambiguousCharacters: false, | |||||
| }, | |||||
| }} | }} | ||||
| onMount={handleEditorDidMount} | onMount={handleEditorDidMount} | ||||
| /> | /> |
| 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 |
| 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', | ||||
| 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.', |
| 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 |
| cancel: '取消', | cancel: '取消', | ||||
| clear: '清空', | clear: '清空', | ||||
| save: '保存', | save: '保存', | ||||
| saveAndEnable: '保存并启用', | |||||
| edit: '编辑', | edit: '编辑', | ||||
| add: '添加', | add: '添加', | ||||
| added: '已添加', | added: '已添加', | ||||
| latestAvailable: 'Dify {{version}} 已是最新版本。', | latestAvailable: 'Dify {{version}} 已是最新版本。', | ||||
| }, | }, | ||||
| appMenus: { | appMenus: { | ||||
| overview: '概览', | |||||
| overview: '监测', | |||||
| promptEng: '编排', | promptEng: '编排', | ||||
| apiAccess: '访问 API', | apiAccess: '访问 API', | ||||
| logAndAnn: '日志与标注', | logAndAnn: '日志与标注', |
| 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 = { | ||||
| 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 | |||||
| } |
| import type { Fetcher } from 'swr' | 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 { 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 }) | ||||
| 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}`) | |||||
| } |