mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
Feature:Add a loading status to the agent canvas page. (#11733)
### What problem does this PR solve? Feature:Add a loading status to the agent canvas page. ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import './css/cloud9_night.less';
|
import './css/cloud9_night.less';
|
||||||
import './css/index.less';
|
import './css/index.less';
|
||||||
import { JsonEditorOptions, JsonEditorProps } from './interface';
|
import { JsonEditorOptions, JsonEditorProps } from './interface';
|
||||||
|
|
||||||
const defaultConfig: JsonEditorOptions = {
|
const defaultConfig: JsonEditorOptions = {
|
||||||
mode: 'code',
|
mode: 'code',
|
||||||
modes: ['tree', 'code'],
|
modes: ['tree', 'code'],
|
||||||
@ -14,6 +15,7 @@ const defaultConfig: JsonEditorOptions = {
|
|||||||
enableTransform: false,
|
enableTransform: false,
|
||||||
indentation: 2,
|
indentation: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const JsonEditor: React.FC<JsonEditorProps> = ({
|
const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
@ -25,43 +27,62 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const currentLanguageRef = useRef<string>(i18n.language);
|
const currentLanguageRef = useRef<string>(i18n.language);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
let isMounted = true;
|
||||||
const JSONEditor = require('jsoneditor');
|
|
||||||
import('jsoneditor/dist/jsoneditor.min.css');
|
|
||||||
|
|
||||||
if (containerRef.current) {
|
const initEditor = async () => {
|
||||||
// Default configuration options
|
if (typeof window !== 'undefined') {
|
||||||
const defaultOptions: JsonEditorOptions = {
|
try {
|
||||||
...defaultConfig,
|
const JSONEditorModule = await import('jsoneditor');
|
||||||
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
const JSONEditor = JSONEditorModule.default || JSONEditorModule;
|
||||||
onChange: () => {
|
|
||||||
if (editorRef.current && onChange) {
|
await import('jsoneditor/dist/jsoneditor.min.css');
|
||||||
try {
|
|
||||||
const updatedJson = editorRef.current.get();
|
if (isMounted && containerRef.current) {
|
||||||
onChange(updatedJson);
|
// Default configuration options
|
||||||
} catch (err) {
|
const defaultOptions: JsonEditorOptions = {
|
||||||
// Do not trigger onChange when parsing error occurs
|
...defaultConfig,
|
||||||
console.error(err);
|
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
||||||
}
|
onChange: () => {
|
||||||
|
if (editorRef.current && onChange) {
|
||||||
|
try {
|
||||||
|
const updatedJson = editorRef.current.get();
|
||||||
|
onChange(updatedJson);
|
||||||
|
} catch (err) {
|
||||||
|
// Do not trigger onChange when parsing error occurs
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...options, // Merge user provided options with defaults
|
||||||
|
};
|
||||||
|
|
||||||
|
editorRef.current = new JSONEditor(
|
||||||
|
containerRef.current,
|
||||||
|
defaultOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
editorRef.current.set(value);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
...options, // Merge user provided options with defaults
|
|
||||||
};
|
|
||||||
|
|
||||||
editorRef.current = new JSONEditor(
|
setIsLoading(false);
|
||||||
containerRef.current,
|
}
|
||||||
defaultOptions,
|
} catch (error) {
|
||||||
);
|
console.error('Failed to load jsoneditor:', error);
|
||||||
|
if (isMounted) {
|
||||||
if (value) {
|
setIsLoading(false);
|
||||||
editorRef.current.set(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
initEditor();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
if (typeof editorRef.current.destroy === 'function') {
|
if (typeof editorRef.current.destroy === 'function') {
|
||||||
editorRef.current.destroy();
|
editorRef.current.destroy();
|
||||||
@ -92,26 +113,38 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recreate the editor with new language
|
// Recreate the editor with new language
|
||||||
const JSONEditor = require('jsoneditor');
|
const initEditorWithNewLanguage = async () => {
|
||||||
|
try {
|
||||||
|
const JSONEditorModule = await import('jsoneditor');
|
||||||
|
const JSONEditor = JSONEditorModule.default || JSONEditorModule;
|
||||||
|
|
||||||
const newOptions: JsonEditorOptions = {
|
const newOptions: JsonEditorOptions = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
if (editorRef.current && onChange) {
|
if (editorRef.current && onChange) {
|
||||||
try {
|
try {
|
||||||
const updatedJson = editorRef.current.get();
|
const updatedJson = editorRef.current.get();
|
||||||
onChange(updatedJson);
|
onChange(updatedJson);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Do not trigger onChange when parsing error occurs
|
// Do not trigger onChange when parsing error occurs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...options, // Merge user provided options with defaults
|
...options, // Merge user provided options with defaults
|
||||||
|
};
|
||||||
|
|
||||||
|
editorRef.current = new JSONEditor(containerRef.current, newOptions);
|
||||||
|
editorRef.current.set(currentData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'Failed to reload jsoneditor with new language:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editorRef.current = new JSONEditor(containerRef.current, newOptions);
|
initEditorWithNewLanguage();
|
||||||
editorRef.current.set(currentData);
|
|
||||||
}
|
}
|
||||||
}, [i18n.language, value, onChange, options]);
|
}, [i18n.language, value, onChange, options]);
|
||||||
|
|
||||||
@ -135,7 +168,13 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{ height }}
|
style={{ height }}
|
||||||
className={`ace-tomorrow-night w-full border border-border-button rounded-lg overflow-hidden bg-bg-input ${className} `}
|
className={`ace-tomorrow-night w-full border border-border-button rounded-lg overflow-hidden bg-bg-input ${className} `}
|
||||||
/>
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="text-text-secondary">Loading editor...</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
6
web/src/custom.d.ts
vendored
6
web/src/custom.d.ts
vendored
@ -2,3 +2,9 @@ declare module '*.md' {
|
|||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'jsoneditor' {
|
||||||
|
const JSONEditor: any;
|
||||||
|
export default JSONEditor;
|
||||||
|
export = JSONEditor;
|
||||||
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { useDropdownManager } from './context';
|
|||||||
|
|
||||||
import { AgentBackground } from '@/components/canvas/background';
|
import { AgentBackground } from '@/components/canvas/background';
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
|
import { useNodeLoading } from '../hooks/use-node-loading';
|
||||||
import {
|
import {
|
||||||
useHideFormSheetOnNodeDeletion,
|
useHideFormSheetOnNodeDeletion,
|
||||||
useShowDrawer,
|
useShowDrawer,
|
||||||
@ -166,6 +167,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
});
|
});
|
||||||
const [lastSendLoading, setLastSendLoading] = useState(false);
|
const [lastSendLoading, setLastSendLoading] = useState(false);
|
||||||
|
|
||||||
|
const [currentSendLoading, setCurrentSendLoading] = useState(false);
|
||||||
|
|
||||||
const { handleBeforeDelete } = useBeforeDelete();
|
const { handleBeforeDelete } = useBeforeDelete();
|
||||||
|
|
||||||
const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance);
|
const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance);
|
||||||
@ -182,6 +185,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
}, [chatVisible, clearEventList, currentTaskId, stopMessage]);
|
}, [chatVisible, clearEventList, currentTaskId, stopMessage]);
|
||||||
|
|
||||||
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
|
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
|
||||||
|
setCurrentSendLoading(!!loading);
|
||||||
if (messageId === currentMessageId) {
|
if (messageId === currentMessageId) {
|
||||||
setLastSendLoading(loading);
|
setLastSendLoading(loading);
|
||||||
} else {
|
} else {
|
||||||
@ -249,7 +253,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
clearActiveDropdown,
|
clearActiveDropdown,
|
||||||
removePlaceholderNode,
|
removePlaceholderNode,
|
||||||
]);
|
]);
|
||||||
|
const { lastNode, setDerivedMessages, startButNotFinishedNodeIds } =
|
||||||
|
useNodeLoading({
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className={cn(styles.canvasWrapper, 'px-5 pb-5')}>
|
<div className={cn(styles.canvasWrapper, 'px-5 pb-5')}>
|
||||||
<svg
|
<svg
|
||||||
@ -285,7 +292,15 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<AgentInstanceContext.Provider value={{ addCanvasNode, showFormDrawer }}>
|
<AgentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
addCanvasNode,
|
||||||
|
showFormDrawer,
|
||||||
|
lastNode,
|
||||||
|
currentSendLoading,
|
||||||
|
startButNotFinishedNodeIds,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
connectionMode={ConnectionMode.Loose}
|
connectionMode={ConnectionMode.Loose}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@ -380,9 +395,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
></FormSheet>
|
></FormSheet>
|
||||||
</AgentInstanceContext.Provider>
|
</AgentInstanceContext.Provider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chatVisible && (
|
{chatVisible && (
|
||||||
<AgentChatContext.Provider
|
<AgentChatContext.Provider
|
||||||
value={{ showLogSheet, setLastSendLoadingFunc }}
|
value={{ showLogSheet, setLastSendLoadingFunc, setDerivedMessages }}
|
||||||
>
|
>
|
||||||
<AgentChatLogContext.Provider
|
<AgentChatLogContext.Provider
|
||||||
value={{ addEventList, setCurrentMessageId }}
|
value={{ addEventList, setCurrentMessageId }}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ function InnerAgentNode({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
{isHeadAgent && (
|
{isHeadAgent && (
|
||||||
<>
|
<>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ function InnerBeginNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
|||||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export function InnerCategorizeNode({
|
|||||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export function ExitLoopNode({ id, data, selected }: NodeProps<BaseNode<any>>) {
|
|||||||
showRun={false}
|
showRun={false}
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ function InnerFileNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
|||||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ function InnerRagNode({
|
|||||||
showRun={needsSingleStepDebugging(data.label)}
|
showRun={needsSingleStepDebugging(data.label)}
|
||||||
showCopy={showCopyIcon(data.label)}
|
showCopy={showCopyIcon(data.label)}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ function InnerMessageNode({ id, data, selected }: NodeProps<IMessageNode>) {
|
|||||||
const messages: string[] = get(data, 'form.content', []);
|
const messages: string[] = get(data, 'form.content', []);
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader
|
<NodeHeader
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { HTMLAttributes } from 'react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { HTMLAttributes, useContext } from 'react';
|
||||||
|
import { AgentInstanceContext } from '../../context';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & { selected?: boolean };
|
type IProps = HTMLAttributes<HTMLDivElement> & { selected?: boolean };
|
||||||
|
|
||||||
export function NodeWrapper({ children, className, selected }: IProps) {
|
export function NodeWrapper({ children, className, selected, id }: IProps) {
|
||||||
|
const { currentSendLoading, startButNotFinishedNodeIds = [] } =
|
||||||
|
useContext(AgentInstanceContext);
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -12,6 +16,13 @@ export function NodeWrapper({ children, className, selected }: IProps) {
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{id &&
|
||||||
|
startButNotFinishedNodeIds.indexOf(id as string) > -1 &&
|
||||||
|
currentSendLoading && (
|
||||||
|
<div className=" absolute right-0 left-0 top-0 flex items-start justify-end p-2">
|
||||||
|
<Loader size={12} className=" animate-spin" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -19,7 +19,7 @@ function ParserNode({
|
|||||||
}: NodeProps<BaseNode<ParserFormSchemaType>>) {
|
}: NodeProps<BaseNode<ParserFormSchemaType>>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|||||||
@ -27,7 +27,7 @@ function InnerRetrievalNode({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.Start}
|
id={NodeHandleId.Start}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ function InnerSplitterNode({
|
|||||||
showCopy={false}
|
showCopy={false}
|
||||||
showRun={false}
|
showRun={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|||||||
@ -65,7 +65,7 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
|||||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
<section className="gap-2.5 flex flex-col">
|
<section className="gap-2.5 flex flex-col">
|
||||||
|
|||||||
@ -27,7 +27,7 @@ function TokenizerNode({
|
|||||||
showRun={false}
|
showRun={false}
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|||||||
@ -44,7 +44,7 @@ function InnerToolNode({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<Handle
|
<Handle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|||||||
@ -13,8 +13,9 @@ import {
|
|||||||
} from '@/hooks/use-agent-request';
|
} from '@/hooks/use-agent-request';
|
||||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback, useContext } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
|
import { AgentChatContext } from '../context';
|
||||||
import DebugContent from '../debug-content';
|
import DebugContent from '../debug-content';
|
||||||
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
||||||
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
||||||
@ -49,6 +50,9 @@ function AgentChatBox() {
|
|||||||
canvasId: canvasId as string,
|
canvasId: canvasId as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { setDerivedMessages } = useContext(AgentChatContext);
|
||||||
|
setDerivedMessages?.(derivedMessages);
|
||||||
|
|
||||||
const isTaskMode = useIsTaskMode();
|
const isTaskMode = useIsTaskMode();
|
||||||
|
|
||||||
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import { INodeEvent } from '@/hooks/use-send-message';
|
||||||
|
import { IMessage } from '@/interfaces/database/chat';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { HandleType, Position } from '@xyflow/react';
|
import { HandleType, Position } from '@xyflow/react';
|
||||||
import { createContext } from 'react';
|
import { Dispatch, SetStateAction, createContext } from 'react';
|
||||||
import { useAddNode } from './hooks/use-add-node';
|
import { useAddNode } from './hooks/use-add-node';
|
||||||
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
||||||
import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer';
|
import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer';
|
||||||
@ -13,7 +15,11 @@ type AgentInstanceContextType = Pick<
|
|||||||
ReturnType<typeof useAddNode>,
|
ReturnType<typeof useAddNode>,
|
||||||
'addCanvasNode'
|
'addCanvasNode'
|
||||||
> &
|
> &
|
||||||
Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'>;
|
Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'> & {
|
||||||
|
lastNode: INodeEvent | null;
|
||||||
|
currentSendLoading: boolean;
|
||||||
|
startButNotFinishedNodeIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
||||||
{} as AgentInstanceContextType,
|
{} as AgentInstanceContextType,
|
||||||
@ -22,7 +28,10 @@ export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
|||||||
type AgentChatContextType = Pick<
|
type AgentChatContextType = Pick<
|
||||||
ReturnType<typeof useShowLogSheet>,
|
ReturnType<typeof useShowLogSheet>,
|
||||||
'showLogSheet'
|
'showLogSheet'
|
||||||
> & { setLastSendLoadingFunc: (loading: boolean, messageId: string) => void };
|
> & {
|
||||||
|
setLastSendLoadingFunc: (loading: boolean, messageId: string) => void;
|
||||||
|
setDerivedMessages: Dispatch<SetStateAction<IMessage[] | undefined>>;
|
||||||
|
};
|
||||||
|
|
||||||
export const AgentChatContext = createContext<AgentChatContextType>(
|
export const AgentChatContext = createContext<AgentChatContextType>(
|
||||||
{} as AgentChatContextType,
|
{} as AgentChatContextType,
|
||||||
|
|||||||
@ -55,7 +55,7 @@ const FormSheet = ({
|
|||||||
<Sheet open={visible} modal={false}>
|
<Sheet open={visible} modal={false}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
className={cn('top-20 p-0 flex flex-col pb-20', {
|
className={cn('top-20 p-0 flex flex-col pb-20', {
|
||||||
'right-[620px]': chatVisible,
|
'right-[clamp(0px,34%,620px)]': chatVisible,
|
||||||
})}
|
})}
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
>
|
>
|
||||||
|
|||||||
88
web/src/pages/agent/hooks/use-node-loading.ts
Normal file
88
web/src/pages/agent/hooks/use-node-loading.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
INodeData,
|
||||||
|
INodeEvent,
|
||||||
|
MessageEventType,
|
||||||
|
} from '@/hooks/use-send-message';
|
||||||
|
import { IMessage } from '@/interfaces/database/chat';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export const useNodeLoading = ({
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
}: {
|
||||||
|
currentEventListWithoutMessageById: (messageId: string) => INodeEvent[];
|
||||||
|
}) => {
|
||||||
|
const [derivedMessages, setDerivedMessages] = useState<IMessage[]>();
|
||||||
|
|
||||||
|
const lastMessageId = useMemo(() => {
|
||||||
|
return derivedMessages?.[derivedMessages?.length - 1]?.id;
|
||||||
|
}, [derivedMessages]);
|
||||||
|
|
||||||
|
const currentEventListWithoutMessage = useMemo(() => {
|
||||||
|
if (!lastMessageId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return currentEventListWithoutMessageById(lastMessageId);
|
||||||
|
}, [currentEventListWithoutMessageById, lastMessageId]);
|
||||||
|
|
||||||
|
const startedNodeList = useMemo(() => {
|
||||||
|
const duplicateList = currentEventListWithoutMessage?.filter(
|
||||||
|
(x) => x.event === MessageEventType.NodeStarted,
|
||||||
|
) as INodeEvent[];
|
||||||
|
|
||||||
|
// Remove duplicate nodes
|
||||||
|
return duplicateList?.reduce<Array<INodeEvent>>((pre, cur) => {
|
||||||
|
if (pre.every((x) => x.data.component_id !== cur.data.component_id)) {
|
||||||
|
pre.push(cur);
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
}, [currentEventListWithoutMessage]);
|
||||||
|
|
||||||
|
const filterFinishedNodeList = useCallback(() => {
|
||||||
|
const nodeEventList = currentEventListWithoutMessage
|
||||||
|
.filter(
|
||||||
|
(x) => x.event === MessageEventType.NodeFinished,
|
||||||
|
// x.event === MessageEventType.NodeFinished &&
|
||||||
|
// (x.data as INodeData)?.component_id === componentId,
|
||||||
|
)
|
||||||
|
.map((x) => x.data);
|
||||||
|
|
||||||
|
return nodeEventList;
|
||||||
|
}, [currentEventListWithoutMessage]);
|
||||||
|
|
||||||
|
const lastNode = useMemo(() => {
|
||||||
|
if (!startedNodeList) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return startedNodeList[startedNodeList.length - 1];
|
||||||
|
}, [startedNodeList]);
|
||||||
|
|
||||||
|
const startNodeIds = useMemo(() => {
|
||||||
|
if (!startedNodeList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return startedNodeList.map((x) => x.data.component_id);
|
||||||
|
}, [startedNodeList]);
|
||||||
|
|
||||||
|
const finishNodeIds = useMemo(() => {
|
||||||
|
if (!lastNode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nodeDataList = filterFinishedNodeList();
|
||||||
|
const finishNodeIdsTemp = nodeDataList.map(
|
||||||
|
(x: INodeData) => x.component_id,
|
||||||
|
);
|
||||||
|
return Array.from(new Set(finishNodeIdsTemp));
|
||||||
|
}, [lastNode, filterFinishedNodeList]);
|
||||||
|
|
||||||
|
const startButNotFinishedNodeIds = useMemo(() => {
|
||||||
|
return startNodeIds.filter((x) => !finishNodeIds.includes(x));
|
||||||
|
}, [finishNodeIds, startNodeIds]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastNode,
|
||||||
|
startButNotFinishedNodeIds,
|
||||||
|
filterFinishedNodeList,
|
||||||
|
setDerivedMessages,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -26,7 +26,7 @@ export function LogSheet({
|
|||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
className={cn('top-20 right-[620px]')}
|
className={cn('top-20 right-[clamp(0px,34%,620px)]')}
|
||||||
onInteractOutside={(e) => e.preventDefault()}
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user