From 3c57a9986ce7d7e44d6a8d832942d73cfe8672c7 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 24 Mar 2025 19:07:55 +0800 Subject: [PATCH] Feat: Add LangfuseCard component. #6155 (#6468) ### What problem does this PR solve? Feat: Add LangfuseCard component. #6155 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/assets/svg/langfuse.svg | 10 ++ web/src/hooks/user-setting-hooks.tsx | 56 ++++++++ web/src/interfaces/database/system.ts | 7 + web/src/interfaces/request/system.ts | 5 + web/src/locales/en.ts | 10 ++ web/src/locales/zh-traditional.ts | 13 +- web/src/locales/zh.ts | 16 ++- .../user-setting/setting-model/index.less | 4 - .../user-setting/setting-model/index.tsx | 4 +- .../setting-model/langfuse/index.tsx | 69 ++++++++++ .../langfuse-configuration-dialog.tsx | 72 ++++++++++ .../langfuse/langfuse-configuration-form.tsx | 126 ++++++++++++++++++ .../use-save-langfuse-configuration.tsx | 33 +++++ web/src/services/user-service.ts | 13 ++ web/src/utils/api.ts | 1 + 15 files changed, 431 insertions(+), 8 deletions(-) create mode 100644 web/src/assets/svg/langfuse.svg create mode 100644 web/src/interfaces/database/system.ts create mode 100644 web/src/interfaces/request/system.ts create mode 100644 web/src/pages/user-setting/setting-model/langfuse/index.tsx create mode 100644 web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-dialog.tsx create mode 100644 web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-form.tsx create mode 100644 web/src/pages/user-setting/setting-model/langfuse/use-save-langfuse-configuration.tsx diff --git a/web/src/assets/svg/langfuse.svg b/web/src/assets/svg/langfuse.svg new file mode 100644 index 000000000..cac46d8c3 --- /dev/null +++ b/web/src/assets/svg/langfuse.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/web/src/hooks/user-setting-hooks.tsx b/web/src/hooks/user-setting-hooks.tsx index 6c608a1bc..98ac41850 100644 --- a/web/src/hooks/user-setting-hooks.tsx +++ b/web/src/hooks/user-setting-hooks.tsx @@ -2,12 +2,14 @@ import { LanguageTranslationMap } from '@/constants/common'; import { ResponseGetType } from '@/interfaces/database/base'; import { IToken } from '@/interfaces/database/chat'; import { ITenantInfo } from '@/interfaces/database/knowledge'; +import { ILangfuseConfig } from '@/interfaces/database/system'; import { ISystemStatus, ITenant, ITenantUser, IUserInfo, } from '@/interfaces/database/user-setting'; +import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system'; import userService, { addTenantUser, agreeTenant, @@ -375,3 +377,57 @@ export const useAgreeTenant = () => { return { data, loading, agreeTenant: mutateAsync }; }; + +export const useSetLangfuseConfig = () => { + const { t } = useTranslation(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: ['setLangfuseConfig'], + mutationFn: async (params: ISetLangfuseConfigRequestBody) => { + const { data } = await userService.setLangfuseConfig(params); + if (data.code === 0) { + message.success(t('message.operated')); + } + return data?.code; + }, + }); + + return { data, loading, setLangfuseConfig: mutateAsync }; +}; + +export const useDeleteLangfuseConfig = () => { + const { t } = useTranslation(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: ['deleteLangfuseConfig'], + mutationFn: async () => { + const { data } = await userService.deleteLangfuseConfig(); + if (data.code === 0) { + message.success(t('message.deleted')); + } + return data?.code; + }, + }); + + return { data, loading, deleteLangfuseConfig: mutateAsync }; +}; + +export const useFetchLangfuseConfig = () => { + const { data, isFetching: loading } = useQuery({ + queryKey: ['fetchLangfuseConfig'], + gcTime: 0, + queryFn: async () => { + const { data } = await userService.getLangfuseConfig(); + + return data?.data; + }, + }); + + return { data, loading }; +}; diff --git a/web/src/interfaces/database/system.ts b/web/src/interfaces/database/system.ts new file mode 100644 index 000000000..3d8621fbd --- /dev/null +++ b/web/src/interfaces/database/system.ts @@ -0,0 +1,7 @@ +export interface ILangfuseConfig { + secret_key: string; + public_key: string; + host: string; + project_id: string; + project_name: string; +} diff --git a/web/src/interfaces/request/system.ts b/web/src/interfaces/request/system.ts new file mode 100644 index 000000000..3be566462 --- /dev/null +++ b/web/src/interfaces/request/system.ts @@ -0,0 +1,5 @@ +export interface ISetLangfuseConfigRequestBody { + secret_key: string; + public_key: string; + host: string; +} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 5dc77bc4c..77b6aaca9 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -699,6 +699,16 @@ This auto-tag feature enhances retrieval by adding another layer of domain-speci sureDelete: 'Are you sure to remove this member?', quit: 'Quit', sureQuit: 'Are you sure you want to quit the team you joined?', + secretKey: 'Secret key', + publicKey: 'Public key', + secretKeyMessage: 'Please enter the secret key', + publicKeyMessage: 'Please enter the public key', + hostMessage: 'Please enter the host', + configuration: 'Configuration', + langfuseDescription: + 'Traces, evals, prompt management and metrics to debug and improve your LLM application.', + viewLangfuseSDocumentation: "View Langfuse's documentation", + view: 'View', }, message: { registered: 'Registered!', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index f176ceaa2..1f7bcf6e7 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -211,7 +211,8 @@ export default { embeddingModelTip: '用於嵌入塊的嵌入模型。一旦知識庫有了塊,它就無法更改。如果你想改變它,你需要刪除所有的塊。', permissionsTip: '如果權限是“團隊”,則所有團隊成員都可以操作知識庫。', - chunkTokenNumberTip: '建議的生成文本塊的 token 數閾值。如果切分得到的小文本段 token 數達不到這一閾值,系統就會不斷與之後的文本段合併,直至再合併下一個文本段會超過這一閾值為止,此時產生一個最終文本塊。如果系統在切分文本段時始終沒有遇到文本分段標識符,即便文本段 token 數已經超過這一閾值,系統也不會生成新文本塊。', + chunkTokenNumberTip: + '建議的生成文本塊的 token 數閾值。如果切分得到的小文本段 token 數達不到這一閾值,系統就會不斷與之後的文本段合併,直至再合併下一個文本段會超過這一閾值為止,此時產生一個最終文本塊。如果系統在切分文本段時始終沒有遇到文本分段標識符,即便文本段 token 數已經超過這一閾值,系統也不會生成新文本塊。', chunkMethod: '切片方法', chunkMethodTip: '說明位於右側。', upload: '上傳', @@ -668,6 +669,16 @@ export default { sureDelete: '您確定刪除該成員嗎?', quit: '退出', sureQuit: '確定退出加入的團隊嗎?', + secretKey: '密鑰', + publicKey: '公鑰', + secretKeyMessage: '請輸入私钥', + publicKeyMessage: '請輸入公钥', + hostMessage: '請輸入 host', + configuration: '配置', + langfuseDescription: + '追蹤、評估、提示管理和指標以調試和改進您的 LLM 應用程式。', + viewLangfuseSDocumentation: '查看 Langfuse 的文檔', + view: '查看', }, message: { registered: '註冊成功', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index ee1dbfc96..15834fdaa 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -210,8 +210,10 @@ export default { chunkTokenNumberMessage: '块Token数是必填项', embeddingModelTip: '用于嵌入块的嵌入模型。 一旦知识库有了块,它就无法更改。 如果你想改变它,你需要删除所有的块。', - permissionsTip: '如果把知识库权限设为“团队”,则所有团队成员都可以操作该知识库。', - chunkTokenNumberTip: '建议的生成文本块的 token 数阈值。如果切分得到的小文本段 token 数达不到这一阈值就会不断与之后的文本段合并,直至再合并下一个文本段会超过这一阈值为止,此时产生一个最终文本块。如果系统在切分文本段时始终没有遇到文本分段标识符,即便文本段 token 数已经超过这一阈值,系统也不会生成新文本块。', + permissionsTip: + '如果把知识库权限设为“团队”,则所有团队成员都可以操作该知识库。', + chunkTokenNumberTip: + '建议的生成文本块的 token 数阈值。如果切分得到的小文本段 token 数达不到这一阈值就会不断与之后的文本段合并,直至再合并下一个文本段会超过这一阈值为止,此时产生一个最终文本块。如果系统在切分文本段时始终没有遇到文本分段标识符,即便文本段 token 数已经超过这一阈值,系统也不会生成新文本块。', chunkMethod: '切片方法', chunkMethodTip: '说明位于右侧。', upload: '上传', @@ -687,6 +689,16 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 sureDelete: '您确定要删除该成员吗?', quit: '退出', sureQuit: '确定退出加入的团队吗?', + secretKey: '密钥', + publicKey: '公钥', + secretKeyMessage: '请输入私钥', + publicKeyMessage: '请输入公钥', + hostMessage: '请输入 host', + configuration: '配置', + langfuseDescription: + '跟踪、评估、提示管理和指标,以调试和改进您的 LLM 应用程序。', + viewLangfuseSDocumentation: '查看 Langfuse 的文档', + view: '查看', }, message: { registered: '注册成功', diff --git a/web/src/pages/user-setting/setting-model/index.less b/web/src/pages/user-setting/setting-model/index.less index 39cdd5a3c..ae2069937 100644 --- a/web/src/pages/user-setting/setting-model/index.less +++ b/web/src/pages/user-setting/setting-model/index.less @@ -1,7 +1,3 @@ -.modelWrapper { - width: 100%; -} - .modelContainer { width: 100%; .factoryOperationWrapper { diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 646404197..527364bff 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -49,6 +49,7 @@ import { } from './hooks'; import HunyuanModal from './hunyuan-modal'; import styles from './index.less'; +import { LangfuseCard } from './langfuse'; import OllamaModal from './ollama-modal'; import SparkModal from './spark-modal'; import SystemModelSettingModal from './system-model-setting-modal'; @@ -358,7 +359,8 @@ const UserSettingModel = () => { ]; return ( -
+
+
{ + window.open( + `https://cloud.langfuse.com/project/${data?.project_id}`, + '_blank', + ); + }, [data?.project_id]); + + return ( + + + +
+ + Langfuse +
+
+ {data && ( + + )} + +
+
+ {t('setting.langfuseDescription')} +
+ {saveLangfuseConfigurationVisible && ( + + )} +
+ ); +} diff --git a/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-dialog.tsx b/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-dialog.tsx new file mode 100644 index 000000000..e0b3bfb02 --- /dev/null +++ b/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-dialog.tsx @@ -0,0 +1,72 @@ +import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { LoadingButton } from '@/components/ui/loading-button'; +import { useDeleteLangfuseConfig } from '@/hooks/user-setting-hooks'; +import { IModalProps } from '@/interfaces/common'; +import { ExternalLink, Trash2 } from 'lucide-react'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + FormId, + LangfuseConfigurationForm, +} from './langfuse-configuration-form'; + +export function LangfuseConfigurationDialog({ + hideModal, + loading, + onOk, +}: IModalProps) { + const { t } = useTranslation(); + const { deleteLangfuseConfig } = useDeleteLangfuseConfig(); + + const handleDelete = useCallback(async () => { + const ret = await deleteLangfuseConfig(); + if (ret === 0) { + hideModal?.(); + } + }, [deleteLangfuseConfig, hideModal]); + + return ( + + + + + + + {t('setting.configuration')} Langfuse + + + + + {t('setting.viewLangfuseSDocumentation')} + + +
+ + + + + + {t('common.save')} + +
+
+
+
+ ); +} diff --git a/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-form.tsx b/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-form.tsx new file mode 100644 index 000000000..f2a7d6bfe --- /dev/null +++ b/web/src/pages/user-setting/setting-model/langfuse/langfuse-configuration-form.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useFetchLangfuseConfig } from '@/hooks/user-setting-hooks'; +import { IModalProps } from '@/interfaces/common'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const FormId = 'LangfuseConfigurationForm'; + +export function LangfuseConfigurationForm({ onOk }: IModalProps) { + const { t } = useTranslation(); + const { data } = useFetchLangfuseConfig(); + + const FormSchema = z.object({ + secret_key: z + .string() + .min(1, { + message: t('setting.secretKeyMessage'), + }) + .trim(), + public_key: z + .string() + .min(1, { + message: t('setting.publicKeyMessage'), + }) + .trim(), + host: z + .string() + .min(0, { + message: t('setting.hostMessage'), + }) + .trim(), + }); + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: {}, + }); + + async function onSubmit(data: z.infer) { + onOk?.(data); + } + + useEffect(() => { + if (data) { + form.reset(data); + } + }, [data, form]); + + return ( +
+ + ( + + {t('setting.secretKey')} + + + + + + )} + /> + ( + + {t('setting.publicKey')} + + + + + + )} + /> + ( + + Host + + + + + + )} + /> + + + ); +} diff --git a/web/src/pages/user-setting/setting-model/langfuse/use-save-langfuse-configuration.tsx b/web/src/pages/user-setting/setting-model/langfuse/use-save-langfuse-configuration.tsx new file mode 100644 index 000000000..4e4a3f987 --- /dev/null +++ b/web/src/pages/user-setting/setting-model/langfuse/use-save-langfuse-configuration.tsx @@ -0,0 +1,33 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { useSetLangfuseConfig } from '@/hooks/user-setting-hooks'; +import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system'; +import { useCallback } from 'react'; + +export const useSaveLangfuseConfiguration = () => { + const { + visible: saveLangfuseConfigurationVisible, + hideModal: hideSaveLangfuseConfigurationModal, + showModal: showSaveLangfuseConfigurationModal, + } = useSetModalState(); + const { setLangfuseConfig, loading } = useSetLangfuseConfig(); + + const onSaveLangfuseConfigurationOk = useCallback( + async (params: ISetLangfuseConfigRequestBody) => { + const ret = await setLangfuseConfig(params); + + if (ret === 0) { + hideSaveLangfuseConfigurationModal(); + } + return ret; + }, + [hideSaveLangfuseConfigurationModal], + ); + + return { + loading, + saveLangfuseConfigurationOk: onSaveLangfuseConfigurationOk, + saveLangfuseConfigurationVisible, + hideSaveLangfuseConfigurationModal, + showSaveLangfuseConfigurationModal, + }; +}; diff --git a/web/src/services/user-service.ts b/web/src/services/user-service.ts index 39a33d0a7..d6f5f920a 100644 --- a/web/src/services/user-service.ts +++ b/web/src/services/user-service.ts @@ -23,6 +23,7 @@ const { removeSystemToken, createSystemToken, getSystemConfig, + setLangfuseConfig, } = api; const methods = { @@ -106,6 +107,18 @@ const methods = { url: getSystemConfig, method: 'get', }, + setLangfuseConfig: { + url: setLangfuseConfig, + method: 'put', + }, + getLangfuseConfig: { + url: setLangfuseConfig, + method: 'get', + }, + deleteLangfuseConfig: { + url: setLangfuseConfig, + method: 'delete', + }, } as const; const userService = registerServer(methods, request); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 3fbac6c4a..e9eff866a 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -120,6 +120,7 @@ export default { listSystemToken: `${api_host}/system/token_list`, removeSystemToken: `${api_host}/system/token`, getSystemConfig: `${api_host}/system/config`, + setLangfuseConfig: `${api_host}/langfuse/api_key`, // flow listTemplates: `${api_host}/canvas/templates`,