diff --git a/web/src/components/originui/underline-tabs.tsx b/web/src/components/originui/underline-tabs.tsx new file mode 100644 index 000000000..8c9a748a7 --- /dev/null +++ b/web/src/components/originui/underline-tabs.tsx @@ -0,0 +1,40 @@ +// registry/default/components/comp-430.tsx + +import { cn } from '@/lib/utils'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; +import React from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; + +export const UnderlineTabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function UnderlineTabsList({ className, ...props }, ref) { + return ( + + ); +}); + +export const UnderlineTabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function UnderlineTabsTrigger({ className, ...props }, ref) { + return ( + + ); +}); + +export { Tabs as UnderlineTabs, TabsContent as UnderlineTabsContent }; diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx index 65df4fffd..3c1b86a6f 100644 --- a/web/src/components/ui/dialog.tsx +++ b/web/src/components/ui/dialog.tsx @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef< & { + token: string; + form: SharedFrom; + beta: string; + isAgent: boolean; +}; + +function EmbedDialog({ + hideModal, + token = '', + form, + beta = '', + isAgent, +}: IProps) { + const { t } = useTranslate('chat'); + + const [visibleAvatar, setVisibleAvatar] = useState(false); + const [locale, setLocale] = useState(''); + + const languageOptions = useMemo(() => { + return Object.values(LanguageAbbreviation).map((x) => ({ + label: LanguageAbbreviationMap[x], + value: x, + })); + }, []); + + const generateIframeSrc = () => { + let src = `${location.origin}/chat/share?shared_id=${token}&from=${form}&auth=${beta}`; + if (visibleAvatar) { + src += '&visible_avatar=1'; + } + if (locale) { + src += `&locale=${locale}`; + } + return src; + }; + + const iframeSrc = generateIframeSrc(); + + const text = ` + ~~~ html + +~~~ + `; + + return ( + + + + + {t('embedIntoSite', { keyPrefix: 'common' })} + + +
+ + + + {t('fullScreenTitle')} + + + {t('partialTitle')} + + + {t('extensionTitle')} + + + +
+ {text} +
+
+ + {t('comingSoon')} + + + {t('comingSoon')} + +
+
+ {t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })} + ID +
+
+ {token} +
+ + {t('howUseId', { keyPrefix: isAgent ? 'flow' : 'chat' })} + +
+
+
+ ); +} + +export default memo(EmbedDialog); diff --git a/web/src/pages/agent/hooks/use-show-dialog.ts b/web/src/pages/agent/hooks/use-show-dialog.ts new file mode 100644 index 000000000..16878d84d --- /dev/null +++ b/web/src/pages/agent/hooks/use-show-dialog.ts @@ -0,0 +1,179 @@ +import { SharedFrom } from '@/constants/chat'; +import { + useSetModalState, + useShowDeleteConfirm, + useTranslate, +} from '@/hooks/common-hooks'; +import { + useCreateSystemToken, + useFetchManualSystemTokenList, + useFetchSystemTokenList, + useRemoveSystemToken, +} from '@/hooks/user-setting-hooks'; +import { IStats } from '@/interfaces/database/chat'; +import { useQueryClient } from '@tanstack/react-query'; +import { message } from 'antd'; +import { useCallback } from 'react'; + +export const useOperateApiKey = (idKey: string, dialogId?: string) => { + const { removeToken } = useRemoveSystemToken(); + const { createToken, loading: creatingLoading } = useCreateSystemToken(); + const { data: tokenList, loading: listLoading } = useFetchSystemTokenList(); + + const showDeleteConfirm = useShowDeleteConfirm(); + + const onRemoveToken = (token: string) => { + showDeleteConfirm({ + onOk: () => removeToken(token), + }); + }; + + const onCreateToken = useCallback(() => { + createToken({ [idKey]: dialogId }); + }, [createToken, idKey, dialogId]); + + return { + removeToken: onRemoveToken, + createToken: onCreateToken, + tokenList, + creatingLoading, + listLoading, + }; +}; + +type ChartStatsType = { + [k in keyof IStats]: Array<{ xAxis: string; yAxis: number }>; +}; + +export const useSelectChartStatsList = (): ChartStatsType => { + const queryClient = useQueryClient(); + const data = queryClient.getQueriesData({ queryKey: ['fetchStats'] }); + const stats: IStats = (data.length > 0 ? data[0][1] : {}) as IStats; + + return Object.keys(stats).reduce((pre, cur) => { + const item = stats[cur as keyof IStats]; + if (item.length > 0) { + pre[cur as keyof IStats] = item.map((x) => ({ + xAxis: x[0] as string, + yAxis: x[1] as number, + })); + } + return pre; + }, {} as ChartStatsType); +}; + +export const useShowTokenEmptyError = () => { + const { t } = useTranslate('chat'); + + const showTokenEmptyError = useCallback(() => { + message.error(t('tokenError')); + }, [t]); + return { showTokenEmptyError }; +}; + +export const useShowBetaEmptyError = () => { + const { t } = useTranslate('chat'); + + const showBetaEmptyError = useCallback(() => { + message.error(t('betaError')); + }, [t]); + return { showBetaEmptyError }; +}; + +const getUrlWithToken = (token: string, from: string = 'chat') => { + const { protocol, host } = window.location; + return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`; +}; + +const useFetchTokenListBeforeOtherStep = () => { + const { showTokenEmptyError } = useShowTokenEmptyError(); + const { showBetaEmptyError } = useShowBetaEmptyError(); + + const { data: tokenList, fetchSystemTokenList } = + useFetchManualSystemTokenList(); + + let token = '', + beta = ''; + + if (Array.isArray(tokenList) && tokenList.length > 0) { + token = tokenList[0].token; + beta = tokenList[0].beta; + } + + token = + Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : ''; + + const handleOperate = useCallback(async () => { + const ret = await fetchSystemTokenList(); + const list = ret; + if (Array.isArray(list) && list.length > 0) { + if (!list[0].beta) { + showBetaEmptyError(); + return false; + } + return list[0]?.token; + } else { + showTokenEmptyError(); + return false; + } + }, [fetchSystemTokenList, showBetaEmptyError, showTokenEmptyError]); + + return { + token, + beta, + handleOperate, + }; +}; + +export const useShowEmbedModal = () => { + const { + visible: embedVisible, + hideModal: hideEmbedModal, + showModal: showEmbedModal, + } = useSetModalState(); + + const { handleOperate, token, beta } = useFetchTokenListBeforeOtherStep(); + + const handleShowEmbedModal = useCallback(async () => { + const succeed = await handleOperate(); + if (succeed) { + showEmbedModal(); + } + }, [handleOperate, showEmbedModal]); + + return { + showEmbedModal: handleShowEmbedModal, + hideEmbedModal, + embedVisible, + embedToken: token, + beta, + }; +}; + +export const usePreviewChat = (idKey: string) => { + const { handleOperate } = useFetchTokenListBeforeOtherStep(); + + const open = useCallback( + (t: string) => { + window.open( + getUrlWithToken( + t, + idKey === 'canvasId' ? SharedFrom.Agent : SharedFrom.Chat, + ), + '_blank', + ); + }, + [idKey], + ); + + const handlePreview = useCallback(async () => { + const token = await handleOperate(); + if (token) { + open(token); + } + }, [handleOperate, open]); + + return { + handlePreview, + }; +}; diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index 4b47fb5cb..fb9da0198 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -7,13 +7,25 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { SharedFrom } from '@/constants/chat'; import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { ReactFlowProvider } from '@xyflow/react'; -import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; +import { + ChevronDown, + CirclePlay, + Download, + History, + Key, + Logs, + ScreenShare, + Upload, +} from 'lucide-react'; import { ComponentPropsWithoutRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { useParams } from 'umi'; import AgentCanvas from './canvas'; +import EmbedDialog from './embed-dialog'; import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; import { useFetchDataOnMount } from './hooks/use-fetch-data'; import { useGetBeginNodeDataQuery } from './hooks/use-get-begin-query'; @@ -22,6 +34,7 @@ import { useSaveGraph, useSaveGraphBeforeOpeningDebugDrawer, } from './hooks/use-save-graph'; +import { useShowEmbedModal } from './hooks/use-show-dialog'; import { BeginQuery } from './interface'; import { UploadAgentDialog } from './upload-agent-dialog'; @@ -30,13 +43,14 @@ function AgentDropdownMenuItem({ ...props }: ComponentPropsWithoutRef) { return ( - + {children} ); } export default function Agent() { + const { id } = useParams(); const { navigateToAgentList } = useNavigatePage(); const { visible: chatDrawerVisible, @@ -53,12 +67,9 @@ export default function Agent() { hideFileUploadModal, } = useHandleExportOrImportJsonFile(); const { saveGraph, loading } = useSaveGraph(); - const { flowDetail } = useFetchDataOnMount(); const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); - const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); - const handleRunAgent = useCallback(() => { const query: BeginQuery[] = getBeginNodeDataQuery(); if (query.length > 0) { @@ -68,47 +79,58 @@ export default function Agent() { } }, [getBeginNodeDataQuery, handleRun, showChatDrawer]); + const { showEmbedModal, hideEmbedModal, embedVisible, beta } = + useShowEmbedModal(); + return (
saveGraph()} loading={loading} > Save - - + + - - API + API + Import - + Export - - + + {t('common.embedIntoSite')} - @@ -126,6 +148,16 @@ export default function Agent() { onOk={onFileUploadOk} > )} + {embedVisible && ( + + )}
); }