mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Feat: Show agent embed dialog #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
40
web/src/components/originui/underline-tabs.tsx
Normal file
40
web/src/components/originui/underline-tabs.tsx
Normal file
@ -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<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(function UnderlineTabsList({ className, ...props }, ref) {
|
||||
return (
|
||||
<TabsList
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-foreground h-auto gap-2 rounded-none border-b bg-transparent px-0 py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const UnderlineTabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(function UnderlineTabsTrigger({ className, ...props }, ref) {
|
||||
return (
|
||||
<TabsTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'hover:bg-accent hover:text-foreground data-[state=active]:after:bg-primary data-[state=active]:hover:bg-accent relative after:absolute after:inset-x-0 after:bottom-0 after:-mb-1 after:h-0.5 data-[state=active]:bg-transparent data-[state=active]:shadow-none',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export { Tabs as UnderlineTabs, TabsContent as UnderlineTabsContent };
|
||||
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-colors-background-neutral-standard p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-2xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-colors-background-neutral-standard p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-colors-background-inverse-strong data-[state=active]:text-colors-text-inverse-strong data-[state=active]:shadow-sm',
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-colors-background-inverse-strong data-[state=active]:text-text-title data-[state=active]:shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
132
web/src/pages/agent/embed-dialog/index.tsx
Normal file
132
web/src/pages/agent/embed-dialog/index.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
||||
import HightLightMarkdown from '@/components/highlight-markdown';
|
||||
import {
|
||||
UnderlineTabs,
|
||||
UnderlineTabsContent,
|
||||
UnderlineTabsList,
|
||||
UnderlineTabsTrigger,
|
||||
} from '@/components/originui/underline-tabs';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { SharedFrom } from '@/constants/chat';
|
||||
import {
|
||||
LanguageAbbreviation,
|
||||
LanguageAbbreviationMap,
|
||||
} from '@/constants/common';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
|
||||
type IProps = IModalProps<any> & {
|
||||
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
|
||||
<iframe
|
||||
src="${iframeSrc}"
|
||||
style="width: 100%; height: 100%; min-height: 600px"
|
||||
frameborder="0"
|
||||
>
|
||||
</iframe>
|
||||
~~~
|
||||
`;
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('embedIntoSite', { keyPrefix: 'common' })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<section className="w-full overflow-auto">
|
||||
<UnderlineTabs defaultValue="1" className="w-full">
|
||||
<UnderlineTabsList>
|
||||
<UnderlineTabsTrigger value="1">
|
||||
{t('fullScreenTitle')}
|
||||
</UnderlineTabsTrigger>
|
||||
<UnderlineTabsTrigger value="2">
|
||||
{t('partialTitle')}
|
||||
</UnderlineTabsTrigger>
|
||||
<UnderlineTabsTrigger value="3">
|
||||
{t('extensionTitle')}
|
||||
</UnderlineTabsTrigger>
|
||||
</UnderlineTabsList>
|
||||
<UnderlineTabsContent value="1">
|
||||
<section>
|
||||
<HightLightMarkdown>{text}</HightLightMarkdown>
|
||||
</section>
|
||||
</UnderlineTabsContent>
|
||||
<UnderlineTabsContent value="2">
|
||||
{t('comingSoon')}
|
||||
</UnderlineTabsContent>
|
||||
<UnderlineTabsContent value="3">
|
||||
{t('comingSoon')}
|
||||
</UnderlineTabsContent>
|
||||
</UnderlineTabs>
|
||||
<div className="text-base font-medium mt-4 mb-1">
|
||||
{t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })}
|
||||
<span className="ml-1 inline-block">ID</span>
|
||||
</div>
|
||||
<div className="bg-background-card rounded-md p-2 ">
|
||||
{token} <CopyToClipboard text={token}></CopyToClipboard>
|
||||
</div>
|
||||
<a
|
||||
className="pt-3 cursor-pointer text-background-checked inline-block"
|
||||
href={
|
||||
isAgent
|
||||
? 'https://ragflow.io/docs/dev/http_api_reference#create-session-with-agent'
|
||||
: 'https://ragflow.io/docs/dev/http_api_reference#create-session-with-chat-assistant'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('howUseId', { keyPrefix: isAgent ? 'flow' : 'chat' })}
|
||||
</a>
|
||||
</section>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(EmbedDialog);
|
||||
179
web/src/pages/agent/hooks/use-show-dialog.ts
Normal file
179
web/src/pages/agent/hooks/use-show-dialog.ts
Normal file
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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<typeof DropdownMenuItem>) {
|
||||
return (
|
||||
<DropdownMenuItem className="flex justify-between items-center" {...props}>
|
||||
<DropdownMenuItem className="justify-start" {...props}>
|
||||
{children}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<section className="h-full">
|
||||
<PageHeader back={navigateToAgentList} title={flowDetail.title}>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonLoading
|
||||
variant={'outline'}
|
||||
variant={'secondary'}
|
||||
onClick={() => saveGraph()}
|
||||
loading={loading}
|
||||
>
|
||||
Save
|
||||
</ButtonLoading>
|
||||
<Button variant={'outline'} onClick={handleRunAgent}>
|
||||
<Button variant={'secondary'} onClick={handleRunAgent}>
|
||||
<CirclePlay />
|
||||
Run app
|
||||
</Button>
|
||||
<Button variant={'outline'}>Publish</Button>
|
||||
<Button variant={'secondary'}>
|
||||
<History />
|
||||
History version
|
||||
</Button>
|
||||
<Button variant={'secondary'}>
|
||||
<Logs />
|
||||
Log
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<EllipsisVertical />
|
||||
<Button variant={'secondary'}>
|
||||
<ChevronDown /> Management
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<AgentDropdownMenuItem onClick={openDocument}>
|
||||
API
|
||||
<Key />
|
||||
API
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem onClick={handleImportJson}>
|
||||
<Download />
|
||||
Import
|
||||
<Import />
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem onClick={handleExportJson}>
|
||||
<Upload />
|
||||
Export
|
||||
<Forward />
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem>
|
||||
<AgentDropdownMenuItem onClick={showEmbedModal}>
|
||||
<ScreenShare />
|
||||
{t('common.embedIntoSite')}
|
||||
<CodeXml />
|
||||
</AgentDropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@ -126,6 +148,16 @@ export default function Agent() {
|
||||
onOk={onFileUploadOk}
|
||||
></UploadAgentDialog>
|
||||
)}
|
||||
{embedVisible && (
|
||||
<EmbedDialog
|
||||
visible={embedVisible}
|
||||
hideModal={hideEmbedModal}
|
||||
token={id!}
|
||||
form={SharedFrom.Agent}
|
||||
beta={beta}
|
||||
isAgent
|
||||
></EmbedDialog>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user