Feat: Modify the style of the agent operator form #10703 (#10821)

### What problem does this PR solve?

Feat: Modify the style of the agent operator form #10703

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-10-27 19:37:52 +08:00
committed by GitHub
parent 0089e2b30c
commit 0a78920bff
17 changed files with 204 additions and 179 deletions

View File

@ -74,7 +74,7 @@ export function Collapse({
<div>{rightContent}</div>
</section>
</CollapsibleTrigger>
<CollapsibleContent className="pt-2">{children}</CollapsibleContent>
<CollapsibleContent className="pt-5">{children}</CollapsibleContent>
</Collapsible>
);
}

View File

@ -70,7 +70,7 @@ export function LargeModelFormField({
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant={'ghost'}>
<Funnel />
<Funnel className="text-text-disabled" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>

View File

@ -140,7 +140,7 @@ export const SelectWithSearch = forwardRef<
ref={ref}
disabled={disabled}
className={cn(
'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px] [&_svg]:pointer-events-auto',
'!bg-bg-input hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px] [&_svg]:pointer-events-auto',
triggerClassName,
)}
>

View File

@ -31,7 +31,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
'flex h-8 w-full rounded-md border border-input bg-bg-base px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-8 w-full rounded-md border border-input bg-bg-input px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-text-primary',
className,
)}
ref={ref}

View File

@ -26,7 +26,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-8 w-full items-center bg-bg-card justify-between rounded-md border border-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
'flex h-8 w-full items-center bg-bg-input justify-between rounded-md border border-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
@ -291,7 +291,7 @@ export const RAGFlowSelect = forwardRef<
onReset={handleReset}
allowClear={allowClear}
ref={ref}
className={cn('bg-bg-base', triggerClassName)}
className={triggerClassName}
>
<SelectValue placeholder={placeholder}>{label}</SelectValue>
</SelectTrigger>

View File

@ -18,7 +18,7 @@ const Separator = React.forwardRef<
decorative={decorative}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
'shrink-0 bg-border-button',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}

View File

@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-50 gap-4 bg-text-title-invert rounded-lg p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
'fixed z-50 gap-4 bg-bg-base rounded-lg p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
@ -73,7 +73,7 @@ const SheetContent = React.forwardRef<
{children}
{closeIcon ? (
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary ">
<X className="h-4 w-4 " />
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
) : null}

View File

