diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index 7776b5df..26294ee3 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -13,6 +13,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.1", "@google/model-viewer": "^4.2.0", + "@guolao/vue-monaco-editor": "^1.6.0", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.48.2", @@ -30,6 +31,7 @@ "marked": "^15.0.6", "marked-highlight": "^2.2.3", "mermaid": "^11.14.0", + "monaco-editor": "^0.55.1", "pinia": "^3.0.1", "pixelarticons": "2.1.0", "vue": "^3.5.13", diff --git a/mateclaw-ui/pnpm-lock.yaml b/mateclaw-ui/pnpm-lock.yaml index c02f3fbf..07dcdf84 100644 --- a/mateclaw-ui/pnpm-lock.yaml +++ b/mateclaw-ui/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@google/model-viewer': specifier: ^4.2.0 version: 4.2.0(three@0.182.0) + '@guolao/vue-monaco-editor': + specifier: ^1.6.0 + version: 1.6.0(monaco-editor@0.55.1)(vue@3.5.31(typescript@5.7.3)) '@vue-flow/background': specifier: ^1.3.2 version: 1.3.2(@vue-flow/core@1.48.2(vue@3.5.31(typescript@5.7.3)))(vue@3.5.31(typescript@5.7.3)) @@ -65,6 +68,9 @@ importers: mermaid: specifier: ^11.14.0 version: 11.14.0 + monaco-editor: + specifier: ^0.55.1 + version: 0.55.1 pinia: specifier: ^3.0.1 version: 3.0.4(typescript@5.7.3)(vue@3.5.31(typescript@5.7.3)) @@ -376,6 +382,16 @@ packages: peerDependencies: three: ^0.182.0 + '@guolao/vue-monaco-editor@1.6.0': + resolution: {integrity: sha512-w2IiJ6eJGGeuIgCK6EKZOAfhHTTUB5aZwslzwGbZ5e89Hb4avx6++GkLTW8p84Sng/arFMjLPPxSBI56cFudyQ==} + peerDependencies: + '@vue/composition-api': ^1.7.2 + monaco-editor: '>=0.43.0' + vue: ^2.6.14 || >=3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -435,6 +451,9 @@ packages: '@mermaid-js/parser@1.1.0': resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + '@monogrid/gainmap-js@3.4.0': resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==} peerDependencies: @@ -1230,6 +1249,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + dompurify@3.3.3: resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} @@ -1656,6 +1678,11 @@ packages: peerDependencies: marked: '>=4 <18' + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + marked@15.0.12: resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} engines: {node: '>= 18'} @@ -1693,6 +1720,9 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1861,6 +1891,9 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2242,6 +2275,13 @@ snapshots: lit: 3.3.2 three: 0.182.0 + '@guolao/vue-monaco-editor@1.6.0(monaco-editor@0.55.1)(vue@3.5.31(typescript@5.7.3))': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + vue: 3.5.31(typescript@5.7.3) + vue-demi: 0.14.10(vue@3.5.31(typescript@5.7.3)) + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -2302,6 +2342,10 @@ snapshots: dependencies: langium: 4.2.2 + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + '@monogrid/gainmap-js@3.4.0(three@0.182.0)': dependencies: promise-worker-transferable: 1.0.4 @@ -3102,6 +3146,10 @@ snapshots: detect-libc@2.1.2: {} + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dompurify@3.3.3: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -3551,6 +3599,8 @@ snapshots: dependencies: marked: 15.0.12 + marked@14.0.0: {} + marked@15.0.12: {} marked@16.4.2: {} @@ -3602,6 +3652,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -3767,6 +3822,8 @@ snapshots: speakingurl@14.0.1: {} + state-local@1.0.7: {} + strip-json-comments@3.1.1: {} stylis@4.4.0: {} diff --git a/mateclaw-ui/src/components/workflow/StepNode.vue b/mateclaw-ui/src/components/workflow/StepNode.vue index 51977b73..80a4726f 100644 --- a/mateclaw-ui/src/components/workflow/StepNode.vue +++ b/mateclaw-ui/src/components/workflow/StepNode.vue @@ -74,7 +74,11 @@ const targetPosition = computed(() => props.targetPosition ?? Position.Left) const data = computed(() => props.data as StepNodeData) -const agentSlug = computed(() => data.value.agentName?.trim() || '') +const agentSlug = computed(() => { + const name = data.value.agentName?.trim() + if (name) return name + return data.value.agentId != null ? `#${data.value.agentId}` : '' +}) const approverHint = computed(() => { if (data.value.modeType !== 'await_approval') return '' diff --git a/mateclaw-ui/src/components/workflow/StepPropertyPanel.vue b/mateclaw-ui/src/components/workflow/StepPropertyPanel.vue index 20b700c8..202f10cb 100644 --- a/mateclaw-ui/src/components/workflow/StepPropertyPanel.vue +++ b/mateclaw-ui/src/components/workflow/StepPropertyPanel.vue @@ -39,41 +39,22 @@ {{ t('workflows.canvas.nodeAgent') }} - + {{ t('workflows.canvas.fields.agentPlaceholder') }} - + {{ a.name }} — {{ a.title }} - - {{ t('workflows.canvas.fields.agentMissing', { name: step.agentName }) }} + + {{ t('workflows.canvas.fields.agentMissing', { name: step.agentName || step.agentId }) }} - {{ t('workflows.canvas.fields.agentUseCustom') }} - - × @@ -109,7 +90,6 @@ > text json - bytes @@ -189,14 +169,40 @@ - + {{ t('workflows.canvas.fields.channels') }} + + + + {{ channel.name }} + {{ channel.channelType }} + + + {{ t('workflows.canvas.fields.channelsEmptyHint') }} + + {{ t('workflows.canvas.fields.channelsUnknown', { channels: unknownSelectedChannels.join(', ') }) }} + + + + {{ t('workflows.canvas.fields.dispatchTarget', { channel: channelLabel(channelType) }) }} @@ -271,7 +277,7 @@ @@ -502,6 +585,35 @@ function onAgentSelect(e: Event) { gap: 4px; } .agent-picker .mc-input { flex: 1; min-width: 0; } +.channel-picker { + display: grid; + gap: 6px; +} +.channel-option { + display: flex; + align-items: center; + gap: 8px; + min-height: 30px; + padding: 6px 8px; + border: 1px solid var(--mc-border, rgba(0, 0, 0, 0.12)); + border-radius: 6px; + background: var(--mc-bg, transparent); + cursor: pointer; +} +.channel-option input { + flex: 0 0 auto; +} +.channel-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.channel-type { + font-size: 10.5px; + color: var(--mc-text-tertiary, #888); +} .agent-toggle { width: 24px; height: 28px; @@ -522,6 +634,9 @@ function onAgentSelect(e: Event) { font-style: italic; margin: 0; } +.panel-hint.danger { + color: var(--mc-danger, #c0392b); +} .raw-section summary { font-size: 11px; color: var(--mc-text-tertiary, #888); diff --git a/mateclaw-ui/src/components/workflow/WorkflowCanvas.vue b/mateclaw-ui/src/components/workflow/WorkflowCanvas.vue index b3ffbad2..72fab20f 100644 --- a/mateclaw-ui/src/components/workflow/WorkflowCanvas.vue +++ b/mateclaw-ui/src/components/workflow/WorkflowCanvas.vue @@ -8,13 +8,13 @@ {{ t('workflows.canvas.addNode') }} {{ t('workflows.canvas.addNode') }} - + sequential - + fan_out - + collect - + conditional - + await_approval - + dispatch_channel - + write_memory + + {{ modeOptionLabel('sequential') }} + + {{ modeOptionLabel('fan_out') }} + + {{ modeOptionLabel('collect') }} + + {{ modeOptionLabel('conditional') }} + + {{ modeOptionLabel('await_approval') }} + + {{ modeOptionLabel('dispatch_channel') }} + + {{ modeOptionLabel('write_memory') }} @@ -141,6 +141,12 @@ function onAddNode(e: Event) { const { t } = useI18n() +function modeOptionLabel(type: string): string { + const key = `workflows.canvas.modeLabels.${type}` + const localized = t(key, '') + return localized && localized !== key ? localized : type +} + const direction = ref<'LR' | 'TB'>('LR') const jsonRef = computed(() => props.modelValue) const graph = useWorkflowGraph(jsonRef, direction) diff --git a/mateclaw-ui/src/components/workflow/WorkflowJsonEditor.vue b/mateclaw-ui/src/components/workflow/WorkflowJsonEditor.vue new file mode 100644 index 00000000..0ffadd60 --- /dev/null +++ b/mateclaw-ui/src/components/workflow/WorkflowJsonEditor.vue @@ -0,0 +1,266 @@ + + + emit('update:modelValue', v ?? '')" + @mount="onEditorMount" + /> + + + + + + diff --git a/mateclaw-ui/src/composables/useWorkflowGraph.ts b/mateclaw-ui/src/composables/useWorkflowGraph.ts index 4ee4d24a..c1ec0c7b 100644 --- a/mateclaw-ui/src/composables/useWorkflowGraph.ts +++ b/mateclaw-ui/src/composables/useWorkflowGraph.ts @@ -28,6 +28,7 @@ import dagre from 'dagre' export interface RawStep { name?: string + agentId?: number agentName?: string mode?: { type?: string; expression?: string; [k: string]: unknown } promptTemplate?: string @@ -44,6 +45,7 @@ export interface StepNodeData { index: number name: string modeType: string + agentId?: number agentName?: string promptTemplate?: string expression?: string @@ -100,6 +102,7 @@ export function buildGraph(json: string): { nodes: Node[]; edges: index: idx, name: step?.name?.trim() || fallbackName(idx), modeType, + agentId: typeof step?.agentId === 'number' ? step.agentId : undefined, agentName: step?.agentName, promptTemplate: step?.promptTemplate, expression: typeof step?.mode?.expression === 'string' ? step.mode!.expression : undefined, diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index ad641899..0cc8c1a6 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1926,7 +1926,7 @@ export default { publish: 'Publish', delete: 'Delete', }, - bodyPlaceholder: '{"steps":[{"name":"step-a","agentName":"...","mode":{"type":"sequential"},"promptTemplate":"..."}]}', + bodyPlaceholder: '{"steps":[{"name":"step-a","agentId":1,"mode":{"type":"sequential"},"promptTemplate":"..."}]}', compileErrorsTitle: '{count} compile error(s)', templates: { label: 'Insert template', @@ -1955,7 +1955,7 @@ export default { fullscreenExit: 'Exit fullscreen', addNode: '+ Add node', modeLabel: 'Mode', - nodeAgent: 'Agent', + nodeAgent: 'Digital employee', nodeExpression: 'Condition', nodeApprovalKind: 'Approval', nodeChannels: 'Channels', @@ -1984,10 +1984,8 @@ export default { fields: { name: 'Name', namePlaceholder: 'step-name', - agentPlaceholder: 'Select agent', - agentMissing: 'Current agentName is not in the list: {name}', - agentUseCustom: '— Type a custom agentName —', - agentBackToList: 'Back to dropdown', + agentPlaceholder: 'Select digital employee', + agentMissing: 'Current digital employee is not in the list: {name}', promptTemplate: 'Prompt template', promptPlaceholder: 'Hello {{ inputs.payload }}', outputVar: 'Output variable', @@ -1997,7 +1995,11 @@ export default { approverChannels: 'Approver channels (comma-sep)', approvalMessage: 'Approval message', timeoutSecs: 'Timeout (s)', - channels: 'Channels (comma-sep)', + channels: 'Dispatch channels', + channelsEmptyHint: 'No enabled channels in this workspace. Enable one in Channel Management first.', + channelsUnknown: 'Draft references unavailable channels: {channels}', + dispatchTarget: '{channel} target', + dispatchTargetPlaceholder: 'Group ID / user ID / conversation ID', dispatchContent: 'Message content', employeeId: 'Employee ID', memoryFile: 'Memory file', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index e1ca59b4..5df48764 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1938,7 +1938,7 @@ export default { publish: '发布', delete: '删除', }, - bodyPlaceholder: '{"steps":[{"name":"step-a","agentName":"...","mode":{"type":"sequential"},"promptTemplate":"..."}]}', + bodyPlaceholder: '{"steps":[{"name":"step-a","agentId":1,"mode":{"type":"sequential"},"promptTemplate":"..."}]}', compileErrorsTitle: '{count} 条编译错误', templates: { label: '插入模板', @@ -1967,7 +1967,7 @@ export default { fullscreenExit: '退出全屏', addNode: '+ 添加节点', modeLabel: '模式', - nodeAgent: '智能体', + nodeAgent: '数字员工', nodeExpression: '条件', nodeApprovalKind: '审批类型', nodeChannels: '渠道', @@ -1996,10 +1996,8 @@ export default { fields: { name: '名称', namePlaceholder: 'step-name', - agentPlaceholder: '选择智能体', - agentMissing: '当前 agentName 不在列表中:{name}', - agentUseCustom: '— 使用自定义 agentName —', - agentBackToList: '回到下拉选择', + agentPlaceholder: '选择数字员工', + agentMissing: '当前数字员工不在列表中:{name}', promptTemplate: 'Prompt 模板', promptPlaceholder: 'Hello {{ inputs.payload }}', outputVar: '输出变量名', @@ -2009,7 +2007,11 @@ export default { approverChannels: '通知渠道(逗号分隔)', approvalMessage: '审批消息', timeoutSecs: '超时(秒)', - channels: '渠道(逗号分隔)', + channels: '分发渠道', + channelsEmptyHint: '当前工作区没有可用渠道,请先在渠道管理中启用渠道。', + channelsUnknown: '草稿里有不可用渠道:{channels}', + dispatchTarget: '{channel} 目标', + dispatchTargetPlaceholder: '群 ID / 用户 ID / 会话 ID', dispatchContent: '消息内容', employeeId: '员工 ID', memoryFile: '记忆文件', diff --git a/mateclaw-ui/src/views/Triggers.vue b/mateclaw-ui/src/views/Triggers.vue index f99e1542..482744d5 100644 --- a/mateclaw-ui/src/views/Triggers.vue +++ b/mateclaw-ui/src/views/Triggers.vue @@ -66,85 +66,92 @@ - - - - {{ editing?.id ? t('triggers.formTitleEdit') : t('triggers.formTitleNew') }} - {{ t('triggers.actions.close') }} - - - {{ t('triggers.fields.name') }} - - - {{ t('triggers.fields.patternType') }} - - cron - channel_message - content_match - agent_lifecycle - workflow_completion - webhook - - - - {{ t('triggers.fields.patternJson') }} - - - {{ t('triggers.fields.targetType') }} - - - workflow - - - {{ t('triggers.fields.targetId') }} - - - {{ t('triggers.targetWorkflowEmpty') }} - - - #{{ wf.id }} — {{ wf.name || t('triggers.unnamed') }} - - - - {{ t('triggers.fields.ratePerMin') }} - - - {{ t('triggers.fields.dedupWindowSecs') }} - - - {{ t('triggers.fields.maxFires') }} - - - - - {{ t('triggers.fields.botSelfFilter') }} - - {{ t('triggers.fields.payloadTemplate') }} - - - - - {{ t('triggers.fields.enabled') }} - + + + + + {{ editing?.id ? t('triggers.formTitleEdit') : t('triggers.formTitleNew') }} + {{ t('triggers.actions.close') }} + + + + + {{ t('triggers.fields.name') }} + + + {{ t('triggers.fields.patternType') }} + + cron + channel_message + content_match + agent_lifecycle + workflow_completion + webhook + + + + {{ t('triggers.fields.patternJson') }} + + + {{ t('triggers.fields.targetType') }} + + + workflow + + + {{ t('triggers.fields.targetId') }} + + + {{ t('triggers.targetWorkflowEmpty') }} + + + #{{ wf.id }} — {{ wf.name || t('triggers.unnamed') }} + + + + {{ t('triggers.fields.ratePerMin') }} + + + {{ t('triggers.fields.dedupWindowSecs') }} + + + {{ t('triggers.fields.maxFires') }} + + + + + {{ t('triggers.fields.botSelfFilter') }} + + {{ t('triggers.fields.payloadTemplate') }} + + + + + {{ t('triggers.fields.enabled') }} + + + + + + - - + @@ -456,19 +463,49 @@ watch(workspaceId, reload) display: flex; gap: 6px; } -.form-card { - padding: 16px; +.trigger-modal-overlay { + position: fixed; + inset: 0; + z-index: 1200; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgba(17, 12, 8, 0.42); + backdrop-filter: blur(6px); } -.form-card header { +.trigger-modal { + width: min(1120px, calc(100vw - 48px)); + max-height: calc(100vh - 48px); + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--mc-border, rgba(0, 0, 0, 0.12)); + border-radius: 12px; + background: var(--mc-bg-elevated, #fff); + color: var(--mc-text-primary, inherit); + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.28); +} +:global(html.dark) .trigger-modal { + background: #1f1713; +} +.trigger-modal-header { display: flex; justify-content: space-between; - margin-bottom: 12px; align-items: center; + gap: 16px; + padding: 16px 20px; + border-bottom: 1px solid var(--mc-border, rgba(0, 0, 0, 0.08)); + flex: 0 0 auto; +} +.trigger-modal-body { + padding: 16px 20px 20px; + overflow: auto; } .form-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 10px; + gap: 14px 16px; } .form-grid .span-2 { grid-column: span 2; @@ -490,10 +527,13 @@ watch(workspaceId, reload) color: inherit; font-family: inherit; font-size: 13px; + min-height: 38px; } .form-grid textarea { font-family: 'JetBrains Mono', Consolas, monospace; font-size: 12px; + min-height: 96px; + resize: vertical; } .pattern-hint { display: block; @@ -511,11 +551,27 @@ watch(workspaceId, reload) margin-bottom: 4px; color: var(--mc-text-secondary, inherit); } -.form-card footer { - margin-top: 12px; +.checkbox-field { + flex-direction: row !important; + align-items: center; + justify-content: flex-start; + min-height: 38px; + padding-top: 20px; +} +.checkbox-field input[type='checkbox'] { + width: 16px; + height: 16px; + min-height: 0; + flex: 0 0 auto; +} +.trigger-modal-footer { + flex: 0 0 auto; + padding: 14px 20px; display: flex; gap: 8px; justify-content: flex-end; + border-top: 1px solid var(--mc-border, rgba(0, 0, 0, 0.08)); + background: var(--mc-bg-elevated, #fff); } .btn-primary, .btn-ghost, diff --git a/mateclaw-ui/src/views/Workflows.vue b/mateclaw-ui/src/views/Workflows.vue index e269d744..7c3fa1a5 100644 --- a/mateclaw-ui/src/views/Workflows.vue +++ b/mateclaw-ui/src/views/Workflows.vue @@ -72,6 +72,7 @@ :step="selectedStep" :index="canvasSelection.index" :available-agents="availableAgents" + :available-channels="availableChannels" @patch="onStepPatch" @duplicate="onStepDuplicate" @delete="onStepDelete" @@ -97,11 +98,10 @@ {{ jsonHint }} - @@ -215,6 +215,7 @@ import { mcConfirm } from '@/components/common/useConfirm' import { ElMessage } from 'element-plus' import { agentApi, + channelApi, workflowApi, type WorkflowSummary, type WorkflowRun, @@ -224,9 +225,11 @@ import { type PausedRunSummary, type ResumeOutcome, } from '@/api' +import type { Channel } from '@/types' import { useWorkspaceStore } from '@/stores/useWorkspaceStore' import WorkflowCanvas from '@/components/workflow/WorkflowCanvas.vue' import StepPropertyPanel from '@/components/workflow/StepPropertyPanel.vue' +import WorkflowJsonEditor from '@/components/workflow/WorkflowJsonEditor.vue' import CreateWorkflowDialog from '@/components/workflow/CreateWorkflowDialog.vue' import PublishDialog from '@/components/workflow/PublishDialog.vue' import type { StepNodeData, RawStep } from '@/composables/useWorkflowGraph' @@ -260,6 +263,13 @@ const resumingId = ref(null) // exist. Loaded once on mount and on workspace switch. interface AgentOption { id: number; name: string; title?: string } const availableAgents = ref([]) +interface ChannelOption { + id: number | string + name: string + channelType: string + enabled?: boolean +} +const availableChannels = ref([]) const templateChoice = ref('') @@ -326,25 +336,20 @@ const jsonHintKind = computed(() => (jsonHint.value === t('workflows.jsonOk') ? const STEP_TEMPLATES: Record = { sequential: { name: 'step-sequential', - agentName: 'agent-name', mode: { type: 'sequential' }, promptTemplate: 'Process: {{ inputs.payload }}', }, fan_out: { name: 'step-fan', - agentName: 'agent-name', mode: { type: 'fan_out' }, promptTemplate: 'Branch task', }, collect: { name: 'step-collect', - agentName: 'agent-name', mode: { type: 'collect' }, - promptTemplate: 'Combine: {{ inputs.payload }}', }, conditional: { name: 'step-conditional', - agentName: 'agent-name', mode: { type: 'conditional', expression: '{{ inputs.payload != null }}' }, promptTemplate: 'Run only when condition holds', }, @@ -366,8 +371,8 @@ const STEP_TEMPLATES: Record = { name: 'step-dispatch', mode: { type: 'dispatch_channel', - channels: ['feishu'], - targets: { feishu: 'group-id-here' }, + channels: [], + targets: {}, content: 'Notification: {{ inputs.payload }}', }, }, @@ -496,6 +501,23 @@ async function reloadAgents() { } } +async function reloadChannels() { + try { + const res = await channelApi.list() + const rows = (res.data as unknown as Channel[]) ?? [] + availableChannels.value = rows + .filter((c) => c && c.channelType && c.enabled !== false) + .map((c) => ({ + id: c.id, + name: c.name || c.channelType, + channelType: c.channelType, + enabled: c.enabled, + })) + } catch (e) { + console.error('listChannels failed', e) + } +} + function truncateToken(token: string | undefined): string { if (!token) return '-' return token.length <= 14 ? token : token.slice(0, 6) + '…' + token.slice(-4) @@ -553,7 +575,6 @@ async function onCreateSubmit(payload: { name: string; description: string }) { steps: [ { name: 'first-step', - agentName: 'agent-name', mode: { type: 'sequential' }, promptTemplate: 'Hello {{ inputs.payload }}', }, @@ -695,11 +716,13 @@ onMounted(async () => { await reload() await reloadPausedRuns() await reloadAgents() + await reloadChannels() }) watch(workspaceId, async () => { await reload() await reloadPausedRuns() await reloadAgents() + await reloadChannels() })
{{ channel.channelType }}
{{ t('workflows.canvas.fields.channelsEmptyHint') }}
+ {{ t('workflows.canvas.fields.channelsUnknown', { channels: unknownSelectedChannels.join(', ') }) }} +