Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions apps/aevatar-console-web/src/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down
20 changes: 20 additions & 0 deletions apps/aevatar-console-web/src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<WorkflowStudioSidePanel
ariaLabel={t(
"teamMemberWorkflowStudio.aiProposal.panelAria",
"AI workflow proposal panel",
)}
onClose={onClose}
subtitle={t(
"teamMemberWorkflowStudio.aiProposal.subtitle",
"Candidate workflow change",
)}
title={t("teamMemberWorkflowStudio.aiProposal.title", "Ask AI")}
width={width}
>
<div style={proposalStackStyle}>
<section style={proposalSectionStyle}>
<label
htmlFor="workflow-ai-change-request"
style={proposalLabelStyle}
>
{t(
"teamMemberWorkflowStudio.aiProposal.changeRequest",
"Change request",
)}
</label>
<Input.TextArea
autoSize={{ minRows: 4, maxRows: 9 }}
disabled={pending}
id="workflow-ai-change-request"
onChange={(event) => onPromptChange(event.target.value)}
placeholder={t(
"teamMemberWorkflowStudio.aiProposal.promptPlaceholder",
"Add an approval step after triage",
)}
value={prompt}
/>
<Button
disabled={!canGenerate}
icon={error ? <ReloadOutlined /> : <RobotOutlined />}
loading={pending}
onClick={onGenerate}
type="primary"
>
{generateLabel}
</Button>
</section>

{error ? <Alert message={error} showIcon type="error" /> : null}
{hasConflict ? (
<Alert
message={t(
"teamMemberWorkflowStudio.aiProposal.stale",
"Draft changed after this proposal was generated. Generate a new proposal from the current draft.",
)}
showIcon
type="warning"
/>
) : null}

{reasoning ? (
<section aria-label={t(
"teamMemberWorkflowStudio.aiProposal.reasoningAria",
"Proposal reasoning",
)} style={proposalSectionStyle}>
<Typography.Text strong>
{t("teamMemberWorkflowStudio.aiProposal.reasoning", "Reasoning")}
</Typography.Text>
<Typography.Paragraph
style={{ color: "#4b5563", margin: 0, whiteSpace: "pre-wrap" }}
>
{reasoning}
</Typography.Paragraph>
</section>
) : null}

{hasProposal ? (
<section style={proposalSectionStyle}>
<Space align="center" wrap>
<Tag color={hasConflict ? "warning" : "success"}>
{hasConflict
? t(
"teamMemberWorkflowStudio.aiProposal.outdated",
"Outdated proposal",
)
: t(
"teamMemberWorkflowStudio.aiProposal.ready",
"Ready to review",
)}
</Tag>
</Space>
<label
htmlFor="workflow-ai-proposed-yaml"
style={proposalLabelStyle}
>
{t(
"teamMemberWorkflowStudio.aiProposal.proposedYaml",
"Proposed workflow YAML",
)}
</label>
<Input.TextArea
id="workflow-ai-proposed-yaml"
readOnly
style={proposalYamlStyle}
value={proposalYaml}
/>
<Button
disabled={hasConflict || pending}
icon={<CheckCircleOutlined />}
onClick={onApply}
type="primary"
>
{t(
"teamMemberWorkflowStudio.aiProposal.apply",
"Apply proposal",
)}
</Button>
</section>
) : null}
</div>
</WorkflowStudioSidePanel>
);
};

export default WorkflowStudioAiProposalPanel;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PlayCircleOutlined,
PlusOutlined,
ReloadOutlined,
RobotOutlined,
SaveOutlined,
} from '@ant-design/icons';
import type { InputRef, MenuProps } from 'antd';
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -677,6 +682,7 @@ const HeaderActions: React.FC<HeaderActionsProps> = ({
canOpenDraftRunPanel,
canOpenInvoke,
canOpenPublishedRuns,
canAskAi,
canSave,
canEditYaml,
invokeHref,
Expand All @@ -689,6 +695,7 @@ const HeaderActions: React.FC<HeaderActionsProps> = ({
onEditYaml,
onOpenInvoke,
onOpenPublishedRuns,
onOpenAiProposal,
onPublishMember,
onRefreshPublishStatus,
onSave,
Expand Down Expand Up @@ -793,6 +800,29 @@ const HeaderActions: React.FC<HeaderActionsProps> = ({
className="workflow-studio-header__action-group workflow-studio-header__action-group--edit"
data-testid="workflow-header-edit-actions"
>
<Button
aria-label={t('teamMemberWorkflowStudio.header.askAi', 'Ask AI')}
className="workflow-studio-header__compact-button"
disabled={!canAskAi}
icon={<RobotOutlined />}
onClick={onOpenAiProposal}
size="small"
title={
canAskAi
? t(
'teamMemberWorkflowStudio.header.askAiTitle',
'Generate a reviewable workflow proposal',
)
: t(
'teamMemberWorkflowStudio.header.askAiUnavailable',
'Load the workflow draft before asking AI.',
)
}
>
<span className="workflow-studio-header__action-label">
{t('teamMemberWorkflowStudio.header.askAi', 'Ask AI')}
</span>
</Button>
<Button
aria-label={t('teamMemberWorkflowStudio.header.addNode', 'Add node')}
className="workflow-studio-header__compact-button"
Expand Down Expand Up @@ -1022,6 +1052,7 @@ const WorkflowStudioHeader: React.FC<WorkflowStudioHeaderProps> = ({
canOpenAutomations,
canOpenInvoke,
canOpenPublishedRuns,
canAskAi,
invokeHref,
invokePlaceholderReason,
memberPublished,
Expand All @@ -1043,6 +1074,7 @@ const WorkflowStudioHeader: React.FC<WorkflowStudioHeaderProps> = ({
onOpenAutomations,
onOpenInvoke,
onOpenPublishedRuns,
onOpenAiProposal,
onRefreshPublishStatus,
onAddNode,
onDeleteConnection,
Expand Down Expand Up @@ -1113,6 +1145,7 @@ const WorkflowStudioHeader: React.FC<WorkflowStudioHeaderProps> = ({
canOpenDraftRunPanel={canOpenDraftRunPanel}
canOpenInvoke={canOpenInvoke}
canOpenPublishedRuns={canOpenPublishedRuns}
canAskAi={canAskAi}
canSave={canSave}
canEditYaml={canEditYaml}
invokeHref={invokeHref}
Expand All @@ -1125,6 +1158,7 @@ const WorkflowStudioHeader: React.FC<WorkflowStudioHeaderProps> = ({
onEditYaml={onEditYaml}
onOpenInvoke={onOpenInvoke}
onOpenPublishedRuns={onOpenPublishedRuns}
onOpenAiProposal={onOpenAiProposal}
onPublishMember={onPublishMember}
onRefreshPublishStatus={onRefreshPublishStatus}
onSave={onSave}
Expand Down
Loading
Loading