@ -1,4 +1,3 @@
import { Input } from '@/components/ui/input';
import {
Sheet,
SheetContent,
@ -10,17 +9,17 @@ import { IModalProps } from '@/interfaces/common';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { cn } from '@/lib/utils';
import { lowerFirst } from 'lodash';
import { Play, X } from 'lucide-react';
import { useMemo } from 'react';
import { BeginId, Operator } from '../constant';
import { CirclePlay, X } from 'lucide-react';
import { Operator } from '../constant';
import { AgentFormContext } from '../context';
import { RunTooltip } from '../flow-tooltip';
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
import { useIsMcp } from '../hooks/use-is-mcp';
import OperatorIcon from '../operator-icon';
import useGraphStore from '../store';
import { needsSingleStepDebugging } from '../utils';
import { FormConfigMap } from './form-config-map';
import SingleDebugSheet from './single-debug-sheet';
import { TitleInput } from './title-input';
interface IProps {
node?: RAGFlowNodeType;
@ -48,17 +47,7 @@ const FormSheet = ({
const OperatorForm = currentFormMap?.component ?? EmptyContent;
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
id: node?.id,
data: node?.data,
});
const isMcp = useMemo(() => {
return (
operatorName === Operator.Tool &&
Object.values(Operator).every((x) => x !== clickedToolId)
);
}, [clickedToolId, operatorName]);
const isMcp = useIsMcp(operatorName);
const { t } = useTranslate('flow');
@ -75,36 +64,19 @@ const FormSheet = ({
<section className="flex-col border-b py-2 px-5">
<div className="flex items-center gap-2 pb-3">
<OperatorIcon name={operatorName}></OperatorIcon>
{isMcp ? (
<div className="flex-1">MCP Config</div>
) : (
<div className="flex items-center gap-1 flex-1">
<label htmlFor="">{t('title')}</label>
{node?.id === BeginId ? (
<span>{t(BeginId)}</span>
) : (
<Input
value={name}
onBlur={handleNameBlur}
onChange={handleNameChange}
></Input>
)}
</div>
)}
<TitleInput node={node}></TitleInput>
{needsSingleStepDebugging(operatorName) && (
<RunTooltip>
<Play
className="size-5 cursor-pointer"
<CirclePlay
className="size-3.5 cursor-pointer"
onClick={showSingleDebugDrawer}
/>
</RunTooltip>
)}
<X onClick={hideModal} />
<X onClick={hideModal} className="size-3.5 cursor-pointer" />
</div>
{isMcp || (
<span>
<span className="text-text-secondary">
{t(
`${lowerFirst(operatorName === Operator.Tool ? clickedToolId : operatorName)}Description`,
)}

View File

@ -0,0 +1,61 @@
import { Input } from '@/components/ui/input';
import { RAGFlowNodeType } from '@/interfaces/database/agent';
import { PenLine } from 'lucide-react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BeginId, Operator } from '../constant';
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
import { useIsMcp } from '../hooks/use-is-mcp';
type TitleInputProps = {
node?: RAGFlowNodeType;
};
export function TitleInput({ node }: TitleInputProps) {
const { t } = useTranslation();
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
id: node?.id,
data: node?.data,
});
const operatorName: Operator = node?.data.label as Operator;
const isMcp = useIsMcp(operatorName);
const [isEditingMode, setIsEditingMode] = useState(false);
const switchIsEditingMode = useCallback(() => {
setIsEditingMode((prev) => !prev);
}, []);
const handleBlur = useCallback(() => {
handleNameBlur();
setIsEditingMode(false);
}, [handleNameBlur]);
if (isMcp) {
return <div className="flex-1 text-base">MCP Config</div>;
}
return (
<div className="flex items-center gap-1 flex-1">
{node?.id === BeginId ? (
<span>{t(BeginId)}</span>
) : isEditingMode ? (
<Input
value={name}
onBlur={handleBlur}
onChange={handleNameChange}
></Input>
) : (
<div className="flex items-center gap-2.5 text-base">
{name}
<PenLine
onClick={switchIsEditingMode}
className="size-3.5 text-text-secondary cursor-pointer"
/>
</div>
)}
</div>
);
}

View File

@ -28,18 +28,31 @@ import { useDeleteAgentNodeMCP } from './tool-popover/use-update-mcp';
import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
import { useGetAgentMCPIds, useGetAgentToolNames } from './use-get-tools';
type ToolCardProps = React.HTMLAttributes<HTMLLIElement> &
PropsWithChildren & {
isNodeTool?: boolean;
};
export function ToolCard({
children,
className,
isNodeTool = true,
...props
}: PropsWithChildren & React.HTMLAttributes<HTMLLIElement>) {
}: ToolCardProps) {
const element = useMemo(() => {
return (
<LabelCard {...props} className={cn('flex justify-between', className)}>
<LabelCard
{...props}
className={cn(
'flex justify-between ',
{ 'p-2.5 text-text-primary text-sm': !isNodeTool },
className,
)}
>
{children}
</LabelCard>
);
}, [children, className, props]);
}, [children, className, isNodeTool, props]);
if (children === Operator.Code) {
return (
@ -67,13 +80,13 @@ function ActionButton<T>({ deleteRecord, record, edit }: ActionButtonProps<T>) {
}, [deleteRecord, record]);
return (
<div className="flex items-center gap-2 text-text-secondary">
<div className="flex items-center gap-4 text-text-secondary">
<PencilLine
className="size-4 cursor-pointer"
className="size-3.5 cursor-pointer"
data-tool={record}
onClick={edit}
/>
<X className="size-4 cursor-pointer" onClick={handleDelete} />
<X className="size-3.5 cursor-pointer" onClick={handleDelete} />
</div>
);
}
@ -102,10 +115,10 @@ export function AgentTools() {
return (
<section className="space-y-2.5">
<span className="text-text-secondary">{t('flow.tools')}</span>
<ul className="space-y-2">
<span className="text-text-secondary text-sm">{t('flow.tools')}</span>
<ul className="space-y-2.5">
{toolNames.map((x) => (
<ToolCard key={x}>
<ToolCard key={x} isNodeTool={false}>
<div className="flex gap-2 items-center">
<OperatorIcon name={x as Operator}></OperatorIcon>
{x}
@ -118,7 +131,7 @@ export function AgentTools() {
</ToolCard>
))}
{mcpIds.map((id) => (
<ToolCard key={id}>
<ToolCard key={id} isNodeTool={false}>
{findMcpById(id)?.name}
<ActionButton
record={id}
@ -156,13 +169,13 @@ export function Agents({ node }: INextOperatorForm) {
return (
<section className="space-y-2.5">
<span className="text-text-secondary">{t('flow.agent')}</span>
<ul className="space-y-2">
<span className="text-text-secondary text-sm">{t('flow.agent')}</span>
<ul className="space-y-2.5">
{subBottomAgentNodeIds.map((id) => {
const currentNode = getNode(id);
return (
<ToolCard key={id}>
<ToolCard key={id} isNodeTool={false}>
{currentNode?.data.name}
<ActionButton
record={id}

View File

@ -1,5 +1,4 @@
import { Collapse } from '@/components/collapse';
import { FormContainer } from '@/components/form-container';
import {
LargeModelFilterFormSchema,
LargeModelFormField,
@ -15,6 +14,7 @@ import {
FormLabel,
} from '@/components/ui/form';
import { Input, NumberInput } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch';
import { LlmModelType } from '@/constants/knowledge';
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
@ -124,7 +124,6 @@ function AgentForm({ node }: INextOperatorForm) {
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
{isSubAgent && <DescriptionField></DescriptionField>}
<LargeModelFormField showSpeech2TextModel></LargeModelFormField>
{findLlmByUuid(llmId)?.model_type === LlmModelType.Image2text && (
@ -134,9 +133,6 @@ function AgentForm({ node }: INextOperatorForm) {
type={VariableType.File}
></QueryVariable>
)}
</FormContainer>
<FormContainer>
<FormField
control={form.control}
name={`sys_prompt`}
@ -154,10 +150,7 @@ function AgentForm({ node }: INextOperatorForm) {
</FormItem>
)}
/>
</FormContainer>
{isSubAgent || (
<FormContainer>
{/* <DynamicPrompt></DynamicPrompt> */}
<FormField
control={form.control}
name={`prompts`}
@ -166,24 +159,18 @@ function AgentForm({ node }: INextOperatorForm) {
<FormLabel>{t('flow.userPrompt')}</FormLabel>
<FormControl>
<section>
<PromptEditor
{...field}
showToolbar={true}
></PromptEditor>
<PromptEditor {...field} showToolbar={true}></PromptEditor>
</section>
</FormControl>
</FormItem>
)}
/>
</FormContainer>
)}
<FormContainer>
<Separator></Separator>
<AgentTools></AgentTools>
<Agents node={node}></Agents>
</FormContainer>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<section className="space-y-5">
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
<FormField
control={form.control}
@ -270,7 +257,7 @@ function AgentForm({ node }: INextOperatorForm) {
)}
/>
)}
</FormContainer>
</section>
</Collapse>
<Output list={outputList}></Output>
</FormWrapper>

View File

@ -147,10 +147,9 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
});
return (
<div className="w-full">
<div className="rounded-md border">
<div className="rounded-md border w-full bg-bg-card">
<Table rootClassName="rounded-md">
<TableHeader>
<TableHeader className="bg-bg-card">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
@ -180,10 +179,7 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
key={cell.id}
className={cn(cell.column.columnDef.meta?.cellClassName)}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
@ -194,6 +190,5 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
</TableBody>
</Table>
</div>
</div>
);
}

View File

@ -1,5 +1,4 @@
.typeahead-popover {
background: #fff;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
border-radius: 8px;
position: fixed;
@ -10,16 +9,6 @@
list-style: none;
margin: 0;
max-height: 200px;
overflow-y: scroll;
}
.typeahead-popover ul::-webkit-scrollbar {
display: none;
}
.typeahead-popover ul {
-ms-overflow-style: none;
scrollbar-width: none;
}
.typeahead-popover ul li {
@ -28,7 +17,6 @@
font-size: 14px;
outline: none;
cursor: pointer;
border-radius: 8px;
}
.typeahead-popover ul li.selected {
@ -37,7 +25,6 @@
.typeahead-popover li {
margin: 0 8px 0 8px;
color: #050505;
cursor: pointer;
line-height: 16px;
font-size: 15px;
@ -45,7 +32,6 @@
align-content: center;
flex-direction: row;
flex-shrink: 0;
background-color: #fff;
border: 0;
}

View File

@ -89,7 +89,7 @@ function PromptContent({
return (
<section
className={cn('border rounded-sm ', { 'border-blue-400': !isBlur })}
className={cn('border rounded-sm ', { 'border-accent-primary': !isBlur })}
>
{showToolbar && (
<div className="border-b px-2 py-2 justify-end flex">
@ -107,7 +107,7 @@ function PromptContent({
)}
<ContentEditable
className={cn(
'relative px-2 py-1 focus-visible:outline-none max-h-[50vh] overflow-auto',
'relative px-2 py-1 focus-visible:outline-none max-h-[50vh] overflow-auto text-sm',
{
'min-h-40': multiLine,
},
@ -163,7 +163,7 @@ export function PromptEditor({
placeholder={
<div
className={cn(
'absolute top-1 left-2 text-text-secondary pointer-events-none',
'absolute top-1 left-2 text-text-disabled pointer-events-none',
{
'truncate w-[90%]': !multiLine,
'translate-y-10': multiLine,

View File

@ -49,7 +49,7 @@ export class VariableNode extends DecoratorNode<ReactNode> {
decorate(): ReactNode {
let content: ReactNode = (
<div className="text-blue-600">{this.__label}</div>
<div className="text-accent-primary">{this.__label}</div>
);
if (this.__parentLabel) {
content = (
@ -62,7 +62,7 @@ export class VariableNode extends DecoratorNode<ReactNode> {
);
}
return (
<div className="bg-gray-200 dark:bg-gray-400 text-sm inline-flex items-center rounded-md px-2 py-1">
<div className="bg-accent-primary-5 text-sm inline-flex items-center rounded-md px-2 py-1">
{content}
</div>
);

View File

@ -91,13 +91,13 @@ function VariablePickerMenuItem({
id={'typeahead-item-' + index}
>
<div>
<span className="text text-slate-500">{option.title}</span>
<span className="text text-text-secondary">{option.title}</span>
<ul className="pl-2 py-1">
{option.options.map((x) => (
<li
key={x.value}
onClick={() => selectOptionAndCleanUp(x)}
className="hover:bg-slate-300 p-1"
className="hover:bg-bg-card p-1 text-text-primary rounded-sm"
>
{x.label}
</li>
@ -335,8 +335,8 @@ export default function VariablePickerMenuPlugin({
const nextOptions = buildNextOptions();
return anchorElementRef.current && nextOptions.length
? ReactDOM.createPortal(
<div className="typeahead-popover w-[200px] p-2">
<ul className="overflow-y-auto !scrollbar-thin overflow-x-hidden">
<div className="typeahead-popover w-[200px] p-2 bg-bg-base">
<ul className="scroll-auto overflow-x-hidden">
{nextOptions.map((option, i: number) => (
<VariablePickerMenuItem
index={i}

View File

@ -0,0 +1,11 @@
import { Operator } from '../constant';
import useGraphStore from '../store';
export function useIsMcp(operatorName: Operator) {
const clickedToolId = useGraphStore((state) => state.clickedToolId);
return (
operatorName === Operator.Tool &&
Object.values(Operator).every((x) => x !== clickedToolId)
);
}