diff --git a/src/api/types.ts b/src/api/types.ts index b0ef9a36..8886d358 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1489,6 +1489,10 @@ export interface Workflow { actions?: any[] // 动作流 flows?: any[] + // 工作流执行配置 + execution_config?: { [key: string]: any } + // 工作流结构化执行状态 + execution_state?: { [key: string]: any } // 创建时间 add_time?: string // 最后执行时间 diff --git a/src/components/dialog/WorkflowActionsDialog.vue b/src/components/dialog/WorkflowActionsDialog.vue index 2945d05b..54d648af 100644 --- a/src/components/dialog/WorkflowActionsDialog.vue +++ b/src/components/dialog/WorkflowActionsDialog.vue @@ -25,9 +25,67 @@ onConnect((connection: Connection) => { $toast.warning(t('dialog.workflowActions.invalidConnection')) return } - addEdges(connection) + addEdges( + normalizeWorkflowEdge({ + ...connection, + id: `edge_${connection.source}_${connection.target}_${Date.now()}`, + type: 'animation', + animated: true, + }), + ) }) +// 当前选中的流程边ID +const selectedEdgeId = ref(null) + +// 当前选中的动作节点ID +const selectedNodeId = ref(null) + +// 流程边配置表单 +const edgeForm = ref({ + condition: '', + join_policy: '', + branch_policy: '', +}) + +// 动作节点执行配置表单 +const nodeForm = ref({ + inputs_text: '', + outputs_text: '', + join_policy: '', + fail_policy: '', + branch_policy: '', + concurrency_key: '', + timeout: null as number | null, + retry_max_attempts: null as number | null, + retry_interval: null as number | null, + retry_backoff: null as number | null, +}) + +// 汇合策略选项 +const joinPolicyOptions = computed(() => [ + { title: t('dialog.workflowActions.joinPolicyDefault'), value: '' }, + { title: t('dialog.workflowActions.joinPolicyAllSuccess'), value: 'all_success' }, + { title: t('dialog.workflowActions.joinPolicyAnySuccess'), value: 'any_success' }, + { title: t('dialog.workflowActions.joinPolicyAllDone'), value: 'all_done' }, + { title: t('dialog.workflowActions.joinPolicyFailFast'), value: 'fail_fast' }, +]) + +// 分支策略选项 +const branchPolicyOptions = computed(() => [ + { title: t('dialog.workflowActions.branchPolicyDefault'), value: '' }, + { title: t('dialog.workflowActions.branchPolicyParallel'), value: 'parallel' }, + { title: t('dialog.workflowActions.branchPolicyExclusive'), value: 'exclusive' }, +]) + +// 失败策略选项 +const failPolicyOptions = computed(() => [ + { title: t('dialog.workflowActions.failPolicyDefault'), value: '' }, + { title: t('dialog.workflowActions.failPolicyStop'), value: 'stop' }, + { title: t('dialog.workflowActions.failPolicyContinue'), value: 'continue' }, + { title: t('dialog.workflowActions.failPolicyIgnore'), value: 'ignore' }, +]) + // 获取指定节点端口的类型(输入/输出) const getPortType = (node: GraphNode, handleId: string) => { // 检查是否是输入端口(对应 handleBounds.target) @@ -59,6 +117,341 @@ const isValidConnection = (connection: Connection) => { return sourcePortType === 'output' && targetPortType === 'input' && connection.source !== connection.target } +// 读取流程边扩展配置,兼容后端支持的顶层字段与 data 字段 +const getEdgeConfigValue = (edge: any, key: string) => { + return edge?.[key] ?? edge?.data?.[key] ?? '' +} + +// 统一流程边数据结构,确保条件和汇合策略能被后端读取 +const normalizeWorkflowEdge = (edge: any) => { + const condition = String(getEdgeConfigValue(edge, 'condition') || '').trim() + const joinPolicy = String(getEdgeConfigValue(edge, 'join_policy') || '').trim() + const branchPolicy = String(getEdgeConfigValue(edge, 'branch_policy') || '').trim() + const edgeClass = String(edge?.class || '') + .replace(/\bworkflow-conditional-edge\b/g, '') + .trim() + const data = { + ...(edge?.data || {}), + condition: condition || undefined, + join_policy: joinPolicy || undefined, + branch_policy: branchPolicy || undefined, + } + + return { + ...edge, + animated: edge?.animated ?? true, + type: edge?.type || 'animation', + label: condition ? t('dialog.workflowActions.edgeConditionalLabel') : undefined, + class: [edgeClass, condition ? 'workflow-conditional-edge' : ''].filter(Boolean).join(' ') || undefined, + condition: condition || undefined, + join_policy: joinPolicy || undefined, + branch_policy: branchPolicy || undefined, + data, + } +} + +// 标准化所有流程边,导入和保存前都会调用 +const normalizeWorkflowEdges = () => { + edges.value = (edges.value || []).map(edge => normalizeWorkflowEdge(edge)) +} + +// 判断扩展配置是否为空,避免旧 data 中的空值覆盖顶层字段 +const isEmptyConfigValue = (value: any) => { + if (value === undefined || value === null || value === '') return true + if (Array.isArray(value)) return value.length === 0 + if (typeof value === 'object') return Object.keys(value).length === 0 + return false +} + +// 读取动作节点扩展配置,兼容顶层字段与 data 字段 +const getNodeConfigValue = (node: any, key: string) => { + const nodeValue = node?.[key] + if (!isEmptyConfigValue(nodeValue)) return nodeValue + const dataValue = node?.data?.[key] + return isEmptyConfigValue(dataValue) ? undefined : dataValue +} + +// 将输入声明统一为路径数组 +const normalizeInputPaths = (value: any) => { + if (Array.isArray(value)) { + return value.map(item => String(item).trim()).filter(Boolean) + } + if (typeof value === 'string') { + return value + .split(/[\n,]+/) + .map(item => item.trim()) + .filter(Boolean) + } + return [] +} + +// 解析 JSON 形式的结构化配置 +const parseStructuredConfig = (value: string, label: string) => { + const text = String(value || '').trim() + if (!text) return undefined + try { + const parsed = JSON.parse(text) + if (parsed && (Array.isArray(parsed) || typeof parsed === 'object')) { + return parsed + } + } catch (error) { + console.error(error) + } + throw new Error(t('dialog.workflowActions.invalidJsonConfig', { label })) +} + +// 尝试把存量结构化配置标准化为对象或数组 +const normalizeStructuredConfig = (value: any) => { + if (isEmptyConfigValue(value)) return undefined + if (Array.isArray(value) || typeof value === 'object') return value + if (typeof value !== 'string') return undefined + try { + const parsed = JSON.parse(value) + return parsed && (Array.isArray(parsed) || typeof parsed === 'object') ? parsed : undefined + } catch { + return undefined + } +} + +// 将结构化配置格式化为面板中的 JSON 文本 +const stringifyStructuredConfig = (value: any) => { + const normalizedValue = normalizeStructuredConfig(value) + return normalizedValue ? JSON.stringify(normalizedValue, null, 2) : '' +} + +// 数值字段统一清洗,空值会在保存时被移除 +const normalizeNumber = (value: any, minValue = 0, integer = false) => { + if (value === undefined || value === null || value === '') return undefined + const numberValue = Number(value) + if (!Number.isFinite(numberValue) || numberValue < minValue) return undefined + return integer ? Math.floor(numberValue) : numberValue +} + +// 读取节点重试策略 +const normalizeRetryConfig = (value: any) => { + const retryConfig = normalizeStructuredConfig(value) + return retryConfig && !Array.isArray(retryConfig) ? retryConfig : {} +} + +// 根据面板表单构造重试策略 +const buildRetryConfigFromForm = () => { + const retryConfig: Record = {} + const maxAttempts = normalizeNumber(nodeForm.value.retry_max_attempts, 1, true) + const interval = normalizeNumber(nodeForm.value.retry_interval, 0) + const backoff = normalizeNumber(nodeForm.value.retry_backoff, 1) + if (maxAttempts !== undefined) retryConfig.max_attempts = maxAttempts + if (interval !== undefined) retryConfig.interval = interval + if (backoff !== undefined) retryConfig.backoff = backoff + return Object.keys(retryConfig).length ? retryConfig : undefined +} + +// 统一动作节点数据结构,确保执行策略能被后端读取 +const normalizeWorkflowNode = (node: any) => { + const inputPaths = normalizeInputPaths(getNodeConfigValue(node, 'inputs')) + const outputs = normalizeStructuredConfig(getNodeConfigValue(node, 'outputs')) + const joinPolicy = String(getNodeConfigValue(node, 'join_policy') || '').trim() + const failPolicy = String(getNodeConfigValue(node, 'fail_policy') || '').trim() + const branchPolicy = String(getNodeConfigValue(node, 'branch_policy') || '').trim() + const concurrencyKey = String(getNodeConfigValue(node, 'concurrency_key') || '').trim() + const timeout = normalizeNumber(getNodeConfigValue(node, 'timeout'), 1, true) + const retryConfig = normalizeRetryConfig(getNodeConfigValue(node, 'retry')) + const retry = Object.keys(retryConfig).length ? retryConfig : undefined + const data = { + ...(node?.data || {}), + inputs: inputPaths.length ? inputPaths : undefined, + outputs: outputs || undefined, + join_policy: joinPolicy || undefined, + fail_policy: failPolicy || undefined, + branch_policy: branchPolicy || undefined, + concurrency_key: concurrencyKey || undefined, + timeout: timeout || undefined, + retry, + } + + return { + ...node, + inputs: inputPaths.length ? inputPaths : undefined, + outputs: outputs || undefined, + join_policy: joinPolicy || undefined, + fail_policy: failPolicy || undefined, + branch_policy: branchPolicy || undefined, + concurrency_key: concurrencyKey || undefined, + timeout: timeout || undefined, + retry, + data, + } +} + +// 标准化所有动作节点,导入和保存前都会调用 +const normalizeWorkflowNodes = () => { + nodes.value = (nodes.value || []).map(node => normalizeWorkflowNode(node)) +} + +// 获取节点名称,便于在边设置面板展示流转关系 +const getNodeName = (nodeId?: string) => { + const node = nodes.value.find(item => item.id === nodeId) + return (node as any)?.name || node?.data?.label || nodeId || '' +} + +// 选中流程边时打开设置面板 +function handleEdgeClick(params: any) { + const edge = params?.edge + if (!edge) return + selectedNodeId.value = null + selectedEdgeId.value = edge.id + edgeForm.value = { + condition: String(getEdgeConfigValue(edge, 'condition') || ''), + join_policy: String(getEdgeConfigValue(edge, 'join_policy') || ''), + branch_policy: String(getEdgeConfigValue(edge, 'branch_policy') || ''), + } +} + +// 关闭流程边设置面板 +function closeEdgeSettings() { + selectedEdgeId.value = null + edgeForm.value = { + condition: '', + join_policy: '', + branch_policy: '', + } +} + +// 保存流程边设置 +function saveEdgeSettings() { + if (!selectedEdgeId.value) return + edges.value = edges.value.map(edge => { + if (edge.id !== selectedEdgeId.value) return edge + return normalizeWorkflowEdge({ + ...edge, + condition: edgeForm.value.condition, + join_policy: edgeForm.value.join_policy, + data: { + ...(edge.data || {}), + condition: edgeForm.value.condition, + join_policy: edgeForm.value.join_policy, + branch_policy: edgeForm.value.branch_policy, + }, + branch_policy: edgeForm.value.branch_policy, + }) + }) + $toast.success(t('dialog.workflowActions.edgeSaveSuccess')) +} + +// 删除当前选中的流程边 +function deleteSelectedEdge() { + if (!selectedEdgeId.value) return + edges.value = edges.value.filter(edge => edge.id !== selectedEdgeId.value) + closeEdgeSettings() +} + +// 当前选中的流程边 +const selectedEdge = computed(() => { + if (!selectedEdgeId.value) return null + return edges.value.find(edge => edge.id === selectedEdgeId.value) || null +}) + +// 当前选中的动作节点 +const selectedNode = computed(() => { + if (!selectedNodeId.value) return null + return nodes.value.find(node => node.id === selectedNodeId.value) || null +}) + +// 将节点数据填入右侧执行配置面板 +function fillNodeForm(node: any) { + const retryConfig = normalizeRetryConfig(getNodeConfigValue(node, 'retry')) + nodeForm.value = { + inputs_text: normalizeInputPaths(getNodeConfigValue(node, 'inputs')).join('\n'), + outputs_text: stringifyStructuredConfig(getNodeConfigValue(node, 'outputs')), + join_policy: String(getNodeConfigValue(node, 'join_policy') || ''), + fail_policy: String(getNodeConfigValue(node, 'fail_policy') || ''), + branch_policy: String(getNodeConfigValue(node, 'branch_policy') || ''), + concurrency_key: String(getNodeConfigValue(node, 'concurrency_key') || ''), + timeout: normalizeNumber(getNodeConfigValue(node, 'timeout'), 1, true) ?? null, + retry_max_attempts: normalizeNumber(retryConfig.max_attempts, 1, true) ?? null, + retry_interval: normalizeNumber(retryConfig.interval, 0) ?? null, + retry_backoff: normalizeNumber(retryConfig.backoff, 1) ?? null, + } +} + +// 选中动作节点时打开执行配置面板 +function handleNodeClick(params: any) { + const node = params?.node + if (!node) return + if (node.name == '备注') return + selectedEdgeId.value = null + selectedNodeId.value = node.id + fillNodeForm(node) +} + +// 关闭动作节点执行配置面板 +function closeNodeSettings() { + selectedNodeId.value = null + nodeForm.value = { + inputs_text: '', + outputs_text: '', + join_policy: '', + fail_policy: '', + branch_policy: '', + concurrency_key: '', + timeout: null, + retry_max_attempts: null, + retry_interval: null, + retry_backoff: null, + } +} + +// 根据面板表单构造动作节点执行配置 +function buildNodeConfigFromForm() { + return { + inputs: normalizeInputPaths(nodeForm.value.inputs_text), + outputs: parseStructuredConfig(nodeForm.value.outputs_text, t('dialog.workflowActions.nodeOutputsLabel')), + join_policy: nodeForm.value.join_policy, + fail_policy: nodeForm.value.fail_policy, + branch_policy: nodeForm.value.branch_policy, + concurrency_key: nodeForm.value.concurrency_key, + timeout: normalizeNumber(nodeForm.value.timeout, 1, true), + retry: buildRetryConfigFromForm(), + } +} + +// 保存动作节点执行配置 +function saveNodeSettings() { + if (!selectedNodeId.value) return + let nodeConfig: any + try { + nodeConfig = buildNodeConfigFromForm() + } catch (error: any) { + $toast.error(error.message) + return + } + nodes.value = nodes.value.map(node => { + if (node.id !== selectedNodeId.value) return node + return normalizeWorkflowNode({ + ...node, + inputs: nodeConfig.inputs, + outputs: nodeConfig.outputs, + join_policy: nodeConfig.join_policy, + fail_policy: nodeConfig.fail_policy, + branch_policy: nodeConfig.branch_policy, + concurrency_key: nodeConfig.concurrency_key, + timeout: nodeConfig.timeout, + retry: nodeConfig.retry, + data: { + ...(node.data || {}), + inputs: nodeConfig.inputs, + outputs: nodeConfig.outputs, + join_policy: nodeConfig.join_policy, + fail_policy: nodeConfig.fail_policy, + branch_policy: nodeConfig.branch_policy, + concurrency_key: nodeConfig.concurrency_key, + timeout: nodeConfig.timeout, + retry: nodeConfig.retry, + }, + }) + }) + $toast.success(t('dialog.workflowActions.nodeSaveSuccess')) +} + // 自定义节点类型 const nodeTypes: Record = ref({}) @@ -142,8 +535,10 @@ function handleComponentClick(action: any) { // 调用API 编辑任务 async function updateWorkflow() { // 更新节点和流程 - workflowForm.value.actions = nodes - workflowForm.value.flows = edges + normalizeWorkflowNodes() + normalizeWorkflowEdges() + workflowForm.value.actions = nodes.value + workflowForm.value.flows = edges.value try { const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value) @@ -166,6 +561,11 @@ function saveCodeString(type: string, code: any) { if (type === 'workflow') { nodes.value = codeObject.actions || [] edges.value = codeObject.flows || [] + if (codeObject.execution_config) { + workflowForm.value.execution_config = codeObject.execution_config + } + normalizeWorkflowNodes() + normalizeWorkflowEdges() } importCodeDialog.value = false $toast.success(t('dialog.workflowActions.importSuccess')) @@ -178,7 +578,13 @@ function saveCodeString(type: string, code: any) { // 分享工作流程 function shareWorkflow() { - const codeString = JSON.stringify({ actions: nodes.value, flows: edges.value }) + normalizeWorkflowNodes() + normalizeWorkflowEdges() + const codeString = JSON.stringify({ + actions: nodes.value, + flows: edges.value, + execution_config: workflowForm.value.execution_config, + }) navigator.clipboard.writeText(codeString) $toast.success(t('dialog.workflowActions.codeCopied')) } @@ -187,9 +593,31 @@ onMounted(() => { if (props.workflow) { nodes.value = props.workflow.actions ?? [] edges.value = props.workflow.flows ?? [] + normalizeWorkflowNodes() + normalizeWorkflowEdges() } }) +watch( + edges, + () => { + if (selectedEdgeId.value && !selectedEdge.value) { + closeEdgeSettings() + } + }, + { deep: true }, +) + +watch( + nodes, + () => { + if (selectedNodeId.value && !selectedNode.value) { + closeNodeSettings() + } + }, + { deep: true }, +) + // 判断是不是MACOS const isMacOS = computed(() => { return /Macintosh|MacIntel|MacPPC|Mac68K/.test(navigator.userAgent) @@ -231,6 +659,8 @@ const isMacOS = computed(() => { :edge-updater-radius="10" @dragover="onDragOver" @dragleave="onDragLeave" + @node-click="handleNodeClick" + @edge-click="handleEdgeClick" :delete-key-code="isMacOS ? 'Backspace' : 'Delete'" auto-connect > @@ -243,6 +673,187 @@ const isMacOS = computed(() => { > + +
+
+
+ + {{ t('dialog.workflowActions.edgeSettingsTitle') }} +
+ + + +
+ +
+ {{ getNodeName(selectedEdge.source) }} + + {{ getNodeName(selectedEdge.target) }} +
+ + + + + + + +
+ + + + + + {{ t('dialog.workflowActions.edgeCancel') }} + + + {{ t('dialog.workflowActions.edgeSave') }} + +
+
+ +
+
+
+ + {{ t('dialog.workflowActions.nodeSettingsTitle') }} +
+ + + +
+ +
+ + {{ getNodeName(selectedNode.id) }} +
+ + + + + + + + + +
+ + + + +
+ +
+ + + {{ t('dialog.workflowActions.edgeCancel') }} + + + {{ t('dialog.workflowActions.edgeSave') }} + +
+
+ @@ -285,6 +896,74 @@ const isMacOS = computed(() => { inline-size: 100%; } +.workflow-edge-panel { + position: absolute; + z-index: 120; + display: flex; + flex-direction: column; + padding: 16px; + border: 1px solid rgb(var(--v-theme-primary)); + border-radius: 8px; + background-color: rgb(var(--v-theme-surface)); + box-shadow: 0 8px 24px rgba(var(--v-shadow-key-umbra-color), 0.32); + gap: 14px; + inline-size: min(360px, calc(100vw - 32px)); + inset-block-start: 20px; + inset-inline-end: 20px; + max-block-size: calc(100% - 40px); + overflow-y: auto; +} + +.workflow-node-panel { + inline-size: min(420px, calc(100vw - 32px)); +} + +.edge-panel-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.edge-panel-title { + display: flex; + align-items: center; + color: rgb(var(--v-theme-on-surface)); + font-size: 16px; + font-weight: 600; + gap: 8px; +} + +.edge-route { + display: flex; + align-items: center; + border-radius: 6px; + background-color: rgba(var(--v-theme-primary), 0.08); + color: rgb(var(--v-theme-on-surface)); + font-size: 13px; + gap: 8px; + padding-block: 8px; + padding-inline: 10px; + + span { + overflow: hidden; + flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.edge-panel-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.workflow-number-grid { + display: grid; + gap: 12px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + .vue-flow__minimap { overflow: hidden; border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); @@ -345,9 +1024,26 @@ const isMacOS = computed(() => { } } +.vue-flow__edge.workflow-conditional-edge { + .vue-flow__edge-path { + stroke: rgb(var(--v-theme-warning)); + } +} + @media screen and (width <= 600px) { .vue-flow__minimap { display: none; } + + .workflow-edge-panel { + inline-size: auto; + inset-block: auto 88px; + inset-inline: 16px; + max-block-size: min(72vh, calc(100% - 112px)); + } + + .workflow-number-grid { + grid-template-columns: 1fr; + } } diff --git a/src/components/dialog/WorkflowAddEditDialog.vue b/src/components/dialog/WorkflowAddEditDialog.vue index 359a310b..654ae4ad 100644 --- a/src/components/dialog/WorkflowAddEditDialog.vue +++ b/src/components/dialog/WorkflowAddEditDialog.vue @@ -37,9 +37,35 @@ const workflowForm = ref( event_type: undefined, state: 'P', run_count: 0, + execution_config: {}, }, ) +// 将并发数清洗为正整数,空值表示使用后端默认值 +const normalizePositiveInteger = (value: any) => { + if (value === undefined || value === null || value === '') return undefined + const numberValue = Number(value) + if (!Number.isFinite(numberValue) || numberValue < 1) return undefined + return Math.floor(numberValue) +} + +// 工作流级执行配置中的最大并行数 +const workflowMaxWorkers = computed({ + get() { + return normalizePositiveInteger(workflowForm.value.execution_config?.max_workers) ?? null + }, + set(value) { + const executionConfig = { ...(workflowForm.value.execution_config || {}) } + const maxWorkers = normalizePositiveInteger(value) + if (maxWorkers) { + executionConfig.max_workers = maxWorkers + } else { + delete executionConfig.max_workers + } + workflowForm.value.execution_config = Object.keys(executionConfig).length ? executionConfig : undefined + }, +}) + // 监听props变化,处理存量数据 watch( () => props.workflow, @@ -49,7 +75,10 @@ watch( if (!newWorkflow.trigger_type) { newWorkflow.trigger_type = 'timer' } - workflowForm.value = { ...newWorkflow } + workflowForm.value = { + ...newWorkflow, + execution_config: { ...(newWorkflow.execution_config || {}) }, + } } }, { immediate: true }, @@ -99,6 +128,18 @@ watch( // 提示框 const $toast = useToast() +// 保存前统一清洗工作流执行配置 +function normalizeWorkflowExecutionConfig() { + const executionConfig = { ...(workflowForm.value.execution_config || {}) } + const maxWorkers = normalizePositiveInteger(executionConfig.max_workers) + if (maxWorkers) { + executionConfig.max_workers = maxWorkers + } else { + delete executionConfig.max_workers + } + workflowForm.value.execution_config = Object.keys(executionConfig).length ? executionConfig : undefined +} + // 调用API 新增任务 async function addWorkflow() { if (!workflowForm.value.name) { @@ -122,6 +163,7 @@ async function addWorkflow() { return } + normalizeWorkflowExecutionConfig() startNProgress() try { const result: { [key: string]: string } = await api.post('workflow/', workflowForm.value) @@ -160,6 +202,7 @@ async function editWorkflow() { return } + normalizeWorkflowExecutionConfig() startNProgress() try { const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value) @@ -256,6 +299,16 @@ onMounted(() => { prepend-inner-icon="mdi-text-box-outline" /> + + + diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 476a1416..c344b8a0 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -2415,6 +2415,7 @@ export default { namePlaceholder: 'Workflow name', desc: 'Description', descPlaceholder: 'Workflow description', + maxWorkers: 'Max Parallel Actions', enabled: 'Enabled', triggerType: 'Trigger Type', triggerTypeTimer: 'Timer Trigger', @@ -2465,6 +2466,42 @@ export default { importSuccess: 'Import successful!', importFailed: 'Import failed!', codeCopied: 'Task workflow code copied to clipboard!', + edgeSettingsTitle: 'Flow Condition', + edgeConditionLabel: 'Condition', + edgeConditionPlaceholder: 'outputs.A.items.count > 0', + edgeJoinPolicyLabel: 'Target Join Policy', + edgeBranchPolicyLabel: 'Source Branch Policy', + joinPolicyDefault: 'Default', + joinPolicyAllSuccess: 'All Success', + joinPolicyAnySuccess: 'Any Success', + joinPolicyAllDone: 'All Done', + joinPolicyFailFast: 'Fail Fast', + branchPolicyDefault: 'Default', + branchPolicyParallel: 'Parallel', + branchPolicyExclusive: 'Exclusive First', + failPolicyDefault: 'Default', + failPolicyStop: 'Stop on Failure', + failPolicyContinue: 'Continue on Failure', + failPolicyIgnore: 'Ignore Failure', + edgeConditionalLabel: 'Condition', + edgeSave: 'Save', + edgeCancel: 'Cancel', + edgeSaveSuccess: 'Flow condition saved', + nodeSettingsTitle: 'Action Execution Policy', + nodeInputsLabel: 'Input Declarations', + nodeInputsPlaceholder: 'outputs.FetchTorrentsAction.torrents', + nodeOutputsLabel: 'Output Declarations', + nodeJoinPolicyLabel: 'Node Join Policy', + nodeFailPolicyLabel: 'Failure Policy', + nodeBranchPolicyLabel: 'Branch Policy', + nodeConcurrencyKeyLabel: 'Concurrency Key', + nodeConcurrencyKeyPlaceholder: 'downloader', + nodeTimeoutLabel: 'Timeout Seconds', + nodeRetryAttemptsLabel: 'Retry Attempts', + nodeRetryIntervalLabel: 'Retry Interval', + nodeRetryBackoffLabel: 'Backoff Factor', + nodeSaveSuccess: 'Action execution policy saved', + invalidJsonConfig: '{label} is not a valid JSON object or array', }, siteCookieUpdate: { title: 'Update Site Cookie & UA', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 004b92bb..bf7c188d 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -2367,6 +2367,7 @@ export default { namePlaceholder: '工作流名称', desc: '描述', descPlaceholder: '工作流描述', + maxWorkers: '最大并行数', enabled: '启用', triggerType: '触发类型', triggerTypeTimer: '定时触发', @@ -2417,6 +2418,42 @@ export default { importSuccess: '导入成功!', importFailed: '导入失败!', codeCopied: '任务流程代码已复制到剪贴板!', + edgeSettingsTitle: '流程条件', + edgeConditionLabel: '流转条件', + edgeConditionPlaceholder: 'outputs.A.items.count > 0', + edgeJoinPolicyLabel: '目标汇合策略', + edgeBranchPolicyLabel: '源分支策略', + joinPolicyDefault: '默认', + joinPolicyAllSuccess: '全部成功', + joinPolicyAnySuccess: '任一成功', + joinPolicyAllDone: '全部完成', + joinPolicyFailFast: '失败即停', + branchPolicyDefault: '默认', + branchPolicyParallel: '并行', + branchPolicyExclusive: '互斥首选', + failPolicyDefault: '默认', + failPolicyStop: '失败停止', + failPolicyContinue: '失败继续', + failPolicyIgnore: '忽略失败', + edgeConditionalLabel: '条件', + edgeSave: '保存', + edgeCancel: '取消', + edgeSaveSuccess: '流程条件已保存', + nodeSettingsTitle: '动作执行策略', + nodeInputsLabel: '输入声明', + nodeInputsPlaceholder: 'outputs.FetchTorrentsAction.torrents', + nodeOutputsLabel: '输出声明', + nodeJoinPolicyLabel: '节点汇合策略', + nodeFailPolicyLabel: '失败策略', + nodeBranchPolicyLabel: '分支策略', + nodeConcurrencyKeyLabel: '并发互斥键', + nodeConcurrencyKeyPlaceholder: 'downloader', + nodeTimeoutLabel: '超时秒数', + nodeRetryAttemptsLabel: '重试次数', + nodeRetryIntervalLabel: '重试间隔', + nodeRetryBackoffLabel: '退避倍数', + nodeSaveSuccess: '动作执行策略已保存', + invalidJsonConfig: '{label} 不是有效的 JSON 对象或数组', }, siteCookieUpdate: { title: '更新站点Cookie & UA', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 4f386db3..6e5c3154 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -2368,6 +2368,7 @@ export default { namePlaceholder: '工作流名稱', desc: '描述', descPlaceholder: '工作流描述', + maxWorkers: '最大並行數', enabled: '啟用', triggerType: '觸發類型', triggerTypeTimer: '定時觸發', @@ -2418,6 +2419,42 @@ export default { importSuccess: '匯入成功!', importFailed: '匯入失敗!', codeCopied: '任務流程代碼已複製到剪貼簿!', + edgeSettingsTitle: '流程條件', + edgeConditionLabel: '流轉條件', + edgeConditionPlaceholder: 'outputs.A.items.count > 0', + edgeJoinPolicyLabel: '目標匯合策略', + edgeBranchPolicyLabel: '來源分支策略', + joinPolicyDefault: '預設', + joinPolicyAllSuccess: '全部成功', + joinPolicyAnySuccess: '任一成功', + joinPolicyAllDone: '全部完成', + joinPolicyFailFast: '失敗即停', + branchPolicyDefault: '預設', + branchPolicyParallel: '並行', + branchPolicyExclusive: '互斥首選', + failPolicyDefault: '預設', + failPolicyStop: '失敗停止', + failPolicyContinue: '失敗繼續', + failPolicyIgnore: '忽略失敗', + edgeConditionalLabel: '條件', + edgeSave: '儲存', + edgeCancel: '取消', + edgeSaveSuccess: '流程條件已儲存', + nodeSettingsTitle: '動作執行策略', + nodeInputsLabel: '輸入宣告', + nodeInputsPlaceholder: 'outputs.FetchTorrentsAction.torrents', + nodeOutputsLabel: '輸出宣告', + nodeJoinPolicyLabel: '節點匯合策略', + nodeFailPolicyLabel: '失敗策略', + nodeBranchPolicyLabel: '分支策略', + nodeConcurrencyKeyLabel: '並發互斥鍵', + nodeConcurrencyKeyPlaceholder: 'downloader', + nodeTimeoutLabel: '超時秒數', + nodeRetryAttemptsLabel: '重試次數', + nodeRetryIntervalLabel: '重試間隔', + nodeRetryBackoffLabel: '退避倍數', + nodeSaveSuccess: '動作執行策略已儲存', + invalidJsonConfig: '{label} 不是有效的 JSON 物件或陣列', }, siteCookieUpdate: { title: '更新站點Cookie & UA',