diff --git a/apps/aevatar-console-web/src/locales/en-US.ts b/apps/aevatar-console-web/src/locales/en-US.ts index 16bf596ed6..6e03913966 100644 --- a/apps/aevatar-console-web/src/locales/en-US.ts +++ b/apps/aevatar-console-web/src/locales/en-US.ts @@ -1128,6 +1128,24 @@ const enUSMessages = { 'pages.runs.memberPublishedRuns.refresh': 'Refresh', 'pages.runs.memberPublishedRuns.selectPublishedRun': 'Select a published run', 'pages.runs.memberPublishedRuns.timeline': 'Timeline', + 'teamMemberWorkflowStudio.aiProposal.apply': 'Apply proposal', + 'teamMemberWorkflowStudio.aiProposal.changeRequest': 'Change request', + 'teamMemberWorkflowStudio.aiProposal.generate': 'Generate proposal', + 'teamMemberWorkflowStudio.aiProposal.outdated': 'Outdated proposal', + 'teamMemberWorkflowStudio.aiProposal.panelAria': + 'AI workflow proposal panel', + 'teamMemberWorkflowStudio.aiProposal.promptPlaceholder': + 'Add an approval step after triage', + 'teamMemberWorkflowStudio.aiProposal.proposedYaml': + 'Proposed workflow YAML', + 'teamMemberWorkflowStudio.aiProposal.ready': 'Ready to review', + 'teamMemberWorkflowStudio.aiProposal.reasoning': 'Reasoning', + 'teamMemberWorkflowStudio.aiProposal.reasoningAria': 'Proposal reasoning', + 'teamMemberWorkflowStudio.aiProposal.retry': 'Retry proposal', + 'teamMemberWorkflowStudio.aiProposal.stale': + 'Draft changed after this proposal was generated. Generate a new proposal from the current draft.', + 'teamMemberWorkflowStudio.aiProposal.subtitle': 'Candidate workflow change', + 'teamMemberWorkflowStudio.aiProposal.title': 'Ask AI', 'teamMemberWorkflowStudio.alerts.linkedWorkflowMissing.description': 'You can build or edit the workflow YAML here. Saving creates a reusable workflow draft until the member link is materialized.', 'teamMemberWorkflowStudio.alerts.linkedWorkflowMissing.publishedDescription': @@ -1216,6 +1234,11 @@ const enUSMessages = { 'teamMemberWorkflowStudio.header.activation.publishing': 'Publishing', 'teamMemberWorkflowStudio.header.activation.ready': 'Ready', 'teamMemberWorkflowStudio.header.addNode': 'Add node', + 'teamMemberWorkflowStudio.header.askAi': 'Ask AI', + 'teamMemberWorkflowStudio.header.askAiTitle': + 'Generate a reviewable workflow proposal', + 'teamMemberWorkflowStudio.header.askAiUnavailable': + 'Load the workflow draft before asking AI.', 'teamMemberWorkflowStudio.header.automations.publishFirst': 'Publish this member before adding recurring work.', 'teamMemberWorkflowStudio.header.automations.saveFirst': diff --git a/apps/aevatar-console-web/src/locales/zh-CN.ts b/apps/aevatar-console-web/src/locales/zh-CN.ts index e6ba6949e9..eb22377082 100644 --- a/apps/aevatar-console-web/src/locales/zh-CN.ts +++ b/apps/aevatar-console-web/src/locales/zh-CN.ts @@ -1061,6 +1061,22 @@ const zhCNMessages = { 'pages.runs.memberPublishedRuns.refresh': '刷新', 'pages.runs.memberPublishedRuns.selectPublishedRun': '选择一次发布运行', 'pages.runs.memberPublishedRuns.timeline': '时间线', + 'teamMemberWorkflowStudio.aiProposal.apply': '应用提案', + 'teamMemberWorkflowStudio.aiProposal.changeRequest': '变更请求', + 'teamMemberWorkflowStudio.aiProposal.generate': '生成提案', + 'teamMemberWorkflowStudio.aiProposal.outdated': '提案已过期', + 'teamMemberWorkflowStudio.aiProposal.panelAria': 'AI Workflow 提案面板', + 'teamMemberWorkflowStudio.aiProposal.promptPlaceholder': + '在分流节点后添加审批步骤', + 'teamMemberWorkflowStudio.aiProposal.proposedYaml': '提案 Workflow YAML', + 'teamMemberWorkflowStudio.aiProposal.ready': '可供审阅', + 'teamMemberWorkflowStudio.aiProposal.reasoning': '推理摘要', + 'teamMemberWorkflowStudio.aiProposal.reasoningAria': '提案推理摘要', + 'teamMemberWorkflowStudio.aiProposal.retry': '重试提案', + 'teamMemberWorkflowStudio.aiProposal.stale': + '生成提案后草稿已发生更改。请基于当前草稿重新生成提案。', + 'teamMemberWorkflowStudio.aiProposal.subtitle': '候选 Workflow 变更', + 'teamMemberWorkflowStudio.aiProposal.title': '询问 AI', 'teamMemberWorkflowStudio.alerts.linkedWorkflowMissing.description': '你可以在这里搭建或编辑 Workflow YAML。保存时会创建可恢复的 Workflow 草稿,直到成员关联完成物化。', 'teamMemberWorkflowStudio.alerts.linkedWorkflowMissing.publishedDescription': @@ -1149,6 +1165,10 @@ const zhCNMessages = { 'teamMemberWorkflowStudio.header.activation.publishing': '发布中', 'teamMemberWorkflowStudio.header.activation.ready': '就绪', 'teamMemberWorkflowStudio.header.addNode': '添加节点', + 'teamMemberWorkflowStudio.header.askAi': '询问 AI', + 'teamMemberWorkflowStudio.header.askAiTitle': '生成可审阅的 Workflow 提案', + 'teamMemberWorkflowStudio.header.askAiUnavailable': + '加载 Workflow 草稿后才能询问 AI。', 'teamMemberWorkflowStudio.header.automations.publishFirst': '先发布这个成员,再添加周期任务。', 'teamMemberWorkflowStudio.header.automations.saveFirst': diff --git a/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioAiProposalPanel.tsx b/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioAiProposalPanel.tsx new file mode 100644 index 0000000000..b32a53b0f3 --- /dev/null +++ b/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioAiProposalPanel.tsx @@ -0,0 +1,203 @@ +import { + CheckCircleOutlined, + ReloadOutlined, + RobotOutlined, +} from "@ant-design/icons"; +import { Alert, Button, Input, Space, Tag, Typography } from "antd"; +import React from "react"; +import { t } from "@/shared/i18n/messages"; +import { aevatarMonoFontFamily } from "@/shared/ui/compactText"; +import WorkflowStudioSidePanel from "./WorkflowStudioSidePanel"; + +type WorkflowStudioAiProposalPanelProps = { + readonly error: string; + readonly hasConflict: boolean; + readonly onApply: () => void; + readonly onClose: () => void; + readonly onGenerate: () => void; + readonly onPromptChange: (prompt: string) => void; + readonly open: boolean; + readonly pending: boolean; + readonly prompt: string; + readonly proposalYaml: string; + readonly reasoning: string; + readonly width: number; +}; + +const proposalStackStyle: React.CSSProperties = { + alignContent: "start", + display: "grid", + gap: 16, + minWidth: 0, +}; + +const proposalSectionStyle: React.CSSProperties = { + display: "grid", + gap: 8, + minWidth: 0, +}; + +const proposalLabelStyle: React.CSSProperties = { + color: "#374151", + fontSize: 12, + fontWeight: 700, +}; + +const proposalYamlStyle: React.CSSProperties = { + fontFamily: aevatarMonoFontFamily, + fontSize: 12, + lineHeight: 1.6, + minHeight: 220, + resize: "vertical", +}; + +const WorkflowStudioAiProposalPanel: React.FC< + WorkflowStudioAiProposalPanelProps +> = ({ + error, + hasConflict, + onApply, + onClose, + onGenerate, + onPromptChange, + open, + pending, + prompt, + proposalYaml, + reasoning, + width, +}) => { + if (!open) { + return null; + } + + const hasProposal = Boolean(proposalYaml.trim()); + const canGenerate = Boolean(prompt.trim()) && !pending; + const generateLabel = error + ? t("teamMemberWorkflowStudio.aiProposal.retry", "Retry proposal") + : t("teamMemberWorkflowStudio.aiProposal.generate", "Generate proposal"); + + return ( + +
+
+ + onPromptChange(event.target.value)} + placeholder={t( + "teamMemberWorkflowStudio.aiProposal.promptPlaceholder", + "Add an approval step after triage", + )} + value={prompt} + /> + +
+ + {error ? : null} + {hasConflict ? ( + + ) : null} + + {reasoning ? ( +
+ + {t("teamMemberWorkflowStudio.aiProposal.reasoning", "Reasoning")} + + + {reasoning} + +
+ ) : null} + + {hasProposal ? ( +
+ + + {hasConflict + ? t( + "teamMemberWorkflowStudio.aiProposal.outdated", + "Outdated proposal", + ) + : t( + "teamMemberWorkflowStudio.aiProposal.ready", + "Ready to review", + )} + + + + + +
+ ) : null} +
+
+ ); +}; + +export default WorkflowStudioAiProposalPanel; diff --git a/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioHeader.tsx b/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioHeader.tsx index bc8d94da25..3f92e01fe7 100644 --- a/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioHeader.tsx +++ b/apps/aevatar-console-web/src/pages/team-member-workflow-studio/components/WorkflowStudioHeader.tsx @@ -10,6 +10,7 @@ import { PlayCircleOutlined, PlusOutlined, ReloadOutlined, + RobotOutlined, SaveOutlined, } from '@ant-design/icons'; import type { InputRef, MenuProps } from 'antd'; @@ -25,6 +26,7 @@ type WorkflowStudioHeaderProps = { readonly canOpenAutomations: boolean; readonly canOpenInvoke: boolean; readonly canOpenPublishedRuns: boolean; + readonly canAskAi: boolean; readonly invokeHref: string; readonly invokePlaceholderReason?: string; readonly memberPublished: boolean; @@ -51,6 +53,7 @@ type WorkflowStudioHeaderProps = { readonly onOpenAutomations: () => void; readonly onOpenInvoke: () => void; readonly onOpenPublishedRuns: () => void; + readonly onOpenAiProposal: () => void; readonly onRefreshPublishStatus: () => void; readonly onAddNode: () => void; readonly onDeleteConnection: () => void; @@ -640,6 +643,7 @@ type HeaderActionsProps = { readonly canOpenDraftRunPanel: boolean; readonly canOpenInvoke: boolean; readonly canOpenPublishedRuns: boolean; + readonly canAskAi: boolean; readonly canSave: boolean; readonly canEditYaml: boolean; readonly invokeHref: string; @@ -651,6 +655,7 @@ type HeaderActionsProps = { readonly onOpenDraftRunPanel: () => void; readonly onOpenInvoke: () => void; readonly onOpenPublishedRuns: () => void; + readonly onOpenAiProposal: () => void; readonly onEditYaml: () => void; readonly onPublishMember: () => void; readonly onRefreshPublishStatus: () => void; @@ -677,6 +682,7 @@ const HeaderActions: React.FC = ({ canOpenDraftRunPanel, canOpenInvoke, canOpenPublishedRuns, + canAskAi, canSave, canEditYaml, invokeHref, @@ -689,6 +695,7 @@ const HeaderActions: React.FC = ({ onEditYaml, onOpenInvoke, onOpenPublishedRuns, + onOpenAiProposal, onPublishMember, onRefreshPublishStatus, onSave, @@ -793,6 +800,29 @@ const HeaderActions: React.FC = ({ className="workflow-studio-header__action-group workflow-studio-header__action-group--edit" data-testid="workflow-header-edit-actions" > +