chore: import upstream snapshot with attribution
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, type PropsWithChildren, useState } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { IconCozArrowRight } from '@coze-arch/coze-design/icons';
|
||||
import { Collapsible } from '@coze-arch/coze-design';
|
||||
|
||||
import { type VariableGroup } from '@/store';
|
||||
|
||||
export const GroupCollapsibleWrapper: FC<
|
||||
PropsWithChildren<{
|
||||
groupInfo: VariableGroup;
|
||||
level?: number;
|
||||
}>
|
||||
> = props => {
|
||||
const { groupInfo, children, level = 0 } = props;
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const isTopLevel = level === 0;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cls(
|
||||
'flex w-full flex-col cursor-pointer px-1 py-2',
|
||||
isTopLevel ? 'hover:coz-mg-secondary-hovered hover:rounded-lg' : '',
|
||||
)}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="w-[22px] h-full flex items-center">
|
||||
<IconCozArrowRight
|
||||
className={cls('w-[14px] h-[14px]', isOpen ? 'rotate-90' : '')}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[370px] h-full flex items-center">
|
||||
<div
|
||||
className={cls(
|
||||
'coz-stroke-primary text-xxl font-medium',
|
||||
!isTopLevel ? '!text-sm my-[10px]' : '',
|
||||
)}
|
||||
>
|
||||
{groupInfo.groupName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isTopLevel ? (
|
||||
<div className="text-sm coz-fg-secondary pl-[22px]">
|
||||
{groupInfo.groupDesc}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Collapsible keepDOM isOpen={isOpen}>
|
||||
<div className={cls('w-full h-full', !isTopLevel ? 'pl-[18px]' : '')}>
|
||||
{children}
|
||||
</div>
|
||||
</Collapsible>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { VariableChannel } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type VariableGroup } from '@/store';
|
||||
|
||||
import { flatGroupVariableMeta } from '../../../variable-tree/utils';
|
||||
|
||||
export const useGetHideKeys = (variableGroup: VariableGroup) => {
|
||||
const hideKeys: string[] = [];
|
||||
|
||||
const hideChannel =
|
||||
flatGroupVariableMeta([variableGroup]).filter(
|
||||
item => (item?.effectiveChannelList?.length ?? 0) > 0,
|
||||
).length <= 0;
|
||||
|
||||
const hideTypeChange = variableGroup.channel === VariableChannel.Custom;
|
||||
|
||||
if (hideChannel) {
|
||||
hideKeys.push('channel');
|
||||
}
|
||||
|
||||
if (hideTypeChange) {
|
||||
hideKeys.push('type');
|
||||
}
|
||||
return hideKeys;
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { VariableGroupParamHeader } from './render';
|
||||
export { useGetHideKeys } from './hooks/use-get-hide-keys';
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
export const VariableGroupParamHeader = ({
|
||||
hideHeaderKeys,
|
||||
}: {
|
||||
hideHeaderKeys?: string[];
|
||||
}) => (
|
||||
<div
|
||||
className={cls(
|
||||
'flex w-full h-[28px] py-[6px] pl-8 items-center gap-x-4 justify-start',
|
||||
'border border-solid coz-stroke-primary border-t-0 border-x-0',
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 h-full flex items-center">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('bot_edit_memory_title_filed')}
|
||||
<span className="coz-fg-hglt-red">*</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 h-full flex items-center">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('bot_edit_memory_title_description')}
|
||||
</div>
|
||||
</div>
|
||||
{!hideHeaderKeys?.includes('type') ? (
|
||||
<div className="flex-none w-[166px] basis-[166px] h-full flex items-center box-content">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('variable_Table_Title_type')}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex-none w-[164px] basis-[164px] h-full flex items-center box-content">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('bot_edit_memory_title_default')}
|
||||
</div>
|
||||
</div>
|
||||
{!hideHeaderKeys?.includes('channel') ? (
|
||||
<div className="flex-none w-[164px] basis-[164px] h-full flex items-center box-content">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('variable_Table_Title_support_channels')}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex-none w-[130px] basis-[130px] h-full flex items-center box-content">
|
||||
<div className="coz-fg-secondary text-[12px] font-[500] leading-[16px]">
|
||||
{I18n.t('bot_edit_memory_title_action')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type VariableGroup as VariableGroupType } from '@/store';
|
||||
|
||||
import { type TreeNodeCustomData } from '../variable-tree/type';
|
||||
import { VariableTree } from '../variable-tree';
|
||||
import { VariableGroupParamHeader, useGetHideKeys } from './group-header';
|
||||
import { GroupCollapsibleWrapper } from './group-collapsible-wraper';
|
||||
|
||||
interface IVariableGroupProps {
|
||||
groupInfo: VariableGroupType;
|
||||
readonly?: boolean;
|
||||
validateExistKeyword?: boolean;
|
||||
onVariableChange: (changeValue: TreeNodeCustomData) => void;
|
||||
}
|
||||
|
||||
export const VariableGroup = (props: IVariableGroupProps) => {
|
||||
const {
|
||||
groupInfo,
|
||||
readonly = true,
|
||||
validateExistKeyword = false,
|
||||
onVariableChange,
|
||||
} = props;
|
||||
const hideHeaderKeys = useGetHideKeys(groupInfo);
|
||||
return (
|
||||
<>
|
||||
<GroupCollapsibleWrapper groupInfo={groupInfo}>
|
||||
<VariableGroupParamHeader hideHeaderKeys={hideHeaderKeys} />
|
||||
<div className="pl-6">
|
||||
{groupInfo.subGroupList?.map(subGroup => (
|
||||
<GroupCollapsibleWrapper groupInfo={subGroup} level={1}>
|
||||
<VariableTree
|
||||
hideHeaderKeys={hideHeaderKeys}
|
||||
groupId={groupInfo.groupId}
|
||||
value={subGroup.varInfoList ?? []}
|
||||
readonly={readonly}
|
||||
channel={subGroup.channel}
|
||||
validateExistKeyword={validateExistKeyword}
|
||||
onChange={onVariableChange}
|
||||
/>
|
||||
</GroupCollapsibleWrapper>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<VariableTree
|
||||
hideHeaderKeys={hideHeaderKeys}
|
||||
groupId={groupInfo.groupId}
|
||||
value={groupInfo.varInfoList ?? []}
|
||||
readonly={readonly}
|
||||
channel={groupInfo.channel}
|
||||
validateExistKeyword={validateExistKeyword}
|
||||
onChange={onVariableChange}
|
||||
/>
|
||||
</div>
|
||||
</GroupCollapsibleWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
import { IconCozAddNode } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, type ButtonProps } from '@coze-arch/coze-design';
|
||||
|
||||
type AddOperationProps = React.PropsWithChildren<{
|
||||
readonly?: boolean;
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
disabled?: boolean;
|
||||
subitem?: boolean;
|
||||
size?: ButtonProps['size'];
|
||||
color?: ButtonProps['color'];
|
||||
}>;
|
||||
|
||||
export default function AddOperation({
|
||||
readonly,
|
||||
onClick,
|
||||
className,
|
||||
style,
|
||||
disabled,
|
||||
subitem = false,
|
||||
size,
|
||||
color,
|
||||
...restProps
|
||||
}: AddOperationProps) {
|
||||
if (readonly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
data-testid={restProps['data-testid']}
|
||||
onClick={onClick}
|
||||
className={`${
|
||||
disabled ? 'disabled:text-[rgb(28,31,35,0.35)]' : 'text-[#4d53e8]'
|
||||
} ${className}`}
|
||||
style={style}
|
||||
icon={
|
||||
subitem ? (
|
||||
<IconCozAddNode />
|
||||
) : (
|
||||
<IconAdd className="text-[#4d53e8] disabled:text-[rgb(28,31,35,0.35)]" />
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type TreeNodeCustomData } from '@/components/variable-tree/type';
|
||||
|
||||
export const ParamChannel = (props: { value: TreeNodeCustomData }) => {
|
||||
const { value } = props;
|
||||
return value.effectiveChannelList?.length ? (
|
||||
<div className="coz-stroke-primary text-[14px] font-[500] leading-[20px]">
|
||||
{value.effectiveChannelList?.join(',') ?? '--'}
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEdit } from '@coze-arch/coze-design/icons';
|
||||
import { CozInputNumber, Switch, Input } from '@coze-arch/coze-design';
|
||||
|
||||
import { ViewVariableType } from '@/store';
|
||||
import { type TreeNodeCustomData } from '@/components/variable-tree/type';
|
||||
|
||||
import { ReadonlyText } from '../readonly-text';
|
||||
import { JSONLikeTypes } from '../../constants';
|
||||
import { JSONImport } from '../../../json-import';
|
||||
|
||||
interface ParamDefaultProps {
|
||||
data: TreeNodeCustomData;
|
||||
onDefaultChange: (value: string | number | boolean) => void;
|
||||
onImportChange: (value: TreeNodeCustomData) => void;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export const ParamDefault = (props: ParamDefaultProps) => {
|
||||
const { data, onDefaultChange, onImportChange, readonly } = props;
|
||||
const [jsonModalVisible, setJsonModalVisible] = useState(false);
|
||||
|
||||
const isRoot = data.meta.level === 0;
|
||||
const isString = isRoot && data.type === ViewVariableType.String;
|
||||
const isNumber =
|
||||
isRoot &&
|
||||
(data.type === ViewVariableType.Number ||
|
||||
data.type === ViewVariableType.Integer);
|
||||
const isBoolean = isRoot && data.type === ViewVariableType.Boolean;
|
||||
const isShowJsonImport = JSONLikeTypes.includes(data.type) && isRoot;
|
||||
|
||||
return (
|
||||
<div className="w-[144px] h-full flex items-center [&_.semi-input-number-suffix-btns]:!h-auto">
|
||||
<div className="flex flex-col w-full relative">
|
||||
{readonly && !isShowJsonImport ? (
|
||||
<ReadonlyText
|
||||
className="w-[144px]"
|
||||
value={data.defaultValue || '-'}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isString ? (
|
||||
<Input
|
||||
value={data.defaultValue}
|
||||
onChange={value => onDefaultChange(value)}
|
||||
placeholder={I18n.t(
|
||||
'workflow_detail_title_testrun_error_input',
|
||||
{
|
||||
a: data.name,
|
||||
},
|
||||
)}
|
||||
maxLength={1000}
|
||||
disabled={readonly}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isNumber ? (
|
||||
<CozInputNumber
|
||||
className={cls('h-full', {
|
||||
'[&_.semi-input-wrapper]:!coz-stroke-plus': true,
|
||||
})}
|
||||
value={data.defaultValue}
|
||||
onChange={value => onDefaultChange(value)}
|
||||
placeholder={I18n.t(
|
||||
'workflow_detail_title_testrun_error_input',
|
||||
{
|
||||
a: data.name,
|
||||
},
|
||||
)}
|
||||
disabled={readonly}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isBoolean ? (
|
||||
<Switch
|
||||
checked={Boolean(data.defaultValue === 'true')}
|
||||
size="small"
|
||||
onChange={value => onDefaultChange(value)}
|
||||
disabled={readonly}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isShowJsonImport ? (
|
||||
<>
|
||||
<div
|
||||
onClick={() => setJsonModalVisible(true)}
|
||||
className={cls(
|
||||
'coz-mg-primary rounded cursor-pointer flex items-center justify-center h-[32px] gap-x-1',
|
||||
{
|
||||
'coz-fg-primary': !readonly,
|
||||
'coz-fg-dim': readonly,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<IconCozEdit />
|
||||
<span className="text-sm font-medium">
|
||||
{readonly
|
||||
? I18n.t('variables_json_input_readonly_button')
|
||||
: I18n.t('variable_button_input_json')}
|
||||
</span>
|
||||
</div>
|
||||
<JSONImport
|
||||
visible={jsonModalVisible}
|
||||
treeData={data}
|
||||
rules={{
|
||||
jsonImport: true,
|
||||
readonly: Boolean(readonly),
|
||||
}}
|
||||
onOk={value => {
|
||||
onImportChange(value);
|
||||
setJsonModalVisible(false);
|
||||
}}
|
||||
onCancel={() => setJsonModalVisible(false)}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Input } from '@coze-arch/coze-design';
|
||||
|
||||
import { type Variable } from '@/store';
|
||||
|
||||
import { ReadonlyText } from '../readonly-text';
|
||||
|
||||
export const ParamDescription = (props: {
|
||||
data: Variable;
|
||||
onChange: (value: string) => void;
|
||||
readonly: boolean;
|
||||
}) => {
|
||||
const { data, onChange, readonly } = props;
|
||||
return !readonly ? (
|
||||
<div className="flex flex-col w-full relative overflow-hidden">
|
||||
<Input
|
||||
value={data.description}
|
||||
placeholder={I18n.t('workflow_detail_llm_output_decription')}
|
||||
maxLength={200}
|
||||
onChange={value => {
|
||||
onChange(value);
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ReadonlyText value={data.description ?? ''} />
|
||||
);
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useFormApi } from '@coze-arch/coze-design';
|
||||
|
||||
import { type Variable } from '@/store';
|
||||
|
||||
export const useCacheField = (data: Variable) => {
|
||||
const formApi = useFormApi();
|
||||
|
||||
const lastValidValueRef = useRef(data.name);
|
||||
|
||||
useEffect(() => {
|
||||
const currentValue = formApi.getValue(`${data.variableId}.name`);
|
||||
if (currentValue) {
|
||||
lastValidValueRef.current = currentValue;
|
||||
} else if (lastValidValueRef.current) {
|
||||
formApi.setValue(`${data.variableId}.name`, lastValidValueRef.current);
|
||||
}
|
||||
}, [data.variableId]);
|
||||
};
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { FormInput, useFormApi } from '@coze-arch/coze-design';
|
||||
|
||||
import { type Variable } from '@/store';
|
||||
import { useVariableContext } from '@/context';
|
||||
|
||||
import { ReadonlyText } from '../readonly-text';
|
||||
import {
|
||||
requiredRules,
|
||||
duplicateRules,
|
||||
existKeywordRules,
|
||||
} from './services/check-rules';
|
||||
import { useCacheField } from './hooks/use-cache-field';
|
||||
|
||||
export const ParamName = (props: {
|
||||
data: Variable;
|
||||
readonly: boolean;
|
||||
onChange: (value: string) => void;
|
||||
validateExistKeyword?: boolean;
|
||||
}) => {
|
||||
const { data, onChange, readonly, validateExistKeyword = false } = props;
|
||||
const { groups } = useVariableContext();
|
||||
const formApi = useFormApi();
|
||||
|
||||
// Use ref to cache the last valid value. When the Tree component is hidden, the component will be destroyed, and the Field field of the Form will be deleted, so it needs to be cached.
|
||||
useCacheField(data);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls(
|
||||
'w-full overflow-hidden',
|
||||
'[&_.semi-form-field-error-message]:absolute',
|
||||
'[&_.semi-form-field-error-message]:text-[12px]',
|
||||
'[&_.semi-form-field-error-message]:font-[400]',
|
||||
'[&_.semi-form-field-error-message]:leading-[16px]',
|
||||
)}
|
||||
>
|
||||
{!readonly ? (
|
||||
<>
|
||||
<FormInput
|
||||
field={`${data.variableId}.name`}
|
||||
placeholder={I18n.t('variable_name_placeholder')}
|
||||
maxLength={50}
|
||||
autoFocus={!data.name}
|
||||
noLabel
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) =>
|
||||
requiredRules.validate({
|
||||
...data,
|
||||
name: value,
|
||||
}),
|
||||
message: requiredRules.message,
|
||||
},
|
||||
{
|
||||
validator: (_, value) =>
|
||||
validateExistKeyword
|
||||
? existKeywordRules.validate({
|
||||
...data,
|
||||
name: value,
|
||||
})
|
||||
: true,
|
||||
message: existKeywordRules.message,
|
||||
},
|
||||
{
|
||||
validator: (_, value) =>
|
||||
duplicateRules.validate(
|
||||
{
|
||||
...data,
|
||||
name: value,
|
||||
},
|
||||
groups,
|
||||
),
|
||||
message: duplicateRules.message,
|
||||
},
|
||||
]}
|
||||
onChange={value => {
|
||||
onChange(value);
|
||||
formApi.setValue(`${data.variableId}.name`, value);
|
||||
}}
|
||||
className="w-full truncate"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ReadonlyText value={data.name} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { VariableChannel } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type Variable, type VariableGroup } from '@/store';
|
||||
|
||||
export const requiredRules = {
|
||||
validate: (value: Variable) => !!value.name,
|
||||
message: I18n.t('bot_edit_variable_field_required_error'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if variable names are duplicate
|
||||
* 1. Check whether the variable names are duplicated in the same group & level
|
||||
* 2. Check whether the variable names are duplicated in the root node names of different groups
|
||||
*/
|
||||
export const duplicateRules = {
|
||||
validate: (value: Variable, groups: VariableGroup[]): boolean => {
|
||||
if (!value.name) {
|
||||
return true;
|
||||
} // Skip check if name is empty
|
||||
|
||||
// 1. Check whether the same group and level are duplicated
|
||||
const currentGroup = groups.find(group => group.groupId === value.groupId);
|
||||
|
||||
if (!currentGroup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get all nodes at the same level as the current node (including those nested in other node children)
|
||||
const findSiblings = (
|
||||
variables: Variable[],
|
||||
targetParentId: string | null,
|
||||
): Variable[] => {
|
||||
let result: Variable[] = [];
|
||||
|
||||
for (const variable of variables) {
|
||||
// If the parentId of the current variable is the same as the target parentId and is not itself, it is added to the result
|
||||
if (
|
||||
variable.parentId === targetParentId &&
|
||||
variable.variableId !== value.variableId
|
||||
) {
|
||||
result.push(variable);
|
||||
}
|
||||
// Check children recursively
|
||||
if (variable.children?.length) {
|
||||
result = result.concat(
|
||||
findSiblings(variable.children, targetParentId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const siblings = findSiblings(currentGroup.varInfoList, value.parentId);
|
||||
|
||||
if (siblings.some(sibling => sibling.name === value.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Check if it has the same name as the root node of other groups
|
||||
// Check only if the current node is the root node
|
||||
if (!value.parentId) {
|
||||
const otherGroupsRootNodes = groups
|
||||
.filter(group => group.groupId !== value.groupId)
|
||||
.flatMap(group => {
|
||||
const rootVariableList = group.varInfoList;
|
||||
const subGroupVarInfoList = group.subGroupList.flatMap(
|
||||
subGroup => subGroup.varInfoList,
|
||||
);
|
||||
return rootVariableList.concat(subGroupVarInfoList);
|
||||
});
|
||||
|
||||
if (otherGroupsRootNodes.some(node => node.name === value.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
message: I18n.t('workflow_detail_node_error_variablename_duplicated'),
|
||||
};
|
||||
|
||||
export const existKeywordRules = {
|
||||
validate: (value: Variable) =>
|
||||
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/.test(
|
||||
value.name,
|
||||
),
|
||||
message: I18n.t('variables_app_name_limit'),
|
||||
};
|
||||
|
||||
export const checkParamNameRules = (
|
||||
value: Variable,
|
||||
groups: VariableGroup[],
|
||||
validateExistKeyword: boolean,
|
||||
):
|
||||
| {
|
||||
valid: boolean;
|
||||
message: string;
|
||||
}
|
||||
| undefined => {
|
||||
if (!requiredRules.validate(value)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: requiredRules.message,
|
||||
};
|
||||
}
|
||||
if (!duplicateRules.validate(value, groups)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: duplicateRules.message,
|
||||
};
|
||||
}
|
||||
if (
|
||||
validateExistKeyword &&
|
||||
!existKeywordRules.validate(value) &&
|
||||
value.channel === VariableChannel.APP
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
message: existKeywordRules.message,
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
message: '',
|
||||
};
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/no-deep-relative-import */
|
||||
import { VariableE2e } from '@coze-data/e2e';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, IconButton, Switch } from '@coze-arch/coze-design';
|
||||
|
||||
import { ObjectLikeTypes } from '@/store/variable-groups/types';
|
||||
import { useVariableContext } from '@/context';
|
||||
|
||||
import AddOperation from '../add-operation';
|
||||
import { type TreeNodeCustomData } from '../../../../type';
|
||||
|
||||
interface ParamOperatorProps {
|
||||
data: TreeNodeCustomData;
|
||||
level: number;
|
||||
onAppend: () => void;
|
||||
onDelete: () => void;
|
||||
onEnabledChange: (enabled: boolean) => void;
|
||||
hasObjectLike?: boolean;
|
||||
needRenderAppendChild?: boolean;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export default function ParamOperator({
|
||||
level,
|
||||
data,
|
||||
hasObjectLike,
|
||||
readonly,
|
||||
needRenderAppendChild = true,
|
||||
onEnabledChange,
|
||||
onDelete,
|
||||
onAppend,
|
||||
}: ParamOperatorProps) {
|
||||
const isLimited = level >= 3;
|
||||
|
||||
// Is it possible to add children?
|
||||
const canAddChild = !readonly && ObjectLikeTypes.includes(data.type);
|
||||
// Is the child button available?
|
||||
const enableAddChildButton =
|
||||
!readonly && hasObjectLike && canAddChild && needRenderAppendChild;
|
||||
// Whether to display the delete button
|
||||
const showDeleteButton = !readonly;
|
||||
// Whether to display the on/off button
|
||||
const enabledSwitch = level === 0;
|
||||
|
||||
const { variablePageCanEdit } = useVariableContext();
|
||||
|
||||
return (
|
||||
<div className="flex items-center h-[24px] flex-shrink-0 justify-start gap-x-2 w-[130px]">
|
||||
{/* Open/close */}
|
||||
<Switch
|
||||
size="small"
|
||||
disabled={!variablePageCanEdit || !enabledSwitch}
|
||||
checked={data.enabled}
|
||||
onChange={onEnabledChange}
|
||||
/>
|
||||
{/* Add child item */}
|
||||
{needRenderAppendChild ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_detail_node_output_add_subitem')}
|
||||
theme="dark"
|
||||
>
|
||||
<div>
|
||||
<AddOperation
|
||||
color="secondary"
|
||||
disabled={isLimited || !enableAddChildButton}
|
||||
className="cursor-pointer"
|
||||
onClick={onAppend}
|
||||
subitem={true}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
{/* delete */}
|
||||
<IconButton
|
||||
data-testid={VariableE2e.VariableTreeDeleteBtn}
|
||||
color="secondary"
|
||||
onClick={onDelete}
|
||||
disabled={!showDeleteButton}
|
||||
icon={<IconCozTrashCan />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
IconCozNumber,
|
||||
IconCozNumberBracket,
|
||||
IconCozString,
|
||||
IconCozStringBracket,
|
||||
IconCozBoolean,
|
||||
IconCozBooleanBracket,
|
||||
IconCozBrace,
|
||||
IconCozBraceBracket,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ViewVariableType } from '@/store';
|
||||
|
||||
export const VARIABLE_TYPE_ICONS_MAP: Record<ViewVariableType, ReactNode> = {
|
||||
[ViewVariableType.String]: <IconCozString />,
|
||||
[ViewVariableType.Integer]: <IconCozNumber />,
|
||||
[ViewVariableType.Boolean]: <IconCozBoolean />,
|
||||
[ViewVariableType.Number]: <IconCozNumber />,
|
||||
[ViewVariableType.Object]: <IconCozBrace />,
|
||||
[ViewVariableType.ArrayString]: <IconCozStringBracket />,
|
||||
[ViewVariableType.ArrayInteger]: <IconCozNumberBracket />,
|
||||
[ViewVariableType.ArrayBoolean]: <IconCozBooleanBracket />,
|
||||
[ViewVariableType.ArrayNumber]: <IconCozNumberBracket />,
|
||||
[ViewVariableType.ArrayObject]: <IconCozBraceBracket />,
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/no-deep-relative-import */
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type SelectProps } from '@coze-arch/bot-semi/Select';
|
||||
import { Cascader } from '@coze-arch/coze-design';
|
||||
|
||||
import { VARIABLE_TYPE_ALIAS_MAP } from '@/types/view-variable-tree';
|
||||
|
||||
import { ReadonlyText } from '../readonly-text';
|
||||
import { type TreeNodeCustomData } from '../../../../type';
|
||||
import {
|
||||
getVariableTypeList,
|
||||
getCascaderVal,
|
||||
allVariableTypeList,
|
||||
} from './utils';
|
||||
import { VARIABLE_TYPE_ICONS_MAP } from './constants';
|
||||
|
||||
interface ParamTypeProps {
|
||||
data: TreeNodeCustomData;
|
||||
level: number;
|
||||
onSelectChange?: SelectProps['onChange'];
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export default function ParamType({
|
||||
data,
|
||||
onSelectChange,
|
||||
level,
|
||||
readonly,
|
||||
}: ParamTypeProps) {
|
||||
const optionList = useMemo(() => getVariableTypeList({ level }), [level]);
|
||||
|
||||
const cascaderVal = useMemo(
|
||||
() => getCascaderVal(data.type, allVariableTypeList),
|
||||
[data.type],
|
||||
);
|
||||
|
||||
return readonly ? (
|
||||
<ReadonlyText
|
||||
className="w-full"
|
||||
value={VARIABLE_TYPE_ALIAS_MAP[data.type]}
|
||||
/>
|
||||
) : (
|
||||
<Cascader
|
||||
placeholder={I18n.t('workflow_detail_start_variable_type')}
|
||||
disabled={readonly}
|
||||
onChange={val => {
|
||||
let newVal = val;
|
||||
if (Array.isArray(val)) {
|
||||
newVal = val[val.length - 1];
|
||||
}
|
||||
onSelectChange?.(newVal);
|
||||
}}
|
||||
className="w-full coz-stroke-plus"
|
||||
displayProp="value"
|
||||
displayRender={selected => {
|
||||
if (!Array.isArray(selected)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
{VARIABLE_TYPE_ICONS_MAP[selected[selected.length - 1]]}
|
||||
<div className="truncate">
|
||||
{VARIABLE_TYPE_ALIAS_MAP[selected[selected.length - 1]]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
treeData={optionList}
|
||||
value={cascaderVal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { VARIABLE_TYPE_ALIAS_MAP } from '@/types/view-variable-tree';
|
||||
import { ObjectLikeTypes } from '@/store/variable-groups/types';
|
||||
import { ViewVariableType } from '@/store';
|
||||
|
||||
const LEVEL_LIMIT = 3;
|
||||
|
||||
export const generateVariableOption = (
|
||||
type: ViewVariableType,
|
||||
label?: string,
|
||||
display?: string,
|
||||
) => ({
|
||||
value: Number(type),
|
||||
label: label || VARIABLE_TYPE_ALIAS_MAP[type],
|
||||
display: display || label || VARIABLE_TYPE_ALIAS_MAP[type],
|
||||
});
|
||||
|
||||
export interface VariableTypeOption {
|
||||
// Value of type, possibly empty when not a leaf node
|
||||
value: number | string;
|
||||
// The display name of the option
|
||||
label: ReactNode;
|
||||
// Echoed display name
|
||||
display?: string;
|
||||
// Is the type disabled?
|
||||
disabled?: boolean;
|
||||
// subtype
|
||||
children?: VariableTypeOption[];
|
||||
}
|
||||
|
||||
export const allVariableTypeList: Array<VariableTypeOption> = [
|
||||
generateVariableOption(ViewVariableType.String),
|
||||
generateVariableOption(ViewVariableType.Integer),
|
||||
generateVariableOption(ViewVariableType.Boolean),
|
||||
generateVariableOption(ViewVariableType.Number),
|
||||
generateVariableOption(ViewVariableType.Object),
|
||||
generateVariableOption(ViewVariableType.ArrayString),
|
||||
generateVariableOption(ViewVariableType.ArrayInteger),
|
||||
generateVariableOption(ViewVariableType.ArrayBoolean),
|
||||
generateVariableOption(ViewVariableType.ArrayNumber),
|
||||
generateVariableOption(ViewVariableType.ArrayObject),
|
||||
];
|
||||
|
||||
const filterTypes = (
|
||||
list: Array<VariableTypeOption>,
|
||||
options?: VariableListOptions,
|
||||
): Array<VariableTypeOption> => {
|
||||
const { level } = options || {};
|
||||
|
||||
return list.reduce((pre, cur) => {
|
||||
const newOption = { ...cur };
|
||||
|
||||
if (newOption.children) {
|
||||
newOption.children = filterTypes(newOption.children, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Disable the ObjectLike type when reaching the level limit to avoid too deep nesting
|
||||
*/
|
||||
const disabled = Boolean(
|
||||
level &&
|
||||
level >= LEVEL_LIMIT &&
|
||||
ObjectLikeTypes.includes(Number(newOption.value)),
|
||||
);
|
||||
|
||||
return [
|
||||
...pre,
|
||||
{
|
||||
...newOption,
|
||||
disabled,
|
||||
},
|
||||
];
|
||||
}, [] as Array<VariableTypeOption>);
|
||||
};
|
||||
|
||||
interface VariableListOptions {
|
||||
level?: number;
|
||||
}
|
||||
|
||||
export const getVariableTypeList = options =>
|
||||
filterTypes(allVariableTypeList, options);
|
||||
|
||||
/**
|
||||
* Get the path of the type in the options list as the cascader value
|
||||
*/
|
||||
export const getCascaderVal = (
|
||||
originalVal: ViewVariableType,
|
||||
list: Array<VariableTypeOption>,
|
||||
path: Array<string | number> = [],
|
||||
) => {
|
||||
let valuePath = [...path];
|
||||
list.forEach(item => {
|
||||
if (item.children) {
|
||||
const childPath = getCascaderVal(originalVal, item.children, [
|
||||
...valuePath,
|
||||
item.value,
|
||||
]);
|
||||
if (childPath[childPath.length - 1] === originalVal) {
|
||||
valuePath = childPath;
|
||||
return;
|
||||
}
|
||||
} else if (item.value === originalVal) {
|
||||
valuePath.push(originalVal);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return valuePath;
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Typography } from '@coze-arch/coze-design';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const ReadonlyText = (props: { value: string; className?: string }) => {
|
||||
const { value, className } = props;
|
||||
return (
|
||||
<Text
|
||||
className={classNames(
|
||||
'w-full coz-fg-primary text-sm !font-medium',
|
||||
className,
|
||||
)}
|
||||
ellipsis
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ViewVariableType } from '@/store';
|
||||
|
||||
export enum ChangeMode {
|
||||
Update,
|
||||
Delete,
|
||||
Append,
|
||||
UpdateEnabled,
|
||||
Replace,
|
||||
}
|
||||
|
||||
// JSON type
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const JSONLikeTypes = [
|
||||
ViewVariableType.Object,
|
||||
ViewVariableType.ArrayObject,
|
||||
ViewVariableType.ArrayBoolean,
|
||||
ViewVariableType.ArrayNumber,
|
||||
ViewVariableType.ArrayString,
|
||||
ViewVariableType.ArrayInteger,
|
||||
];
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
|
||||
import isNumber from 'lodash-es/isNumber';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { type RenderFullLabelProps } from '@coze-arch/bot-semi/Tree';
|
||||
import { IconCozArrowRight } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { type Variable, type ViewVariableType } from '@/store';
|
||||
|
||||
import { type TreeNodeCustomData } from '../../type';
|
||||
import { TreeIndentWidth } from '../../constants';
|
||||
import { ChangeMode } from './constants';
|
||||
import ParamType from './components/param-type';
|
||||
import ParamOperator from './components/param-operator';
|
||||
import { ParamName } from './components/param-name';
|
||||
import { ParamDescription } from './components/param-description';
|
||||
import { ParamDefault } from './components/param-default';
|
||||
import { ParamChannel } from './components/param-channel';
|
||||
export interface CustomTreeNodeProps extends RenderFullLabelProps {
|
||||
level: number;
|
||||
readonly?: boolean;
|
||||
variablePageCanEdit?: boolean;
|
||||
needRenderAppendChild?: boolean;
|
||||
onChange: (mode: ChangeMode, param: TreeNodeCustomData) => void;
|
||||
hasObjectLike?: boolean;
|
||||
disableDelete?: boolean;
|
||||
couldCollapse?: boolean;
|
||||
hideHeaderKeys?: string[];
|
||||
collapsed?: boolean;
|
||||
validateExistKeyword?: boolean;
|
||||
onCollapse?: (collapsed: boolean) => void;
|
||||
}
|
||||
|
||||
export default function CustomTreeNode(props: CustomTreeNodeProps) {
|
||||
const {
|
||||
data,
|
||||
className,
|
||||
level,
|
||||
readonly = false,
|
||||
onChange,
|
||||
hasObjectLike,
|
||||
couldCollapse = true,
|
||||
hideHeaderKeys,
|
||||
collapsed = false,
|
||||
onCollapse,
|
||||
validateExistKeyword = false,
|
||||
} = props;
|
||||
// current value
|
||||
const value = cloneDeep(data) as Variable;
|
||||
const treeNodeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// When deleting
|
||||
const onDelete = () => {
|
||||
onChange(ChangeMode.Delete, value);
|
||||
};
|
||||
|
||||
// When adding a child
|
||||
const onAppend = () => {
|
||||
onChange(ChangeMode.Append, value);
|
||||
};
|
||||
// When switching types
|
||||
const onSelectChange = (
|
||||
val?: string | number | Array<unknown> | Record<string, unknown>,
|
||||
) => {
|
||||
if (val === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!isNumber(val)) {
|
||||
return;
|
||||
}
|
||||
// Clear default
|
||||
value.defaultValue = '';
|
||||
value.children = [];
|
||||
onChange(ChangeMode.Update, { ...value, type: val as ViewVariableType });
|
||||
};
|
||||
const onDefaultChange = (
|
||||
val: string | number | boolean | TreeNodeCustomData,
|
||||
) => {
|
||||
onChange(ChangeMode.Update, { ...value, defaultValue: val.toString() });
|
||||
};
|
||||
|
||||
const onImportChange = (val: TreeNodeCustomData) => {
|
||||
onChange(ChangeMode.Replace, val);
|
||||
};
|
||||
|
||||
const onNameChange = (name: string) => {
|
||||
if (value.name === name) {
|
||||
return;
|
||||
}
|
||||
onChange(ChangeMode.Update, { ...value, name });
|
||||
};
|
||||
|
||||
const onDescriptionChange = useCallback(
|
||||
(description: string) => {
|
||||
if (value.description === description) {
|
||||
return;
|
||||
}
|
||||
onChange(ChangeMode.Update, { ...value, description });
|
||||
},
|
||||
[onChange, value],
|
||||
);
|
||||
|
||||
const onEnabledChange = useCallback(
|
||||
(enabled: boolean) => {
|
||||
onChange(ChangeMode.UpdateEnabled, { ...value, enabled });
|
||||
},
|
||||
[onChange, value],
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={classNames('flex items-center', {
|
||||
[className]: Boolean(className),
|
||||
})}
|
||||
ref={treeNodeRef}
|
||||
>
|
||||
<div className="flex flex-1 my-3 gap-x-4 items-center w-full relative h-[32px]">
|
||||
<div className="flex flex-1 items-center flex-nowrap overflow-x-hidden overflow-y-visible">
|
||||
<div
|
||||
className="flex items-center justify-end"
|
||||
style={{ width: level * TreeIndentWidth }}
|
||||
></div>
|
||||
<IconCozArrowRight
|
||||
className={classNames(
|
||||
'flex-none mr-2 w-[16px] h-[16px]',
|
||||
collapsed ? 'rotate-90' : '',
|
||||
couldCollapse ? '' : 'invisible',
|
||||
'cursor-pointer',
|
||||
level === 0 && !couldCollapse ? 'hidden' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
onCollapse?.(!collapsed);
|
||||
}}
|
||||
/>
|
||||
<ParamName
|
||||
readonly={readonly}
|
||||
data={value}
|
||||
onChange={onNameChange}
|
||||
validateExistKeyword={validateExistKeyword}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<ParamDescription
|
||||
data={value}
|
||||
onChange={onDescriptionChange}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>
|
||||
{!hideHeaderKeys?.includes('type') ? (
|
||||
<div className="flex-none w-[166px] basis-[166px]">
|
||||
<ParamType
|
||||
level={level}
|
||||
readonly={readonly}
|
||||
data={value}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex-none w-[164px] basis-[164px]">
|
||||
<ParamDefault
|
||||
readonly={readonly}
|
||||
data={value}
|
||||
onDefaultChange={onDefaultChange}
|
||||
onImportChange={onImportChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-none w-[164px] basis-[164px] empty:hidden">
|
||||
<ParamChannel value={value} />
|
||||
</div>
|
||||
<div className="flex-none w-[130px] basis-[130px]">
|
||||
<ParamOperator
|
||||
data={value}
|
||||
readonly={readonly}
|
||||
level={level}
|
||||
onDelete={onDelete}
|
||||
onAppend={onAppend}
|
||||
hasObjectLike={hasObjectLike}
|
||||
needRenderAppendChild={!hideHeaderKeys?.includes('type')}
|
||||
onEnabledChange={onEnabledChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
Suspense,
|
||||
lazy,
|
||||
type FC,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozBroom } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip, Modal } from '@coze-arch/coze-design';
|
||||
|
||||
import { MAX_JSON_LENGTH } from '../../constants';
|
||||
import { formatJson } from './utils/format-json';
|
||||
import {
|
||||
convertSchemaService,
|
||||
type SchemaNode,
|
||||
} from './service/convert-schema-service';
|
||||
|
||||
import lightStyles from './light.module.less';
|
||||
|
||||
const LazyBizIDEMonacoEditor = lazy(async () => {
|
||||
const { Editor } = await import('@coze-arch/bot-monaco-editor');
|
||||
return { default: Editor };
|
||||
});
|
||||
|
||||
const BizIDEMonacoEditor = props => (
|
||||
<Suspense>
|
||||
<LazyBizIDEMonacoEditor {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
interface JSONEditorProps {
|
||||
id: string;
|
||||
value: string;
|
||||
groupId: string;
|
||||
setValue: (value: string) => void;
|
||||
visible: boolean;
|
||||
readonly?: boolean;
|
||||
onCancel: () => void;
|
||||
onOk: (value: SchemaNode[]) => void;
|
||||
}
|
||||
|
||||
const ValidateRules = {
|
||||
jsonValid: {
|
||||
message: I18n.t('variables_json_input_error'),
|
||||
validator: (value: string) => {
|
||||
try {
|
||||
const rs = JSON.parse(value);
|
||||
const isJson = typeof rs === 'object';
|
||||
return isJson;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
jsonLength: {
|
||||
message: I18n.t('variables_json_input_limit'),
|
||||
validator: (value: string) => {
|
||||
if (value.length > MAX_JSON_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const JSONEditor: FC<JSONEditorProps> = props => {
|
||||
const { id, value, setValue, visible, onCancel, onOk, readonly } = props;
|
||||
const [schema, setSchema] = useState<SchemaNode[] | undefined>();
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const change = useCallback(async () => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
setError(undefined);
|
||||
return new Promise(resolve => {
|
||||
Modal.warning({
|
||||
title: I18n.t('workflow_json_node_update_tips_title'),
|
||||
content: I18n.t('workflow_json_node_update_tips_content'),
|
||||
okType: 'warning',
|
||||
okText: I18n.t('Confirm'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
onOk: () => {
|
||||
const outputValue = convert(value) || [];
|
||||
onOk(outputValue);
|
||||
resolve(true);
|
||||
},
|
||||
onCancel: () => resolve(false),
|
||||
});
|
||||
});
|
||||
}, [schema]);
|
||||
|
||||
const convert = (jsonString: string): SchemaNode[] | undefined => {
|
||||
if (!jsonString) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(jsonString);
|
||||
const outputValue = convertSchemaService(json);
|
||||
if (
|
||||
!outputValue ||
|
||||
!Array.isArray(outputValue) ||
|
||||
outputValue.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return outputValue;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const validate = (newValue: string) => {
|
||||
const rules = Object.values(ValidateRules);
|
||||
for (const rule of rules) {
|
||||
if (!rule.validator(newValue)) {
|
||||
setError(rule.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setError(undefined);
|
||||
return true;
|
||||
};
|
||||
|
||||
const isValid = useMemo(() => validate(value), [value]);
|
||||
|
||||
// Synchronizing values and schemas
|
||||
useEffect(() => {
|
||||
const _schema = convert(value);
|
||||
setSchema(_schema);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={
|
||||
readonly
|
||||
? I18n.t('variables_json_input_readonly_title')
|
||||
: I18n.t('workflow_json_windows_title')
|
||||
}
|
||||
okText={I18n.t('Confirm')}
|
||||
cancelText={I18n.t('Cancel')}
|
||||
onOk={change}
|
||||
onCancel={onCancel}
|
||||
height={530}
|
||||
okButtonProps={{
|
||||
disabled: !isValid || readonly,
|
||||
}}
|
||||
>
|
||||
<div key={id} className="w-full relative">
|
||||
<div className="w-full h-[48px] coz-bg-primary rounded-t-lg coz-fg-primary font-medium text-sm flex items-center justify-between px-4">
|
||||
<div className="coz-fg-primary">JSON</div>
|
||||
<Tooltip content={I18n.t('workflow_exception_ignore_format')}>
|
||||
<IconButton
|
||||
className="bg-transparent"
|
||||
disabled={readonly}
|
||||
icon={<IconCozBroom />}
|
||||
onClick={() => {
|
||||
setValue(formatJson(value));
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="w-full h-[320px]">
|
||||
<BizIDEMonacoEditor
|
||||
key={id}
|
||||
value={value}
|
||||
defaultLanguage="json"
|
||||
/** Override icube-dark theme with css style */
|
||||
className={lightStyles.light}
|
||||
options={{
|
||||
fontSize: 13,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
contextmenu: false,
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 10,
|
||||
alwaysConsumeMouseWheel: false,
|
||||
},
|
||||
lineNumbers: 'on',
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
lineDecorationsWidth: 2,
|
||||
renderLineHighlight: 'none',
|
||||
glyphMargin: false,
|
||||
scrollBeyondLastLine: false,
|
||||
overviewRulerBorder: false,
|
||||
wordWrap: 'on',
|
||||
fixedOverflowWidgets: true,
|
||||
readOnly: readonly,
|
||||
}}
|
||||
onChange={stringValue => {
|
||||
setValue(stringValue || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="absolute top-full">
|
||||
<span className="coz-fg-hglt-red text-[12px] font-[400] leading-[16px] whitespace-nowrap">
|
||||
{error}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
// fork from biz-ide-component vs-light-theme.module.less
|
||||
// 样式与 ide 保持一致
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.light {
|
||||
:global {
|
||||
.monaco-editor {
|
||||
--vscode-foreground: #616161;
|
||||
--vscode-disabledForeground: rgba(97, 97, 97, 50%);
|
||||
--vscode-errorForeground: #a1260d;
|
||||
--vscode-descriptionForeground: #717171;
|
||||
--vscode-icon-foreground: #424242;
|
||||
--vscode-focusBorder: #0090f1;
|
||||
--vscode-textSeparator-foreground: rgba(0, 0, 0, 18%);
|
||||
--vscode-textLink-foreground: #006ab1;
|
||||
--vscode-textLink-activeForeground: #006ab1;
|
||||
--vscode-textPreformat-foreground: #a31515;
|
||||
--vscode-textBlockQuote-background: rgba(127, 127, 127, 10%);
|
||||
--vscode-textBlockQuote-border: rgba(0, 122, 204, 50%);
|
||||
--vscode-textCodeBlock-background: rgba(220, 220, 220, 40%);
|
||||
--vscode-widget-shadow: rgba(0, 0, 0, 16%);
|
||||
--vscode-input-background: #fff;
|
||||
--vscode-input-foreground: #616161;
|
||||
--vscode-inputOption-activeBorder: #007acc;
|
||||
--vscode-inputOption-hoverBackground: rgba(184, 184, 184, 31%);
|
||||
--vscode-inputOption-activeBackground: rgba(0, 144, 241, 20%);
|
||||
--vscode-inputOption-activeForeground: #000;
|
||||
--vscode-input-placeholderForeground: rgba(97, 97, 97, 50%);
|
||||
--vscode-inputValidation-infoBackground: #d6ecf2;
|
||||
--vscode-inputValidation-infoBorder: #007acc;
|
||||
--vscode-inputValidation-warningBackground: #f6f5d2;
|
||||
--vscode-inputValidation-warningBorder: #b89500;
|
||||
--vscode-inputValidation-errorBackground: #f2dede;
|
||||
--vscode-inputValidation-errorBorder: #be1100;
|
||||
--vscode-dropdown-background: #fff;
|
||||
--vscode-dropdown-foreground: #616161;
|
||||
--vscode-dropdown-border: #cecece;
|
||||
--vscode-button-foreground: #fff;
|
||||
--vscode-button-separator: rgba(255, 255, 255, 40%);
|
||||
--vscode-button-background: #007acc;
|
||||
--vscode-button-hoverBackground: #0062a3;
|
||||
--vscode-button-secondaryForeground: #fff;
|
||||
--vscode-button-secondaryBackground: #5f6a79;
|
||||
--vscode-button-secondaryHoverBackground: #4c5561;
|
||||
--vscode-badge-background: #c4c4c4;
|
||||
--vscode-badge-foreground: #333;
|
||||
--vscode-scrollbar-shadow: #ddd;
|
||||
--vscode-scrollbarSlider-background: rgba(100, 100, 100, 40%);
|
||||
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 70%);
|
||||
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 60%);
|
||||
--vscode-progressBar-background: #0e70c0;
|
||||
--vscode-editorError-foreground: #e51400;
|
||||
--vscode-editorWarning-foreground: #bf8803;
|
||||
--vscode-editorInfo-foreground: #1a85ff;
|
||||
--vscode-editorHint-foreground: #6c6c6c;
|
||||
--vscode-sash-hoverBorder: #0090f1;
|
||||
--vscode-editor-background: #fffffe;
|
||||
--vscode-editor-foreground: #000;
|
||||
--vscode-editorStickyScroll-background: #fffffe;
|
||||
--vscode-editorStickyScrollHover-background: #f0f0f0;
|
||||
--vscode-editorWidget-background: #f3f3f3;
|
||||
--vscode-editorWidget-foreground: #616161;
|
||||
--vscode-editorWidget-border: #c8c8c8;
|
||||
--vscode-quickInput-background: #f3f3f3;
|
||||
--vscode-quickInput-foreground: #616161;
|
||||
--vscode-quickInputTitle-background: rgba(0, 0, 0, 6%);
|
||||
--vscode-pickerGroup-foreground: #0066bf;
|
||||
--vscode-pickerGroup-border: #cccedb;
|
||||
--vscode-keybindingLabel-background: rgba(221, 221, 221, 40%);
|
||||
--vscode-keybindingLabel-foreground: #555;
|
||||
--vscode-keybindingLabel-border: rgba(204, 204, 204, 40%);
|
||||
--vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 40%);
|
||||
--vscode-editor-selectionBackground: #add6ff;
|
||||
--vscode-editor-inactiveSelectionBackground: #e5ebf1;
|
||||
--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 30%);
|
||||
--vscode-editor-findMatchBackground: #a8ac94;
|
||||
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 33%);
|
||||
--vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 30%);
|
||||
--vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 22%);
|
||||
--vscode-search-resultsInfoForeground: #616161;
|
||||
--vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 15%);
|
||||
--vscode-editorHoverWidget-background: #f3f3f3;
|
||||
--vscode-editorHoverWidget-foreground: #616161;
|
||||
--vscode-editorHoverWidget-border: #c8c8c8;
|
||||
--vscode-editorHoverWidget-statusBarBackground: #e7e7e7;
|
||||
--vscode-editorLink-activeForeground: #00f;
|
||||
--vscode-editorInlayHint-foreground: #969696;
|
||||
--vscode-editorInlayHint-background: rgba(196, 196, 196, 10%);
|
||||
--vscode-editorInlayHint-typeForeground: #969696;
|
||||
--vscode-editorInlayHint-typeBackground: rgba(196, 196, 196, 10%);
|
||||
--vscode-editorInlayHint-parameterForeground: #969696;
|
||||
--vscode-editorInlayHint-parameterBackground: rgba(196, 196, 196, 10%);
|
||||
--vscode-editorLightBulb-foreground: #ddb100;
|
||||
--vscode-editorLightBulbAutoFix-foreground: #007acc;
|
||||
--vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 25%);
|
||||
--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 20%);
|
||||
--vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 20%);
|
||||
--vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 20%);
|
||||
--vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 20%);
|
||||
--vscode-diffEditor-unchangedRegionBackground: #e4e4e4;
|
||||
--vscode-diffEditor-unchangedRegionForeground: #4d4c4c;
|
||||
--vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 16%);
|
||||
--vscode-list-focusOutline: #0090f1;
|
||||
--vscode-list-activeSelectionBackground: #0060c0;
|
||||
--vscode-list-activeSelectionForeground: #fff;
|
||||
--vscode-list-inactiveSelectionBackground: #e4e6f1;
|
||||
--vscode-list-hoverBackground: #f0f0f0;
|
||||
--vscode-list-dropBackground: #d6ebff;
|
||||
--vscode-list-highlightForeground: #0066bf;
|
||||
--vscode-list-focusHighlightForeground: #bbe7ff;
|
||||
--vscode-list-invalidItemForeground: #b89500;
|
||||
--vscode-list-errorForeground: #b01011;
|
||||
--vscode-list-warningForeground: #855f00;
|
||||
--vscode-listFilterWidget-background: #f3f3f3;
|
||||
--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0%);
|
||||
--vscode-listFilterWidget-noMatchesOutline: #be1100;
|
||||
--vscode-listFilterWidget-shadow: rgba(0, 0, 0, 16%);
|
||||
--vscode-list-filterMatchBackground: rgba(234, 92, 0, 33%);
|
||||
--vscode-tree-indentGuidesStroke: #a9a9a9;
|
||||
--vscode-tree-inactiveIndentGuidesStroke: rgba(169, 169, 169, 40%);
|
||||
--vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 13%);
|
||||
--vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, 4%);
|
||||
--vscode-list-deemphasizedForeground: #8e8e90;
|
||||
--vscode-checkbox-background: #fff;
|
||||
--vscode-checkbox-selectBackground: #f3f3f3;
|
||||
--vscode-checkbox-foreground: #616161;
|
||||
--vscode-checkbox-border: #cecece;
|
||||
--vscode-checkbox-selectBorder: #424242;
|
||||
--vscode-quickInputList-focusForeground: #fff;
|
||||
--vscode-quickInputList-focusBackground: #0060c0;
|
||||
--vscode-menu-foreground: #616161;
|
||||
--vscode-menu-background: #fff;
|
||||
--vscode-menu-selectionForeground: #fff;
|
||||
--vscode-menu-selectionBackground: #0060c0;
|
||||
--vscode-menu-separatorBackground: #d4d4d4;
|
||||
--vscode-toolbar-hoverBackground: rgba(184, 184, 184, 31%);
|
||||
--vscode-toolbar-activeBackground: rgba(166, 166, 166, 31%);
|
||||
--vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 20%);
|
||||
--vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10,
|
||||
50,
|
||||
100,
|
||||
50%);
|
||||
--vscode-breadcrumb-foreground: rgba(97, 97, 97, 80%);
|
||||
--vscode-breadcrumb-background: #fffffe;
|
||||
--vscode-breadcrumb-focusForeground: #4e4e4e;
|
||||
--vscode-breadcrumb-activeSelectionForeground: #4e4e4e;
|
||||
--vscode-breadcrumbPicker-background: #f3f3f3;
|
||||
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 50%);
|
||||
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 20%);
|
||||
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 50%);
|
||||
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 20%);
|
||||
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 40%);
|
||||
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 16%);
|
||||
--vscode-editorOverviewRuler-currentContentForeground: rgba(64,
|
||||
200,
|
||||
174,
|
||||
50%);
|
||||
--vscode-editorOverviewRuler-incomingContentForeground: rgba(64,
|
||||
166,
|
||||
255,
|
||||
50%);
|
||||
--vscode-editorOverviewRuler-commonContentForeground: rgba(96,
|
||||
96,
|
||||
96,
|
||||
40%);
|
||||
--vscode-editorOverviewRuler-findMatchForeground: rgba(209,
|
||||
134,
|
||||
22,
|
||||
49%);
|
||||
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160,
|
||||
160,
|
||||
160,
|
||||
80%);
|
||||
--vscode-minimap-findMatchHighlight: #d18616;
|
||||
--vscode-minimap-selectionOccurrenceHighlight: #c9c9c9;
|
||||
--vscode-minimap-selectionHighlight: #add6ff;
|
||||
--vscode-minimap-errorHighlight: rgba(255, 18, 18, 70%);
|
||||
--vscode-minimap-warningHighlight: #bf8803;
|
||||
--vscode-minimap-foregroundOpacity: #000;
|
||||
--vscode-minimapSlider-background: rgba(100, 100, 100, 20%);
|
||||
--vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 35%);
|
||||
--vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 30%);
|
||||
--vscode-problemsErrorIcon-foreground: #e51400;
|
||||
--vscode-problemsWarningIcon-foreground: #bf8803;
|
||||
--vscode-problemsInfoIcon-foreground: #1a85ff;
|
||||
--vscode-charts-foreground: #616161;
|
||||
--vscode-charts-lines: rgba(97, 97, 97, 50%);
|
||||
--vscode-charts-red: #e51400;
|
||||
--vscode-charts-blue: #1a85ff;
|
||||
--vscode-charts-yellow: #bf8803;
|
||||
--vscode-charts-orange: #d18616;
|
||||
--vscode-charts-green: #388a34;
|
||||
--vscode-charts-purple: #652d90;
|
||||
--vscode-diffEditor-move-border: rgba(139, 139, 139, 61%);
|
||||
--vscode-diffEditor-moveActive-border: #ffa500;
|
||||
--vscode-symbolIcon-arrayForeground: #616161;
|
||||
--vscode-symbolIcon-booleanForeground: #616161;
|
||||
--vscode-symbolIcon-classForeground: #d67e00;
|
||||
--vscode-symbolIcon-colorForeground: #616161;
|
||||
--vscode-symbolIcon-constantForeground: #616161;
|
||||
--vscode-symbolIcon-constructorForeground: #652d90;
|
||||
--vscode-symbolIcon-enumeratorForeground: #d67e00;
|
||||
--vscode-symbolIcon-enumeratorMemberForeground: #007acc;
|
||||
--vscode-symbolIcon-eventForeground: #d67e00;
|
||||
--vscode-symbolIcon-fieldForeground: #007acc;
|
||||
--vscode-symbolIcon-fileForeground: #616161;
|
||||
--vscode-symbolIcon-folderForeground: #616161;
|
||||
--vscode-symbolIcon-functionForeground: #652d90;
|
||||
--vscode-symbolIcon-interfaceForeground: #007acc;
|
||||
--vscode-symbolIcon-keyForeground: #616161;
|
||||
--vscode-symbolIcon-keywordForeground: #616161;
|
||||
--vscode-symbolIcon-methodForeground: #652d90;
|
||||
--vscode-symbolIcon-moduleForeground: #616161;
|
||||
--vscode-symbolIcon-namespaceForeground: #616161;
|
||||
--vscode-symbolIcon-nullForeground: #616161;
|
||||
--vscode-symbolIcon-numberForeground: #616161;
|
||||
--vscode-symbolIcon-objectForeground: #616161;
|
||||
--vscode-symbolIcon-operatorForeground: #616161;
|
||||
--vscode-symbolIcon-packageForeground: #616161;
|
||||
--vscode-symbolIcon-propertyForeground: #616161;
|
||||
--vscode-symbolIcon-referenceForeground: #616161;
|
||||
--vscode-symbolIcon-snippetForeground: #616161;
|
||||
--vscode-symbolIcon-stringForeground: #616161;
|
||||
--vscode-symbolIcon-structForeground: #616161;
|
||||
--vscode-symbolIcon-textForeground: #616161;
|
||||
--vscode-symbolIcon-typeParameterForeground: #616161;
|
||||
--vscode-symbolIcon-unitForeground: #616161;
|
||||
--vscode-symbolIcon-variableForeground: #007acc;
|
||||
--vscode-actionBar-toggledBackground: rgba(0, 144, 241, 20%);
|
||||
--vscode-editor-lineHighlightBorder: #eee;
|
||||
--vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 20%);
|
||||
--vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 33%);
|
||||
--vscode-editorCursor-foreground: #000;
|
||||
--vscode-editorWhitespace-foreground: rgba(51, 51, 51, 20%);
|
||||
--vscode-editorLineNumber-foreground: #237893;
|
||||
--vscode-editorIndentGuide-background: rgba(51, 51, 51, 20%);
|
||||
--vscode-editorIndentGuide-activeBackground: rgba(51, 51, 51, 20%);
|
||||
--vscode-editorIndentGuide-background1: #d3d3d3;
|
||||
--vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-activeBackground1: #939393;
|
||||
--vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorActiveLineNumber-foreground: #0b216f;
|
||||
--vscode-editorLineNumber-activeForeground: #0b216f;
|
||||
--vscode-editorRuler-foreground: #d3d3d3;
|
||||
--vscode-editorCodeLens-foreground: #919191;
|
||||
--vscode-editorBracketMatch-background: rgba(0, 100, 0, 10%);
|
||||
--vscode-editorBracketMatch-border: #b9b9b9;
|
||||
--vscode-editorOverviewRuler-border: rgba(127, 127, 127, 30%);
|
||||
--vscode-editorGutter-background: #fffffe;
|
||||
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 47%);
|
||||
--vscode-editorGhostText-foreground: rgba(0, 0, 0, 47%);
|
||||
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0,
|
||||
122,
|
||||
204,
|
||||
60%);
|
||||
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 70%);
|
||||
--vscode-editorOverviewRuler-warningForeground: #bf8803;
|
||||
--vscode-editorOverviewRuler-infoForeground: #1a85ff;
|
||||
--vscode-editorBracketHighlight-foreground1: #0431fa;
|
||||
--vscode-editorBracketHighlight-foreground2: #319331;
|
||||
--vscode-editorBracketHighlight-foreground3: #7b3814;
|
||||
--vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketHighlight-unexpectedBracket-foreground: rgba(255,
|
||||
18,
|
||||
18,
|
||||
80%);
|
||||
--vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0%);
|
||||
--vscode-editorUnicodeHighlight-border: #cea33d;
|
||||
--vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, 8%);
|
||||
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
|
||||
--vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 30%);
|
||||
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 25%);
|
||||
--vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 25%);
|
||||
--vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 25%);
|
||||
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(160,
|
||||
160,
|
||||
160,
|
||||
80%);
|
||||
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192,
|
||||
160,
|
||||
192,
|
||||
80%);
|
||||
--vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160,
|
||||
160,
|
||||
160,
|
||||
80%);
|
||||
--vscode-peekViewTitle-background: #f3f3f3;
|
||||
--vscode-peekViewTitleLabel-foreground: #000;
|
||||
--vscode-peekViewTitleDescription-foreground: #616161;
|
||||
--vscode-peekView-border: #1a85ff;
|
||||
--vscode-peekViewResult-background: #f3f3f3;
|
||||
--vscode-peekViewResult-lineForeground: #646465;
|
||||
--vscode-peekViewResult-fileForeground: #1e1e1e;
|
||||
--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 20%);
|
||||
--vscode-peekViewResult-selectionForeground: #6c6c6c;
|
||||
--vscode-peekViewEditor-background: #f2f8fc;
|
||||
--vscode-peekViewEditorGutter-background: #f2f8fc;
|
||||
--vscode-peekViewEditorStickyScroll-background: #f2f8fc;
|
||||
--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 30%);
|
||||
--vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, 87%);
|
||||
--vscode-editorMarkerNavigationError-background: #e51400;
|
||||
--vscode-editorMarkerNavigationError-headerBackground: rgba(229,
|
||||
20,
|
||||
0,
|
||||
10%);
|
||||
--vscode-editorMarkerNavigationWarning-background: #bf8803;
|
||||
--vscode-editorMarkerNavigationWarning-headerBackground: rgba(191,
|
||||
136,
|
||||
3,
|
||||
10%);
|
||||
--vscode-editorMarkerNavigationInfo-background: #1a85ff;
|
||||
--vscode-editorMarkerNavigationInfo-headerBackground: rgba(26,
|
||||
133,
|
||||
255,
|
||||
10%);
|
||||
--vscode-editorMarkerNavigation-background: #fffffe;
|
||||
--vscode-editorHoverWidget-highlightForeground: #0066bf;
|
||||
--vscode-editorSuggestWidget-background: #f3f3f3;
|
||||
--vscode-editorSuggestWidget-border: #c8c8c8;
|
||||
--vscode-editorSuggestWidget-foreground: #000;
|
||||
--vscode-editorSuggestWidget-selectedForeground: #fff;
|
||||
--vscode-editorSuggestWidget-selectedBackground: #0060c0;
|
||||
--vscode-editorSuggestWidget-highlightForeground: #0066bf;
|
||||
--vscode-editorSuggestWidget-focusHighlightForeground: #bbe7ff;
|
||||
--vscode-editorSuggestWidgetStatus-foreground: rgba(0, 0, 0, 50%);
|
||||
--vscode-editor-foldBackground: rgba(173, 214, 255, 30%);
|
||||
--vscode-editorGutter-foldingControlForeground: #424242;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
.bracket-highlighting-0 {
|
||||
color: #d5a00d; // { }
|
||||
}
|
||||
|
||||
.bracket-highlighting-1 {
|
||||
color: #8140e3; // [ ]
|
||||
}
|
||||
|
||||
.mtk1 {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.lines-content .core-guide-indent {
|
||||
box-shadow: 1px 0 0 0 transparent !important;
|
||||
}
|
||||
|
||||
.lines-content .core-guide-indent.indent-active {
|
||||
box-shadow: 1px 0 0 0 transparent !important;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
color: #A7A7B0;
|
||||
}
|
||||
|
||||
.marker-widget {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.find-widget {
|
||||
// 搜索框
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
|
||||
.monaco-sash {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.button:not(.disabled):hover,
|
||||
.monaco-editor .find-widget .codicon-find-selection:hover {
|
||||
background-color: #f8f8f8 !important;
|
||||
}
|
||||
|
||||
.monaco-inputbox {
|
||||
border: 1px solid #e4e3e4 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface SchemaNode {
|
||||
name: string;
|
||||
type: number;
|
||||
children?: SchemaNode[];
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
// modify from @byted/biz-ide-component
|
||||
export const convertSchemaService = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
object: any,
|
||||
maxDepth = 20,
|
||||
currentDepth = 1,
|
||||
): SchemaNode[] => {
|
||||
if (currentDepth > maxDepth) {
|
||||
return [];
|
||||
}
|
||||
const paramSchema: SchemaNode[] = [];
|
||||
Object.keys(object).forEach(key => {
|
||||
const value = object[key];
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 1 /* String */,
|
||||
});
|
||||
break;
|
||||
case 'number':
|
||||
if (Number.isInteger(value)) {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 2 /* Integer */,
|
||||
});
|
||||
} else {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 4 /* Number */,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 3 /* Boolean */,
|
||||
});
|
||||
break;
|
||||
case 'object':
|
||||
if (value === null) {
|
||||
break;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length > 0) {
|
||||
switch (typeof value[0]) {
|
||||
case 'string':
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 99 /* ArrayString */,
|
||||
});
|
||||
break;
|
||||
case 'number':
|
||||
if (Number.isInteger(value[0])) {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 100 /* ArrayInteger */,
|
||||
});
|
||||
} else {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 102 /* ArrayNumber */,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 101 /* ArrayBoolean */,
|
||||
});
|
||||
break;
|
||||
case 'object':
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 103 /* ArrayObject */,
|
||||
children: convertSchemaService(
|
||||
value[0],
|
||||
maxDepth,
|
||||
currentDepth + 1,
|
||||
),
|
||||
});
|
||||
break;
|
||||
default:
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 99 /* ArrayString */,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 99 /* ArrayString */,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
paramSchema.push({
|
||||
name: key,
|
||||
defaultValue: JSON.stringify(value),
|
||||
type: 6 /* Object */,
|
||||
children: convertSchemaService(value, maxDepth, currentDepth + 1),
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('ContainsInvalidValue');
|
||||
}
|
||||
});
|
||||
return paramSchema;
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const INDENT = 4;
|
||||
export const formatJson = (json: string) => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(json), null, INDENT);
|
||||
} catch (e) {
|
||||
return json;
|
||||
}
|
||||
};
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState, type FC } from 'react';
|
||||
|
||||
import { merge, cloneDeep } from 'lodash-es';
|
||||
|
||||
import { type SchemaNode } from '../json-editor/service/convert-schema-service';
|
||||
import { JSONEditor } from '../json-editor';
|
||||
import type { TreeNodeCustomData } from '../../type';
|
||||
import {
|
||||
MAX_LEVEL,
|
||||
MAX_NAME_LENGTH,
|
||||
MAX_JSON_VARIABLE_COUNT,
|
||||
} from '../../constants';
|
||||
import { cutOffInvalidData } from './utils/cut-off';
|
||||
import { exportVariableService } from './services/use-case-service/export-variable-service';
|
||||
import { getEditorViewVariableJson } from './services/life-cycle-service/init-service';
|
||||
|
||||
interface JSONImportProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
treeData: TreeNodeCustomData;
|
||||
rules: {
|
||||
jsonImport: boolean;
|
||||
readonly: boolean;
|
||||
};
|
||||
onOk: (value: TreeNodeCustomData) => void;
|
||||
}
|
||||
|
||||
export const JSONImport: FC<JSONImportProps> = props => {
|
||||
const { treeData, rules, visible, onCancel, onOk } = props;
|
||||
const { jsonImport, readonly } = rules;
|
||||
const [jsonString, setJsonString] = useState('');
|
||||
|
||||
const handleImport = (data: SchemaNode[]) => {
|
||||
const allowDepth = MAX_LEVEL; // Maximum depth limit
|
||||
const allowNameLength = MAX_NAME_LENGTH; // Name length limit
|
||||
const maxVariableCount = MAX_JSON_VARIABLE_COUNT; // Maximum number of variables limit
|
||||
const variables = exportVariableService(
|
||||
data,
|
||||
{
|
||||
groupId: treeData.groupId,
|
||||
channel: treeData.channel,
|
||||
},
|
||||
treeData, // Pass in the original variable to maintain the variableId.
|
||||
);
|
||||
|
||||
// Crop illegal data
|
||||
const dataCutoff = cutOffInvalidData({
|
||||
data: variables,
|
||||
allowDepth,
|
||||
allowNameLength,
|
||||
maxVariableCount,
|
||||
});
|
||||
|
||||
// First deep copy the original data source
|
||||
const clonedTreeData = cloneDeep(treeData);
|
||||
// Merge old and new data
|
||||
const mergedData = merge(clonedTreeData, dataCutoff[0]);
|
||||
|
||||
// update data
|
||||
return onOk(mergedData);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setJsonString(getEditorViewVariableJson(treeData));
|
||||
}, [treeData]);
|
||||
|
||||
if (!jsonImport) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<JSONEditor
|
||||
id={treeData.variableId}
|
||||
groupId={treeData.groupId}
|
||||
value={jsonString}
|
||||
readonly={readonly}
|
||||
setValue={(value: string) => {
|
||||
setJsonString(value);
|
||||
}}
|
||||
visible={visible}
|
||||
onOk={handleImport}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ViewVariableType } from '@/store/variable-groups/types';
|
||||
import { type TreeNodeCustomData } from '@/components/variable-tree/type';
|
||||
import { formatJson } from '@/components/variable-tree/components/json-editor/utils/format-json';
|
||||
|
||||
const getDefaultValueByType = (type: ViewVariableType) => {
|
||||
switch (type) {
|
||||
case ViewVariableType.String:
|
||||
return '';
|
||||
case ViewVariableType.Integer:
|
||||
case ViewVariableType.Number:
|
||||
return 0;
|
||||
case ViewVariableType.Boolean:
|
||||
return false;
|
||||
case ViewVariableType.Object:
|
||||
return {};
|
||||
case ViewVariableType.ArrayString:
|
||||
return [''];
|
||||
case ViewVariableType.ArrayInteger:
|
||||
return [0];
|
||||
case ViewVariableType.ArrayBoolean:
|
||||
return [true];
|
||||
case ViewVariableType.ArrayNumber:
|
||||
return [0];
|
||||
case ViewVariableType.ArrayObject:
|
||||
return [{}];
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const isArrayType = (type: ViewVariableType) =>
|
||||
[
|
||||
ViewVariableType.ArrayString,
|
||||
ViewVariableType.ArrayInteger,
|
||||
ViewVariableType.ArrayBoolean,
|
||||
ViewVariableType.ArrayNumber,
|
||||
ViewVariableType.ArrayObject,
|
||||
].includes(type);
|
||||
|
||||
export const getEditorViewVariableJson = (treeData: TreeNodeCustomData) => {
|
||||
const { defaultValue, type, name, children } = treeData;
|
||||
|
||||
if (defaultValue) {
|
||||
const json = JSON.parse(defaultValue);
|
||||
return formatJson(
|
||||
JSON.stringify({
|
||||
[name]: json,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// If there is no name, return an empty object
|
||||
if (!name) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
const isArray = isArrayType(type);
|
||||
|
||||
// Recursive processing of children
|
||||
const processChildren = (
|
||||
nodes?: TreeNodeCustomData[],
|
||||
parentType?: ViewVariableType,
|
||||
) => {
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return getDefaultValueByType(parentType || type);
|
||||
}
|
||||
|
||||
if (isArray && !parentType) {
|
||||
const firstChild = nodes[0];
|
||||
if (!firstChild) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If it is an array type, generate a default value based on the type of the first child element
|
||||
const result = {};
|
||||
if (firstChild.children && firstChild.children.length > 0) {
|
||||
result[firstChild.name] = processChildren(
|
||||
firstChild.children,
|
||||
firstChild.type,
|
||||
);
|
||||
} else {
|
||||
result[firstChild.name] = getDefaultValueByType(firstChild.type);
|
||||
}
|
||||
return [result];
|
||||
}
|
||||
|
||||
return nodes.reduce((acc, node) => {
|
||||
if (!node.name) {
|
||||
return acc;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const value = processChildren(node.children, node.type);
|
||||
acc[node.name] = isArrayType(node.type) ? [value] : value;
|
||||
} else {
|
||||
acc[node.name] = getDefaultValueByType(node.type);
|
||||
}
|
||||
return acc;
|
||||
}, {} satisfies Record<string, unknown>);
|
||||
};
|
||||
|
||||
// Generate the final JSON structure
|
||||
const result = {
|
||||
[name]: processChildren(children),
|
||||
};
|
||||
|
||||
return formatJson(JSON.stringify(result));
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type VariableChannel } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type ViewVariableType } from '@/store/variable-groups/types';
|
||||
import { useVariableGroupsStore } from '@/store/variable-groups/store';
|
||||
import { type Variable } from '@/store';
|
||||
|
||||
import { type SchemaNode } from '../../../json-editor/service/convert-schema-service';
|
||||
|
||||
/**
|
||||
* Converting Converted Data to Variables
|
||||
* @param data converted data
|
||||
* @param baseInfo
|
||||
* @param originalVariable to hold variableId
|
||||
* @returns Variable[]
|
||||
*/
|
||||
export const exportVariableService = (
|
||||
data: SchemaNode[],
|
||||
baseInfo: {
|
||||
groupId: string;
|
||||
channel: VariableChannel;
|
||||
},
|
||||
originalVariable?: Variable,
|
||||
): Variable[] => {
|
||||
const store = useVariableGroupsStore.getState();
|
||||
|
||||
const convertNode = (
|
||||
node: SchemaNode,
|
||||
parentId = '',
|
||||
originalNode?: Variable,
|
||||
): Variable => {
|
||||
// Create the underlying variable using the createVariable method in the store
|
||||
const baseVariable = store.createVariable({
|
||||
variableType: node.type as ViewVariableType,
|
||||
groupId: baseInfo.groupId,
|
||||
parentId,
|
||||
channel: baseInfo.channel,
|
||||
});
|
||||
|
||||
// If the original node exists, keep its variableId.
|
||||
if (originalNode) {
|
||||
baseVariable.variableId = originalNode.variableId;
|
||||
baseVariable.description = originalNode.description;
|
||||
}
|
||||
|
||||
// Update basic information about variables
|
||||
baseVariable.name = node.name;
|
||||
baseVariable.defaultValue = node.defaultValue;
|
||||
|
||||
// Recursively process the sub-node and try to match the original sub-node.
|
||||
if (node.children?.length) {
|
||||
baseVariable.children = node.children.map((child, index) => {
|
||||
const originalChild = originalNode?.children?.[index];
|
||||
return convertNode(child, baseVariable.variableId, originalChild);
|
||||
});
|
||||
}
|
||||
|
||||
return baseVariable;
|
||||
};
|
||||
|
||||
const variables = data.map(node => convertNode(node, '', originalVariable));
|
||||
|
||||
// Update meta information using the updateMeta method in the store
|
||||
store.updateMeta({ variables });
|
||||
|
||||
return variables;
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Variable, ViewVariableType } from '@/store';
|
||||
|
||||
import {
|
||||
traverse,
|
||||
type TraverseContext,
|
||||
type TraverseHandler,
|
||||
} from './traverse';
|
||||
|
||||
const isOutputValueContext = (context: TraverseContext): boolean => {
|
||||
if (
|
||||
typeof context.node.value !== 'object' ||
|
||||
typeof context.node.value.type === 'undefined'
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const cutOffNameLength =
|
||||
(length: number): TraverseHandler =>
|
||||
(context: TraverseContext): void => {
|
||||
if (!isOutputValueContext(context)) {
|
||||
return;
|
||||
}
|
||||
if (context.node.value.name.length > length) {
|
||||
context.node.value.name = context.node.value.name.slice(0, length);
|
||||
}
|
||||
};
|
||||
|
||||
const cutOffDepth =
|
||||
(depth: number): TraverseHandler =>
|
||||
(context: TraverseContext): void => {
|
||||
if (
|
||||
!isOutputValueContext(context) ||
|
||||
context.node.value.level !== depth ||
|
||||
![ViewVariableType.Object, ViewVariableType.ArrayObject].includes(
|
||||
context.node.value.type,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
context.deleteSelf();
|
||||
};
|
||||
|
||||
export const cutOffInvalidData = (params: {
|
||||
data: Variable[];
|
||||
allowDepth: number;
|
||||
allowNameLength: number;
|
||||
maxVariableCount: number;
|
||||
}): Variable[] => {
|
||||
const { data, allowDepth, allowNameLength, maxVariableCount } = params;
|
||||
const cutOffVariableCountData = data.slice(0, maxVariableCount);
|
||||
return traverse<Variable[]>(cutOffVariableCountData, [
|
||||
cutOffNameLength(allowNameLength),
|
||||
cutOffDepth(allowDepth),
|
||||
]);
|
||||
};
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import type { TreeNodeCustomData } from '../../../type';
|
||||
import { traverse, type TraverseContext } from './traverse';
|
||||
|
||||
/** Compute Path */
|
||||
const getTreePath = (context: TraverseContext): string => {
|
||||
const parents = context
|
||||
.getParents()
|
||||
.filter(
|
||||
node =>
|
||||
typeof node.value === 'object' &&
|
||||
typeof node.value.name !== 'undefined' &&
|
||||
typeof node.value.type !== 'undefined',
|
||||
);
|
||||
return parents.map(node => node.value.name).join('/');
|
||||
};
|
||||
|
||||
/** Old and new data keep keys to prevent variable system references from invalidating */
|
||||
export const mergeData = (params: {
|
||||
newData: TreeNodeCustomData;
|
||||
oldData: TreeNodeCustomData;
|
||||
}): TreeNodeCustomData => {
|
||||
const { newData, oldData } = params;
|
||||
|
||||
// Compute the mapping of paths and keys in old data
|
||||
const treeDataPathKeyMap = new Map<
|
||||
string,
|
||||
{
|
||||
key: string;
|
||||
}
|
||||
>();
|
||||
traverse(oldData, context => {
|
||||
if (
|
||||
typeof context.node.value !== 'object' ||
|
||||
typeof context.node.value.key === 'undefined' ||
|
||||
typeof context.node.value.type === 'undefined'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const stringifyPath = getTreePath(context);
|
||||
treeDataPathKeyMap.set(stringifyPath, {
|
||||
key: context.node.value.key,
|
||||
});
|
||||
});
|
||||
|
||||
// The new data reuses the key of the old data, and if it fails, it is regenerated.
|
||||
const newDataWithKey = traverse(newData, context => {
|
||||
if (
|
||||
typeof context.node.value !== 'object' ||
|
||||
typeof context.node.value.type === 'undefined'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const stringifyPath = getTreePath(context);
|
||||
const { key } = treeDataPathKeyMap.get(stringifyPath) || {
|
||||
key: nanoid(),
|
||||
};
|
||||
context.node.value.key = key;
|
||||
});
|
||||
|
||||
return newDataWithKey;
|
||||
};
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type TraverseValue = any;
|
||||
export interface TraverseNode {
|
||||
value: TraverseValue;
|
||||
container?: TraverseValue;
|
||||
parent?: TraverseNode;
|
||||
key?: string;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface TraverseContext {
|
||||
node: TraverseNode;
|
||||
setValue: (value: TraverseValue) => void;
|
||||
getParents: () => TraverseNode[];
|
||||
getPath: () => Array<string | number>;
|
||||
getStringifyPath: () => string;
|
||||
deleteSelf: () => void;
|
||||
}
|
||||
|
||||
export type TraverseHandler = (context: TraverseContext) => void;
|
||||
|
||||
/**
|
||||
* Traverse the object in depth, processing each value
|
||||
* @param value over object
|
||||
* @param handling function
|
||||
*/
|
||||
export const traverse = <T extends TraverseValue = TraverseValue>(
|
||||
value: T,
|
||||
handler: TraverseHandler | TraverseHandler[],
|
||||
): T => {
|
||||
const traverseHandler: TraverseHandler = Array.isArray(handler)
|
||||
? (context: TraverseContext) => {
|
||||
handler.forEach(handlerFn => handlerFn(context));
|
||||
}
|
||||
: handler;
|
||||
TraverseUtils.traverseNodes({ value }, traverseHandler);
|
||||
return value;
|
||||
};
|
||||
|
||||
namespace TraverseUtils {
|
||||
/**
|
||||
* Traverse the object in depth, processing each value
|
||||
* @param node traverse node
|
||||
* @param handling function
|
||||
*/
|
||||
export const traverseNodes = (
|
||||
node: TraverseNode,
|
||||
handle: TraverseHandler,
|
||||
): void => {
|
||||
const { value } = node;
|
||||
if (!value) {
|
||||
// exception handling
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.toString.call(value) === '[object Object]') {
|
||||
// Object, iterate through each property of the object
|
||||
Object.entries(value).forEach(([key, item]) =>
|
||||
traverseNodes(
|
||||
{
|
||||
value: item,
|
||||
container: value,
|
||||
key,
|
||||
parent: node,
|
||||
},
|
||||
handle,
|
||||
),
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
// Array, iterate through each element of the array
|
||||
// The iteration starts at the end of the array, so that even if an element is removed halfway through, it will not affect the index of the unprocessed element
|
||||
for (let index = value.length - 1; index >= 0; index--) {
|
||||
const item: string = value[index];
|
||||
traverseNodes(
|
||||
{
|
||||
value: item,
|
||||
container: value,
|
||||
index,
|
||||
parent: node,
|
||||
},
|
||||
handle,
|
||||
);
|
||||
}
|
||||
}
|
||||
const context: TraverseContext = createContext({ node });
|
||||
handle(context);
|
||||
};
|
||||
|
||||
const createContext = ({
|
||||
node,
|
||||
}: {
|
||||
node: TraverseNode;
|
||||
}): TraverseContext => ({
|
||||
node,
|
||||
setValue: (value: unknown) => setValue(node, value),
|
||||
getParents: () => getParents(node),
|
||||
getPath: () => getPath(node),
|
||||
getStringifyPath: () => getStringifyPath(node),
|
||||
deleteSelf: () => deleteSelf(node),
|
||||
});
|
||||
|
||||
const setValue = (node: TraverseNode, value: unknown) => {
|
||||
// Set Value Function
|
||||
// Reference type, you need to modify the value with the help of the parent element
|
||||
// Since it is a recursive traversal, it is necessary to determine which property of the object to assign a value to, or which element of the array to assign a value to, according to node
|
||||
if (!value || !node) {
|
||||
return;
|
||||
}
|
||||
node.value = value;
|
||||
// Remove container, key, index from upper scope node
|
||||
const { container, key, index } = node;
|
||||
if (key && container) {
|
||||
container[key] = value;
|
||||
} else if (typeof index === 'number') {
|
||||
container[index] = value;
|
||||
}
|
||||
};
|
||||
|
||||
const getParents = (node: TraverseNode): TraverseNode[] => {
|
||||
const parents: TraverseNode[] = [];
|
||||
let currentNode: TraverseNode | undefined = node;
|
||||
while (currentNode) {
|
||||
parents.unshift(currentNode);
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
return parents;
|
||||
};
|
||||
|
||||
const getPath = (node: TraverseNode): Array<string | number> => {
|
||||
const path: Array<string | number> = [];
|
||||
const parents = getParents(node);
|
||||
parents.forEach(parent => {
|
||||
if (parent.key) {
|
||||
path.unshift(parent.key);
|
||||
} else if (parent.index) {
|
||||
path.unshift(parent.index);
|
||||
}
|
||||
});
|
||||
return path;
|
||||
};
|
||||
|
||||
const getStringifyPath = (node: TraverseNode): string => {
|
||||
const path = getPath(node);
|
||||
return path.reduce((stringifyPath: string, pathItem: string | number) => {
|
||||
if (typeof pathItem === 'string') {
|
||||
const re = /\W/g;
|
||||
if (re.test(pathItem)) {
|
||||
// Contains special characters
|
||||
return `${stringifyPath}["${pathItem}"]`;
|
||||
}
|
||||
return `${stringifyPath}.${pathItem}`;
|
||||
} else {
|
||||
return `${stringifyPath}[${pathItem}]`;
|
||||
}
|
||||
}, '');
|
||||
};
|
||||
|
||||
const deleteSelf = (node: TraverseNode): void => {
|
||||
const { container, key, index } = node;
|
||||
if (key && container) {
|
||||
delete container[key];
|
||||
} else if (typeof index === 'number') {
|
||||
container.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
/** Indent width of each level of tree */
|
||||
export const TreeIndentWidth = 30;
|
||||
/** Tree Node Expand Collapse Button Width */
|
||||
export const TreeCollapseWidth = 24;
|
||||
|
||||
// Name Maximum 50 characters
|
||||
export const MAX_NAME_LENGTH = 50;
|
||||
|
||||
// Maximum depth limit
|
||||
export const MAX_LEVEL = 3;
|
||||
// Maximum number of variables limit
|
||||
export const MAX_JSON_VARIABLE_COUNT = 1;
|
||||
// Maximum JSON length limit 30kb
|
||||
export const MAX_JSON_LENGTH = 30 * 1024;
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, {
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { type TreeProps } from '@coze-arch/bot-semi/Tree';
|
||||
import { type VariableChannel } from '@coze-arch/bot-api/memory';
|
||||
import { IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Toast, Tree, useFormApi } from '@coze-arch/coze-design';
|
||||
|
||||
import { traverse } from '@/utils/traverse';
|
||||
import { useVariableGroupsStore, ViewVariableType } from '@/store';
|
||||
import { VariableTreeContext } from '@/context/variable-tree-context';
|
||||
|
||||
import { flatVariableTreeData } from './utils';
|
||||
import { type TreeNodeCustomData } from './type';
|
||||
import { ChangeMode } from './components/custom-tree-node/constants';
|
||||
import CustomTreeNode from './components/custom-tree-node';
|
||||
|
||||
export interface VariableTreeProps {
|
||||
groupId: string;
|
||||
value: Array<TreeNodeCustomData>;
|
||||
treeProps?: TreeProps;
|
||||
readonly?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
showAddButton?: boolean;
|
||||
/** Default variable type */
|
||||
defaultVariableType?: ViewVariableType;
|
||||
defaultCollapse?: boolean;
|
||||
children?: React.ReactNode;
|
||||
maxLimit?: number;
|
||||
hideHeaderKeys?: string[];
|
||||
channel: VariableChannel;
|
||||
validateExistKeyword?: boolean;
|
||||
onChange?: (changeValue: TreeNodeCustomData) => void;
|
||||
}
|
||||
|
||||
export interface VariableTreeRef {
|
||||
validate: () => void;
|
||||
}
|
||||
|
||||
function useExpandedKeys(keys: string[], defaultCollapse: boolean) {
|
||||
const [expandedKeys, setExpandedKeys] = useState(defaultCollapse ? [] : keys);
|
||||
|
||||
const expandTreeNode = useCallback((key: string) => {
|
||||
setExpandedKeys(prev => [...new Set([...prev, key])]);
|
||||
}, []);
|
||||
|
||||
const collapseTreeNode = useCallback((key: string) => {
|
||||
setExpandedKeys(prev => prev.filter(expandedKey => expandedKey !== key));
|
||||
}, []);
|
||||
|
||||
return { expandedKeys, expandTreeNode, collapseTreeNode };
|
||||
}
|
||||
|
||||
export function Index(
|
||||
props: VariableTreeProps,
|
||||
ref: React.Ref<VariableTreeRef>,
|
||||
) {
|
||||
const {
|
||||
readonly = false,
|
||||
treeProps,
|
||||
className,
|
||||
style,
|
||||
value,
|
||||
defaultVariableType = ViewVariableType.String,
|
||||
defaultCollapse = false,
|
||||
maxLimit,
|
||||
groupId,
|
||||
channel,
|
||||
hideHeaderKeys,
|
||||
validateExistKeyword = false,
|
||||
onChange,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
createVariable,
|
||||
addRootVariable,
|
||||
addChildVariable,
|
||||
updateVariable,
|
||||
deleteVariable,
|
||||
findAndModifyVariable,
|
||||
} = useVariableGroupsStore(
|
||||
useShallow(state => ({
|
||||
createVariable: state.createVariable,
|
||||
addRootVariable: state.addRootVariable,
|
||||
addChildVariable: state.addChildVariable,
|
||||
updateVariable: state.updateVariable,
|
||||
deleteVariable: state.deleteVariable,
|
||||
findAndModifyVariable: state.findAndModifyVariable,
|
||||
})),
|
||||
);
|
||||
|
||||
const formApi = useFormApi();
|
||||
|
||||
const isValueEmpty = !value || value.length === 0;
|
||||
|
||||
const itemKeysWithChildren = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
traverse(value, item => {
|
||||
if (item.children?.length > 0) {
|
||||
keys.push(item.variableId);
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}, [value]);
|
||||
|
||||
const flatTreeData = useMemo(() => flatVariableTreeData(value), [value]);
|
||||
|
||||
const { expandedKeys, expandTreeNode, collapseTreeNode } = useExpandedKeys(
|
||||
itemKeysWithChildren,
|
||||
defaultCollapse,
|
||||
);
|
||||
const params = useParams<DynamicParams>();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: () => formApi.validate(),
|
||||
}));
|
||||
|
||||
const disableAdd = useMemo(() => {
|
||||
if (maxLimit === undefined) {
|
||||
return false;
|
||||
}
|
||||
return (value?.length ?? 0) >= maxLimit;
|
||||
}, [value, maxLimit]);
|
||||
|
||||
const showAddButton = !readonly && !disableAdd;
|
||||
|
||||
const onAdd = () => {
|
||||
const newVariable = createVariable({
|
||||
groupId,
|
||||
parentId: '',
|
||||
variableType: defaultVariableType,
|
||||
channel,
|
||||
});
|
||||
|
||||
addRootVariable(newVariable);
|
||||
onChange?.(newVariable);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'add',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
};
|
||||
|
||||
// Tree node change method
|
||||
const onTreeNodeChange = (mode: ChangeMode, param: TreeNodeCustomData) => {
|
||||
const findResult = findAndModifyVariable(
|
||||
groupId,
|
||||
item => item.variableId === param.variableId,
|
||||
);
|
||||
if (!findResult) {
|
||||
Toast.error(I18n.t('workflow_detail_node_output_parsingfailed'));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case ChangeMode.Append: {
|
||||
const { variableId: parentId, channel: parentChannel } = findResult;
|
||||
const childVariable = createVariable({
|
||||
groupId,
|
||||
parentId,
|
||||
variableType: defaultVariableType,
|
||||
channel: parentChannel,
|
||||
});
|
||||
addChildVariable(childVariable);
|
||||
|
||||
// Add a new node under the current node and expand the current node
|
||||
if (findResult?.variableId) {
|
||||
expandTreeNode(findResult.variableId);
|
||||
}
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'add',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ChangeMode.Update: {
|
||||
updateVariable(param);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'edit',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ChangeMode.Delete: {
|
||||
deleteVariable(param);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'delete',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ChangeMode.UpdateEnabled: {
|
||||
findResult.enabled = param.enabled;
|
||||
// Close all sub-nodes with one click
|
||||
traverse<TreeNodeCustomData>(findResult, node => {
|
||||
if (!param.enabled) {
|
||||
node.enabled = param.enabled;
|
||||
}
|
||||
});
|
||||
// The child point is turned on, and the parent node is also turned on.
|
||||
if (findResult.parentId && findResult.enabled) {
|
||||
const parentData = findAndModifyVariable(
|
||||
groupId,
|
||||
item => item.variableId === findResult.parentId,
|
||||
);
|
||||
if (parentData) {
|
||||
parentData.enabled = findResult.enabled;
|
||||
updateVariable(parentData);
|
||||
}
|
||||
}
|
||||
updateVariable(findResult);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: param.enabled ? 'turn_on' : 'turn_off',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ChangeMode.Replace: {
|
||||
updateVariable(param);
|
||||
expandTreeNode(findResult.variableId);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: params?.project_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'edit',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
onChange?.(param);
|
||||
};
|
||||
|
||||
if (readonly && isValueEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VariableTreeContext.Provider value={{ groupId, variables: flatTreeData }}>
|
||||
<div
|
||||
className={classNames(
|
||||
// basic container style
|
||||
'relative h-full',
|
||||
// interaction state
|
||||
!readonly && 'cursor-default',
|
||||
// custom class name
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<Tree
|
||||
style={readonly ? {} : { overflow: 'inherit' }}
|
||||
motion={false}
|
||||
keyMaps={{
|
||||
key: 'variableId',
|
||||
}}
|
||||
disabled={readonly}
|
||||
className={classNames(
|
||||
// basic scrolling behavior
|
||||
'overflow-x-auto',
|
||||
|
||||
// Tree list base style
|
||||
[
|
||||
// list container style
|
||||
'[&_.semi-tree-option-list]:overflow-visible',
|
||||
'[&_.semi-tree-option-list]:p-0',
|
||||
'[&_.semi-tree-option-list>div:first-child]:mt-0',
|
||||
// Option style
|
||||
'[&_.semi-tree-option]:!pl-2',
|
||||
].join(' '),
|
||||
|
||||
// interaction state style
|
||||
readonly
|
||||
? '[&_.semi-tree-option-list-block_.semi-tree-option:hover]:bg-inherit'
|
||||
: [
|
||||
'[&_.semi-tree-option-list-block_.semi-tree-option:hover]:bg-transparent',
|
||||
'[&_.semi-tree-option-list-block_.semi-tree-option:active]:bg-transparent',
|
||||
].join(' '),
|
||||
)}
|
||||
renderFullLabel={renderFullLabelProps => {
|
||||
const { data } = renderFullLabelProps;
|
||||
const currentLevelReadOnly = readonly || data.IsReadOnly;
|
||||
|
||||
const onCollapse = (collapsed: boolean) => {
|
||||
const { variableId } = renderFullLabelProps.data;
|
||||
|
||||
if (!variableId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (collapsed) {
|
||||
expandTreeNode(variableId);
|
||||
} else {
|
||||
collapseTreeNode(variableId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CustomTreeNode
|
||||
{...renderFullLabelProps}
|
||||
hideHeaderKeys={hideHeaderKeys}
|
||||
validateExistKeyword={validateExistKeyword}
|
||||
onChange={onTreeNodeChange}
|
||||
hasObjectLike={data.meta.hasObjectLike}
|
||||
readonly={currentLevelReadOnly}
|
||||
couldCollapse={(data.children?.length ?? 0) > 0}
|
||||
collapsed={renderFullLabelProps.expandStatus.expanded}
|
||||
onCollapse={onCollapse}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
emptyContent={<></>}
|
||||
expandedKeys={[...expandedKeys, nanoid()]}
|
||||
treeData={value}
|
||||
{...treeProps}
|
||||
/>
|
||||
{showAddButton ? (
|
||||
<div className="flex items-center my-3">
|
||||
<IconButton icon={<IconCozPlus />} onClick={onAdd}>
|
||||
{I18n.t('workflow_detail_node_output_add_subitem')}
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</VariableTreeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Export components that can call the ref method
|
||||
export const VariableTree = React.forwardRef(Index);
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CSSProperties } from 'react';
|
||||
|
||||
import { type Variable, type ViewVariableType } from '@/store';
|
||||
|
||||
import { type ChangeMode } from './components/custom-tree-node/constants';
|
||||
|
||||
export interface RecursedParamDefinition {
|
||||
name?: string;
|
||||
/** The Tree component requires each node to have a key, and the key is not suitable for assignment in any way such as name (before and after). Finally, the interface conversion layer provides a random key at one time. */
|
||||
fieldRandomKey?: string;
|
||||
desc?: string;
|
||||
type: ViewVariableType;
|
||||
children?: RecursedParamDefinition[];
|
||||
}
|
||||
|
||||
export type TreeNodeCustomData = Variable;
|
||||
|
||||
export interface CustomTreeNodeFuncRef {
|
||||
data: TreeNodeCustomData;
|
||||
level: number;
|
||||
readonly: boolean;
|
||||
// General change method
|
||||
onChange: (mode: ChangeMode, param: TreeNodeCustomData) => void;
|
||||
// Customized type change method, mainly used for custom rendering
|
||||
// Add child item
|
||||
onAppend: () => void;
|
||||
// Delete this item
|
||||
onDelete: () => void;
|
||||
// The internal call method when the type changes, mainly used to delete all children when converting from the class Object type to other types
|
||||
onSelectChange: (
|
||||
val?: string | number | Array<unknown> | Record<string, unknown>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type WithCustomStyle<T = object> = {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
} & T;
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { traverse } from '@/utils/traverse';
|
||||
import { type Variable, type VariableGroup } from '@/store';
|
||||
|
||||
import { type TreeNodeCustomData } from './type';
|
||||
|
||||
interface RootFindResult {
|
||||
isRoot: true;
|
||||
data: TreeNodeCustomData;
|
||||
parentData: null;
|
||||
}
|
||||
interface ChildrenFindResult {
|
||||
isRoot: false;
|
||||
parentData: TreeNodeCustomData;
|
||||
data: TreeNodeCustomData;
|
||||
}
|
||||
|
||||
export type FindDataResult = RootFindResult | ChildrenFindResult | null;
|
||||
/**
|
||||
* According to the target array, find the value and position of the key in the item, mainly to obtain the position, which is convenient for operating the children of the parent.
|
||||
*/
|
||||
export function findCustomTreeNodeDataResult(
|
||||
target: Array<TreeNodeCustomData>,
|
||||
variableId: string,
|
||||
): FindDataResult {
|
||||
const dataInRoot = target.find(item => item.variableId === variableId);
|
||||
if (dataInRoot) {
|
||||
// If it is the root node
|
||||
return {
|
||||
isRoot: true,
|
||||
parentData: null,
|
||||
data: dataInRoot,
|
||||
};
|
||||
}
|
||||
function findDataInChildrenLoop(
|
||||
customChildren: Array<TreeNodeCustomData>,
|
||||
parentData?: TreeNodeCustomData,
|
||||
): FindDataResult {
|
||||
function findDataLoop(
|
||||
customData: TreeNodeCustomData,
|
||||
_parentData: TreeNodeCustomData,
|
||||
): FindDataResult {
|
||||
if (customData.variableId === variableId) {
|
||||
return {
|
||||
isRoot: false,
|
||||
parentData: _parentData,
|
||||
data: customData,
|
||||
};
|
||||
}
|
||||
if (customData.children && customData.children.length > 0) {
|
||||
return findDataInChildrenLoop(
|
||||
customData.children as Array<TreeNodeCustomData>,
|
||||
customData,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
for (const child of customChildren) {
|
||||
const childResult = findDataLoop(child, parentData || child);
|
||||
if (childResult) {
|
||||
return childResult;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return findDataInChildrenLoop(target);
|
||||
}
|
||||
|
||||
// Flatten groupVariableMeta to viewVariableTreeNode []
|
||||
export function flatGroupVariableMeta(
|
||||
groupVariableMeta: VariableGroup[],
|
||||
maxDepth = Infinity,
|
||||
) {
|
||||
const res: Variable[] = [];
|
||||
traverse(
|
||||
groupVariableMeta,
|
||||
item => {
|
||||
res.push(...item.varInfoList);
|
||||
},
|
||||
'subGroupList',
|
||||
maxDepth,
|
||||
);
|
||||
return res;
|
||||
}
|
||||
export const flatVariableTreeData = (treeData: Variable[]) => {
|
||||
const res: Variable[] = [];
|
||||
traverse(
|
||||
treeData,
|
||||
item => {
|
||||
res.push(item);
|
||||
},
|
||||
'children',
|
||||
);
|
||||
return res;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { type VariableGroup } from '../store';
|
||||
|
||||
interface VariableContextType {
|
||||
variablePageCanEdit?: boolean;
|
||||
groups: VariableGroup[];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const VariableContext = createContext<VariableContextType>({
|
||||
groups: [],
|
||||
});
|
||||
|
||||
export const useVariableContext = () => useContext(VariableContext);
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { type Variable } from '../store';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const VariableTreeContext = createContext<{
|
||||
groupId: string;
|
||||
variables: Variable[];
|
||||
}>({
|
||||
groupId: '',
|
||||
variables: [],
|
||||
});
|
||||
|
||||
export const useVariableTreeContext = () => useContext(VariableTreeContext);
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useVariableGroupsStore } from '../../store';
|
||||
|
||||
export const useDestory = () => {
|
||||
const { clear } = useVariableGroupsStore();
|
||||
useEffect(
|
||||
() => () => {
|
||||
clear();
|
||||
},
|
||||
[clear],
|
||||
);
|
||||
return {
|
||||
clear,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { type project_memory as ProjectMemory } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { useVariableGroupsStore } from '../../store';
|
||||
|
||||
export const useInit = (projectID?: string, version?: string) => {
|
||||
const { data: reqData, loading } = useGetVariableList(projectID, version);
|
||||
const { initStore } = useVariableGroupsStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { variableGroups, canEdit } = reqData;
|
||||
|
||||
initStore({
|
||||
variableGroups,
|
||||
canEdit: canEdit && !version,
|
||||
});
|
||||
}, [loading]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
const useGetVariableList = (
|
||||
projectID?: string,
|
||||
version?: string,
|
||||
): {
|
||||
data: {
|
||||
variableGroups: ProjectMemory.GroupVariableInfo[];
|
||||
canEdit: boolean;
|
||||
};
|
||||
loading: boolean;
|
||||
error: string;
|
||||
} => {
|
||||
const {
|
||||
data: reqData,
|
||||
loading,
|
||||
error,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
if (!projectID) {
|
||||
throw new CustomError(
|
||||
'useListDataSetReq_error',
|
||||
'projectID cannot be empty',
|
||||
);
|
||||
}
|
||||
const res = await MemoryApi.GetProjectVariableList({
|
||||
ProjectID: projectID,
|
||||
version: version || undefined,
|
||||
});
|
||||
|
||||
const { GroupConf, code, CanEdit: canEdit, msg } = res;
|
||||
|
||||
if (code !== 0) {
|
||||
return {
|
||||
error: msg,
|
||||
data: {
|
||||
variableGroups: [],
|
||||
canEdit: false,
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!GroupConf) {
|
||||
return {
|
||||
data: {
|
||||
variableGroups: [],
|
||||
canEdit,
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
variableGroups: GroupConf,
|
||||
canEdit,
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
onError: () => {
|
||||
Toast.error({
|
||||
content: I18n.t('Network_error'),
|
||||
showClose: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: {
|
||||
variableGroups: reqData?.variableGroups ?? [],
|
||||
canEdit: reqData?.canEdit ?? false,
|
||||
},
|
||||
loading,
|
||||
error: error?.message ?? '',
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useHiddenSession } from '@/hooks/use-case/use-hidden-session';
|
||||
|
||||
export const useChangeWarning = () => {
|
||||
const [isShowBanner, setIsShowBanner] = useState(false);
|
||||
const { isSessionHidden, hideSession } = useHiddenSession(
|
||||
'variable_config_change_banner_remind',
|
||||
);
|
||||
|
||||
const showBanner = () => {
|
||||
setIsShowBanner(true);
|
||||
};
|
||||
|
||||
const hideBanner = () => {
|
||||
setIsShowBanner(false);
|
||||
};
|
||||
|
||||
const hideBannerForever = () => {
|
||||
hideSession();
|
||||
setIsShowBanner(false);
|
||||
};
|
||||
|
||||
return {
|
||||
isShowBanner: isShowBanner && !isSessionHidden,
|
||||
showBanner,
|
||||
hideBanner,
|
||||
hideBannerForever,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { localStorageService } from '@coze-foundation/local-storage';
|
||||
|
||||
const SESSION_HIDDEN_KEY = 'coze-home-session-area-hidden-key';
|
||||
|
||||
export const useHiddenSession = (key: string) => {
|
||||
const [isSessionHidden, setIsSessionHidden] = useState(isKeyExist(key));
|
||||
return {
|
||||
isSessionHidden,
|
||||
hideSession: () => {
|
||||
if (isKeyExist(key)) {
|
||||
return;
|
||||
}
|
||||
const oldValue = localStorageService.getValue(SESSION_HIDDEN_KEY) || '';
|
||||
localStorageService.setValue(
|
||||
SESSION_HIDDEN_KEY,
|
||||
oldValue ? `${oldValue},${key}` : key,
|
||||
);
|
||||
setIsSessionHidden(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const isKeyExist = (key: string) => {
|
||||
const oldValue = localStorageService.getValue(SESSION_HIDDEN_KEY);
|
||||
return oldValue?.includes(key);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useDataNavigate } from '@coze-data/knowledge-stores';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, Toast } from '@coze-arch/coze-design';
|
||||
|
||||
export const useLeaveWarning = () => {
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
const location = useLocation();
|
||||
const prevPathRef = useRef(location.pathname);
|
||||
const resourceNavigate = useDataNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const currentPath = location.pathname;
|
||||
const wasInVariablePage = prevPathRef.current.includes('/variables');
|
||||
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (hasUnsavedChanges) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
wasInVariablePage &&
|
||||
!currentPath.includes('/variables') &&
|
||||
hasUnsavedChanges
|
||||
) {
|
||||
Toast.warning({
|
||||
content: (
|
||||
<div>
|
||||
<span className="text-sm font-medium coz-fg-plus mr-2">
|
||||
{I18n.t('variable_config_toast_savetips')}
|
||||
</span>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
resourceNavigate.navigateTo?.('/variables');
|
||||
}}
|
||||
>
|
||||
{I18n.t('variable_config_toast_return_button')}
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (currentPath.includes('/variables') && hasUnsavedChanges) {
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
}
|
||||
|
||||
prevPathRef.current = currentPath;
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, [location, hasUnsavedChanges]);
|
||||
|
||||
return {
|
||||
hasUnsavedChanges,
|
||||
setHasUnsavedChanges,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding-bottom: 16px; // 给底部留出间距
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
|
||||
padding: 16px 24px;
|
||||
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
|
||||
// 如果需要阴影效果
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 6%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { TabBar, TabPane } from '@coze-arch/coze-design';
|
||||
|
||||
import { VariablesValue } from './variables-value';
|
||||
import { VariablesConfig } from './variables-config';
|
||||
|
||||
export const VariablesPage = () => {
|
||||
const params = useKnowledgeParams();
|
||||
const { projectID = '', version } = params;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'h-full w-full overflow-hidden',
|
||||
'border border-solid coz-stroke-primary coz-bg-max',
|
||||
)}
|
||||
>
|
||||
<TabBar
|
||||
lazyRender
|
||||
type="text"
|
||||
className={classNames(
|
||||
'h-full flex flex-col',
|
||||
// Scroll bar position is adjusted to tab content
|
||||
'[&_.semi-tabs-content]:p-0 [&_.semi-tabs-content]:grow [&_.semi-tabs-content]:overflow-hidden',
|
||||
'[&_.semi-tabs-pane-active]:h-full',
|
||||
'[&_.semi-tabs-pane-motion-overlay]:h-full [&_.semi-tabs-pane-motion-overlay]:overflow-auto',
|
||||
)}
|
||||
tabBarClassName="flex items-center h-[56px] mx-[16px]"
|
||||
>
|
||||
<TabPane tab={I18n.t('db_optimize_033')} itemKey="config">
|
||||
<VariablesConfig projectID={projectID} version={version} />
|
||||
</TabPane>
|
||||
<TabPane tab={I18n.t('variable_Tabname_test_data')} itemKey="values">
|
||||
<VariablesValue projectID={projectID} version={version} />
|
||||
</TabPane>
|
||||
</TabBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { useVariableGroupsStore } from '../../store';
|
||||
/**
|
||||
* commit variable
|
||||
* @param projectID
|
||||
* @returns
|
||||
*/
|
||||
export async function submit(projectID: string) {
|
||||
const { getAllRootVariables, getDtoVariable } =
|
||||
useVariableGroupsStore.getState();
|
||||
const res = await MemoryApi.UpdateProjectVariable({
|
||||
ProjectID: projectID,
|
||||
VariableList: getAllRootVariables().map(item => getDtoVariable(item)),
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
Toast.success(I18n.t('Update_success'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and make sure projectID is a non-empty string
|
||||
* @param projectID possibly empty project ID
|
||||
* @Returns whether projectID is a non-empty string
|
||||
*/
|
||||
export const checkProjectID = (projectID: unknown): projectID is string =>
|
||||
typeof projectID === 'string' && projectID.length > 0;
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
useVariableGroupsStore,
|
||||
type VariableGroup,
|
||||
type Variable,
|
||||
type VariableMeta,
|
||||
VariableTypeDTO,
|
||||
VariableSchemaDTO,
|
||||
ViewVariableType,
|
||||
} from './variable-groups';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
useVariableGroupsStore,
|
||||
type VariableGroup,
|
||||
type Variable,
|
||||
type VariableMeta,
|
||||
} from './store';
|
||||
export { ViewVariableType, VariableTypeDTO, VariableSchemaDTO } from './types';
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { devtools, subscribeWithSelector } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { produce } from 'immer';
|
||||
import {
|
||||
type project_memory as ProjectMemory,
|
||||
type VariableChannel,
|
||||
VariableType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { traverse } from '../../utils/traverse';
|
||||
import { type ViewVariableType, ObjectLikeTypes } from './types';
|
||||
import { getDtoVariable } from './transform/vo2dto';
|
||||
import { getGroupListByDto } from './transform/dto2vo';
|
||||
|
||||
export interface Variable {
|
||||
variableId: string;
|
||||
type: ViewVariableType;
|
||||
name: string;
|
||||
children: Variable[];
|
||||
defaultValue: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
channel: VariableChannel;
|
||||
effectiveChannelList: string[];
|
||||
variableType: VariableType;
|
||||
readonly: boolean;
|
||||
groupId: string;
|
||||
parentId: string;
|
||||
meta: VariableMeta;
|
||||
}
|
||||
|
||||
export interface VariableMeta {
|
||||
isHistory: boolean;
|
||||
level?: number;
|
||||
hasObjectLike?: boolean;
|
||||
field?: string;
|
||||
}
|
||||
|
||||
export interface VariableGroup {
|
||||
groupId: string;
|
||||
groupName: string;
|
||||
groupDesc: string;
|
||||
groupExtDesc: string;
|
||||
isReadOnly: boolean;
|
||||
channel: VariableChannel;
|
||||
subGroupList: VariableGroup[];
|
||||
varInfoList: Variable[];
|
||||
raw: ProjectMemory.GroupVariableInfo;
|
||||
}
|
||||
|
||||
export interface VariableGroupsStore {
|
||||
variableGroups: VariableGroup[];
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
export const getDefaultVariableGroupStore = (): VariableGroupsStore => ({
|
||||
canEdit: false,
|
||||
variableGroups: [],
|
||||
});
|
||||
|
||||
export interface VariableGroupsAction {
|
||||
setVariableGroups: (variableGroups: VariableGroup[]) => void;
|
||||
createVariable: (variableInfo: {
|
||||
variableType: ViewVariableType;
|
||||
groupId: string;
|
||||
parentId: string;
|
||||
channel: VariableChannel;
|
||||
}) => Variable;
|
||||
// Update variables, according to groupId and variableId
|
||||
updateVariable: (newVariable: Variable) => void;
|
||||
// Update the meta information of the variable
|
||||
updateMeta: (params: {
|
||||
variables: Variable[];
|
||||
level?: number;
|
||||
parentId?: string;
|
||||
}) => void;
|
||||
// Add root node variable
|
||||
addRootVariable: (variable: Omit<Variable, 'channel'>) => void;
|
||||
// Add sub-node variable
|
||||
addChildVariable: (variable: Variable) => void;
|
||||
// Delete variable
|
||||
deleteVariable: (variable: Variable) => void;
|
||||
// After being preserved, it is treated as a historical variable
|
||||
saveHistory: () => void;
|
||||
// Get DTO variable
|
||||
getDtoVariable: (variable: Variable) => ProjectMemory.Variable;
|
||||
// Get all the variables under groups
|
||||
getAllRootVariables: () => Variable[];
|
||||
// Get all the variables under groups
|
||||
getAllVariables: () => Variable[];
|
||||
transformDto2Vo: (data: ProjectMemory.GroupVariableInfo[]) => VariableGroup[];
|
||||
initStore: (data: {
|
||||
variableGroups: ProjectMemory.GroupVariableInfo[];
|
||||
canEdit: boolean;
|
||||
}) => void;
|
||||
clear: () => void;
|
||||
// Locate variables in the variable tree and optionally modify or delete them
|
||||
findAndModifyVariable: (
|
||||
groupId: string,
|
||||
predicate: (variable: Variable) => boolean,
|
||||
options?: {
|
||||
modifyVariable?: (variable: Variable) => void;
|
||||
removeVariable?: boolean;
|
||||
mark?: string;
|
||||
},
|
||||
) => Variable | null;
|
||||
}
|
||||
|
||||
export const useVariableGroupsStore = create<
|
||||
VariableGroupsStore & VariableGroupsAction
|
||||
>()(
|
||||
devtools(
|
||||
subscribeWithSelector((set, get) => ({
|
||||
...getDefaultVariableGroupStore(),
|
||||
setVariableGroups: variableGroups =>
|
||||
set({ variableGroups }, false, 'setVariableGroups'),
|
||||
createVariable: baseInfo => ({
|
||||
variableId: nanoid(),
|
||||
type: baseInfo.variableType,
|
||||
name: '',
|
||||
enabled: true,
|
||||
children: [],
|
||||
defaultValue: '',
|
||||
description: '',
|
||||
channel: baseInfo.channel,
|
||||
effectiveChannelList: [],
|
||||
variableType: VariableType.KVVariable,
|
||||
readonly: false,
|
||||
groupId: baseInfo.groupId,
|
||||
parentId: baseInfo.parentId,
|
||||
meta: {
|
||||
isHistory: false,
|
||||
},
|
||||
}),
|
||||
addRootVariable: variable => {
|
||||
set(
|
||||
produce<VariableGroupsStore>(state => {
|
||||
const findGroup = state.variableGroups.find(
|
||||
item => item.groupId === variable.groupId,
|
||||
);
|
||||
if (!findGroup) {
|
||||
return;
|
||||
}
|
||||
findGroup.varInfoList.push({
|
||||
...variable,
|
||||
channel: findGroup.channel,
|
||||
});
|
||||
get().updateMeta({
|
||||
variables: findGroup.varInfoList,
|
||||
level: 0,
|
||||
parentId: '',
|
||||
});
|
||||
}),
|
||||
false,
|
||||
'addRootVariable',
|
||||
);
|
||||
},
|
||||
addChildVariable: variable => {
|
||||
get().findAndModifyVariable(
|
||||
variable.groupId,
|
||||
item => item.variableId === variable.parentId,
|
||||
{
|
||||
modifyVariable: parentNode => {
|
||||
parentNode.children.push(variable);
|
||||
get().updateMeta({
|
||||
variables: parentNode.children,
|
||||
level: (parentNode.meta.level || 0) + 1,
|
||||
parentId: parentNode.variableId,
|
||||
});
|
||||
},
|
||||
mark: 'addChildVariable',
|
||||
},
|
||||
);
|
||||
},
|
||||
deleteVariable: variable => {
|
||||
get().findAndModifyVariable(
|
||||
variable.groupId,
|
||||
item => item.variableId === variable.variableId,
|
||||
{ removeVariable: true, mark: 'deleteVariable' },
|
||||
);
|
||||
},
|
||||
findAndModifyVariable: (groupId, predicate, options) => {
|
||||
let foundVariable: Variable | null = null;
|
||||
|
||||
set(
|
||||
produce<VariableGroupsStore>(state => {
|
||||
const findInGroups = (groups: VariableGroup[]): boolean => {
|
||||
for (const group of groups) {
|
||||
if (group.groupId === groupId) {
|
||||
if (findInTree(group.varInfoList, predicate, options)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (group.subGroupList?.length) {
|
||||
if (findInGroups(group.subGroupList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const findInTree = (
|
||||
variables: Variable[],
|
||||
predicateIn: (variable: Variable) => boolean,
|
||||
optionsIn?: {
|
||||
modifyVariable?: (variable: Variable) => void;
|
||||
removeVariable?: boolean;
|
||||
},
|
||||
): boolean => {
|
||||
for (let i = 0; i < variables.length; i++) {
|
||||
if (predicateIn(variables[i])) {
|
||||
foundVariable = cloneDeep(variables[i]);
|
||||
if (optionsIn?.removeVariable) {
|
||||
variables.splice(i, 1);
|
||||
}
|
||||
if (optionsIn?.modifyVariable) {
|
||||
optionsIn.modifyVariable(variables[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (variables[i].children?.length) {
|
||||
if (
|
||||
findInTree(variables[i].children, predicateIn, optionsIn)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
findInGroups(state.variableGroups);
|
||||
}),
|
||||
false,
|
||||
options?.mark || 'findVariableInTree',
|
||||
);
|
||||
|
||||
return foundVariable;
|
||||
},
|
||||
updateVariable: newVariable => {
|
||||
get().findAndModifyVariable(
|
||||
newVariable.groupId,
|
||||
variable => variable.variableId === newVariable.variableId,
|
||||
{
|
||||
mark: 'updateVariable',
|
||||
modifyVariable: variable => {
|
||||
Object.assign(variable, newVariable);
|
||||
get().updateMeta({
|
||||
variables: [variable],
|
||||
level: variable.meta.level,
|
||||
parentId: variable.parentId,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
updateMeta: ({ variables, level = 0, parentId = '' }) => {
|
||||
variables.forEach(item => {
|
||||
item.meta.level = level;
|
||||
item.meta.hasObjectLike = ObjectLikeTypes.includes(item.type);
|
||||
item.parentId = parentId;
|
||||
if (item.children?.length) {
|
||||
get().updateMeta({
|
||||
variables: item.children,
|
||||
level: level + 1,
|
||||
parentId: item.variableId,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
saveHistory: () => {
|
||||
set(
|
||||
produce<VariableGroupsStore>(state => {
|
||||
state.variableGroups.forEach(item => {
|
||||
traverse(item.varInfoList, itemIn => {
|
||||
itemIn.meta.isHistory = true;
|
||||
});
|
||||
});
|
||||
}),
|
||||
false,
|
||||
'saveHistory',
|
||||
);
|
||||
},
|
||||
getAllRootVariables: () => {
|
||||
const { variableGroups } = get();
|
||||
const res: Variable[] = [];
|
||||
traverse(
|
||||
variableGroups,
|
||||
item => {
|
||||
res.push(...item.varInfoList);
|
||||
},
|
||||
'subGroupList',
|
||||
);
|
||||
return res;
|
||||
},
|
||||
getAllVariables: () => {
|
||||
const { variableGroups } = get();
|
||||
const variables = variableGroups.map(item => item.varInfoList).flat();
|
||||
const res: Variable[] = [];
|
||||
traverse(
|
||||
variables,
|
||||
item => {
|
||||
res.push(item);
|
||||
},
|
||||
'children',
|
||||
);
|
||||
return res;
|
||||
},
|
||||
transformDto2Vo: data => {
|
||||
const transformedData = getGroupListByDto(data);
|
||||
// After the data conversion is completed, update the meta information immediately
|
||||
transformedData.forEach(group => {
|
||||
get().updateMeta({ variables: group.varInfoList });
|
||||
});
|
||||
return transformedData;
|
||||
},
|
||||
getDtoVariable: (variable: Variable) => getDtoVariable(variable),
|
||||
initStore: data => {
|
||||
const { transformDto2Vo } = get();
|
||||
const transformedData = transformDto2Vo(data.variableGroups);
|
||||
set(
|
||||
{
|
||||
variableGroups: transformedData,
|
||||
canEdit: data.canEdit,
|
||||
},
|
||||
false,
|
||||
'initStore',
|
||||
);
|
||||
},
|
||||
clear: () => {
|
||||
set({ ...getDefaultVariableGroupStore() }, false, 'clear');
|
||||
},
|
||||
})),
|
||||
{
|
||||
enabled: IS_DEV_MODE,
|
||||
name: 'knowledge.variableGroups',
|
||||
},
|
||||
),
|
||||
);
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import {
|
||||
exhaustiveCheckSimple,
|
||||
safeAsyncThrow,
|
||||
} from '@coze-common/chat-area-utils';
|
||||
import { typeSafeJSONParse } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
type project_memory as ProjectMemory,
|
||||
VariableChannel,
|
||||
VariableType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
|
||||
import {
|
||||
VariableTypeDTO,
|
||||
type VariableSchemaDTO,
|
||||
ViewVariableType,
|
||||
} from '../types';
|
||||
import { type VariableGroup, type Variable } from '../store';
|
||||
|
||||
export const getGroupListByDto = (
|
||||
dtoGroups: ProjectMemory.GroupVariableInfo[],
|
||||
): VariableGroup[] => {
|
||||
const groups = dtoGroups?.map(group => {
|
||||
const baseGroupInfo = getBaseGroupInfoByDto(group);
|
||||
const { groupId } = baseGroupInfo;
|
||||
const varInfoList = getGroupVariableListByDto({
|
||||
group,
|
||||
groupId,
|
||||
});
|
||||
return {
|
||||
...baseGroupInfo,
|
||||
varInfoList,
|
||||
subGroupList: getSubGroupListByDto({
|
||||
group,
|
||||
groupId,
|
||||
}),
|
||||
};
|
||||
});
|
||||
return groups || [];
|
||||
};
|
||||
|
||||
const getBaseGroupInfoByDto = (
|
||||
group: Partial<ProjectMemory.GroupVariableInfo>,
|
||||
): Omit<VariableGroup, 'subGroupList' | 'varInfoList'> => {
|
||||
const {
|
||||
GroupName: groupName = '',
|
||||
GroupDesc: groupDesc = '',
|
||||
GroupExtDesc: groupExtDesc = '',
|
||||
IsReadOnly: isReadOnly = false,
|
||||
DefaultChannel: channel = VariableChannel.Custom,
|
||||
} = group;
|
||||
const groupId = nanoid();
|
||||
return {
|
||||
groupId,
|
||||
groupName,
|
||||
groupDesc,
|
||||
groupExtDesc,
|
||||
channel,
|
||||
isReadOnly,
|
||||
raw: group,
|
||||
};
|
||||
};
|
||||
|
||||
const getGroupVariableListByDto = ({
|
||||
group,
|
||||
groupId,
|
||||
}: {
|
||||
group: Partial<ProjectMemory.GroupVariableInfo>;
|
||||
groupId: string;
|
||||
}): Variable[] => {
|
||||
const { VarInfoList: varInfoList = [] } = group;
|
||||
return (
|
||||
varInfoList?.map(dtoVariable =>
|
||||
getViewVariableByDto(dtoVariable, groupId),
|
||||
) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
const getSubGroupListByDto = ({
|
||||
group,
|
||||
groupId,
|
||||
}: {
|
||||
group: Partial<ProjectMemory.GroupVariableInfo>;
|
||||
groupId: string;
|
||||
}): VariableGroup[] => {
|
||||
const { SubGroupList: subGroupList = [] } = group;
|
||||
return (
|
||||
subGroupList?.map(subGroup => ({
|
||||
...getBaseGroupInfoByDto({
|
||||
...subGroup,
|
||||
DefaultChannel: group.DefaultChannel, // The subGroup returned by the server level has no DefaultChannel and needs to be set manually
|
||||
}),
|
||||
groupId,
|
||||
varInfoList: getGroupVariableListByDto({
|
||||
group: subGroup,
|
||||
groupId,
|
||||
}),
|
||||
subGroupList: [],
|
||||
})) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
export function getViewVariableByDto(
|
||||
dtoVariable: ProjectMemory.Variable,
|
||||
groupId: string,
|
||||
): Variable {
|
||||
const variableSchema = typeSafeJSONParse(
|
||||
dtoVariable.Schema || '{}',
|
||||
) as VariableSchemaDTO;
|
||||
|
||||
const { type } = variableSchema;
|
||||
|
||||
const baseVariable = createBaseVariable({
|
||||
dtoVariable,
|
||||
groupId,
|
||||
});
|
||||
|
||||
if (type === VariableTypeDTO.List) {
|
||||
return convertListVariable(baseVariable, variableSchema);
|
||||
}
|
||||
|
||||
if (type === VariableTypeDTO.Object) {
|
||||
return convertObjectVariable(baseVariable, variableSchema);
|
||||
}
|
||||
|
||||
return {
|
||||
...baseVariable,
|
||||
type: dTOTypeToViewType(variableSchema.type),
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function dTOTypeToViewType(
|
||||
type: VariableTypeDTO,
|
||||
{
|
||||
arrayItemType,
|
||||
}: {
|
||||
arrayItemType?: VariableTypeDTO;
|
||||
} = {},
|
||||
): ViewVariableType {
|
||||
switch (type) {
|
||||
case VariableTypeDTO.Boolean:
|
||||
return ViewVariableType.Boolean;
|
||||
case VariableTypeDTO.Integer:
|
||||
return ViewVariableType.Integer;
|
||||
case VariableTypeDTO.Float:
|
||||
return ViewVariableType.Number;
|
||||
case VariableTypeDTO.String:
|
||||
return ViewVariableType.String;
|
||||
case VariableTypeDTO.Object:
|
||||
return ViewVariableType.Object;
|
||||
case VariableTypeDTO.List:
|
||||
if (!arrayItemType) {
|
||||
throw new Error(
|
||||
`Unkown variable DTO list need sub type but get ${arrayItemType}`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (arrayItemType) {
|
||||
case VariableTypeDTO.Boolean:
|
||||
return ViewVariableType.ArrayBoolean;
|
||||
case VariableTypeDTO.Integer:
|
||||
return ViewVariableType.ArrayInteger;
|
||||
case VariableTypeDTO.Float:
|
||||
return ViewVariableType.ArrayNumber;
|
||||
case VariableTypeDTO.String:
|
||||
return ViewVariableType.ArrayString;
|
||||
case VariableTypeDTO.Object:
|
||||
return ViewVariableType.ArrayObject;
|
||||
case VariableTypeDTO.List:
|
||||
safeAsyncThrow(
|
||||
`List type variable can't have sub list type: ${type}:${arrayItemType}`,
|
||||
);
|
||||
return ViewVariableType.String;
|
||||
default:
|
||||
exhaustiveCheckSimple(arrayItemType);
|
||||
safeAsyncThrow(`Unknown variable DTO Type: ${type}:${arrayItemType}`);
|
||||
return ViewVariableType.String;
|
||||
}
|
||||
default:
|
||||
exhaustiveCheckSimple(type);
|
||||
safeAsyncThrow(`Unknown variable DTO Type: ${type}:${arrayItemType}`);
|
||||
return ViewVariableType.String;
|
||||
}
|
||||
}
|
||||
|
||||
function createBaseVariable({
|
||||
dtoVariable,
|
||||
groupId,
|
||||
}: {
|
||||
dtoVariable: ProjectMemory.Variable;
|
||||
groupId: string;
|
||||
}): Omit<Variable, 'type' | 'children'> {
|
||||
return {
|
||||
variableId: nanoid(),
|
||||
name: dtoVariable.Keyword ?? '',
|
||||
description: dtoVariable.Description ?? '',
|
||||
enabled: dtoVariable.Enable ?? true,
|
||||
defaultValue: dtoVariable.DefaultValue ?? '',
|
||||
channel: dtoVariable.Channel ?? VariableChannel.Custom,
|
||||
effectiveChannelList: dtoVariable.EffectiveChannelList ?? [],
|
||||
variableType: dtoVariable.VariableType ?? VariableType.KVVariable,
|
||||
readonly: dtoVariable.IsReadOnly ?? false,
|
||||
groupId,
|
||||
parentId: '',
|
||||
meta: {
|
||||
isHistory: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function convertListVariable(
|
||||
baseVariable: Omit<Variable, 'type' | 'children'>,
|
||||
variableSchema: VariableSchemaDTO,
|
||||
): Variable {
|
||||
const subVariableSchema = variableSchema.schema as VariableSchemaDTO;
|
||||
|
||||
const { type: subVariableType } = subVariableSchema;
|
||||
|
||||
if (subVariableType === VariableTypeDTO.Object) {
|
||||
return convertListObjectVariable(baseVariable, variableSchema);
|
||||
}
|
||||
|
||||
return {
|
||||
...baseVariable,
|
||||
type: dTOTypeToViewType(variableSchema.type, {
|
||||
arrayItemType: subVariableType,
|
||||
}),
|
||||
children: [],
|
||||
} as unknown as Variable;
|
||||
}
|
||||
|
||||
/**
|
||||
*@example schema: array<object>
|
||||
{
|
||||
"type": "list",
|
||||
"name": "arr_obj",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"schema": [{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"required": false
|
||||
}, {
|
||||
"type": "integer",
|
||||
"name": "age",
|
||||
"required": false
|
||||
}]
|
||||
},
|
||||
}
|
||||
*/
|
||||
function convertListObjectVariable(
|
||||
baseVariable: Omit<Variable, 'type' | 'children'>,
|
||||
variableSchema: VariableSchemaDTO,
|
||||
): Variable {
|
||||
const subVariableSchema = variableSchema.schema;
|
||||
|
||||
if (!subVariableSchema) {
|
||||
throw new Error('List object variable schema is invalid');
|
||||
}
|
||||
|
||||
const { type: subVariableType } = subVariableSchema;
|
||||
|
||||
return {
|
||||
...baseVariable,
|
||||
type: dTOTypeToViewType(VariableTypeDTO.List, {
|
||||
arrayItemType: subVariableType,
|
||||
}),
|
||||
children: Array.isArray(subVariableSchema.schema)
|
||||
? subVariableSchema.schema.map(schema =>
|
||||
createVariableBySchema(schema, {
|
||||
groupId: baseVariable.groupId,
|
||||
parentId: baseVariable.variableId,
|
||||
}),
|
||||
)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @example schema: object
|
||||
* object
|
||||
{
|
||||
"type": "object",
|
||||
"name": "obj",
|
||||
"schema": [{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"required": false
|
||||
}, {
|
||||
"type": "integer",
|
||||
"name": "age",
|
||||
"required": false
|
||||
}],
|
||||
}
|
||||
* @returns
|
||||
*/
|
||||
function convertObjectVariable(
|
||||
baseVariable: Omit<Variable, 'type' | 'children'>,
|
||||
variableSchema: VariableSchemaDTO,
|
||||
): Variable {
|
||||
const schema = variableSchema.schema || [];
|
||||
|
||||
return {
|
||||
...baseVariable,
|
||||
type: dTOTypeToViewType(variableSchema.type),
|
||||
children: Array.isArray(schema)
|
||||
? schema.map(subMeta =>
|
||||
createVariableBySchema(subMeta, {
|
||||
groupId: baseVariable.groupId,
|
||||
parentId: baseVariable.variableId,
|
||||
}),
|
||||
)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
function createVariableBySchema(
|
||||
subMeta: VariableSchemaDTO,
|
||||
{
|
||||
groupId,
|
||||
parentId,
|
||||
}: {
|
||||
groupId: string;
|
||||
parentId: string;
|
||||
},
|
||||
): Variable {
|
||||
return getViewVariableByDto(
|
||||
{
|
||||
Keyword: subMeta.name,
|
||||
Description: subMeta.description,
|
||||
Schema: JSON.stringify(subMeta),
|
||||
Enable: true,
|
||||
IsReadOnly: subMeta.readonly,
|
||||
},
|
||||
groupId,
|
||||
);
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
exhaustiveCheckSimple,
|
||||
safeAsyncThrow,
|
||||
} from '@coze-common/chat-area-utils';
|
||||
import { type project_memory as ProjectMemory } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type VariableSchemaDTO, VariableTypeDTO } from '../types';
|
||||
import { type Variable } from '../store';
|
||||
|
||||
/**
|
||||
* Front-end variable type
|
||||
*/
|
||||
export enum ViewVariableType {
|
||||
String = 1,
|
||||
Integer,
|
||||
Boolean,
|
||||
Number,
|
||||
Object = 6,
|
||||
// The above is the InputType defined in the api. The following is the integrated one. Start from 99 to avoid collisions with the backend definition.
|
||||
ArrayString = 99,
|
||||
ArrayInteger,
|
||||
ArrayBoolean,
|
||||
ArrayNumber,
|
||||
ArrayObject,
|
||||
}
|
||||
|
||||
export function viewTypeToDTO(type: ViewVariableType): {
|
||||
type: VariableTypeDTO;
|
||||
arrayItemType?: VariableTypeDTO;
|
||||
} {
|
||||
switch (type) {
|
||||
case ViewVariableType.Boolean:
|
||||
return { type: VariableTypeDTO.Boolean };
|
||||
case ViewVariableType.Integer:
|
||||
return { type: VariableTypeDTO.Integer };
|
||||
case ViewVariableType.Number:
|
||||
return { type: VariableTypeDTO.Float };
|
||||
case ViewVariableType.String:
|
||||
return { type: VariableTypeDTO.String };
|
||||
case ViewVariableType.Object:
|
||||
return { type: VariableTypeDTO.Object };
|
||||
case ViewVariableType.ArrayBoolean:
|
||||
return {
|
||||
type: VariableTypeDTO.List,
|
||||
arrayItemType: VariableTypeDTO.Boolean,
|
||||
};
|
||||
case ViewVariableType.ArrayInteger:
|
||||
return {
|
||||
type: VariableTypeDTO.List,
|
||||
arrayItemType: VariableTypeDTO.Integer,
|
||||
};
|
||||
case ViewVariableType.ArrayNumber:
|
||||
return {
|
||||
type: VariableTypeDTO.List,
|
||||
arrayItemType: VariableTypeDTO.Float,
|
||||
};
|
||||
case ViewVariableType.ArrayString:
|
||||
return {
|
||||
type: VariableTypeDTO.List,
|
||||
arrayItemType: VariableTypeDTO.String,
|
||||
};
|
||||
case ViewVariableType.ArrayObject:
|
||||
return {
|
||||
type: VariableTypeDTO.List,
|
||||
arrayItemType: VariableTypeDTO.Object,
|
||||
};
|
||||
default:
|
||||
exhaustiveCheckSimple(type);
|
||||
safeAsyncThrow(`Unknown view variable type: ${type}`);
|
||||
return { type: VariableTypeDTO.String };
|
||||
}
|
||||
}
|
||||
|
||||
export const getDtoVariable = (
|
||||
viewVariable: Variable,
|
||||
): ProjectMemory.Variable => {
|
||||
const { type, arrayItemType } = viewTypeToDTO(viewVariable.type);
|
||||
|
||||
const schema: VariableSchemaDTO = {
|
||||
name: viewVariable.name,
|
||||
enable: viewVariable.enabled,
|
||||
description: viewVariable.description || '',
|
||||
type,
|
||||
readonly: Boolean(viewVariable.readonly),
|
||||
schema: '',
|
||||
};
|
||||
|
||||
// Working with array types
|
||||
if (type === VariableTypeDTO.List && arrayItemType) {
|
||||
if (arrayItemType === VariableTypeDTO.Object) {
|
||||
schema.schema = {
|
||||
type: VariableTypeDTO.Object,
|
||||
schema: viewVariable.children?.map(child => {
|
||||
const childDTO = getDtoVariable(child);
|
||||
return JSON.parse(childDTO.Schema || '{}');
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
schema.schema = {
|
||||
type: arrayItemType,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handling object types
|
||||
if (type === VariableTypeDTO.Object) {
|
||||
schema.schema = viewVariable.children?.map(child => {
|
||||
const childDTO = getDtoVariable(child);
|
||||
return JSON.parse(childDTO.Schema || '{}');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Keyword: viewVariable.name,
|
||||
Channel: viewVariable.channel,
|
||||
VariableType: viewVariable.variableType ?? 1,
|
||||
DefaultValue: viewVariable.defaultValue,
|
||||
Description: viewVariable.description,
|
||||
EffectiveChannelList: viewVariable.effectiveChannelList,
|
||||
Enable: Boolean(viewVariable.enabled),
|
||||
IsReadOnly: Boolean(viewVariable.readonly),
|
||||
Schema: JSON.stringify(schema, null, 0),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum VariableTypeDTO {
|
||||
Object = 'object',
|
||||
List = 'list',
|
||||
String = 'string',
|
||||
Integer = 'integer',
|
||||
Boolean = 'boolean',
|
||||
Float = 'float',
|
||||
}
|
||||
|
||||
export interface VariableSchemaDTO {
|
||||
type: VariableTypeDTO;
|
||||
name: string;
|
||||
enable: boolean;
|
||||
description: string;
|
||||
readonly: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
schema?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Front-end variable type
|
||||
*/
|
||||
export enum ViewVariableType {
|
||||
String = 1,
|
||||
Integer,
|
||||
Boolean,
|
||||
Number,
|
||||
Object = 6,
|
||||
// The above is the InputType defined in the api. The following is the integrated one. Start from 99 to avoid collisions with the backend definition.
|
||||
ArrayString = 99,
|
||||
ArrayInteger,
|
||||
ArrayBoolean,
|
||||
ArrayNumber,
|
||||
ArrayObject,
|
||||
}
|
||||
|
||||
export const BASE_ARRAY_PAIR: [ViewVariableType, ViewVariableType][] = [
|
||||
[ViewVariableType.String, ViewVariableType.ArrayString],
|
||||
[ViewVariableType.Integer, ViewVariableType.ArrayInteger],
|
||||
[ViewVariableType.Boolean, ViewVariableType.ArrayBoolean],
|
||||
[ViewVariableType.Number, ViewVariableType.ArrayNumber],
|
||||
[ViewVariableType.Object, ViewVariableType.ArrayObject],
|
||||
];
|
||||
|
||||
export const VARIABLE_TYPE_ALIAS_MAP: Record<ViewVariableType, string> = {
|
||||
[ViewVariableType.String]: 'String',
|
||||
[ViewVariableType.Integer]: 'Integer',
|
||||
[ViewVariableType.Boolean]: 'Boolean',
|
||||
[ViewVariableType.Number]: 'Number',
|
||||
[ViewVariableType.Object]: 'Object',
|
||||
[ViewVariableType.ArrayString]: 'Array<String>',
|
||||
[ViewVariableType.ArrayInteger]: 'Array<Integer>',
|
||||
[ViewVariableType.ArrayBoolean]: 'Array<Boolean>',
|
||||
[ViewVariableType.ArrayNumber]: 'Array<Number>',
|
||||
[ViewVariableType.ArrayObject]: 'Array<Object>',
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace ViewVariableType {
|
||||
/**
|
||||
* Get the complement of all variable types
|
||||
* @param inputTypes
|
||||
*/
|
||||
export function getComplement(inputTypes: ViewVariableType[]) {
|
||||
const allTypes: ViewVariableType[] = [
|
||||
...BASE_ARRAY_PAIR.map(_pair => _pair[0]),
|
||||
...BASE_ARRAY_PAIR.map(_pair => _pair[1]),
|
||||
];
|
||||
|
||||
return allTypes.filter(type => !inputTypes.includes(type));
|
||||
}
|
||||
|
||||
export function isArrayType(type: ViewVariableType): boolean {
|
||||
const arrayTypes = BASE_ARRAY_PAIR.map(_pair => _pair[1]);
|
||||
return arrayTypes.includes(type);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const ObjectLikeTypes = [
|
||||
ViewVariableType.Object,
|
||||
ViewVariableType.ArrayObject,
|
||||
];
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CSSProperties } from 'react';
|
||||
|
||||
import { ViewVariableType } from '../store';
|
||||
|
||||
export type WithCustomStyle<T = object> = {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
} & T;
|
||||
|
||||
export const VARIABLE_TYPE_ALIAS_MAP: Record<ViewVariableType, string> = {
|
||||
[ViewVariableType.String]: 'String',
|
||||
[ViewVariableType.Integer]: 'Integer',
|
||||
[ViewVariableType.Boolean]: 'Boolean',
|
||||
[ViewVariableType.Number]: 'Number',
|
||||
[ViewVariableType.Object]: 'Object',
|
||||
[ViewVariableType.ArrayString]: 'Array<String>',
|
||||
[ViewVariableType.ArrayInteger]: 'Array<Integer>',
|
||||
[ViewVariableType.ArrayBoolean]: 'Array<Boolean>',
|
||||
[ViewVariableType.ArrayNumber]: 'Array<Number>',
|
||||
[ViewVariableType.ArrayObject]: 'Array<Object>',
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
export function traverse<
|
||||
T extends { [key in K]?: T[] },
|
||||
K extends string = 'children',
|
||||
>(
|
||||
nodeOrNodes: T | T[],
|
||||
action: (node: T) => void,
|
||||
traverseKey: K = 'children' as K,
|
||||
maxDepth = Infinity,
|
||||
currentDepth = 0,
|
||||
) {
|
||||
const nodes = Array.isArray(nodeOrNodes) ? nodeOrNodes : [nodeOrNodes];
|
||||
nodes.forEach(node => {
|
||||
action(node);
|
||||
if (currentDepth < maxDepth) {
|
||||
const children = node[traverseKey] ?? [];
|
||||
if (children?.length > 0) {
|
||||
traverse(children, action, traverseKey, maxDepth, currentDepth + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import cls from 'classnames';
|
||||
import { IllustrationNoContent } from '@douyinfe/semi-illustrations';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { VariableChannel } from '@coze-arch/bot-api/memory';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Empty,
|
||||
Form,
|
||||
type FormApi,
|
||||
IconButton,
|
||||
Spin,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useVariableGroupsStore, type Variable } from '@/store';
|
||||
import { useLeaveWarning } from '@/hooks/use-case/use-leave-waring';
|
||||
import { useInit } from '@/hooks/life-cycle/use-init';
|
||||
import { useDestory } from '@/hooks/life-cycle/use-destory';
|
||||
import { VariableContext } from '@/context';
|
||||
import { VariableGroup as VariableGroupComponent } from '@/components/variable-group';
|
||||
|
||||
import {
|
||||
checkProjectID,
|
||||
submit,
|
||||
} from './service/use-case-service/submit-service';
|
||||
import { useChangeWarning } from './hooks/use-case/use-change-warning';
|
||||
|
||||
export interface VariableConfigProps {
|
||||
projectID: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export const VariablesConfig = ({
|
||||
projectID,
|
||||
version,
|
||||
}: VariableConfigProps) => {
|
||||
const formApiRef = useRef<FormApi | null>(null);
|
||||
|
||||
const { loading } = useInit(projectID, version);
|
||||
useDestory();
|
||||
const { setHasUnsavedChanges, hasUnsavedChanges } = useLeaveWarning();
|
||||
const { variableGroups, canEdit, saveHistory, getAllVariables } =
|
||||
useVariableGroupsStore(
|
||||
useShallow(state => ({
|
||||
variableGroups: state.variableGroups,
|
||||
canEdit: state.canEdit,
|
||||
saveHistory: state.saveHistory,
|
||||
getAllVariables: state.getAllVariables,
|
||||
})),
|
||||
);
|
||||
|
||||
const { isShowBanner, showBanner, hideBanner, hideBannerForever } =
|
||||
useChangeWarning();
|
||||
|
||||
const isEmpty = !variableGroups.length;
|
||||
|
||||
const onVariableChange = (changeValue: Variable) => {
|
||||
setHasUnsavedChanges(true);
|
||||
if (changeValue.meta?.isHistory) {
|
||||
showBanner();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!checkProjectID(projectID)) {
|
||||
return;
|
||||
}
|
||||
const formApi = formApiRef.current;
|
||||
if (!formApi) {
|
||||
return;
|
||||
}
|
||||
const isValid = await formApi.validate();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
saveHistory();
|
||||
await submit(projectID);
|
||||
setHasUnsavedChanges(false);
|
||||
};
|
||||
|
||||
const initValues = getAllVariables().reduce((acc, curr) => {
|
||||
acc[curr.variableId] = { name: curr.name };
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
formApiRef.current?.setValues(initValues);
|
||||
}, [loading, initValues]);
|
||||
|
||||
return (
|
||||
<VariableContext.Provider
|
||||
value={{
|
||||
variablePageCanEdit: canEdit,
|
||||
groups: variableGroups,
|
||||
}}
|
||||
>
|
||||
<div className="p-4 pb-[72px]">
|
||||
{loading ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spin />
|
||||
</div>
|
||||
) : isEmpty ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Empty
|
||||
image={<IllustrationNoContent className="w-[140px] h-[140px]" />}
|
||||
title={I18n.t('card_builder_varpanel_var_empty')}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{isShowBanner ? (
|
||||
<div className="h-[36px] flex items-center justify-center coz-mg-hglt coz-fg-primary text-sm mb-4 mt-[-16px] mx-[-16px]">
|
||||
<div className="flex items-center ml-auto">
|
||||
{I18n.t('variable_config_change_banner')}
|
||||
</div>
|
||||
<div className="flex items-center ml-auto cursor-pointer">
|
||||
<div
|
||||
className="coz-fg-secondary text-xs"
|
||||
onClick={hideBannerForever}
|
||||
>
|
||||
{I18n.t('do_not_remind_again')}
|
||||
</div>
|
||||
<IconButton
|
||||
className="ml-2 !bg-transparent"
|
||||
onClick={hideBanner}
|
||||
icon={<IconCozCross />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Form<typeof initValues>
|
||||
getFormApi={formApi => {
|
||||
formApiRef.current = formApi;
|
||||
}}
|
||||
showValidateIcon={false}
|
||||
autoScrollToError
|
||||
initValues={initValues}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
{variableGroups.map(item => (
|
||||
<VariableGroupComponent
|
||||
readonly={!canEdit || item.isReadOnly}
|
||||
groupInfo={item}
|
||||
onVariableChange={onVariableChange}
|
||||
validateExistKeyword={item.channel === VariableChannel.APP}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Form>
|
||||
<div
|
||||
className={cls(
|
||||
'flex items-center justify-end',
|
||||
'fixed bottom-[1px] right-[1px] left-[1px] pb-4 pt-6',
|
||||
'bg-white mr-4 px-4',
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!canEdit || !hasUnsavedChanges}
|
||||
>
|
||||
{I18n.t('edit_variables_modal_ok_text')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</VariableContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import classNames from 'classnames';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { IllustrationNoContent } from '@douyinfe/semi-illustrations';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { typeSafeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { type KVItem } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IconCozRefresh,
|
||||
IconCozCrossCircleFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Table,
|
||||
Select,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Empty,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
export interface VariablesValueProps {
|
||||
projectID: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export function VariablesValue({ projectID, version }: VariablesValueProps) {
|
||||
const { loading, data, refresh } = useRequest(async () => {
|
||||
const res = await MemoryApi.GetPlayGroundMemory({
|
||||
project_id: projectID,
|
||||
...(version ? { version } : {}),
|
||||
});
|
||||
return res.memories ?? [];
|
||||
});
|
||||
|
||||
const handleClear = async (item: KVItem) => {
|
||||
if (!item.keyword) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: projectID,
|
||||
resource_type: 'variable',
|
||||
action: 'reset',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_preview',
|
||||
});
|
||||
|
||||
await MemoryApi.DelProfileMemory({
|
||||
project_id: projectID,
|
||||
keywords: [item.keyword],
|
||||
});
|
||||
|
||||
refresh();
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
project_id: projectID,
|
||||
resource_type: 'variable',
|
||||
action: 'reset',
|
||||
source: 'app_detail_page',
|
||||
source_detail: 'memory_preview',
|
||||
});
|
||||
|
||||
await MemoryApi.DelProfileMemory({ project_id: projectID });
|
||||
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'h-full p-4',
|
||||
'[&_.semi-table-row]:!bg-transparent',
|
||||
'[&_.semi-table-row-head]:!bg-transparent',
|
||||
'[&_.semi-table-row-cell]:text-[14px]',
|
||||
)}
|
||||
>
|
||||
<Table
|
||||
useHoverStyle={false}
|
||||
empty={
|
||||
<Empty
|
||||
image={<IllustrationNoContent className="w-[140px] h-[140px]" />}
|
||||
title={I18n.t('variables_user_data_empty')}
|
||||
/>
|
||||
}
|
||||
tableProps={{
|
||||
loading,
|
||||
dataSource: data,
|
||||
columns: [
|
||||
{
|
||||
title: I18n.t('variable_Table_Title_name'),
|
||||
dataIndex: 'keyword',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div className={'flex items-center'}>
|
||||
<span className={'mr-4px'}>
|
||||
{I18n.t('variable_Table_Title_value')}
|
||||
</span>
|
||||
<Tooltip
|
||||
theme={'dark'}
|
||||
content={I18n.t('variable_Button_reset_variable')}
|
||||
>
|
||||
<IconButton
|
||||
color={'primary'}
|
||||
icon={<IconCozRefresh />}
|
||||
size={'small'}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'value',
|
||||
render: (value: string, item: KVItem) => {
|
||||
const schema = typeSafeJSONParse(item?.schema) as
|
||||
| { readonly?: boolean }
|
||||
| undefined;
|
||||
|
||||
if (schema?.readonly) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="w-full truncate"
|
||||
value={value}
|
||||
showArrow={false}
|
||||
showClear={true}
|
||||
emptyContent={null}
|
||||
onClear={() => handleClear(item)}
|
||||
clearIcon={
|
||||
<IconButton
|
||||
theme={'borderless'}
|
||||
color={'secondary'}
|
||||
icon={<IconCozCrossCircleFill />}
|
||||
size={'large'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('variable_Table_Title_edit_time'),
|
||||
align: 'left',
|
||||
dataIndex: 'update_time',
|
||||
width: 150,
|
||||
render: (time: number, item: KVItem) =>
|
||||
item.value && dayjs.unix(time).format('YYYY-MM-DD HH:mm'),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user