feat(code): add sync function signature button and variable type support

add sync button to update function signature based on variables
store variable type in var-list for better type handling
pull/21667/head
Mminamiyama 11 months ago
parent 2f0ebaefa0
commit a0185b0665

@ -65,10 +65,11 @@ const VarList: FC<Props> = ({
}, [list, onVarNameChange, onChange]) }, [list, onVarNameChange, onChange])
const handleVarReferenceChange = useCallback((index: number) => { const handleVarReferenceChange = useCallback((index: number) => {
return (value: ValueSelector | string, varKindType: VarKindType) => { return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => {
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
if (!isSupportConstantValue || varKindType === VarKindType.variable) { if (!isSupportConstantValue || varKindType === VarKindType.variable) {
draft[index].value_selector = value as ValueSelector draft[index].value_selector = value as ValueSelector
draft[index].value_type = varInfo?.type
if (isSupportConstantValue) if (isSupportConstantValue)
draft[index].variable_type = VarKindType.variable draft[index].variable_type = VarKindType.variable

@ -14,6 +14,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import type { NodePanelProps } from '@/app/components/workflow/types' import type { NodePanelProps } from '@/app/components/workflow/types'
import SyncButton from '@/app/components/base/button/sync-button'
const i18nPrefix = 'workflow.nodes.code' const i18nPrefix = 'workflow.nodes.code'
const codeLanguages = [ const codeLanguages = [
@ -40,6 +41,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
handleVarListChange, handleVarListChange,
handleAddVariable, handleAddVariable,
handleRemoveVariable, handleRemoveVariable,
handleSyncFunctionSignature,
handleCodeChange, handleCodeChange,
handleCodeLanguageChange, handleCodeLanguageChange,
handleVarsChange, handleVarsChange,
@ -68,7 +70,12 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
<Field <Field
title={t(`${i18nPrefix}.inputVars`)} title={t(`${i18nPrefix}.inputVars`)}
operations={ operations={
!readOnly ? <AddButton onClick={handleAddVariable} /> : undefined !readOnly ? (
<div className="flex gap-1">
<SyncButton popupContent={t(`${i18nPrefix}.syncFunctionSignature`)} onClick={handleSyncFunctionSignature} />
<AddButton onClick={handleAddVariable} />
</div>
) : undefined
} }
> >
<VarList <VarList

@ -84,6 +84,65 @@ const useConfig = (id: string, payload: CodeNodeType) => {
setInputs(newInputs) setInputs(newInputs)
}, [allLanguageDefault, inputs, setInputs]) }, [allLanguageDefault, inputs, setInputs])
const handleSyncFunctionSignature = useCallback(() => {
const generateSyncSignatureCode = (code: string) => {
let mainDefRe
let newMainDef
if (inputs.code_language === CodeLanguage.javascript) {
mainDefRe = /function\s+main\b\s*\([\s\S]*?\)/g
newMainDef = 'function main({{var_list}})'
let param_list = inputs.variables?.map(item => item.variable).join(', ') || ''
param_list = param_list ? `{${param_list}}` : ''
newMainDef = newMainDef.replace('{{var_list}}', param_list)
}
else if (inputs.code_language === CodeLanguage.python3) {
mainDefRe = /def\s+main\b\s*\([\s\S]*?\)/g
const param_list = []
for (const item of inputs.variables) {
let param = item.variable
let param_type = ''
switch (item.value_type) {
case VarType.string:
param_type = ': str'
break
case VarType.number:
param_type = ': float'
break
case VarType.object:
param_type = ': dict'
break
case VarType.array:
param_type = ': list'
break
case VarType.arrayNumber:
param_type = ': list[float]'
break
case VarType.arrayString:
param_type = ': list[str]'
break
case VarType.arrayObject:
param_type = ': list[dict]'
break
}
param += param_type
param_list.push(`${param}`)
}
newMainDef = `def main(${param_list.join(', ')})`
}
else { return code }
const newCode = code.replace(mainDefRe, newMainDef)
return newCode
}
const newInputs = produce(inputs, (draft) => {
draft.code = generateSyncSignatureCode(draft.code)
})
setInputs(newInputs)
}, [inputs, setInputs])
const { const {
handleVarsChange, handleVarsChange,
handleAddVariable: handleAddOutputVariable, handleAddVariable: handleAddOutputVariable,
@ -119,6 +178,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
handleVarListChange, handleVarListChange,
handleAddVariable, handleAddVariable,
handleRemoveVariable, handleRemoveVariable,
handleSyncFunctionSignature,
handleCodeChange, handleCodeChange,
handleCodeLanguageChange, handleCodeLanguageChange,
handleVarsChange, handleVarsChange,

@ -1,443 +1,444 @@
import type { import type {
Edge as ReactFlowEdge, Edge as ReactFlowEdge,
Node as ReactFlowNode, Node as ReactFlowNode,
Viewport, Viewport,
XYPosition, XYPosition,
} from 'reactflow' } from 'reactflow'
import type { Resolution, TransferMethod } from '@/types/app' import type { Resolution, TransferMethod } from '@/types/app'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import type { FileResponse, NodeTracing, PanelProps } from '@/types/workflow' import type { FileResponse, NodeTracing, PanelProps } from '@/types/workflow'
import type { Collection, Tool } from '@/app/components/tools/types' import type { Collection, Tool } from '@/app/components/tools/types'
import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import type { import type {
DefaultValueForm, DefaultValueForm,
ErrorHandleTypeEnum, ErrorHandleTypeEnum,
} from '@/app/components/workflow/nodes/_base/components/error-handle/types' } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types' import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types'
import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types'
export enum BlockEnum { export enum BlockEnum {
Start = 'start', Start = 'start',
End = 'end', End = 'end',
Answer = 'answer', Answer = 'answer',
LLM = 'llm', LLM = 'llm',
KnowledgeRetrieval = 'knowledge-retrieval', KnowledgeRetrieval = 'knowledge-retrieval',
QuestionClassifier = 'question-classifier', QuestionClassifier = 'question-classifier',
IfElse = 'if-else', IfElse = 'if-else',
Code = 'code', Code = 'code',
TemplateTransform = 'template-transform', TemplateTransform = 'template-transform',
HttpRequest = 'http-request', HttpRequest = 'http-request',
VariableAssigner = 'variable-assigner', VariableAssigner = 'variable-assigner',
VariableAggregator = 'variable-aggregator', VariableAggregator = 'variable-aggregator',
Tool = 'tool', Tool = 'tool',
ParameterExtractor = 'parameter-extractor', ParameterExtractor = 'parameter-extractor',
Iteration = 'iteration', Iteration = 'iteration',
DocExtractor = 'document-extractor', DocExtractor = 'document-extractor',
ListFilter = 'list-operator', ListFilter = 'list-operator',
IterationStart = 'iteration-start', IterationStart = 'iteration-start',
Assigner = 'assigner', // is now named as VariableAssigner Assigner = 'assigner', // is now named as VariableAssigner
Agent = 'agent', Agent = 'agent',
Loop = 'loop', Loop = 'loop',
LoopStart = 'loop-start', LoopStart = 'loop-start',
LoopEnd = 'loop-end', LoopEnd = 'loop-end',
} }
export enum ControlMode { export enum ControlMode {
Pointer = 'pointer', Pointer = 'pointer',
Hand = 'hand', Hand = 'hand',
} }
export enum ErrorHandleMode { export enum ErrorHandleMode {
Terminated = 'terminated', Terminated = 'terminated',
ContinueOnError = 'continue-on-error', ContinueOnError = 'continue-on-error',
RemoveAbnormalOutput = 'remove-abnormal-output', RemoveAbnormalOutput = 'remove-abnormal-output',
} }
export type Branch = { export type Branch = {
id: string id: string
name: string name: string
} }
export type CommonNodeType<T = {}> = { export type CommonNodeType<T = {}> = {
_connectedSourceHandleIds?: string[] _connectedSourceHandleIds?: string[]
_connectedTargetHandleIds?: string[] _connectedTargetHandleIds?: string[]
_targetBranches?: Branch[] _targetBranches?: Branch[]
_isSingleRun?: boolean _isSingleRun?: boolean
_runningStatus?: NodeRunningStatus _runningStatus?: NodeRunningStatus
_runningBranchId?: string _runningBranchId?: string
_singleRunningStatus?: NodeRunningStatus _singleRunningStatus?: NodeRunningStatus
_isCandidate?: boolean _isCandidate?: boolean
_isBundled?: boolean _isBundled?: boolean
_children?: { nodeId: string; nodeType: BlockEnum }[] _children?: { nodeId: string; nodeType: BlockEnum }[]
_isEntering?: boolean _isEntering?: boolean
_showAddVariablePopup?: boolean _showAddVariablePopup?: boolean
_holdAddVariablePopup?: boolean _holdAddVariablePopup?: boolean
_iterationLength?: number _iterationLength?: number
_iterationIndex?: number _iterationIndex?: number
_inParallelHovering?: boolean _inParallelHovering?: boolean
_waitingRun?: boolean _waitingRun?: boolean
_retryIndex?: number _retryIndex?: number
isInIteration?: boolean isInIteration?: boolean
iteration_id?: string iteration_id?: string
selected?: boolean selected?: boolean
title: string title: string
desc: string desc: string
type: BlockEnum type: BlockEnum
width?: number width?: number
height?: number height?: number
position?: XYPosition position?: XYPosition
_loopLength?: number _loopLength?: number
_loopIndex?: number _loopIndex?: number
isInLoop?: boolean isInLoop?: boolean
loop_id?: string loop_id?: string
error_strategy?: ErrorHandleTypeEnum error_strategy?: ErrorHandleTypeEnum
retry_config?: WorkflowRetryConfig retry_config?: WorkflowRetryConfig
default_value?: DefaultValueForm[] default_value?: DefaultValueForm[]
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>> } & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
export type CommonEdgeType = { export type CommonEdgeType = {
_hovering?: boolean _hovering?: boolean
_connectedNodeIsHovering?: boolean _connectedNodeIsHovering?: boolean
_connectedNodeIsSelected?: boolean _connectedNodeIsSelected?: boolean
_isBundled?: boolean _isBundled?: boolean
_sourceRunningStatus?: NodeRunningStatus _sourceRunningStatus?: NodeRunningStatus
_targetRunningStatus?: NodeRunningStatus _targetRunningStatus?: NodeRunningStatus
_waitingRun?: boolean _waitingRun?: boolean
isInIteration?: boolean isInIteration?: boolean
iteration_id?: string iteration_id?: string
isInLoop?: boolean isInLoop?: boolean
loop_id?: string loop_id?: string
sourceType: BlockEnum sourceType: BlockEnum
targetType: BlockEnum targetType: BlockEnum
} }
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>> export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
export type SelectedNode = Pick<Node, 'id' | 'data'> export type SelectedNode = Pick<Node, 'id' | 'data'>
export type NodeProps<T = unknown> = { id: string; data: CommonNodeType<T> } export type NodeProps<T = unknown> = { id: string; data: CommonNodeType<T> }
export type NodePanelProps<T> = { export type NodePanelProps<T> = {
id: string id: string
data: CommonNodeType<T> data: CommonNodeType<T>
panelProps: PanelProps panelProps: PanelProps
} }
export type Edge = ReactFlowEdge<CommonEdgeType> export type Edge = ReactFlowEdge<CommonEdgeType>
export type WorkflowDataUpdater = { export type WorkflowDataUpdater = {
nodes: Node[] nodes: Node[]
edges: Edge[] edges: Edge[]
viewport: Viewport viewport: Viewport
} }
export type ValueSelector = string[] // [nodeId, key | obj key path] export type ValueSelector = string[] // [nodeId, key | obj key path]
export type Variable = { export type Variable = {
variable: string variable: string
label?: string | { label?: string | {
nodeType: BlockEnum nodeType: BlockEnum
nodeName: string nodeName: string
variable: string variable: string
} }
value_selector: ValueSelector value_selector: ValueSelector
variable_type?: VarKindType value_type?: VarType
value?: string variable_type?: VarKindType
options?: string[] value?: string
required?: boolean options?: string[]
isParagraph?: boolean required?: boolean
} isParagraph?: boolean
}
export type EnvironmentVariable = {
id: string export type EnvironmentVariable = {
name: string id: string
value: any name: string
value_type: 'string' | 'number' | 'secret' value: any
} value_type: 'string' | 'number' | 'secret'
}
export type ConversationVariable = {
id: string export type ConversationVariable = {
name: string id: string
value_type: ChatVarType name: string
value: any value_type: ChatVarType
description: string value: any
} description: string
}
export type GlobalVariable = {
name: string export type GlobalVariable = {
value_type: 'string' | 'number' name: string
description: string value_type: 'string' | 'number'
} description: string
}
export type VariableWithValue = {
key: string export type VariableWithValue = {
value: string key: string
} value: string
}
export enum InputVarType {
textInput = 'text-input', export enum InputVarType {
paragraph = 'paragraph', textInput = 'text-input',
select = 'select', paragraph = 'paragraph',
number = 'number', select = 'select',
url = 'url', number = 'number',
files = 'files', url = 'url',
json = 'json', // obj, array files = 'files',
contexts = 'contexts', // knowledge retrieval json = 'json', // obj, array
iterator = 'iterator', // iteration input contexts = 'contexts', // knowledge retrieval
singleFile = 'file', iterator = 'iterator', // iteration input
multiFiles = 'file-list', singleFile = 'file',
loop = 'loop', // loop input multiFiles = 'file-list',
} loop = 'loop', // loop input
}
export type InputVar = {
type: InputVarType export type InputVar = {
label: string | { type: InputVarType
nodeType: BlockEnum label: string | {
nodeName: string nodeType: BlockEnum
variable: string nodeName: string
isChatVar?: boolean variable: string
} isChatVar?: boolean
variable: string }
max_length?: number variable: string
default?: string max_length?: number
required: boolean default?: string
hint?: string required: boolean
options?: string[] hint?: string
value_selector?: ValueSelector options?: string[]
getVarValueFromDependent?: boolean value_selector?: ValueSelector
hide?: boolean getVarValueFromDependent?: boolean
isFileItem?: boolean hide?: boolean
} & Partial<UploadFileSetting> isFileItem?: boolean
} & Partial<UploadFileSetting>
export type ModelConfig = {
provider: string export type ModelConfig = {
name: string provider: string
mode: string name: string
completion_params: Record<string, any> mode: string
} completion_params: Record<string, any>
}
export enum PromptRole {
system = 'system', export enum PromptRole {
user = 'user', system = 'system',
assistant = 'assistant', user = 'user',
} assistant = 'assistant',
}
export enum EditionType {
basic = 'basic', export enum EditionType {
jinja2 = 'jinja2', basic = 'basic',
} jinja2 = 'jinja2',
}
export type PromptItem = {
id?: string export type PromptItem = {
role?: PromptRole id?: string
text: string role?: PromptRole
edition_type?: EditionType text: string
jinja2_text?: string edition_type?: EditionType
} jinja2_text?: string
}
export enum MemoryRole {
user = 'user', export enum MemoryRole {
assistant = 'assistant', user = 'user',
} assistant = 'assistant',
}
export type RolePrefix = {
user: string export type RolePrefix = {
assistant: string user: string
} assistant: string
}
export type Memory = {
role_prefix?: RolePrefix export type Memory = {
window: { role_prefix?: RolePrefix
enabled: boolean window: {
size: number | string | null enabled: boolean
} size: number | string | null
query_prompt_template: string }
} query_prompt_template: string
}
export enum VarType {
string = 'string', export enum VarType {
number = 'number', string = 'string',
secret = 'secret', number = 'number',
boolean = 'boolean', secret = 'secret',
object = 'object', boolean = 'boolean',
file = 'file', object = 'object',
array = 'array', file = 'file',
arrayString = 'array[string]', array = 'array',
arrayNumber = 'array[number]', arrayString = 'array[string]',
arrayObject = 'array[object]', arrayNumber = 'array[number]',
arrayFile = 'array[file]', arrayObject = 'array[object]',
any = 'any', arrayFile = 'array[file]',
arrayAny = 'array[any]', any = 'any',
} arrayAny = 'array[any]',
}
export enum ValueType {
variable = 'variable', export enum ValueType {
constant = 'constant', variable = 'variable',
} constant = 'constant',
}
export type Var = {
variable: string export type Var = {
type: VarType variable: string
children?: Var[] | StructuredOutput // if type is obj, has the children struct type: VarType
isParagraph?: boolean children?: Var[] | StructuredOutput // if type is obj, has the children struct
isSelect?: boolean isParagraph?: boolean
options?: string[] isSelect?: boolean
required?: boolean options?: string[]
des?: string required?: boolean
isException?: boolean des?: string
isLoopVariable?: boolean isException?: boolean
nodeId?: string isLoopVariable?: boolean
} nodeId?: string
}
export type NodeOutPutVar = {
nodeId: string export type NodeOutPutVar = {
title: string nodeId: string
vars: Var[] title: string
isStartNode?: boolean vars: Var[]
isLoop?: boolean isStartNode?: boolean
} isLoop?: boolean
}
export type Block = {
classification?: string export type Block = {
type: BlockEnum classification?: string
title: string type: BlockEnum
description?: string title: string
} description?: string
}
export type NodeDefault<T> = {
defaultValue: Partial<T> export type NodeDefault<T> = {
defaultRunInputData?: Record<string, any> defaultValue: Partial<T>
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] defaultRunInputData?: Record<string, any>
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string } getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
} checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string }
}
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
export enum WorkflowRunningStatus {
Waiting = 'waiting', export enum WorkflowRunningStatus {
Running = 'running', Waiting = 'waiting',
Succeeded = 'succeeded', Running = 'running',
Failed = 'failed', Succeeded = 'succeeded',
Stopped = 'stopped', Failed = 'failed',
} Stopped = 'stopped',
}
export enum WorkflowVersion {
Draft = 'draft', export enum WorkflowVersion {
Latest = 'latest', Draft = 'draft',
} Latest = 'latest',
}
export enum NodeRunningStatus {
NotStart = 'not-start', export enum NodeRunningStatus {
Waiting = 'waiting', NotStart = 'not-start',
Running = 'running', Waiting = 'waiting',
Succeeded = 'succeeded', Running = 'running',
Failed = 'failed', Succeeded = 'succeeded',
Exception = 'exception', Failed = 'failed',
Retry = 'retry', Exception = 'exception',
Stopped = 'stopped', Retry = 'retry',
} Stopped = 'stopped',
}
export type OnNodeAdd = (
newNodePayload: { export type OnNodeAdd = (
nodeType: BlockEnum newNodePayload: {
sourceHandle?: string nodeType: BlockEnum
targetHandle?: string sourceHandle?: string
toolDefaultValue?: ToolDefaultValue targetHandle?: string
}, toolDefaultValue?: ToolDefaultValue
oldNodesPayload: { },
prevNodeId?: string oldNodesPayload: {
prevNodeSourceHandle?: string prevNodeId?: string
nextNodeId?: string prevNodeSourceHandle?: string
nextNodeTargetHandle?: string nextNodeId?: string
} nextNodeTargetHandle?: string
) => void }
) => void
export type CheckValidRes = {
isValid: boolean export type CheckValidRes = {
errorMessage?: string isValid: boolean
} errorMessage?: string
}
export type RunFile = {
type: string export type RunFile = {
transfer_method: TransferMethod[] type: string
url?: string transfer_method: TransferMethod[]
upload_file_id?: string url?: string
related_id?: string upload_file_id?: string
} related_id?: string
}
export type WorkflowRunningData = {
task_id?: string export type WorkflowRunningData = {
message_id?: string task_id?: string
conversation_id?: string message_id?: string
result: { conversation_id?: string
workflow_id?: string result: {
inputs?: string workflow_id?: string
process_data?: string inputs?: string
outputs?: string process_data?: string
status: string outputs?: string
error?: string status: string
elapsed_time?: number error?: string
total_tokens?: number elapsed_time?: number
created_at?: number total_tokens?: number
created_by?: string created_at?: number
finished_at?: number created_by?: string
steps?: number finished_at?: number
showSteps?: boolean steps?: number
total_steps?: number showSteps?: boolean
files?: FileResponse[] total_steps?: number
exceptions_count?: number files?: FileResponse[]
} exceptions_count?: number
tracing?: NodeTracing[] }
} tracing?: NodeTracing[]
}
export type HistoryWorkflowData = {
id: string export type HistoryWorkflowData = {
status: string id: string
conversation_id?: string status: string
finished_at?: number conversation_id?: string
} finished_at?: number
}
export enum ChangeType {
changeVarName = 'changeVarName', export enum ChangeType {
remove = 'remove', changeVarName = 'changeVarName',
} remove = 'remove',
}
export type MoreInfo = {
type: ChangeType export type MoreInfo = {
payload?: { type: ChangeType
beforeKey: string payload?: {
afterKey?: string beforeKey: string
} afterKey?: string
} }
}
export type ToolWithProvider = Collection & {
tools: Tool[] export type ToolWithProvider = Collection & {
} tools: Tool[]
}
export enum SupportUploadFileTypes {
image = 'image', export enum SupportUploadFileTypes {
document = 'document', image = 'image',
audio = 'audio', document = 'document',
video = 'video', audio = 'audio',
custom = 'custom', video = 'video',
} custom = 'custom',
}
export type UploadFileSetting = {
allowed_file_upload_methods: TransferMethod[] export type UploadFileSetting = {
allowed_file_types: SupportUploadFileTypes[] allowed_file_upload_methods: TransferMethod[]
allowed_file_extensions?: string[] allowed_file_types: SupportUploadFileTypes[]
max_length: number allowed_file_extensions?: string[]
number_limits?: number max_length: number
} number_limits?: number
}
export type VisionSetting = {
variable_selector: ValueSelector export type VisionSetting = {
detail: Resolution variable_selector: ValueSelector
} detail: Resolution
}
export enum WorkflowVersionFilterOptions {
all = 'all', export enum WorkflowVersionFilterOptions {
onlyYours = 'onlyYours', all = 'all',
} onlyYours = 'onlyYours',
}
export enum VersionHistoryContextMenuOptions {
restore = 'restore', export enum VersionHistoryContextMenuOptions {
edit = 'edit', restore = 'restore',
delete = 'delete', edit = 'edit',
} delete = 'delete',
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save