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:
chanx
2025-07-31 16:09:45 +08:00
committed by GitHub
parent 3f6177b5e5
commit 26042343d8
14 changed files with 173 additions and 70 deletions

View File

@ -13,7 +13,7 @@ import {
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { NotebookPen } from 'lucide-react';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ChatSheet } from '../chat/chat-sheet';
import { AgentBackground } from '../components/background';
@ -132,6 +132,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
setCurrentMessageId,
});
const [lastSendLoading, setLastSendLoading] = useState(false);
const { handleBeforeDelete } = useBeforeDelete();
@ -152,7 +153,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
clearEventList();
}
}, [chatVisible, clearEventList]);
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
if (messageId === currentMessageId) {
setLastSendLoading(loading);
} else {
setLastSendLoading(false);
}
};
return (
<div className={styles.canvasWrapper}>
<svg
@ -243,7 +250,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
</AgentInstanceContext.Provider>
)}
{chatVisible && (
<AgentChatContext.Provider value={{ showLogSheet }}>
<AgentChatContext.Provider
value={{ showLogSheet, setLastSendLoadingFunc }}
>
<AgentChatLogContext.Provider
value={{ addEventList, setCurrentMessageId }}
>
@ -264,6 +273,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
currentEventListWithoutMessageById
}
currentMessageId={currentMessageId}
sendLoading={lastSendLoading}
></LogSheet>
)}
</div>

View File

@ -251,12 +251,12 @@ export const useSendAgentMessage = (
},
[
agentId,
sessionId,
send,
clearUploadResponseList,
inputs,
beginParams,
uploadResponseList,
sessionId,
setValue,
removeLatestMessage,
],

View File

