Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions web/src/components/dynamic-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,19 @@ interface DynamicFormProps<T extends FieldValues> {
className?: string;
children?: React.ReactNode;
defaultValues?: DefaultValues<T>;
onFieldUpdate?: (
fieldName: string,
updatedField: Partial<FormFieldConfig>,
) => void;
}

// Form ref interface
export interface DynamicFormRef {
submit: () => void;
getValues: () => any;
reset: (values?: any) => void;
watch: (field: string, callback: (value: any) => void) => () => void;
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
}

// Generate Zod validation schema based on field configurations
Expand Down Expand Up @@ -277,6 +283,7 @@ const DynamicForm = {
className = '',
children,
defaultValues: formDefaultValues = {} as DefaultValues<T>,
onFieldUpdate,
}: DynamicFormProps<T>,
ref: React.Ref<any>,
) => {
Expand Down Expand Up @@ -311,6 +318,29 @@ const DynamicForm = {
setError: form.setError,
clearErrors: form.clearErrors,
trigger: form.trigger,
watch: (field: string, callback: (value: any) => void) => {
const { unsubscribe } = form.watch((values: any) => {
if (values && values[field] !== undefined) {
callback(values[field]);
}
});
return unsubscribe;
},

onFieldUpdate: (
fieldName: string,
updatedField: Partial<FormFieldConfig>,
) => {
setTimeout(() => {
if (onFieldUpdate) {
onFieldUpdate(fieldName, updatedField);
} else {
console.warn(
'onFieldUpdate prop is not provided. Cannot update field type.',
);
}
}, 0);
},
}));

