mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-04-27 22:07:58 +08:00
Feat: Add Explore page (#13043)
### What problem does this PR solve? Feat: Add Explore page ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -95,6 +95,13 @@ export const useNavigatePage = () => {
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgentExplore = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Agent}/${id}/explore`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgentLogs = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.AgentLogPage}/${id}`);
|
||||
@ -176,7 +183,7 @@ export const useNavigatePage = () => {
|
||||
|
||||
const navigateToDataflowResult = useCallback(
|
||||
(props: NavigateToDataflowResultProps) => () => {
|
||||
let params: string[] = [];
|
||||
const params: string[] = [];
|
||||
Object.keys(props).forEach((key) => {
|
||||
if (props[key as keyof typeof props]) {
|
||||
params.push(`${key}=${props[key as keyof typeof props]}`);
|
||||
@ -203,6 +210,7 @@ export const useNavigatePage = () => {
|
||||
navigateToChunk,
|
||||
navigateToAgents,
|
||||
navigateToAgent,
|
||||
navigateToAgentExplore,
|
||||
navigateToAgentLogs,
|
||||
navigateToAgentTemplates,
|
||||
navigateToSearchList,
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import message from '@/components/ui/message';
|
||||
import { AgentGlobals, initialBeginValues } from '@/constants/agent';
|
||||
import {
|
||||
AgentCategory,
|
||||
AgentGlobals,
|
||||
initialBeginValues,
|
||||
} from '@/constants/agent';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import {
|
||||
IAgentLogResponse,
|
||||
IAgentLogsRequest,
|
||||
IAgentLogsResponse,
|
||||
IFlow,
|
||||
@ -20,7 +26,10 @@ import { BeginId } from '@/pages/agent/constant';
|
||||
import { IInputs } from '@/pages/agent/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||
import agentService, {
|
||||
createAgentSession,
|
||||
deleteAgentSession,
|
||||
fetchAgentLogsByCanvasId,
|
||||
fetchAgentLogsById,
|
||||
fetchPipeLineList,
|
||||
fetchTrace,
|
||||
fetchWebhookTrace,
|
||||
@ -39,6 +48,7 @@ import {
|
||||
|
||||
export const enum AgentApiAction {
|
||||
FetchAgentListByPage = 'fetchAgentListByPage',
|
||||
FetchAllAgentList = 'fetchAllAgentList',
|
||||
FetchAgentList = 'fetchAgentList',
|
||||
UpdateAgentSetting = 'updateAgentSetting',
|
||||
DeleteAgent = 'deleteAgent',
|
||||
@ -61,6 +71,11 @@ export const enum AgentApiAction {
|
||||
CancelDataflow = 'cancelDataflow',
|
||||
CancelCanvas = 'cancelCanvas',
|
||||
FetchWebhookTrace = 'fetchWebhookTrace',
|
||||
FetchSessionsByCanvasId = 'fetchSessionsByCanvasId',
|
||||
FetchSessionById = 'fetchSessionById',
|
||||
CreateAgentSession = 'createAgentSession',
|
||||
DeleteAgentSession = 'deleteAgentSession',
|
||||
FetchSessionByIdManually = 'fetchSessionByIdManually',
|
||||
}
|
||||
|
||||
export const EmptyDsl = {
|
||||
@ -194,6 +209,28 @@ export const useFetchAgentListByPage = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export function useFetchAllAgentList() {
|
||||
const { data, isFetching: loading } = useQuery<IFlow[]>({
|
||||
queryKey: [AgentApiAction.FetchAllAgentList],
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.listCanvas(
|
||||
{
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 100000,
|
||||
canvas_category: AgentCategory.AgentCanvas,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data?.canvas;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export const useUpdateAgentSetting = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -627,6 +664,37 @@ export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchSessionsByCanvasId = () => {
|
||||
const { id: canvasId } = useParams();
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IAgentLogsResponse>({
|
||||
queryKey: [AgentApiAction.FetchSessionsByCanvasId, canvasId],
|
||||
initialData: { total: 0, sessions: [] } as IAgentLogsResponse,
|
||||
gcTime: 0,
|
||||
enabled: !!canvasId && !isEmpty(tenantInfo),
|
||||
queryFn: async () => {
|
||||
if (!canvasId) {
|
||||
return { total: 0, sessions: [] };
|
||||
}
|
||||
|
||||
const { data } = await fetchAgentLogsByCanvasId(canvasId, {
|
||||
page: 1,
|
||||
page_size: 100000,
|
||||
exp_user_id: tenantInfo.tenant_id,
|
||||
});
|
||||
|
||||
return data?.data ?? { total: 0, sessions: [] };
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: data?.sessions ?? [],
|
||||
loading,
|
||||
total: data?.total ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchExternalAgentInputs = () => {
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
@ -873,3 +941,82 @@ export const useFetchWebhookTrace = (autoStart: boolean = true) => {
|
||||
currentNextSinceTs,
|
||||
};
|
||||
};
|
||||
|
||||
export function useCreateAgentSession() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.CreateAgentSession],
|
||||
mutationFn: async (payload: { id: string; name: string }) => {
|
||||
const { data } = await createAgentSession(payload);
|
||||
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchSessionsByCanvasId],
|
||||
});
|
||||
}
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createAgentSession: mutateAsync };
|
||||
}
|
||||
|
||||
export function useDeleteAgentSession() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.DeleteAgentSession],
|
||||
mutationFn: async ({
|
||||
canvasId,
|
||||
sessionId,
|
||||
}: {
|
||||
canvasId: string;
|
||||
sessionId: string;
|
||||
}) => {
|
||||
const { data } = await deleteAgentSession(canvasId, sessionId);
|
||||
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchSessionsByCanvasId],
|
||||
});
|
||||
}
|
||||
|
||||
return data?.code ?? -1;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteAgentSession: mutateAsync };
|
||||
}
|
||||
|
||||
export function useFetchSessionManually() {
|
||||
const { id: canvasId } = useParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation<IAgentLogResponse, unknown, string>({
|
||||
mutationKey: [AgentApiAction.FetchSessionByIdManually, canvasId],
|
||||
mutationFn: async (sessionId) => {
|
||||
if (!canvasId || !sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data } = await fetchAgentLogsById(canvasId, sessionId);
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchSessionManually: mutateAsync };
|
||||
}
|
||||
|
||||
61
web/src/hooks/use-client-search.ts
Normal file
61
web/src/hooks/use-client-search.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
export interface SearchFilterOptions<T> {
|
||||
data: T[];
|
||||
searchFields: Array<keyof T | ((item: T) => string)>;
|
||||
debounceMs?: number;
|
||||
}
|
||||
|
||||
export function useClientSearch<T>({
|
||||
data,
|
||||
searchFields,
|
||||
debounceMs = 300,
|
||||
}: SearchFilterOptions<T>) {
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
|
||||
const debouncedSearchKeyword = useDebounce(searchKeyword, {
|
||||
wait: debounceMs,
|
||||
});
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setSearchKeyword(e.target.value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
setSearchKeyword('');
|
||||
}, []);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!debouncedSearchKeyword.trim()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const keyword = debouncedSearchKeyword.toLowerCase().trim();
|
||||
|
||||
return data.filter((item) => {
|
||||
return searchFields.some((field) => {
|
||||
let value: string;
|
||||
|
||||
if (typeof field === 'function') {
|
||||
value = field(item);
|
||||
} else {
|
||||
value = String(item[field] ?? '');
|
||||
}
|
||||
|
||||
return value?.toLowerCase().includes(keyword);
|
||||
});
|
||||
});
|
||||
}, [data, debouncedSearchKeyword, searchFields]);
|
||||
|
||||
return {
|
||||
filteredData,
|
||||
searchKeyword,
|
||||
handleSearchChange,
|
||||
clearSearch,
|
||||
isSearching: debouncedSearchKeyword !== searchKeyword,
|
||||
};
|
||||
}
|
||||
@ -256,6 +256,7 @@ export interface IAgentLogResponse {
|
||||
user_id: string;
|
||||
dsl: string;
|
||||
reference: IReference;
|
||||
name: string;
|
||||
}
|
||||
export interface IAgentLogsResponse {
|
||||
total: number;
|
||||
@ -269,6 +270,7 @@ export interface IAgentLogsRequest {
|
||||
desc?: boolean;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
exp_user_id?: string; // tenant id
|
||||
}
|
||||
|
||||
export interface IAgentLogMessage {
|
||||
|
||||
@ -468,7 +468,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
dataSource: 'Data source',
|
||||
linkSourceSetTip: 'Manage data source linkage with this dataset',
|
||||
linkDataSource: 'Link data source',
|
||||
tocExtraction: 'TOC enhance',
|
||||
tocExtraction: 'PageIndex',
|
||||
tocExtractionTip:
|
||||
" For existing chunks, generate a hierarchical table of contents (one directory per file). During queries, when Directory Enhancement is activated, the system will use a large model to determine which directory items are relevant to the user's question, thereby identifying the relevant chunks.",
|
||||
deleteGenerateModalContent: `
|
||||
@ -884,7 +884,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
},
|
||||
cancel: 'Cancel',
|
||||
chatSetting: 'Chat setting',
|
||||
tocEnhance: 'TOC enhance',
|
||||
tocEnhance: 'PageIndex',
|
||||
tocEnhanceTip: ` During the parsing of the document, table of contents information was generated (see the 'Enable Table of Contents Extraction' option in the General method). This allows the large model to return table of contents items relevant to the user's query, thereby using these items to retrieve related chunks and apply weighting to these chunks during the sorting process. This approach is derived from mimicking the behavioral logic of how humans search for knowledge in books.`,
|
||||
batchDeleteSessions: 'Batch delete',
|
||||
deleteSelectedConfirm: 'Delete the selected {count} session(s)?',
|
||||
@ -2581,5 +2581,23 @@ Important structured information may include: names, dates, locations, events, k
|
||||
timeout: 'Timeout',
|
||||
fail: 'Fail',
|
||||
},
|
||||
explore: {
|
||||
title: 'Launch',
|
||||
canvasList: 'Canvas List',
|
||||
sessions: 'Sessions',
|
||||
newSession: 'New Session',
|
||||
newSessionLabel: 'Start a new conversation',
|
||||
deleteSession: 'Delete Session',
|
||||
searchCanvas: 'Search canvas...',
|
||||
searchSessions: 'Search sessions...',
|
||||
noCanvasSelected: 'Please select a canvas',
|
||||
noSessionSelected: 'Please select a session or create a new one',
|
||||
noSessionsFound: 'No sessions found',
|
||||
createFirstSession: 'Create your first session',
|
||||
noCanvasFound: 'No canvas found',
|
||||
deleteSelectedConfirm:
|
||||
'Are you sure you want to delete {{count}} session(s)?',
|
||||
batchDeleteSessions: 'Delete Sessions',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -423,7 +423,7 @@ export default {
|
||||
linkSourceSetTip: '管理与此数据集的数据源链接',
|
||||
linkDataSource: '链接数据源',
|
||||
tocExtractionTip:
|
||||
'对于已有的chunk生成层级结构的目录信息(每个文件一个目录)。在查询时,激活`目录增强`后,系统会用大模型去判断用户问题和哪些目录项相关,从而找到相关的chunk。',
|
||||
'对于已有的chunk生成层级结构的目录信息(每个文件一个目录)。在查询时,激活`Page Index`后,系统会用大模型去判断用户问题和哪些目录项相关,从而找到相关的chunk。',
|
||||
deleteGenerateModalContent: `
|
||||
<p>删除生成的 <strong class='text-text-primary'>{{type}}</strong> 结果
|
||||
将从此数据集中移除所有派生实体和关系。
|
||||
@ -830,7 +830,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
chatSetting: '聊天设置',
|
||||
avatarHidden: '隐藏头像',
|
||||
locale: '地区',
|
||||
tocEnhance: '目录增强',
|
||||
tocEnhance: 'Page Index',
|
||||
tocEnhanceTip: `解析文档时生成了目录信息(见General方法的'启用目录抽取'),让大模型返回和用户问题相关的目录项,从而利用目录项拿到相关chunk,对这些chunk在排序中进行加权。这种方法来源于模仿人类查询书本中知识的行为逻辑`,
|
||||
batchDeleteSessions: '批量删除',
|
||||
deleteSelectedConfirm: '删除选中的 {count} 个会话?',
|
||||
@ -2188,5 +2188,23 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
||||
notFoundMemory: '未查询到记忆',
|
||||
addNow: '立即添加',
|
||||
},
|
||||
|
||||
explore: {
|
||||
title: '探索',
|
||||
canvasList: '画布列表',
|
||||
sessions: '会话列表',
|
||||
newSession: '新建会话',
|
||||
newSessionLabel: '开始新对话',
|
||||
deleteSession: '删除会话',
|
||||
searchCanvas: '搜索画布...',
|
||||
searchSessions: '搜索会话...',
|
||||
noCanvasSelected: '请选择一个画布',
|
||||
noSessionSelected: '请选择一个会话或创建新会话',
|
||||
noSessionsFound: '未找到会话',
|
||||
createFirstSession: '创建您的第一个会话',
|
||||
noCanvasFound: '未找到画布',
|
||||
deleteSelectedConfirm: '确定要删除 {{count}} 个会话吗?',
|
||||
batchDeleteSessions: '删除会话',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -252,6 +252,7 @@ export const useSendAgentMessage = ({
|
||||
removeAllMessagesExceptFirst,
|
||||
scrollToBottom,
|
||||
addPrologue,
|
||||
setDerivedMessages,
|
||||
} = useSelectDerivedMessages();
|
||||
const { addEventList: addEventListFun } = useContext(AgentChatLogContext);
|
||||
const {
|
||||
@ -274,10 +275,12 @@ export const useSendAgentMessage = ({
|
||||
async ({
|
||||
message,
|
||||
beginInputs,
|
||||
exploreSessionId,
|
||||
}: {
|
||||
message: Message;
|
||||
messages?: Message[];
|
||||
beginInputs?: BeginQuery[];
|
||||
exploreSessionId?: string;
|
||||
}) => {
|
||||
const params: Record<string, unknown> = {
|
||||
id: agentId,
|
||||
@ -297,7 +300,7 @@ export const useSendAgentMessage = ({
|
||||
|
||||
params.files = uploadResponseList;
|
||||
|
||||
params.session_id = sessionId;
|
||||
params.session_id = sessionId || exploreSessionId;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -364,28 +367,32 @@ export const useSendAgentMessage = ({
|
||||
removeAllMessagesExceptFirst,
|
||||
]);
|
||||
|
||||
const handlePressEnter = useCallback(() => {
|
||||
if (trim(value) === '') return;
|
||||
const msgBody = buildRequestBody(value);
|
||||
if (done) {
|
||||
setValue('');
|
||||
sendMessage({
|
||||
message: msgBody,
|
||||
});
|
||||
}
|
||||
addNewestOneQuestion({ ...msgBody, files: fileList });
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 100);
|
||||
}, [
|
||||
value,
|
||||
done,
|
||||
addNewestOneQuestion,
|
||||
fileList,
|
||||
setValue,
|
||||
sendMessage,
|
||||
scrollToBottom,
|
||||
]);
|
||||
const handlePressEnter = useCallback(
|
||||
({ exploreSessionId }: { exploreSessionId?: string } = {}) => {
|
||||
if (trim(value) === '') return;
|
||||
const msgBody = buildRequestBody(value);
|
||||
if (done) {
|
||||
setValue('');
|
||||
sendMessage({
|
||||
message: msgBody,
|
||||
exploreSessionId,
|
||||
});
|
||||
}
|
||||
addNewestOneQuestion({ ...msgBody, files: fileList });
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 100);
|
||||
},
|
||||
[
|
||||
value,
|
||||
done,
|
||||
addNewestOneQuestion,
|
||||
fileList,
|
||||
setValue,
|
||||
sendMessage,
|
||||
scrollToBottom,
|
||||
],
|
||||
);
|
||||
|
||||
const sendedTaskMessage = useRef<boolean>(false);
|
||||
|
||||
@ -471,5 +478,7 @@ export const useSendAgentMessage = ({
|
||||
addNewestOneAnswer,
|
||||
sendMessage,
|
||||
removeFile,
|
||||
setDerivedMessages,
|
||||
addPrologue,
|
||||
};
|
||||
};
|
||||
|
||||
38
web/src/pages/agent/explore/components/canvas-card.tsx
Normal file
38
web/src/pages/agent/explore/components/canvas-card.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface CanvasCardProps {
|
||||
canvas: IFlow;
|
||||
selected?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function CanvasCard({ canvas, selected, onClick }: CanvasCardProps) {
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'cursor-pointer hover:shadow-md transition-shadow',
|
||||
selected && 'bg-bg-card',
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-3 flex gap-2 items-start">
|
||||
<RAGFlowAvatar
|
||||
className="w-[32px] h-[32px] flex-shrink-0"
|
||||
avatar={canvas.avatar}
|
||||
name={canvas.title}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-base font-bold truncate">{canvas.title}</div>
|
||||
{canvas.description && (
|
||||
<div className="text-sm text-text-secondary truncate">
|
||||
{canvas.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
47
web/src/pages/agent/explore/components/session-card.tsx
Normal file
47
web/src/pages/agent/explore/components/session-card.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SessionDropdown } from './session-dropdown';
|
||||
|
||||
interface SessionCardProps {
|
||||
session: IAgentLogResponse & { is_new?: boolean };
|
||||
selected?: boolean;
|
||||
onClick: () => void;
|
||||
removeTemporarySession?: (sessionId: string) => void;
|
||||
}
|
||||
|
||||
export function SessionCard({
|
||||
session,
|
||||
selected,
|
||||
onClick,
|
||||
removeTemporarySession,
|
||||
}: SessionCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const isNewSession = session.is_new;
|
||||
|
||||
const displayName = isNewSession ? t('explore.newSession') : session.name;
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'cursor-pointer hover:shadow-md transition-shadow',
|
||||
selected && 'bg-bg-card',
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-3 flex justify-between items-center gap-2 group">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium truncate">{displayName}</div>
|
||||
</div>
|
||||
<SessionDropdown
|
||||
session={session}
|
||||
removeTemporarySession={removeTemporarySession}
|
||||
>
|
||||
<MoreButton />
|
||||
</SessionDropdown>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
190
web/src/pages/agent/explore/components/session-chat.tsx
Normal file
190
web/src/pages/agent/explore/components/session-chat.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import { NextMessageInput } from '@/components/message-input/next';
|
||||
import MessageItem from '@/components/next-message-item';
|
||||
import PdfSheet from '@/components/pdf-drawer';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useUploadCanvasFileWithProgress } from '@/hooks/use-agent-request';
|
||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { IMessage } from '@/interfaces/database/chat';
|
||||
import { BeginQuery } from '@/pages/agent/interface';
|
||||
import { ParameterDialog } from '@/pages/agent/share/parameter-dialog';
|
||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useExploreUrlParams } from '../hooks/use-explore-url-params';
|
||||
import { useSendSessionMessage } from '../hooks/use-send-session-message';
|
||||
|
||||
interface SessionChatProps {
|
||||
session?: IAgentLogResponse;
|
||||
}
|
||||
|
||||
export function SessionChat({ session }: SessionChatProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { sessionId, isNew } = useExploreUrlParams();
|
||||
const hasLocalMessageRef = useRef(false);
|
||||
|
||||
const sessionLoading = false;
|
||||
|
||||
const {
|
||||
value,
|
||||
derivedMessages,
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
sendLoading,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
stopOutputMessage,
|
||||
canvasInfo,
|
||||
findReferenceByMessageId,
|
||||
appendUploadResponseList,
|
||||
removeFile,
|
||||
parameterDialogVisible,
|
||||
handleParametersOk,
|
||||
beginInputs,
|
||||
shouldShowParameterDialog,
|
||||
setDerivedMessages,
|
||||
} = useSendSessionMessage();
|
||||
const hasActiveSession = Boolean(
|
||||
sessionId || isNew || hasLocalMessageRef.current,
|
||||
);
|
||||
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
// File upload
|
||||
const { uploadCanvasFile, loading: isUploading } =
|
||||
useUploadCanvasFileWithProgress();
|
||||
|
||||
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
||||
useCallback(
|
||||
async (files, options) => {
|
||||
const ret = await uploadCanvasFile({ files, options });
|
||||
appendUploadResponseList(ret.data, files);
|
||||
},
|
||||
[appendUploadResponseList, uploadCanvasFile],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
shouldShowParameterDialog();
|
||||
}, [shouldShowParameterDialog]);
|
||||
|
||||
useEffect(() => {
|
||||
hasLocalMessageRef.current = false;
|
||||
}, [sessionId, isNew]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasLocalMessageRef.current) {
|
||||
return;
|
||||
}
|
||||
if (sessionId && session?.id === sessionId && session?.message) {
|
||||
const messages = session.message;
|
||||
setDerivedMessages(messages as IMessage[]);
|
||||
}
|
||||
}, [session?.id, session?.message, sessionId, setDerivedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId && !isNew && !hasLocalMessageRef.current && !sendLoading) {
|
||||
setDerivedMessages([]);
|
||||
}
|
||||
}, [sessionId, isNew, sendLoading, setDerivedMessages]);
|
||||
|
||||
const handleSessionPressEnter = useCallback(async () => {
|
||||
if (value.trim()) {
|
||||
hasLocalMessageRef.current = true;
|
||||
}
|
||||
return handlePressEnter();
|
||||
}, [handlePressEnter, value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="flex flex-col h-full">
|
||||
{!hasActiveSession && (
|
||||
<div className="flex-1 flex items-center justify-center text-text-secondary">
|
||||
{t('explore.noSessionSelected')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasActiveSession && (
|
||||
<div
|
||||
ref={messageContainerRef}
|
||||
className="flex-1 overflow-auto min-h-0 p-5"
|
||||
>
|
||||
{sessionLoading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
Loading...
|
||||
</div>
|
||||
) : derivedMessages.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full text-text-secondary">
|
||||
No messages in this session
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full pr-5">
|
||||
{derivedMessages.map((message, i) => (
|
||||
<MessageItem
|
||||
loading={
|
||||
message.role === MessageType.Assistant &&
|
||||
sendLoading &&
|
||||
derivedMessages.length - 1 === i
|
||||
}
|
||||
key={buildMessageUuidWithRole(message)}
|
||||
item={message}
|
||||
nickname={userInfo.nickname}
|
||||
avatar={userInfo.avatar}
|
||||
avatarDialog={canvasInfo?.avatar || ''}
|
||||
reference={findReferenceByMessageId(message.id)}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
index={i}
|
||||
showLikeButton={false}
|
||||
sendLoading={sendLoading}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div ref={scrollRef} />
|
||||
</div>
|
||||
)}
|
||||
<section className="p-4">
|
||||
<NextMessageInput
|
||||
value={value}
|
||||
sendLoading={sendLoading}
|
||||
disabled={false}
|
||||
sendDisabled={sendLoading}
|
||||
isUploading={isUploading}
|
||||
onPressEnter={handleSessionPressEnter}
|
||||
onInputChange={handleInputChange}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
onUpload={handleUploadFile}
|
||||
removeFile={removeFile}
|
||||
conversationId=""
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{parameterDialogVisible && beginInputs.length > 0 && (
|
||||
<ParameterDialog
|
||||
ok={handleParametersOk}
|
||||
data={beginInputs.reduce(
|
||||
(acc, item) => {
|
||||
const { key, ...rest } = item;
|
||||
acc[key] = rest;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Omit<BeginQuery, 'key'>>,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
<PdfSheet
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
72
web/src/pages/agent/explore/components/session-dropdown.tsx
Normal file
72
web/src/pages/agent/explore/components/session-dropdown.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useDeleteAgentSession } from '@/hooks/use-agent-request';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useExploreUrlParams } from '../hooks/use-explore-url-params';
|
||||
|
||||
interface SessionDropdownProps {
|
||||
session: IAgentLogResponse & { is_new?: boolean };
|
||||
removeTemporarySession?: (sessionId: string) => void;
|
||||
}
|
||||
|
||||
export function SessionDropdown({
|
||||
children,
|
||||
session,
|
||||
removeTemporarySession,
|
||||
}: PropsWithChildren<SessionDropdownProps>) {
|
||||
const { t } = useTranslation();
|
||||
const { canvasId, setSessionId, sessionId } = useExploreUrlParams();
|
||||
const { deleteAgentSession } = useDeleteAgentSession();
|
||||
|
||||
const handleDelete: MouseEventHandler<HTMLDivElement> =
|
||||
useCallback(async () => {
|
||||
if (session.is_new && removeTemporarySession) {
|
||||
removeTemporarySession(session.id);
|
||||
} else if (canvasId) {
|
||||
const code = await deleteAgentSession({
|
||||
canvasId,
|
||||
sessionId: session.id,
|
||||
});
|
||||
if (code === 0 && sessionId === session.id) {
|
||||
setSessionId('', true);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
session.is_new,
|
||||
session.id,
|
||||
removeTemporarySession,
|
||||
canvasId,
|
||||
deleteAgentSession,
|
||||
sessionId,
|
||||
setSessionId,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<ConfirmDeleteDialog onOk={handleDelete}>
|
||||
<DropdownMenuItem
|
||||
className="text-state-error"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
68
web/src/pages/agent/explore/components/session-list.tsx
Normal file
68
web/src/pages/agent/explore/components/session-list.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { useClientSearch } from '@/hooks/use-client-search';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelectDerivedSessionList } from '../hooks/use-select-derived-session-list';
|
||||
import { SessionCard } from './session-card';
|
||||
|
||||
interface SessionListProps {
|
||||
selectedSessionId?: string;
|
||||
onSelectSession: (sessionId: string, isNew?: boolean) => void;
|
||||
}
|
||||
|
||||
export function SessionList({
|
||||
selectedSessionId,
|
||||
onSelectSession,
|
||||
}: SessionListProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { sessions, loading, addTemporarySession, removeTemporarySession } =
|
||||
useSelectDerivedSessionList();
|
||||
|
||||
const { filteredData, handleSearchChange, searchKeyword } =
|
||||
useClientSearch<IAgentLogResponse>({
|
||||
data: sessions,
|
||||
searchFields: ['name'],
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="p-5 flex flex-col h-full">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-base font-bold">{t('explore.sessions')}</h2>
|
||||
<span className="text-xs text-text-secondary">{sessions.length}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={addTemporarySession}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<SearchInput
|
||||
placeholder={t('explore.searchSessions')}
|
||||
onChange={handleSearchChange}
|
||||
value={searchKeyword}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto space-y-3">
|
||||
{filteredData.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
selected={session.id === selectedSessionId}
|
||||
onClick={() => onSelectSession(session.id, session.is_new)}
|
||||
removeTemporarySession={removeTemporarySession}
|
||||
/>
|
||||
))}
|
||||
{!loading && filteredData.length === 0 && (
|
||||
<div className="text-center text-text-secondary py-8">
|
||||
{searchKeyword
|
||||
? t('explore.noSessionsFound')
|
||||
: t('explore.noSessionsFound')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
45
web/src/pages/agent/explore/hooks/use-explore-url-params.ts
Normal file
45
web/src/pages/agent/explore/hooks/use-explore-url-params.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router';
|
||||
|
||||
export const useExploreUrlParams = () => {
|
||||
const { id: canvasId } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const sessionId = useMemo(
|
||||
() => searchParams.get('sessionId') || undefined,
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const isNew = useMemo(
|
||||
() => searchParams.get('isNew') || undefined,
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const setCanvasId = useCallback(
|
||||
(id: string) => {
|
||||
navigate(`/agent/${id}/explore`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const setSessionId = useCallback(
|
||||
(id: string, isNewParam?: boolean) => {
|
||||
const params = new URLSearchParams();
|
||||
if (id) params.set('sessionId', id);
|
||||
if (isNewParam) params.set('isNew', 'true');
|
||||
navigate(
|
||||
`/agent/${canvasId}/explore${params.toString() ? `?${params}` : ''}`,
|
||||
);
|
||||
},
|
||||
[canvasId, navigate],
|
||||
);
|
||||
|
||||
return {
|
||||
canvasId,
|
||||
sessionId,
|
||||
isNew,
|
||||
setCanvasId,
|
||||
setSessionId,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
import { useFetchSessionsByCanvasId } from '@/hooks/use-agent-request';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { generateConversationId } from '@/utils/chat';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useExploreUrlParams } from './use-explore-url-params';
|
||||
|
||||
export const useSelectDerivedSessionList = () => {
|
||||
const [list, setList] = useState<
|
||||
Array<IAgentLogResponse & { is_new?: boolean }>
|
||||
>([]);
|
||||
|
||||
const { data: sessions = [], loading } = useFetchSessionsByCanvasId();
|
||||
|
||||
const { setSessionId } = useExploreUrlParams();
|
||||
|
||||
const addTemporarySession = useCallback(() => {
|
||||
const sessionId = generateConversationId();
|
||||
const now = Date.now() / 1000;
|
||||
|
||||
const tempSession: IAgentLogResponse & { is_new?: boolean } = {
|
||||
id: sessionId,
|
||||
message: [],
|
||||
create_date: '',
|
||||
create_time: now,
|
||||
update_date: '',
|
||||
update_time: now,
|
||||
round: 0,
|
||||
thumb_up: 0,
|
||||
errors: '',
|
||||
source: '',
|
||||
user_id: '',
|
||||
dsl: '',
|
||||
reference: {},
|
||||
is_new: true,
|
||||
};
|
||||
|
||||
setList([tempSession, ...sessions]);
|
||||
|
||||
setSessionId('', true);
|
||||
}, [sessions, setSessionId]);
|
||||
|
||||
const removeTemporarySession = useCallback((sessionId: string) => {
|
||||
setList((prevList) => {
|
||||
return prevList.filter((session) => session.id !== sessionId);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Sync server data to local state
|
||||
useEffect(() => {
|
||||
setList(sessions);
|
||||
}, [sessions]);
|
||||
|
||||
return {
|
||||
sessions: list,
|
||||
loading,
|
||||
addTemporarySession,
|
||||
removeTemporarySession,
|
||||
};
|
||||
};
|
||||
162
web/src/pages/agent/explore/hooks/use-send-session-message.ts
Normal file
162
web/src/pages/agent/explore/hooks/use-send-session-message.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import sonnerMessage from '@/components/ui/message';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useCreateAgentSession,
|
||||
useFetchAgent,
|
||||
} from '@/hooks/use-agent-request';
|
||||
import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message';
|
||||
import { buildBeginInputListFromObject } from '@/pages/agent/form/begin-form/utils';
|
||||
import api from '@/utils/api';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { BeginId } from '../../constant';
|
||||
import { useExploreUrlParams } from './use-explore-url-params';
|
||||
|
||||
export const useGetBeginNodePrologue = () => {
|
||||
const { data } = useFetchAgent();
|
||||
const nodes = get(data, 'dsl.graph.nodes', []);
|
||||
|
||||
return useMemo(() => {
|
||||
const beginNode = nodes.find((node: any) => node.id === BeginId);
|
||||
const formData: Record<string, any> = get(beginNode, 'data.form', {});
|
||||
if (formData?.enablePrologue) {
|
||||
return formData?.prologue;
|
||||
}
|
||||
}, [nodes]);
|
||||
};
|
||||
|
||||
export const useSendSessionMessage = () => {
|
||||
const { setSessionId, sessionId, isNew } = useExploreUrlParams();
|
||||
|
||||
const { data: canvasInfo } = useFetchAgent();
|
||||
|
||||
const { id: canvasId } = useParams();
|
||||
|
||||
const { createAgentSession } = useCreateAgentSession();
|
||||
|
||||
const isCreatingSession = useRef(false);
|
||||
|
||||
const [beginParams, setBeginParams] = useState<any[]>([]);
|
||||
|
||||
const prologue = useGetBeginNodePrologue();
|
||||
|
||||
const {
|
||||
visible: parameterDialogVisible,
|
||||
hideModal: hideParameterDialog,
|
||||
showModal: showParameterDialog,
|
||||
} = useSetModalState();
|
||||
|
||||
const beginInputs = useMemo(() => {
|
||||
const beginNode = canvasInfo?.dsl?.graph?.nodes?.find(
|
||||
(node: any) => node.id === BeginId,
|
||||
);
|
||||
const inputs = beginNode?.data?.form?.inputs;
|
||||
return buildBeginInputListFromObject(inputs || {});
|
||||
}, [canvasInfo]);
|
||||
|
||||
const {
|
||||
setDerivedMessages,
|
||||
addPrologue,
|
||||
derivedMessages,
|
||||
handlePressEnter: handleSendPressEnter,
|
||||
value,
|
||||
...chatLogic
|
||||
} = useSendAgentMessage({
|
||||
url: api.runCanvasExplore(canvasId!),
|
||||
beginParams,
|
||||
});
|
||||
|
||||
const handleParametersOk = useCallback(
|
||||
(params: any[]) => {
|
||||
setBeginParams(params);
|
||||
hideParameterDialog();
|
||||
},
|
||||
[hideParameterDialog],
|
||||
);
|
||||
|
||||
const shouldShowParameterDialog = useCallback(() => {
|
||||
if (beginInputs.length > 0 && beginParams.length === 0) {
|
||||
showParameterDialog();
|
||||
}
|
||||
}, [beginInputs, beginParams, showParameterDialog]);
|
||||
|
||||
const handlePressEnter = useCallback(async () => {
|
||||
if (isCreatingSession.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
prologue &&
|
||||
isEmpty(sessionId) &&
|
||||
!isNew &&
|
||||
derivedMessages.length === 0
|
||||
) {
|
||||
addPrologue(prologue);
|
||||
}
|
||||
|
||||
let exploreSessionId = sessionId;
|
||||
|
||||
if (isEmpty(sessionId) && canvasId) {
|
||||
isCreatingSession.current = true;
|
||||
try {
|
||||
const sessionName = value?.trim() || 'New Session';
|
||||
const result = await createAgentSession({
|
||||
id: canvasId,
|
||||
name: sessionName,
|
||||
});
|
||||
|
||||
exploreSessionId = result.id;
|
||||
|
||||
setSessionId(result.id, false);
|
||||
|
||||
setTimeout(() => {
|
||||
isCreatingSession.current = false;
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
isCreatingSession.current = false;
|
||||
sonnerMessage.error('Failed to create session');
|
||||
console.error('Failed to create session:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return handleSendPressEnter?.({ exploreSessionId });
|
||||
}, [
|
||||
addPrologue,
|
||||
canvasId,
|
||||
createAgentSession,
|
||||
derivedMessages.length,
|
||||
handleSendPressEnter,
|
||||
isNew,
|
||||
prologue,
|
||||
sessionId,
|
||||
setSessionId,
|
||||
value,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNew && isEmpty(sessionId)) {
|
||||
setDerivedMessages([]);
|
||||
}
|
||||
}, [isNew, sessionId, setDerivedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prologue && isNew && isEmpty(sessionId)) {
|
||||
addPrologue(prologue);
|
||||
}
|
||||
}, [addPrologue, isNew, prologue, sessionId]);
|
||||
|
||||
return {
|
||||
...chatLogic,
|
||||
value,
|
||||
derivedMessages,
|
||||
handlePressEnter,
|
||||
canvasInfo,
|
||||
parameterDialogVisible,
|
||||
handleParametersOk,
|
||||
beginInputs,
|
||||
shouldShowParameterDialog,
|
||||
setDerivedMessages,
|
||||
};
|
||||
};
|
||||
75
web/src/pages/agent/explore/index.tsx
Normal file
75
web/src/pages/agent/explore/index.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchSessionManually } from '@/hooks/use-agent-request';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
import { useFetchDataOnMount } from '../hooks/use-fetch-data';
|
||||
import { SessionChat } from './components/session-chat';
|
||||
import { SessionList } from './components/session-list';
|
||||
import { useExploreUrlParams } from './hooks/use-explore-url-params';
|
||||
|
||||
export default function AgentExplore() {
|
||||
const { sessionId, setSessionId } = useExploreUrlParams();
|
||||
const { navigateToAgent } = useNavigatePage();
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams();
|
||||
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
||||
const { fetchSessionManually, data: session } = useFetchSessionManually();
|
||||
|
||||
const handleBackToAgent = useCallback(() => {
|
||||
const navigateFn = navigateToAgent(id as string);
|
||||
navigateFn();
|
||||
}, [id, navigateToAgent]);
|
||||
|
||||
const handleSessionSelect = useCallback(
|
||||
(id: string, isNew?: boolean) => {
|
||||
setSessionId(id, isNew);
|
||||
fetchSessionManually(id);
|
||||
},
|
||||
[fetchSessionManually, setSessionId],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col">
|
||||
<PageHeader>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink onClick={handleBackToAgent}>
|
||||
{t('header.flow')}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>
|
||||
{agentDetail?.title || t('explore.title')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</PageHeader>
|
||||
|
||||
<section className="flex flex-1 min-h-0">
|
||||
<div className="w-[296px] border-r min-w-0">
|
||||
<SessionList
|
||||
selectedSessionId={sessionId}
|
||||
onSelectSession={handleSessionSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<SessionChat session={session} />
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -8,3 +8,11 @@ export function useIsWebhookMode() {
|
||||
|
||||
return beginNode?.data.form?.mode === AgentDialogueMode.Webhook;
|
||||
}
|
||||
|
||||
export function useIsConversationMode() {
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
|
||||
const beginNode = getNode(BeginId);
|
||||
|
||||
return beginNode?.data.form?.mode === AgentDialogueMode.Conversational;
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import { ReactFlowProvider } from '@xyflow/react';
|
||||
import {
|
||||
ChevronDown,
|
||||
CirclePlay,
|
||||
Compass,
|
||||
History,
|
||||
LaptopMinimalCheck,
|
||||
Logs,
|
||||
@ -46,7 +47,10 @@ import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||
import { useFetchPipelineLog } from './hooks/use-fetch-pipeline-log';
|
||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
||||
import { useIsPipeline } from './hooks/use-is-pipeline';
|
||||
import { useIsWebhookMode } from './hooks/use-is-webhook';
|
||||
import {
|
||||
useIsConversationMode,
|
||||
useIsWebhookMode,
|
||||
} from './hooks/use-is-webhook';
|
||||
import { useRunDataflow } from './hooks/use-run-dataflow';
|
||||
import {
|
||||
useSaveGraph,
|
||||
@ -110,10 +114,12 @@ export default function Agent() {
|
||||
|
||||
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
||||
useShowEmbedModal();
|
||||
const { navigateToAgentLogs } = useNavigatePage();
|
||||
const { navigateToAgentLogs, navigateToAgentExplore } = useNavigatePage();
|
||||
const time = useWatchAgentChange(chatDrawerVisible);
|
||||
const isWebhookMode = useIsWebhookMode();
|
||||
|
||||
const isConversationMode = useIsConversationMode();
|
||||
|
||||
// pipeline
|
||||
|
||||
const {
|
||||
@ -257,6 +263,15 @@ export default function Agent() {
|
||||
{t('flow.log')}
|
||||
</Button>
|
||||
)}
|
||||
{isConversationMode && (
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={navigateToAgentExplore(id as string)}
|
||||
>
|
||||
<Compass />
|
||||
{t('explore.title')}
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'secondary'}>
|
||||
|
||||
@ -14,6 +14,8 @@ export enum Routes {
|
||||
Agent = '/agent',
|
||||
AgentTemplates = '/agent-templates',
|
||||
Agents = '/agents',
|
||||
Explore = '/explore',
|
||||
AgentExplore = `${Routes.Agent}/:id/explore`,
|
||||
Memories = '/memories',
|
||||
Memory = '/memory',
|
||||
MemoryMessage = '/memory-message',
|
||||
@ -249,11 +251,18 @@ const routeConfigOptions = [
|
||||
layout: false,
|
||||
Component: () => import('@/pages/agent'),
|
||||
},
|
||||
{
|
||||
path: Routes.AgentExplore,
|
||||
layout: false,
|
||||
Component: () => import('@/pages/agent/explore'),
|
||||
errorElement: <FallbackComponent />,
|
||||
},
|
||||
{
|
||||
path: Routes.AgentTemplates,
|
||||
layout: false,
|
||||
Component: () => import('@/pages/agents/agent-templates'),
|
||||
},
|
||||
|
||||
{
|
||||
path: Routes.Files,
|
||||
layout: false,
|
||||
|
||||
@ -126,6 +126,10 @@ const methods = {
|
||||
url: cancelCanvas,
|
||||
method: 'put',
|
||||
},
|
||||
createAgentSession: {
|
||||
url: fetchAgentLogs,
|
||||
method: 'put',
|
||||
},
|
||||
} as const;
|
||||
|
||||
const agentService = registerNextServer<keyof typeof methods>(methods);
|
||||
@ -140,6 +144,10 @@ export const fetchAgentLogsByCanvasId = (
|
||||
return request.get(methods.fetchAgentLogs.url(canvasId), { params: params });
|
||||
};
|
||||
|
||||
export const fetchAgentLogsById = (canvasId: string, sessionId: string) => {
|
||||
return request.get(api.fetchAgentLogsById(canvasId, sessionId));
|
||||
};
|
||||
|
||||
export const fetchPipeLineList = (params: IPipeLineListRequest) => {
|
||||
return request.get(api.listCanvas, { params: params });
|
||||
};
|
||||
@ -151,4 +159,12 @@ export const fetchWebhookTrace = (
|
||||
return request.get(api.fetchWebhookTrace(id), { params: params });
|
||||
};
|
||||
|
||||
export function createAgentSession({ id, name }: { id: string; name: string }) {
|
||||
return request.put(api.fetchAgentLogs(id), { data: { name } });
|
||||
}
|
||||
|
||||
export const deleteAgentSession = (canvasId: string, sessionId: string) => {
|
||||
return request.delete(api.fetchAgentLogsById(canvasId, sessionId));
|
||||
};
|
||||
|
||||
export default agentService;
|
||||
|
||||
@ -201,6 +201,8 @@ export default {
|
||||
uploadAgentFile: (id?: string) => `${api_host}/canvas/upload/${id}`,
|
||||
fetchAgentLogs: (canvasId: string) =>
|
||||
`${api_host}/canvas/${canvasId}/sessions`,
|
||||
fetchAgentLogsById: (canvasId: string, sessionId: string) =>
|
||||
`${api_host}/canvas/${canvasId}/sessions/${sessionId}`,
|
||||
fetchExternalAgentInputs: (canvasId: string) =>
|
||||
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
||||
prompt: `${api_host}/canvas/prompts`,
|
||||
@ -210,6 +212,11 @@ export default {
|
||||
fetchWebhookTrace: (id: string) =>
|
||||
`${ExternalApi}${api_host}/webhook_trace/${id}`,
|
||||
|
||||
// explore
|
||||
|
||||
runCanvasExplore: (canvasId: string) =>
|
||||
`${api_host}/canvas/${canvasId}/completion`,
|
||||
|
||||
// mcp server
|
||||
listMcpServer: `${api_host}/mcp_server/list`,
|
||||
getMcpServer: `${api_host}/mcp_server/detail`,
|
||||
|
||||
Reference in New Issue
Block a user