Fixes: Added session variable types and modified configuration (#11269)

### What problem does this PR solve?

Fixes: Added session variable types and modified configuration

- Added more types of session variables
- Modified the embedding model switching logic in the knowledge base
configuration

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-11-14 13:56:56 +08:00
committed by GitHub
parent 72c20022f6
commit 87e69868c0
18 changed files with 712 additions and 213 deletions

View File

@ -0,0 +1,134 @@
import {
DynamicForm,
DynamicFormRef,
FormFieldConfig,
} from '@/components/dynamic-form';
import { Modal } from '@/components/ui/modal/modal';
import { t } from 'i18next';
import { useEffect, useRef } from 'react';
import { FieldValues } from 'react-hook-form';
import { TypeMaps, TypesWithArray } from '../constant';
import { useHandleForm } from '../hooks/use-form';
import { useObjectFields } from '../hooks/use-object-fields';
export const AddVariableModal = (props: {
fields?: FormFieldConfig[];
setFields: (value: any) => void;
visible?: boolean;
hideModal: () => void;
defaultValues?: FieldValues;
setDefaultValues?: (value: FieldValues) => void;
}) => {
const {
fields,
setFields,
visible,
hideModal,
defaultValues,
setDefaultValues,
} = props;
const { handleSubmit: submitForm, loading } = useHandleForm();
const { handleCustomValidate, handleCustomSchema, handleRender } =
useObjectFields();
const formRef = useRef<DynamicFormRef>(null);
const handleFieldUpdate = (
fieldName: string,
updatedField: Partial<FormFieldConfig>,
) => {
setFields((prevFields: any) =>
prevFields.map((field: any) =>
field.name === fieldName ? { ...field, ...updatedField } : field,
),
);
};
useEffect(() => {
const typeField = fields?.find((item) => item.name === 'type');
if (typeField) {
typeField.onChange = (value) => {
handleFieldUpdate('value', {
type: TypeMaps[value as keyof typeof TypeMaps],
render: handleRender(value),
customValidate: handleCustomValidate(value),
schema: handleCustomSchema(value),
});
const values = formRef.current?.getValues();
// setTimeout(() => {
switch (value) {
case TypesWithArray.Boolean:
setDefaultValues?.({ ...values, value: false });
break;
case TypesWithArray.Number:
setDefaultValues?.({ ...values, value: 0 });
break;
case TypesWithArray.Object:
setDefaultValues?.({ ...values, value: {} });
break;
case TypesWithArray.ArrayString:
setDefaultValues?.({ ...values, value: [''] });
break;
case TypesWithArray.ArrayNumber:
setDefaultValues?.({ ...values, value: [''] });
break;
case TypesWithArray.ArrayBoolean:
setDefaultValues?.({ ...values, value: [false] });
break;
case TypesWithArray.ArrayObject:
setDefaultValues?.({ ...values, value: [] });
break;
default:
setDefaultValues?.({ ...values, value: '' });
break;
}
// }, 0);
};
}
}, [fields]);
const handleSubmit = async (fieldValue: FieldValues) => {
await submitForm(fieldValue);
hideModal();
};
return (
<Modal
title={t('flow.add') + t('flow.conversationVariable')}
open={visible || false}
onCancel={hideModal}
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={() => {
hideModal?.();
}}
/>
<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>
);
};

View File

@ -13,14 +13,14 @@ export enum TypesWithArray {
String = 'string',
Number = 'number',
Boolean = 'boolean',
// Object = 'object',
// ArrayString = 'array<string>',
// ArrayNumber = 'array<number>',
// ArrayBoolean = 'array<boolean>',
// ArrayObject = 'array<object>',
Object = 'object',
ArrayString = 'array<string>',
ArrayNumber = 'array<number>',
ArrayBoolean = 'array<boolean>',
ArrayObject = 'array<object>',
}
export const GobalFormFields = [
export const GlobalFormFields = [
{
label: t('flow.name'),
name: 'name',
@ -50,11 +50,11 @@ export const GobalFormFields = [
label: t('flow.description'),
name: 'description',
placeholder: t('flow.variableDescription'),
type: 'textarea',
type: FormFieldType.Textarea,
},
] as FormFieldConfig[];
export const GobalVariableFormDefaultValues = {
export const GlobalVariableFormDefaultValues = {
name: '',
type: TypesWithArray.String,
value: '',
@ -65,9 +65,9 @@ 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,
[TypesWithArray.Object]: FormFieldType.Textarea,
[TypesWithArray.ArrayString]: FormFieldType.Textarea,
[TypesWithArray.ArrayNumber]: FormFieldType.Textarea,
[TypesWithArray.ArrayBoolean]: FormFieldType.Textarea,
[TypesWithArray.ArrayObject]: FormFieldType.Textarea,
};

View File

@ -0,0 +1,41 @@
import { useFetchAgent } from '@/hooks/use-agent-request';
import { GlobalVariableType } from '@/interfaces/database/agent';
import { useCallback } from 'react';
import { FieldValues } from 'react-hook-form';
import { useSaveGraph } from '../../hooks/use-save-graph';
import { TypesWithArray } from '../constant';
export const useHandleForm = () => {
const { data, refetch } = useFetchAgent();
const { saveGraph, loading } = useSaveGraph();
const handleObjectData = (value: any) => {
try {
return JSON.parse(value);
} catch (error) {
return value;
}
};
const handleSubmit = useCallback(async (fieldValue: FieldValues) => {
const param = {
...(data.dsl?.variables || {}),
[fieldValue.name]: {
...fieldValue,
value:
fieldValue.type === TypesWithArray.Object ||
fieldValue.type === TypesWithArray.ArrayObject
? handleObjectData(fieldValue.value)
: fieldValue.value,
},
} as Record<string, GlobalVariableType>;
const res = await saveGraph(undefined, {
globalVariables: param,
});
if (res.code === 0) {
refetch();
}
}, []);
return { handleSubmit, loading };
};

View File

@ -0,0 +1,246 @@
import { BlockButton, Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Segmented } from '@/components/ui/segmented';
import { Editor } from '@monaco-editor/react';
import { t } from 'i18next';
import { Trash2, X } from 'lucide-react';
import { useCallback } from 'react';
import { FieldValues } from 'react-hook-form';
import { z } from 'zod';
import { TypesWithArray } from '../constant';
export const useObjectFields = () => {
const booleanRender = useCallback(
(field: FieldValues, className?: string) => {
const fieldValue = field.value ? true : false;
return (
<Segmented
options={
[
{ value: true, label: 'True' },
{ value: false, label: 'False' },
] as any
}
sizeType="sm"
value={fieldValue}
onChange={field.onChange}
className={className}
itemClassName="justify-center flex-1"
></Segmented>
);
},
[],
);
const objectRender = useCallback((field: FieldValues) => {
const fieldValue =
typeof field.value === 'object'
? JSON.stringify(field.value, null, 2)
: JSON.stringify({}, null, 2);
console.log('object-render-field', field, fieldValue);
return (
<Editor
height={200}
defaultLanguage="json"
theme="vs-dark"
value={fieldValue}
onChange={field.onChange}
/>
);
}, []);
const objectValidate = useCallback((value: any) => {
try {
if (!JSON.parse(value)) {
throw new Error(t('knowledgeDetails.formatTypeError'));
}
return true;
} catch (e) {
throw new Error(t('knowledgeDetails.formatTypeError'));
}
}, []);
const arrayStringRender = useCallback((field: FieldValues, type = 'text') => {
const values = Array.isArray(field.value)
? field.value
: [type === 'number' ? 0 : ''];
return (
<>
{values?.map((item: any, index: number) => (
<div key={index} className="flex gap-1 items-center">
<Input
type={type}
value={item}
onChange={(e) => {
const newValues = [...values];
newValues[index] = e.target.value;
field.onChange(newValues);
}}
/>
<Button
variant={'secondary'}
onClick={() => {
const newValues = [...values];
newValues.splice(index, 1);
field.onChange(newValues);
}}
>
<Trash2 />
</Button>
</div>
))}
<BlockButton
type="button"
onClick={() => {
field.onChange([...field.value, '']);
}}
>
{t('flow.add')}
</BlockButton>
</>
);
}, []);
const arrayBooleanRender = useCallback(
(field: FieldValues) => {
// const values = field.value || [false];
const values = Array.isArray(field.value) ? field.value : [false];
return (
<div className="flex items-center gap-1 flex-wrap ">
{values?.map((item: any, index: number) => (
<div
key={index}
className="flex gap-1 items-center bg-bg-card rounded-lg border-[0.5px] border-border-button"
>
{booleanRender(
{
value: item,
onChange: (value) => {
values[index] = !!value;
field.onChange(values);
},
},
'bg-transparent',
)}
<Button
variant={'transparent'}
className="border-none py-0 px-1"
onClick={() => {
const newValues = [...values];
newValues.splice(index, 1);
field.onChange(newValues);
}}
>
<X />
</Button>
</div>
))}
<BlockButton
className="w-auto"
type="button"
onClick={() => {
field.onChange([...field.value, false]);
}}
>
{t('flow.add')}
</BlockButton>
</div>
);
},
[booleanRender],
);
const arrayNumberRender = useCallback(
(field: FieldValues) => {
return arrayStringRender(field, 'number');
},
[arrayStringRender],
);
const arrayValidate = useCallback((value: any, type: string = 'string') => {
if (!Array.isArray(value) || !value.every((item) => typeof item === type)) {
throw new Error(t('flow.formatTypeError'));
}
return true;
}, []);
const arrayStringValidate = useCallback(
(value: any) => {
return arrayValidate(value, 'string');
},
[arrayValidate],
);
const arrayNumberValidate = useCallback(
(value: any) => {
return arrayValidate(value, 'number');
},
[arrayValidate],
);
const arrayBooleanValidate = useCallback(
(value: any) => {
return arrayValidate(value, 'boolean');
},
[arrayValidate],
);
const handleRender = (value: TypesWithArray) => {
switch (value) {
case TypesWithArray.Boolean:
return booleanRender;
case TypesWithArray.Object:
case TypesWithArray.ArrayObject:
return objectRender;
case TypesWithArray.ArrayString:
return arrayStringRender;
case TypesWithArray.ArrayNumber:
return arrayNumberRender;
case TypesWithArray.ArrayBoolean:
return arrayBooleanRender;
default:
return undefined;
}
};
const handleCustomValidate = (value: TypesWithArray) => {
switch (value) {
case TypesWithArray.Object:
case TypesWithArray.ArrayObject:
return objectValidate;
case TypesWithArray.ArrayString:
return arrayStringValidate;
case TypesWithArray.ArrayNumber:
return arrayNumberValidate;
case TypesWithArray.ArrayBoolean:
return arrayBooleanValidate;
default:
return undefined;
}
};
const handleCustomSchema = (value: TypesWithArray) => {
switch (value) {
case TypesWithArray.ArrayString:
return z.array(z.string());
case TypesWithArray.ArrayNumber:
return z.array(z.number());
case TypesWithArray.ArrayBoolean:
return z.array(z.boolean());
default:
return undefined;
}
};
return {
objectRender,
objectValidate,
arrayStringRender,
arrayStringValidate,
arrayNumberRender,
booleanRender,
arrayBooleanRender,
arrayNumberValidate,
arrayBooleanValidate,
handleRender,
handleCustomValidate,
handleCustomSchema,
};
};

View File

@ -1,12 +1,6 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
DynamicForm,
DynamicFormRef,
FormFieldConfig,
FormFieldType,
} from '@/components/dynamic-form';
import { FormFieldConfig } from '@/components/dynamic-form';
import { BlockButton, Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import {
Sheet,
SheetContent,
@ -19,117 +13,65 @@ import { GlobalVariableType } 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 { useState } from 'react';
import { FieldValues } from 'react-hook-form';
import { useSaveGraph } from '../hooks/use-save-graph';
import { AddVariableModal } from './component/add-variable-modal';
import {
GobalFormFields,
GobalVariableFormDefaultValues,
GlobalFormFields,
GlobalVariableFormDefaultValues,
TypeMaps,
TypesWithArray,
} from './contant';
} from './constant';
import { useObjectFields } from './hooks/use-object-fields';
export type IGobalParamModalProps = {
export type IGlobalParamModalProps = {
data: any;
hideModal: (open: boolean) => void;
};
export const GobalParamSheet = (props: IGobalParamModalProps) => {
export const GlobalParamSheet = (props: IGlobalParamModalProps) => {
const { hideModal } = props;
const { data, refetch } = useFetchAgent();
const [fields, setFields] = useState<FormFieldConfig[]>(GobalFormFields);
const { visible, showModal, hideModal: hideAddModal } = useSetModalState();
const [fields, setFields] = useState<FormFieldConfig[]>(GlobalFormFields);
const [defaultValues, setDefaultValues] = useState<FieldValues>(
GobalVariableFormDefaultValues,
GlobalVariableFormDefaultValues,
);
const formRef = useRef<DynamicFormRef>(null);
const { handleCustomValidate, handleCustomSchema, handleRender } =
useObjectFields();
const { saveGraph } = useSaveGraph();
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 = async (value: FieldValues) => {
const param = {
...(data.dsl?.variables || {}),
[value.name]: value,
} as Record<string, GlobalVariableType>;
const res = await saveGraph(undefined, {
gobalVariables: param,
});
if (res.code === 0) {
refetch();
}
hideAddModal();
};
const handleDeleteGobalVariable = async (key: string) => {
const handleDeleteGlobalVariable = async (key: string) => {
const param = {
...(data.dsl?.variables || {}),
} as Record<string, GlobalVariableType>;
delete param[key];
const res = await saveGraph(undefined, {
gobalVariables: param,
globalVariables: param,
});
console.log('delete gobal variable-->', res);
if (res.code === 0) {
refetch();
}
};
const handleEditGobalVariable = (item: FieldValues) => {
fields.forEach((field) => {
if (field.name === 'value') {
switch (item.type) {
// [TypesWithArray.String]: FormFieldType.Textarea,
// [TypesWithArray.Number]: FormFieldType.Number,
// [TypesWithArray.Boolean]: FormFieldType.Checkbox,
case TypesWithArray.Boolean:
field.type = FormFieldType.Checkbox;
break;
case TypesWithArray.Number:
field.type = FormFieldType.Number;
break;
default:
field.type = FormFieldType.Textarea;
}
const handleEditGlobalVariable = (item: FieldValues) => {
const newFields = fields.map((field) => {
let newField = field;
newField.render = undefined;
newField.schema = undefined;
newField.customValidate = undefined;
if (newField.name === 'value') {
newField = {
...newField,
type: TypeMaps[item.type as keyof typeof TypeMaps],
render: handleRender(item.type),
customValidate: handleCustomValidate(item.type),
schema: handleCustomSchema(item.type),
};
}
return newField;
});
setFields(newFields);
setDefaultValues(item);
showModal();
};
@ -149,8 +91,8 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
<div className="px-5 pb-5">
<BlockButton
onClick={() => {
setFields(GobalFormFields);
setDefaultValues(GobalVariableFormDefaultValues);
setFields(GlobalFormFields);
setDefaultValues(GlobalVariableFormDefaultValues);
showModal();
}}
>
@ -167,7 +109,7 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
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);
handleEditGlobalVariable(item);
}}
>
<div className="flex flex-col">
@ -177,13 +119,23 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
{item.type}
</span>
</div>
<div>
<span className="text-text-primary">{item.value}</span>
</div>
{![
TypesWithArray.Object,
TypesWithArray.ArrayObject,
TypesWithArray.ArrayString,
TypesWithArray.ArrayNumber,
TypesWithArray.ArrayBoolean,
].includes(item.type as TypesWithArray) && (
<div>
<span className="text-text-primary">
{item.value}
</span>
</div>
)}
</div>
<div>
<ConfirmDeleteDialog
onOk={() => handleDeleteGobalVariable(key)}
onOk={() => handleDeleteGlobalVariable(key)}
>
<Button
variant={'secondary'}
@ -201,40 +153,14 @@ export const GobalParamSheet = (props: IGobalParamModalProps) => {
})}
</div>
</SheetContent>
<Modal
title={t('flow.add') + t('flow.conversationVariable')}
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>
<AddVariableModal
visible={visible}
hideModal={hideAddModal}
fields={fields}
setFields={setFields}
defaultValues={defaultValues}
setDefaultValues={setDefaultValues}
/>
</Sheet>
</>
);