useEffect(() => {
Expand Down
8 changes: 8 additions & 0 deletions web/src/interfaces/database/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface DSL {
messages?: Message[];
reference?: IReference[];
globals: Record<string, any>;
variables: Record<string, GobalVariableType>;
retrieval: IReference[];
}

Expand Down Expand Up @@ -283,3 +284,10 @@ export interface IPipeLineListRequest {
desc?: boolean;
canvas_category?: AgentCategory;
}

export interface GobalVariableType {
name: string;
value: any;
description: string;
type: string;
}
5 changes: 5 additions & 0 deletions web/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,11 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
pleaseUploadAtLeastOneFile: 'Please upload at least one file',
},
flow: {
variableNameMessage:
'Variable name can only contain letters and underscores',
variableDescription: 'Variable Description',
defaultValue: 'Default Value',
gobalVariable: 'Global Variable',
recommended: 'Recommended',
customerSupport: 'Customer Support',
marketing: 'Marketing',
Expand Down
4 changes: 4 additions & 0 deletions web/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,10 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
},
flow: {
variableNameMessage: '名称只能包含字母和下划线',
variableDescription: '变量的描述',
defaultValue: '默认值',
gobalVariable: '全局变量',
recommended: '推荐',
customerSupport: '客户支持',
marketing: '营销',
Expand Down
73 changes: 73 additions & 0 deletions web/src/pages/agent/gobal-variable-sheet/contant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
import { buildSelectOptions } from '@/utils/component-util';
import { t } from 'i18next';
// const TypesWithoutArray = Object.values(JsonSchemaDataType).filter(
// (item) => item !== JsonSchemaDataType.Array,
// );
// const TypesWithArray = [
// ...TypesWithoutArray,
// ...TypesWithoutArray.map((item) => `array<${item}>`),
// ];

export enum TypesWithArray {
String = 'string',
Number = 'number',
Boolean = 'boolean',
// Object = 'object',
// ArrayString = 'array<string>',
// ArrayNumber = 'array<number>',
// ArrayBoolean = 'array<boolean>',
// ArrayObject = 'array<object>',
}

export const GobalFormFields = [
{
label: t('flow.name'),
name: 'name',
placeholder: t('common.namePlaceholder'),
required: true,
validation: {
pattern: /^[a-zA-Z_]+$/,
message: t('flow.variableNameMessage'),
},
type: FormFieldType.Text,
},
{
label: t('flow.type'),
name: 'type',
placeholder: '',
required: true,
type: FormFieldType.Select,
options: buildSelectOptions(Object.values(TypesWithArray)),
},
{
label: t('flow.defaultValue'),
name: 'value',
placeholder: '',
type: FormFieldType.Textarea,
},
{
label: t('flow.description'),
name: 'description',
placeholder: t('flow.variableDescription'),
type: 'textarea',
},
] as FormFieldConfig[];

export const GobalVariableFormDefaultValues = {
name: '',
type: TypesWithArray.String,
value: '',
description: '',
};

export const TypeMaps = {
[TypesWithArray.String]: FormFieldType.Textarea,
[TypesWithArray.Number]: FormFieldType.Number,
[TypesWithArray.Boolean]: FormFieldType.Checkbox,
// [TypesWithArray.Object]: FormFieldType.Textarea,
// [TypesWithArray.ArrayString]: FormFieldType.Textarea,
// [TypesWithArray.ArrayNumber]: FormFieldType.Textarea,
// [TypesWithArray.ArrayBoolean]: FormFieldType.Textarea,
// [TypesWithArray.ArrayObject]: FormFieldType.Textarea,
};
221 changes: 221 additions & 0 deletions web/src/pages/agent/gobal-variable-sheet/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
DynamicForm,
DynamicFormRef,
FormFieldConfig,
} from '@/components/dynamic-form';
import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
import { useSetModalState } from '@/hooks/common-hooks';
import { useFetchAgent } from '@/hooks/use-agent-request';
import { GobalVariableType } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { Trash2 } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { FieldValues } from 'react-hook-form';
import { useSaveGraph } from '../hooks/use-save-graph';
import {
GobalFormFields,
GobalVariableFormDefaultValues,
TypeMaps,
TypesWithArray,
} from './contant';

export type IGobalParamModalProps = {
data: any;
hideModal: (open: boolean) => void;
};
export const GobalParamSheet = (props: IGobalParamModalProps) => {
const { hideModal } = props;
const { data, refetch } = useFetchAgent();
const [fields, setFields] = useState<FormFieldConfig[]>(GobalFormFields);
const { visible, showModal, hideModal: hideAddModal } = useSetModalState();
const [defaultValues, setDefaultValues] = useState<FieldValues>(
GobalVariableFormDefaultValues,
);
const formRef = useRef<DynamicFormRef>(null);

const handleFieldUpdate = (
fieldName: string,
updatedField: Partial<FormFieldConfig>,
) => {
setFields((prevFields) =>
prevFields.map((field) =>
field.name === fieldName ? { ...field, ...updatedField } : field,
),
);
};

useEffect(() => {
const typefileld = fields.find((item) => item.name === 'type');

if (typefileld) {
typefileld.onChange = (value) => {
// setWatchType(value);
handleFieldUpdate('value', {
type: TypeMaps[value as keyof typeof TypeMaps],
});
const values = formRef.current?.getValues();
setTimeout(() => {
switch (value) {
case TypesWithArray.Boolean:
setDefaultValues({ ...values, value: false });
break;
case TypesWithArray.Number:
setDefaultValues({ ...values, value: 0 });
break;
default:
setDefaultValues({ ...values, value: '' });
}
}, 0);
};
}
}, [fields]);

const { saveGraph, loading } = useSaveGraph();

const handleSubmit = (value: FieldValues) => {
const param = {
...(data.dsl?.variables || {}),
[value.name]: value,
} as Record<string, GobalVariableType>;
saveGraph(undefined, {
gobalVariables: param,
});
if (!loading) {
setTimeout(() => {
refetch();
}, 500);
}
hideAddModal();
};

const handleDeleteGobalVariable = (key: string) => {
const param = {
...(data.dsl?.variables || {}),
} as Record<string, GobalVariableType>;
delete param[key];
saveGraph(undefined, {
gobalVariables: param,
});
refetch();
};

const handleEditGobalVariable = (item: FieldValues) => {
setDefaultValues(item);
showModal();
};
return (
<>
<Sheet open onOpenChange={hideModal} modal={false}>
<SheetContent
className={cn('top-20 h-auto flex flex-col p-0 gap-0')}
onInteractOutside={(e) => e.preventDefault()}
>
<SheetHeader className="p-5">
<SheetTitle className="flex items-center gap-2.5">
{t('flow.gobalVariable')}
</SheetTitle>
</SheetHeader>

<div className="px-5 pb-5">
<Button
variant={'secondary'}
onClick={() => {
setFields(GobalFormFields);
setDefaultValues(GobalVariableFormDefaultValues);
showModal();
}}
>
{t('flow.add')}
</Button>
</div>

<div className="flex flex-col gap-2 px-5 ">
{data?.dsl?.variables &&
Object.keys(data.dsl.variables).map((key) => {
const item = data.dsl.variables[key];
return (
<div
key={key}
className="flex items-center gap-3 min-h-14 justify-between px-5 py-3 border border-border-default rounded-lg hover:bg-bg-card group"
onClick={() => {
handleEditGobalVariable(item);
}}
>
<div className="flex flex-col">
<div className="flex items-center gap-2">
<span className=" font-medium">{item.name}</span>
<span className="text-sm font-medium text-text-secondary">
{item.type}
</span>
</div>
<div>
<span className="text-text-primary">{item.value}</span>
</div>
</div>
<div>
<ConfirmDeleteDialog
onOk={() => handleDeleteGobalVariable(key)}
>
<Button
variant={'secondary'}
className="bg-transparent hidden text-text-secondary border-none group-hover:bg-bg-card group-hover:text-text-primary group-hover:border group-hover:block"
onClick={(e) => {
e.stopPropagation();
}}
>
<Trash2 className="w-4 h-4" />
</Button>
</ConfirmDeleteDialog>
</div>
</div>
);
})}
</div>
</SheetContent>
<Modal
title={t('flow.add') + t('flow.gobalVariable')}
open={visible}
onCancel={hideAddModal}
showfooter={false}
>
<DynamicForm.Root
ref={formRef}
fields={fields}
onSubmit={(data) => {
console.log(data);
}}
defaultValues={defaultValues}
onFieldUpdate={handleFieldUpdate}
>
<div className="flex items-center justify-end w-full gap-2">
<DynamicForm.CancelButton
handleCancel={() => {
hideAddModal?.();
}}
/>
<DynamicForm.SavingButton
submitLoading={loading || false}
buttonText={t('common.ok')}
submitFunc={(values: FieldValues) => {
handleSubmit(values);
// console.log(values);
// console.log(nodes, edges);
// handleOk(values);
}}
/>
</div>
</DynamicForm.Root>
</Modal>
</Sheet>
</>
);
};
Loading