@ -22,7 +22,7 @@ export const AgentInstanceContext = createContext<AgentInstanceContextType>(
type AgentChatContextType = Pick<
ReturnType<typeof useShowLogSheet>,
'showLogSheet'
>;
> & { setLastSendLoadingFunc: (loading: boolean, messageId: string) => void };
export const AgentChatContext = createContext<AgentChatContextType>(
{} as AgentChatContextType,

View File

@ -123,7 +123,7 @@ function BeginForm({ node }: INextOperatorForm) {
)}
/>
)}
{enablePrologue && (
{mode === AgentDialogueMode.Conversational && enablePrologue && (
<FormField
control={form.control}
name={'prologue'}
@ -175,7 +175,6 @@ function BeginForm({ node }: INextOperatorForm) {
deleteRecord={handleDeleteRecord}
></QueryTable>
</Collapse>
{visible && (
<ParameterDialog
hideModal={hideModal}

View File

@ -17,10 +17,11 @@ import {
import { Input } from '@/components/ui/input';
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { ChangeEvent, useEffect, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
@ -41,6 +42,7 @@ function ParameterForm({
otherThanCurrentQuery,
submit,
}: ModalFormProps) {
const { t } = useTranslate('flow');
const FormSchema = z.object({
type: z.string(),
key: z
@ -84,7 +86,7 @@ function ParameterForm({
<Icon
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
></Icon>
{cur}
{t(cur.toLowerCase())}
</div>
),
value: cur,
@ -116,6 +118,13 @@ function ParameterForm({
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 (
<Form {...form}>
<form
@ -144,7 +153,7 @@ function ParameterForm({
<FormItem>
<FormLabel>Key</FormLabel>
<FormControl>
<Input {...field} autoComplete="off" />
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
</FormControl>
<FormMessage />
</FormItem>

View File

@ -53,7 +53,7 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
const columns: ColumnDef<BeginQuery>[] = [
{
accessorKey: 'key',
header: 'key',
header: 'Key',
meta: { cellClassName: 'max-w-30' },
cell: ({ row }) => {
const key: string = row.getValue('key');
@ -90,7 +90,11 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
{
accessorKey: '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',

View File

@ -14,12 +14,13 @@ type LogSheetProps = IModalProps<any> &
Pick<
ReturnType<typeof useCacheChatLog>,
'currentEventListWithoutMessageById' | 'currentMessageId'
>;
> & { sendLoading: boolean };
export function LogSheet({
hideModal,
currentEventListWithoutMessageById,
currentMessageId,
sendLoading,
}: LogSheetProps) {
return (
<Sheet open onOpenChange={hideModal} modal={false}>
@ -36,6 +37,7 @@ export function LogSheet({
currentMessageId,
)}
currentMessageId={currentMessageId}
sendLoading={sendLoading}
/>
</section>
</SheetContent>

View File

@ -16,7 +16,13 @@ import { Operator } from '../constant';
import OperatorIcon from '../operator-icon';
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;
const blackList = ['add_memory', 'gen_citations'];
const filteredTools = tools.filter(
@ -32,6 +38,15 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
})
.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 (
<>
{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',
{
'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', {
' border-muted-foreground border-2 border-t-transparent animate-spin ':
idx >= filteredTools.length - 1 &&
tool.result === '...',
tool.result === '...' &&
sendLoading,
})}
></div>
</div>
@ -93,7 +111,7 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
<AccordionTrigger>
<div className="flex gap-2 items-center">
<span>
{tool.path + ' '}
{parentName(tool.path) + ' '}
{capitalizeWords(tool.tool_name, '_')}
</span>
<span className="text-text-sub-title text-xs">

View File

@ -20,6 +20,7 @@ import {
} from '@/hooks/use-send-message';
import { ITraceData } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { get } from 'lodash';
import { useCallback, useEffect, useMemo, useState } from 'react';
import JsonView from 'react18-json-view';
@ -30,7 +31,7 @@ import ToolTimelineItem from './toolTimelineItem';
type LogFlowTimelineProps = Pick<
ReturnType<typeof useCacheChatLog>,
'currentEventListWithoutMessage' | 'currentMessageId'
> & { canvasId?: string };
> & { canvasId?: string; sendLoading: boolean };
export function JsonViewer({
data,
title,
@ -67,6 +68,7 @@ export const WorkFlowTimeline = ({
currentEventListWithoutMessage,
currentMessageId,
canvasId,
sendLoading,
}: LogFlowTimelineProps) => {
// const getNode = useGraphStore((state) => state.getNode);
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
@ -79,31 +81,20 @@ export const WorkFlowTimeline = ({
useEffect(() => {
setMessageId(currentMessageId);
}, [currentMessageId, setMessageId]);
// const getNodeName = useCallback(
// (nodeId: string) => {
// if ('begin' === nodeId) return t('flow.begin');
// return getNode(nodeId)?.data.name;
// },
// [getNode],
// );
// const getNodeById = useCallback(
// (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 getNodeName = (nodeId: string) => {
if ('begin' === nodeId) return t('flow.begin');
return nodeId;
};
useEffect(() => {
setISStopFetchTrace(!sendLoading);
}, [sendLoading]);
const startedNodeList = useMemo(() => {
const finish = currentEventListWithoutMessage?.some(
(item) => item.event === MessageEventType.WorkflowFinished,
);
setISStopFetchTrace(finish);
setISStopFetchTrace(finish || !sendLoading);
const duplicateList = currentEventListWithoutMessage?.filter(
(x) => x.event === MessageEventType.NodeStarted,
) as INodeEvent[];
@ -115,7 +106,7 @@ export const WorkFlowTimeline = ({
}
return pre;
}, []);
}, [currentEventListWithoutMessage]);
}, [currentEventListWithoutMessage, sendLoading]);
const hasTrace = useCallback(
(componentId: string) => {
@ -198,7 +189,8 @@ export const WorkFlowTimeline = ({
<div
className={cn('rounded-full w-6 h-6', {
' 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>
@ -212,7 +204,7 @@ export const WorkFlowTimeline = ({
</TimelineIndicator>
</TimelineHeader>
<TimelineContent className="text-foreground rounded-lg border mb-5">
<section key={idx}>
<section key={'content_' + idx}>
<Accordion
type="single"
collapsible
@ -221,7 +213,7 @@ export const WorkFlowTimeline = ({
<AccordionItem value={idx.toString()}>
<AccordionTrigger>
<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">
{x.data.elapsed_time?.toString().slice(0, 6)}
</span>
@ -253,7 +245,9 @@ export const WorkFlowTimeline = ({
</TimelineItem>
{hasTrace(x.data.component_id) && (
<ToolTimelineItem
key={'tool_' + idx}
tools={filterTrace(x.data.component_id)}
sendLoading={sendLoading}
></ToolTimelineItem>
)}
</>