mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Fix: Improve Agent templates functionality and fix some UI style issues (#9129)
### What problem does this PR solve? Fix: Improve Agent templates functionality and fix some UI style issues #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -132,7 +132,7 @@ export function NextMessageInput({
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
placeholder="Type your message here..."
|
placeholder="Type your message here..."
|
||||||
className="field-sizing-content min-h-10 w-full resize-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 dark:bg-transparent"
|
className="field-sizing-content min-h-10 w-full resize-none border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 dark:bg-transparent"
|
||||||
disabled={isUploading || disabled}
|
disabled={isUploading || disabled || sendLoading}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between gap-1.5">
|
<div className="flex items-center justify-between gap-1.5">
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
import { INodeEvent } from '@/hooks/use-send-message';
|
import { INodeEvent } from '@/hooks/use-send-message';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { AgentChatContext } from '@/pages/agent/context';
|
||||||
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/pages/chat/interface';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
@ -74,6 +76,14 @@ function MessageItem({
|
|||||||
const { visible, hideModal, showModal } = useSetModalState();
|
const { visible, hideModal, showModal } = useSetModalState();
|
||||||
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
||||||
|
|
||||||
|
const { setLastSendLoadingFunc } = useContext(AgentChatContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof setLastSendLoadingFunc === 'function') {
|
||||||
|
setLastSendLoadingFunc(loading, item.id);
|
||||||
|
}
|
||||||
|
}, [loading, setLastSendLoadingFunc, item.id]);
|
||||||
|
|
||||||
const referenceDocuments = useMemo(() => {
|
const referenceDocuments = useMemo(() => {
|
||||||
const docs = reference?.doc_aggs ?? {};
|
const docs = reference?.doc_aggs ?? {};
|
||||||
|
|
||||||
@ -115,7 +125,6 @@ function MessageItem({
|
|||||||
) : (
|
) : (
|
||||||
<AssistantIcon />
|
<AssistantIcon />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<section className="flex-col gap-2 flex-1">
|
<section className="flex-col gap-2 flex-1">
|
||||||
<div className="space-x-1">
|
<div className="space-x-1">
|
||||||
{isAssistant ? (
|
{isAssistant ? (
|
||||||
@ -177,6 +186,7 @@ function MessageItem({
|
|||||||
)}
|
)}
|
||||||
currentMessageId={item.id}
|
currentMessageId={item.id}
|
||||||
canvasId={conversationId}
|
canvasId={conversationId}
|
||||||
|
sendLoading={loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1308,6 +1308,15 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
export: 'Export',
|
export: 'Export',
|
||||||
seconds: 'Seconds',
|
seconds: 'Seconds',
|
||||||
subject: 'Subject',
|
subject: 'Subject',
|
||||||
|
tag: 'Tag',
|
||||||
|
tagPlaceholder: 'Please enter tag',
|
||||||
|
descriptionPlaceholder: 'Please enter description',
|
||||||
|
line: 'Single-line text',
|
||||||
|
paragraph: 'Paragraph text',
|
||||||
|
options: 'Dropdown options',
|
||||||
|
file: 'File upload',
|
||||||
|
integer: 'Number',
|
||||||
|
boolean: 'Boolean',
|
||||||
},
|
},
|
||||||
llmTools: {
|
llmTools: {
|
||||||
bad_calculator: {
|
bad_calculator: {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import { NotebookPen } from 'lucide-react';
|
import { NotebookPen } from 'lucide-react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ChatSheet } from '../chat/chat-sheet';
|
import { ChatSheet } from '../chat/chat-sheet';
|
||||||
import { AgentBackground } from '../components/background';
|
import { AgentBackground } from '../components/background';
|
||||||
@ -132,6 +132,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
|
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
|
||||||
setCurrentMessageId,
|
setCurrentMessageId,
|
||||||
});
|
});
|
||||||
|
const [lastSendLoading, setLastSendLoading] = useState(false);
|
||||||
|
|
||||||
const { handleBeforeDelete } = useBeforeDelete();
|
const { handleBeforeDelete } = useBeforeDelete();
|
||||||
|
|
||||||
@ -152,7 +153,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
clearEventList();
|
clearEventList();
|
||||||
}
|
}
|
||||||
}, [chatVisible, clearEventList]);
|
}, [chatVisible, clearEventList]);
|
||||||
|
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
|
||||||
|
if (messageId === currentMessageId) {
|
||||||
|
setLastSendLoading(loading);
|
||||||
|
} else {
|
||||||
|
setLastSendLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className={styles.canvasWrapper}>
|
<div className={styles.canvasWrapper}>
|
||||||
<svg
|
<svg
|
||||||
@ -243,7 +250,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
</AgentInstanceContext.Provider>
|
</AgentInstanceContext.Provider>
|
||||||
)}
|
)}
|
||||||
{chatVisible && (
|
{chatVisible && (
|
||||||
<AgentChatContext.Provider value={{ showLogSheet }}>
|
<AgentChatContext.Provider
|
||||||
|
value={{ showLogSheet, setLastSendLoadingFunc }}
|
||||||
|
>
|
||||||
<AgentChatLogContext.Provider
|
<AgentChatLogContext.Provider
|
||||||
value={{ addEventList, setCurrentMessageId }}
|
value={{ addEventList, setCurrentMessageId }}
|
||||||
>
|
>
|
||||||
@ -264,6 +273,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
currentEventListWithoutMessageById
|
currentEventListWithoutMessageById
|
||||||
}
|
}
|
||||||
currentMessageId={currentMessageId}
|
currentMessageId={currentMessageId}
|
||||||
|
sendLoading={lastSendLoading}
|
||||||
></LogSheet>
|
></LogSheet>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -251,12 +251,12 @@ export const useSendAgentMessage = (
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
agentId,
|
agentId,
|
||||||
|
sessionId,
|
||||||
send,
|
send,
|
||||||
clearUploadResponseList,
|
clearUploadResponseList,
|
||||||
inputs,
|
inputs,
|
||||||
beginParams,
|
beginParams,
|
||||||
uploadResponseList,
|
uploadResponseList,
|
||||||
sessionId,
|
|
||||||
setValue,
|
setValue,
|
||||||
removeLatestMessage,
|
removeLatestMessage,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -22,7 +22,7 @@ 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 };
|
||||||
|
|
||||||
export const AgentChatContext = createContext<AgentChatContextType>(
|
export const AgentChatContext = createContext<AgentChatContextType>(
|
||||||
{} as AgentChatContextType,
|
{} as AgentChatContextType,
|
||||||
|
|||||||
@ -123,7 +123,7 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{enablePrologue && (
|
{mode === AgentDialogueMode.Conversational && enablePrologue && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={'prologue'}
|
name={'prologue'}
|
||||||
@ -175,7 +175,6 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
deleteRecord={handleDeleteRecord}
|
deleteRecord={handleDeleteRecord}
|
||||||
></QueryTable>
|
></QueryTable>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
{visible && (
|
{visible && (
|
||||||
<ParameterDialog
|
<ParameterDialog
|
||||||
hideModal={hideModal}
|
hideModal={hideModal}
|
||||||
|
|||||||
@ -17,10 +17,11 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
|
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { ChangeEvent, useEffect, useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -41,6 +42,7 @@ function ParameterForm({
|
|||||||
otherThanCurrentQuery,
|
otherThanCurrentQuery,
|
||||||
submit,
|
submit,
|
||||||
}: ModalFormProps) {
|
}: ModalFormProps) {
|
||||||
|
const { t } = useTranslate('flow');
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
key: z
|
key: z
|
||||||
@ -84,7 +86,7 @@ function ParameterForm({
|
|||||||
<Icon
|
<Icon
|
||||||
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
||||||
></Icon>
|
></Icon>
|
||||||
{cur}
|
{t(cur.toLowerCase())}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
value: cur,
|
value: cur,
|
||||||
@ -116,6 +118,13 @@ function ParameterForm({
|
|||||||
submit(values);
|
submit(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleKeyChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const name = form.getValues().name || '';
|
||||||
|
form.setValue('key', e.target.value.trim());
|
||||||
|
if (!name) {
|
||||||
|
form.setValue('name', e.target.value.trim());
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@ -144,7 +153,7 @@ function ParameterForm({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Key</FormLabel>
|
<FormLabel>Key</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} autoComplete="off" />
|
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
|
|||||||
const columns: ColumnDef<BeginQuery>[] = [
|
const columns: ColumnDef<BeginQuery>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'key',
|
accessorKey: 'key',
|
||||||
header: 'key',
|
header: 'Key',
|
||||||
meta: { cellClassName: 'max-w-30' },
|
meta: { cellClassName: 'max-w-30' },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const key: string = row.getValue('key');
|
const key: string = row.getValue('key');
|
||||||
@ -90,7 +90,11 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
|
|||||||
{
|
{
|
||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
header: t('flow.type'),
|
header: t('flow.type'),
|
||||||
cell: ({ row }) => <div>{row.getValue('type')}</div>,
|
cell: ({ row }) => (
|
||||||
|
<div>
|
||||||
|
{t(`flow.${(row.getValue('type')?.toString() || '').toLowerCase()}`)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'optional',
|
accessorKey: 'optional',
|
||||||
|
|||||||
@ -14,12 +14,13 @@ type LogSheetProps = IModalProps<any> &
|
|||||||
Pick<
|
Pick<
|
||||||
ReturnType<typeof useCacheChatLog>,
|
ReturnType<typeof useCacheChatLog>,
|
||||||
'currentEventListWithoutMessageById' | 'currentMessageId'
|
'currentEventListWithoutMessageById' | 'currentMessageId'
|
||||||
>;
|
> & { sendLoading: boolean };
|
||||||
|
|
||||||
export function LogSheet({
|
export function LogSheet({
|
||||||
hideModal,
|
hideModal,
|
||||||
currentEventListWithoutMessageById,
|
currentEventListWithoutMessageById,
|
||||||
currentMessageId,
|
currentMessageId,
|
||||||
|
sendLoading,
|
||||||
}: LogSheetProps) {
|
}: LogSheetProps) {
|
||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
@ -36,6 +37,7 @@ export function LogSheet({
|
|||||||
currentMessageId,
|
currentMessageId,
|
||||||
)}
|
)}
|
||||||
currentMessageId={currentMessageId}
|
currentMessageId={currentMessageId}
|
||||||
|
sendLoading={sendLoading}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
@ -16,7 +16,13 @@ import { Operator } from '../constant';
|
|||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
import { JsonViewer } from './workFlowTimeline';
|
import { JsonViewer } from './workFlowTimeline';
|
||||||
|
|
||||||
const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
|
const ToolTimelineItem = ({
|
||||||
|
tools,
|
||||||
|
sendLoading = false,
|
||||||
|
}: {
|
||||||
|
tools: Record<string, any>[];
|
||||||
|
sendLoading: boolean;
|
||||||
|
}) => {
|
||||||
if (!tools || tools.length === 0 || !Array.isArray(tools)) return null;
|
if (!tools || tools.length === 0 || !Array.isArray(tools)) return null;
|
||||||
const blackList = ['add_memory', 'gen_citations'];
|
const blackList = ['add_memory', 'gen_citations'];
|
||||||
const filteredTools = tools.filter(
|
const filteredTools = tools.filter(
|
||||||
@ -32,6 +38,15 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
|
|||||||
})
|
})
|
||||||
.join(' ');
|
.join(' ');
|
||||||
};
|
};
|
||||||
|
const parentName = (str: string, separator: string = '-->') => {
|
||||||
|
if (!str) return '';
|
||||||
|
const strs = str.split(separator);
|
||||||
|
if (strs.length > 1) {
|
||||||
|
return strs[strs.length - 1];
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{filteredTools?.map((tool, idx) => {
|
{filteredTools?.map((tool, idx) => {
|
||||||
@ -58,7 +73,9 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
|
|||||||
'group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
'group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
{
|
{
|
||||||
'border border-blue-500': !(
|
'border border-blue-500': !(
|
||||||
idx >= filteredTools.length - 1 && tool.result === '...'
|
idx >= filteredTools.length - 1 &&
|
||||||
|
tool.result === '...' &&
|
||||||
|
sendLoading
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
@ -69,7 +86,8 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
|
|||||||
className={cn('rounded-full w-6 h-6', {
|
className={cn('rounded-full w-6 h-6', {
|
||||||
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
||||||
idx >= filteredTools.length - 1 &&
|
idx >= filteredTools.length - 1 &&
|
||||||
tool.result === '...',
|
tool.result === '...' &&
|
||||||
|
sendLoading,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -93,7 +111,7 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
|
|||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<span>
|
<span>
|
||||||
{tool.path + ' '}
|
{parentName(tool.path) + ' '}
|
||||||
{capitalizeWords(tool.tool_name, '_')}
|
{capitalizeWords(tool.tool_name, '_')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-text-sub-title text-xs">
|
<span className="text-text-sub-title text-xs">
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
} from '@/hooks/use-send-message';
|
} from '@/hooks/use-send-message';
|
||||||
import { ITraceData } from '@/interfaces/database/agent';
|
import { ITraceData } from '@/interfaces/database/agent';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { t } from 'i18next';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import JsonView from 'react18-json-view';
|
import JsonView from 'react18-json-view';
|
||||||
@ -30,7 +31,7 @@ import ToolTimelineItem from './toolTimelineItem';
|
|||||||
type LogFlowTimelineProps = Pick<
|
type LogFlowTimelineProps = Pick<
|
||||||
ReturnType<typeof useCacheChatLog>,
|
ReturnType<typeof useCacheChatLog>,
|
||||||
'currentEventListWithoutMessage' | 'currentMessageId'
|
'currentEventListWithoutMessage' | 'currentMessageId'
|
||||||
> & { canvasId?: string };
|
> & { canvasId?: string; sendLoading: boolean };
|
||||||
export function JsonViewer({
|
export function JsonViewer({
|
||||||
data,
|
data,
|
||||||
title,
|
title,
|
||||||
@ -67,6 +68,7 @@ export const WorkFlowTimeline = ({
|
|||||||
currentEventListWithoutMessage,
|
currentEventListWithoutMessage,
|
||||||
currentMessageId,
|
currentMessageId,
|
||||||
canvasId,
|
canvasId,
|
||||||
|
sendLoading,
|
||||||
}: LogFlowTimelineProps) => {
|
}: LogFlowTimelineProps) => {
|
||||||
// const getNode = useGraphStore((state) => state.getNode);
|
// const getNode = useGraphStore((state) => state.getNode);
|
||||||
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
|
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
|
||||||
@ -79,31 +81,20 @@ export const WorkFlowTimeline = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMessageId(currentMessageId);
|
setMessageId(currentMessageId);
|
||||||
}, [currentMessageId, setMessageId]);
|
}, [currentMessageId, setMessageId]);
|
||||||
// const getNodeName = useCallback(
|
const getNodeName = (nodeId: string) => {
|
||||||
// (nodeId: string) => {
|
if ('begin' === nodeId) return t('flow.begin');
|
||||||
// if ('begin' === nodeId) return t('flow.begin');
|
return nodeId;
|
||||||
// return getNode(nodeId)?.data.name;
|
};
|
||||||
// },
|
|
||||||
// [getNode],
|
useEffect(() => {
|
||||||
// );
|
setISStopFetchTrace(!sendLoading);
|
||||||
// const getNodeById = useCallback(
|
}, [sendLoading]);
|
||||||
// (nodeId: string) => {
|
|
||||||
// const data = currentEventListWithoutMessage
|
|
||||||
// .map((x) => x.data)
|
|
||||||
// .filter((x) => x.component_id === nodeId);
|
|
||||||
// if ('begin' === nodeId) return t('flow.begin');
|
|
||||||
// if (data && data.length) {
|
|
||||||
// return data[0];
|
|
||||||
// }
|
|
||||||
// return {};
|
|
||||||
// },
|
|
||||||
// [currentEventListWithoutMessage],
|
|
||||||
// );
|
|
||||||
const startedNodeList = useMemo(() => {
|
const startedNodeList = useMemo(() => {
|
||||||
const finish = currentEventListWithoutMessage?.some(
|
const finish = currentEventListWithoutMessage?.some(
|
||||||
(item) => item.event === MessageEventType.WorkflowFinished,
|
(item) => item.event === MessageEventType.WorkflowFinished,
|
||||||
);
|
);
|
||||||
setISStopFetchTrace(finish);
|
setISStopFetchTrace(finish || !sendLoading);
|
||||||
const duplicateList = currentEventListWithoutMessage?.filter(
|
const duplicateList = currentEventListWithoutMessage?.filter(
|
||||||
(x) => x.event === MessageEventType.NodeStarted,
|
(x) => x.event === MessageEventType.NodeStarted,
|
||||||
) as INodeEvent[];
|
) as INodeEvent[];
|
||||||
@ -115,7 +106,7 @@ export const WorkFlowTimeline = ({
|
|||||||
}
|
}
|
||||||
return pre;
|
return pre;
|
||||||
}, []);
|
}, []);
|
||||||
}, [currentEventListWithoutMessage]);
|
}, [currentEventListWithoutMessage, sendLoading]);
|
||||||
|
|
||||||
const hasTrace = useCallback(
|
const hasTrace = useCallback(
|
||||||
(componentId: string) => {
|
(componentId: string) => {
|
||||||
@ -198,7 +189,8 @@ export const WorkFlowTimeline = ({
|
|||||||
<div
|
<div
|
||||||
className={cn('rounded-full w-6 h-6', {
|
className={cn('rounded-full w-6 h-6', {
|
||||||
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
||||||
!finishNodeIds.includes(x.data.component_id),
|
!finishNodeIds.includes(x.data.component_id) &&
|
||||||
|
sendLoading,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -212,7 +204,7 @@ export const WorkFlowTimeline = ({
|
|||||||
</TimelineIndicator>
|
</TimelineIndicator>
|
||||||
</TimelineHeader>
|
</TimelineHeader>
|
||||||
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
||||||
<section key={idx}>
|
<section key={'content_' + idx}>
|
||||||
<Accordion
|
<Accordion
|
||||||
type="single"
|
type="single"
|
||||||
collapsible
|
collapsible
|
||||||
@ -221,7 +213,7 @@ export const WorkFlowTimeline = ({
|
|||||||
<AccordionItem value={idx.toString()}>
|
<AccordionItem value={idx.toString()}>
|
||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<span>{x.data?.component_name}</span>
|
<span>{getNodeName(x.data?.component_name)}</span>
|
||||||
<span className="text-text-sub-title text-xs">
|
<span className="text-text-sub-title text-xs">
|
||||||
{x.data.elapsed_time?.toString().slice(0, 6)}
|
{x.data.elapsed_time?.toString().slice(0, 6)}
|
||||||
</span>
|
</span>
|
||||||
@ -253,7 +245,9 @@ export const WorkFlowTimeline = ({
|
|||||||
</TimelineItem>
|
</TimelineItem>
|
||||||
{hasTrace(x.data.component_id) && (
|
{hasTrace(x.data.component_id) && (
|
||||||
<ToolTimelineItem
|
<ToolTimelineItem
|
||||||
|
key={'tool_' + idx}
|
||||||
tools={filterTrace(x.data.component_id)}
|
tools={filterTrace(x.data.component_id)}
|
||||||
|
sendLoading={sendLoading}
|
||||||
></ToolTimelineItem>
|
></ToolTimelineItem>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CreateAgentDialog } from './create-agent-dialog';
|
import { CreateAgentDialog } from './create-agent-dialog';
|
||||||
import { TemplateCard } from './template-card';
|
import { TemplateCard } from './template-card';
|
||||||
import { SideBar } from './template-sidebar';
|
import { MenuItemKey, SideBar } from './template-sidebar';
|
||||||
|
|
||||||
export default function AgentTemplates() {
|
export default function AgentTemplates() {
|
||||||
const { navigateToAgentList } = useNavigatePage();
|
const { navigateToAgentList } = useNavigatePage();
|
||||||
@ -23,7 +23,9 @@ export default function AgentTemplates() {
|
|||||||
const list = useFetchAgentTemplates();
|
const list = useFetchAgentTemplates();
|
||||||
const { loading, setAgent } = useSetAgent();
|
const { loading, setAgent } = useSetAgent();
|
||||||
const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]);
|
const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]);
|
||||||
|
const [selectMenuItem, setSelectMenuItem] = useState<string>(
|
||||||
|
MenuItemKey.Recommended,
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTemplateList(list);
|
setTemplateList(list);
|
||||||
}, [list]);
|
}, [list]);
|
||||||
@ -70,10 +72,12 @@ export default function AgentTemplates() {
|
|||||||
const handleSiderBarChange = (keyword: string) => {
|
const handleSiderBarChange = (keyword: string) => {
|
||||||
const tempList = list.filter(
|
const tempList = list.filter(
|
||||||
(item, index) =>
|
(item, index) =>
|
||||||
item.title.toLocaleLowerCase().includes(keyword?.toLocaleLowerCase()) ||
|
item.canvas_type
|
||||||
index === 0,
|
?.toLocaleLowerCase()
|
||||||
|
.includes(keyword?.toLocaleLowerCase()) || index === 0,
|
||||||
);
|
);
|
||||||
setTemplateList(tempList);
|
setTemplateList(tempList);
|
||||||
|
setSelectMenuItem(keyword);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@ -93,9 +97,12 @@ export default function AgentTemplates() {
|
|||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div className="flex flex-1 h-dvh">
|
<div className="flex flex-1 h-dvh">
|
||||||
<SideBar change={handleSiderBarChange}></SideBar>
|
<SideBar
|
||||||
|
change={handleSiderBarChange}
|
||||||
|
selected={selectMenuItem}
|
||||||
|
></SideBar>
|
||||||
|
|
||||||
<main className="flex-1 bg-muted/50 h-dvh">
|
<main className="flex-1 bg-text-title-invert/50 h-dvh">
|
||||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[94vh] overflow-auto px-8 pt-8">
|
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[94vh] overflow-auto px-8 pt-8">
|
||||||
{templateList?.map((x, index) => {
|
{templateList?.map((x, index) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,44 +1,85 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useSecondPathName } from '@/hooks/route-hook';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Banknote, LayoutGrid, User } from 'lucide-react';
|
import {
|
||||||
|
Box,
|
||||||
|
ChartPie,
|
||||||
|
Component,
|
||||||
|
MessageCircleCode,
|
||||||
|
PencilRuler,
|
||||||
|
Sparkle,
|
||||||
|
} from 'lucide-react';
|
||||||
|
export enum MenuItemKey {
|
||||||
|
Recommended = 'Recommended',
|
||||||
|
Agent = 'Agent',
|
||||||
|
CustomerSupport = 'Customer Support',
|
||||||
|
Marketing = 'Marketing',
|
||||||
|
ConsumerApp = 'Consumer App',
|
||||||
|
Other = 'Other',
|
||||||
|
}
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
section: 'All Templates',
|
// section: 'All Templates',
|
||||||
|
section: '',
|
||||||
items: [
|
items: [
|
||||||
{ icon: User, label: 'Assistant', key: 'Assistant' },
|
{
|
||||||
{ icon: LayoutGrid, label: 'chatbot', key: 'chatbot' },
|
icon: Sparkle,
|
||||||
{ icon: Banknote, label: 'generator', key: 'generator' },
|
label: MenuItemKey.Recommended,
|
||||||
{ icon: Banknote, label: 'Intel', key: 'Intel' },
|
key: MenuItemKey.Recommended,
|
||||||
|
},
|
||||||
|
{ icon: Box, label: MenuItemKey.Agent, key: MenuItemKey.Agent },
|
||||||
|
{
|
||||||
|
icon: MessageCircleCode,
|
||||||
|
label: MenuItemKey.CustomerSupport,
|
||||||
|
key: MenuItemKey.CustomerSupport,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ChartPie,
|
||||||
|
label: MenuItemKey.Marketing,
|
||||||
|
key: MenuItemKey.Marketing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Component,
|
||||||
|
label: MenuItemKey.ConsumerApp,
|
||||||
|
key: MenuItemKey.ConsumerApp,
|
||||||
|
},
|
||||||
|
{ icon: PencilRuler, label: MenuItemKey.Other, key: MenuItemKey.Other },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SideBar({ change }: { change: (keyword: string) => void }) {
|
export function SideBar({
|
||||||
const pathName = useSecondPathName();
|
change,
|
||||||
|
selected = MenuItemKey.Recommended,
|
||||||
|
}: {
|
||||||
|
change: (keyword: string) => void;
|
||||||
|
selected?: string;
|
||||||
|
}) {
|
||||||
const handleMenuClick = (key: string) => {
|
const handleMenuClick = (key: string) => {
|
||||||
change(key);
|
change(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="w-[303px] bg-background border-r flex flex-col">
|
<aside className="w-[303px] bg-text-title-invert border-r flex flex-col">
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
{menuItems.map((section, idx) => (
|
{menuItems.map((section, idx) => (
|
||||||
<div key={idx}>
|
<div key={idx}>
|
||||||
|
{section.section && (
|
||||||
<h2
|
<h2
|
||||||
className="p-6 text-sm font-semibold hover:bg-muted/50 cursor-pointer"
|
className="p-6 text-sm font-semibold hover:bg-muted/50 cursor-pointer"
|
||||||
onClick={() => handleMenuClick('')}
|
onClick={() => handleMenuClick('')}
|
||||||
>
|
>
|
||||||
{section.section}
|
{section.section}
|
||||||
</h2>
|
</h2>
|
||||||
|
)}
|
||||||
{section.items.map((item, itemIdx) => {
|
{section.items.map((item, itemIdx) => {
|
||||||
const active = pathName === item.key;
|
const active = selected === item.key;
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={itemIdx}
|
key={itemIdx}
|
||||||
variant={active ? 'secondary' : 'ghost'}
|
variant={active ? 'secondary' : 'ghost'}
|
||||||
className={cn('w-full justify-start gap-2.5 p-6 relative')}
|
className={cn(
|
||||||
|
'w-full justify-start gap-4 px-6 py-8 relative rounded-none',
|
||||||
|
)}
|
||||||
onClick={() => handleMenuClick(item.key)}
|
onClick={() => handleMenuClick(item.key)}
|
||||||
>
|
>
|
||||||
<item.icon className="w-6 h-6" />
|
<item.icon className="w-6 h-6" />
|
||||||
|
|||||||
Reference in New Issue
Block a user