小黑马 Kevin Hu commited on
Commit
d11c358
·
1 Parent(s): 1125fe8

Email sending tool (#3837)

Browse files

### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._
Added the function of sending emails through SMTP
Instructions for use-
Corresponding parameters need to be configured
Need to output upstream in a fixed format

![image](https://github.com/user-attachments/assets/93bc1af7-6d4f-4406-bd1d-bc042535dd82)


### Type of change


- [√] New Feature (non-breaking change which adds functionality)

---------

Co-authored-by: Kevin Hu <[email protected]>

agent/component/__init__.py CHANGED
@@ -31,6 +31,8 @@ from .akshare import AkShare, AkShareParam
31
  from .crawler import Crawler, CrawlerParam
32
  from .invoke import Invoke, InvokeParam
33
  from .template import Template, TemplateParam
 
 
34
 
35
 
36
  def component_class(class_name):
 
31
  from .crawler import Crawler, CrawlerParam
32
  from .invoke import Invoke, InvokeParam
33
  from .template import Template, TemplateParam
34
+ from .email import Email, EmailParam
35
+
36
 
37
 
38
  def component_class(class_name):
agent/component/email.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ from abc import ABC
18
+ import json
19
+ import smtplib
20
+ import logging
21
+ from email.mime.text import MIMEText
22
+ from email.mime.multipart import MIMEMultipart
23
+ from email.header import Header
24
+ from email.utils import formataddr
25
+ from agent.component.base import ComponentBase, ComponentParamBase
26
+
27
+ class EmailParam(ComponentParamBase):
28
+ """
29
+ Define the Email component parameters.
30
+ """
31
+ def __init__(self):
32
+ super().__init__()
33
+ # Fixed configuration parameters
34
+ self.smtp_server = "" # SMTP server address
35
+ self.smtp_port = 465 # SMTP port
36
+ self.email = "" # Sender email
37
+ self.password = "" # Email authorization code
38
+ self.sender_name = "" # Sender name
39
+
40
+ def check(self):
41
+ # Check required parameters
42
+ self.check_empty(self.smtp_server, "SMTP Server")
43
+ self.check_empty(self.email, "Email")
44
+ self.check_empty(self.password, "Password")
45
+ self.check_empty(self.sender_name, "Sender Name")
46
+
47
+ class Email(ComponentBase, ABC):
48
+ component_name = "Email"
49
+
50
+ def _run(self, history, **kwargs):
51
+ # Get upstream component output and parse JSON
52
+ ans = self.get_input()
53
+ content = "".join(ans["content"]) if "content" in ans else ""
54
+ if not content:
55
+ return Email.be_output("No content to send")
56
+
57
+ success = False
58
+ try:
59
+ # Parse JSON string passed from upstream
60
+ email_data = json.loads(content)
61
+
62
+ # Validate required fields
63
+ if "to_email" not in email_data:
64
+ return Email.be_output("Missing required field: to_email")
65
+
66
+ # Create email object
67
+ msg = MIMEMultipart('alternative')
68
+
69
+ # Properly handle sender name encoding
70
+ msg['From'] = formataddr((str(Header(self._param.sender_name,'utf-8')), self._param.email))
71
+ msg['To'] = email_data["to_email"]
72
+ if "cc_email" in email_data and email_data["cc_email"]:
73
+ msg['Cc'] = email_data["cc_email"]
74
+ msg['Subject'] = Header(email_data.get("subject", "No Subject"), 'utf-8').encode()
75
+
76
+ # Use content from email_data or default content
77
+ email_content = email_data.get("content", "No content provided")
78
+ # msg.attach(MIMEText(email_content, 'plain', 'utf-8'))
79
+ msg.attach(MIMEText(email_content, 'html', 'utf-8'))
80
+
81
+ # Connect to SMTP server and send
82
+ logging.info(f"Connecting to SMTP server {self._param.smtp_server}:{self._param.smtp_port}")
83
+
84
+ context = smtplib.ssl.create_default_context()
85
+ with smtplib.SMTP_SSL(self._param.smtp_server, self._param.smtp_port, context=context) as server:
86
+ # Login
87
+ logging.info(f"Attempting to login with email: {self._param.email}")
88
+ server.login(self._param.email, self._param.password)
89
+
90
+ # Get all recipient list
91
+ recipients = [email_data["to_email"]]
92
+ if "cc_email" in email_data and email_data["cc_email"]:
93
+ recipients.extend(email_data["cc_email"].split(','))
94
+
95
+ # Send email
96
+ logging.info(f"Sending email to recipients: {recipients}")
97
+ try:
98
+ server.send_message(msg, self._param.email, recipients)
99
+ success = True
100
+ except Exception as e:
101
+ logging.error(f"Error during send_message: {str(e)}")
102
+ # Try alternative method
103
+ server.sendmail(self._param.email, recipients, msg.as_string())
104
+ success = True
105
+
106
+ try:
107
+ server.quit()
108
+ except Exception as e:
109
+ # Ignore errors when closing connection
110
+ logging.warning(f"Non-fatal error during connection close: {str(e)}")
111
+
112
+ if success:
113
+ return Email.be_output("Email sent successfully")
114
+
115
+ except json.JSONDecodeError:
116
+ error_msg = "Invalid JSON format in input"
117
+ logging.error(error_msg)
118
+ return Email.be_output(error_msg)
119
+
120
+ except smtplib.SMTPAuthenticationError:
121
+ error_msg = "SMTP Authentication failed. Please check your email and authorization code."
122
+ logging.error(error_msg)
123
+ return Email.be_output(f"Failed to send email: {error_msg}")
124
+
125
+ except smtplib.SMTPConnectError:
126
+ error_msg = f"Failed to connect to SMTP server {self._param.smtp_server}:{self._param.smtp_port}"
127
+ logging.error(error_msg)
128
+ return Email.be_output(f"Failed to send email: {error_msg}")
129
+
130
+ except smtplib.SMTPException as e:
131
+ error_msg = f"SMTP error occurred: {str(e)}"
132
+ logging.error(error_msg)
133
+ return Email.be_output(f"Failed to send email: {error_msg}")
134
+
135
+ except Exception as e:
136
+ error_msg = f"Unexpected error: {str(e)}"
137
+ logging.error(error_msg)
138
+ return Email.be_output(f"Failed to send email: {error_msg}")
web/src/assets/svg/email.svg ADDED
web/src/locales/en.ts CHANGED
@@ -1050,6 +1050,31 @@ When you want to search the given knowledge base at first place, set a higher pa
1050
  template: 'Template',
1051
  templateDescription:
1052
  'This component is used for typesetting the outputs of various components.',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1053
  },
1054
  footer: {
1055
  profile: 'All rights reserved @ React',
 
1050
  template: 'Template',
1051
  templateDescription:
1052
  'This component is used for typesetting the outputs of various components.',
1053
+ emailComponent: 'Email',
1054
+ emailDescription: 'Send email to specified address',
1055
+ smtpServer: 'SMTP Server',
1056
+ smtpPort: 'SMTP Port',
1057
+ senderEmail: 'Sender Email',
1058
+ authCode: 'Authorization Code',
1059
+ senderName: 'Sender Name',
1060
+ toEmail: 'Recipient Email',
1061
+ ccEmail: 'CC Email',
1062
+ emailSubject: 'Subject',
1063
+ emailContent: 'Content',
1064
+ smtpServerRequired: 'Please input SMTP server address',
1065
+ senderEmailRequired: 'Please input sender email',
1066
+ authCodeRequired: 'Please input authorization code',
1067
+ toEmailRequired: 'Please input recipient email',
1068
+ emailContentRequired: 'Please input email content',
1069
+ emailSentSuccess: 'Email sent successfully',
1070
+ emailSentFailed: 'Failed to send email',
1071
+ dynamicParameters: 'Dynamic Parameters',
1072
+ jsonFormatTip:
1073
+ 'Upstream component should provide JSON string in following format:',
1074
+ toEmailTip: 'to_email: Recipient email (Required)',
1075
+ ccEmailTip: 'cc_email: CC email (Optional)',
1076
+ subjectTip: 'subject: Email subject (Optional)',
1077
+ contentTip: 'content: Email content (Optional)',
1078
  },
1079
  footer: {
1080
  profile: 'All rights reserved @ React',
web/src/locales/zh.ts CHANGED
@@ -1029,6 +1029,30 @@ export default {
1029
  testRun: '试运行',
1030
  template: '模板转换',
1031
  templateDescription: '该组件用于排版各种组件的输出。',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1032
  },
1033
  footer: {
1034
  profile: 'All rights reserved @ React',
 
1029
  testRun: '试运行',
1030
  template: '模板转换',
1031
  templateDescription: '该组件用于排版各种组件的输出。',
1032
+ emailComponent: '邮件',
1033
+ emailDescription: '发送邮件到指定邮箱',
1034
+ smtpServer: 'SMTP服务器',
1035
+ smtpPort: 'SMTP端口',
1036
+ senderEmail: '发件人邮箱',
1037
+ authCode: '授权码',
1038
+ senderName: '发件人名称',
1039
+ toEmail: '收件人邮箱',
1040
+ ccEmail: '抄送邮箱',
1041
+ emailSubject: '邮件主题',
1042
+ emailContent: '邮件内容',
1043
+ smtpServerRequired: '请输入SMTP服务器地址',
1044
+ senderEmailRequired: '请输入发件人邮箱',
1045
+ authCodeRequired: '请输入授权码',
1046
+ toEmailRequired: '请输入收件人邮箱',
1047
+ emailContentRequired: '请输入邮件内容',
1048
+ emailSentSuccess: '邮件发送成功',
1049
+ emailSentFailed: '邮件发送失败',
1050
+ dynamicParameters: '动态参数说明',
1051
+ jsonFormatTip: '上游组件需要传入以下格式的JSON字符串:',
1052
+ toEmailTip: 'to_email: 收件人邮箱(必填)',
1053
+ ccEmailTip: 'cc_email: 抄送邮箱(可选)',
1054
+ subjectTip: 'subject: 邮件主题(可选)',
1055
+ contentTip: 'content: 邮件内容(可选)',
1056
  },
1057
  footer: {
1058
  profile: 'All rights reserved @ React',
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -25,6 +25,7 @@ import styles from './index.less';
25
  import { RagNode } from './node';
26
  import { BeginNode } from './node/begin-node';
27
  import { CategorizeNode } from './node/categorize-node';
 
28
  import { GenerateNode } from './node/generate-node';
29
  import { InvokeNode } from './node/invoke-node';
30
  import { KeywordNode } from './node/keyword-node';
@@ -52,6 +53,7 @@ const nodeTypes = {
52
  keywordNode: KeywordNode,
53
  invokeNode: InvokeNode,
54
  templateNode: TemplateNode,
 
55
  };
56
 
57
  const edgeTypes = {
 
25
  import { RagNode } from './node';
26
  import { BeginNode } from './node/begin-node';
27
  import { CategorizeNode } from './node/categorize-node';
28
+ import { EmailNode } from './node/email-node';
29
  import { GenerateNode } from './node/generate-node';
30
  import { InvokeNode } from './node/invoke-node';
31
  import { KeywordNode } from './node/keyword-node';
 
53
  keywordNode: KeywordNode,
54
  invokeNode: InvokeNode,
55
  templateNode: TemplateNode,
56
+ emailNode: EmailNode,
57
  };
58
 
59
  const edgeTypes = {
web/src/pages/flow/canvas/node/email-node.tsx ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { useState } from 'react';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { NodeData } from '../../interface';
6
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
7
+ import styles from './index.less';
8
+ import NodeHeader from './node-header';
9
+
10
+ export function EmailNode({
11
+ id,
12
+ data,
13
+ isConnectable = true,
14
+ selected,
15
+ }: NodeProps<NodeData>) {
16
+ const [showDetails, setShowDetails] = useState(false);
17
+
18
+ return (
19
+ <section
20
+ className={classNames(styles.ragNode, {
21
+ [styles.selectedNode]: selected,
22
+ })}
23
+ >
24
+ <Handle
25
+ id="c"
26
+ type="source"
27
+ position={Position.Left}
28
+ isConnectable={isConnectable}
29
+ className={styles.handle}
30
+ style={LeftHandleStyle}
31
+ ></Handle>
32
+ <Handle
33
+ type="source"
34
+ position={Position.Right}
35
+ isConnectable={isConnectable}
36
+ className={styles.handle}
37
+ style={RightHandleStyle}
38
+ id="b"
39
+ ></Handle>
40
+ <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
41
+
42
+ <Flex vertical gap={8} className={styles.emailNodeContainer}>
43
+ <div
44
+ className={styles.emailConfig}
45
+ onClick={() => setShowDetails(!showDetails)}
46
+ >
47
+ <div className={styles.configItem}>
48
+ <span className={styles.configLabel}>SMTP:</span>
49
+ <span className={styles.configValue}>{data.form?.smtp_server}</span>
50
+ </div>
51
+ <div className={styles.configItem}>
52
+ <span className={styles.configLabel}>Port:</span>
53
+ <span className={styles.configValue}>{data.form?.smtp_port}</span>
54
+ </div>
55
+ <div className={styles.configItem}>
56
+ <span className={styles.configLabel}>From:</span>
57
+ <span className={styles.configValue}>{data.form?.email}</span>
58
+ </div>
59
+ <div className={styles.expandIcon}>{showDetails ? '▼' : '▶'}</div>
60
+ </div>
61
+
62
+ {showDetails && (
63
+ <div className={styles.jsonExample}>
64
+ <div className={styles.jsonTitle}>Expected Input JSON:</div>
65
+ <pre className={styles.jsonContent}>
66
+ {`{
67
+ "to_email": "...",
68
+ "cc_email": "...",
69
+ "subject": "...",
70
+ "content": "..."
71
+ }`}
72
+ </pre>
73
+ </div>
74
+ )}
75
+ </Flex>
76
+ </section>
77
+ );
78
+ }
web/src/pages/flow/canvas/node/index.less CHANGED
@@ -193,3 +193,80 @@
193
  .conditionLine;
194
  }
195
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  .conditionLine;
194
  }
195
  }
196
+
197
+ .emailNodeContainer {
198
+ padding: 8px;
199
+ font-size: 12px;
200
+
201
+ .emailConfig {
202
+ background: rgba(0, 0, 0, 0.02);
203
+ border-radius: 4px;
204
+ padding: 8px;
205
+ position: relative;
206
+ cursor: pointer;
207
+
208
+ &:hover {
209
+ background: rgba(0, 0, 0, 0.04);
210
+ }
211
+
212
+ .configItem {
213
+ display: flex;
214
+ align-items: center;
215
+ margin-bottom: 4px;
216
+
217
+ &:last-child {
218
+ margin-bottom: 0;
219
+ }
220
+
221
+ .configLabel {
222
+ color: #666;
223
+ width: 45px;
224
+ flex-shrink: 0;
225
+ }
226
+
227
+ .configValue {
228
+ color: #333;
229
+ word-break: break-all;
230
+ }
231
+ }
232
+
233
+ .expandIcon {
234
+ position: absolute;
235
+ right: 8px;
236
+ top: 50%;
237
+ transform: translateY(-50%);
238
+ color: #666;
239
+ font-size: 12px;
240
+ }
241
+ }
242
+
243
+ .jsonExample {
244
+ background: #f5f5f5;
245
+ border-radius: 4px;
246
+ padding: 8px;
247
+ margin-top: 4px;
248
+ animation: slideDown 0.2s ease-out;
249
+
250
+ .jsonTitle {
251
+ color: #666;
252
+ margin-bottom: 4px;
253
+ }
254
+
255
+ .jsonContent {
256
+ margin: 0;
257
+ color: #333;
258
+ font-family: monospace;
259
+ }
260
+ }
261
+ }
262
+
263
+ @keyframes slideDown {
264
+ from {
265
+ opacity: 0;
266
+ transform: translateY(-10px);
267
+ }
268
+ to {
269
+ opacity: 1;
270
+ transform: translateY(0);
271
+ }
272
+ }
web/src/pages/flow/constant.tsx CHANGED
@@ -8,6 +8,7 @@ import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.sv
8
  import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
9
  import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg';
10
  import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
 
11
  import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
12
  import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
13
  import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
@@ -25,6 +26,8 @@ import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
25
  import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
26
  import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
27
 
 
 
28
  import { variableEnabledFieldMap } from '@/constants/chat';
29
  import i18n from '@/locales/config';
30
 
@@ -87,6 +90,7 @@ export enum Operator {
87
  Crawler = 'Crawler',
88
  Invoke = 'Invoke',
89
  Template = 'Template',
 
90
  }
91
 
92
  export const CommonOperatorList = Object.values(Operator).filter(
@@ -127,6 +131,7 @@ export const operatorIconMap = {
127
  [Operator.Crawler]: CrawlerIcon,
128
  [Operator.Invoke]: InvokeIcon,
129
  [Operator.Template]: TemplateIcon,
 
130
  };
131
 
132
  export const operatorMap: Record<
@@ -259,6 +264,7 @@ export const operatorMap: Record<
259
  [Operator.Template]: {
260
  backgroundColor: '#dee0e2',
261
  },
 
262
  };
263
 
264
  export const componentMenuList = [
@@ -358,6 +364,9 @@ export const componentMenuList = [
358
  {
359
  name: Operator.Invoke,
360
  },
 
 
 
361
  ];
362
 
363
  const initialQueryBaseValues = {
@@ -580,6 +589,18 @@ export const initialTemplateValues = {
580
  parameters: [],
581
  };
582
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  export const CategorizeAnchorPointPositions = [
584
  { top: 1, right: 34 },
585
  { top: 8, right: 18 },
@@ -660,6 +681,7 @@ export const RestrictedUpstreamMap = {
660
  [Operator.Note]: [],
661
  [Operator.Invoke]: [Operator.Begin],
662
  [Operator.Template]: [Operator.Begin, Operator.Relevant],
 
663
  };
664
 
665
  export const NodeMap = {
@@ -696,6 +718,7 @@ export const NodeMap = {
696
  [Operator.Crawler]: 'ragNode',
697
  [Operator.Invoke]: 'invokeNode',
698
  [Operator.Template]: 'templateNode',
 
699
  };
700
 
701
  export const LanguageOptions = [
 
8
  import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
9
  import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg';
10
  import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
11
+ import { ReactComponent as EmailIcon } from '@/assets/svg/email.svg';
12
  import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
13
  import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
14
  import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
 
26
  import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
27
  import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
28
 
29
+ // 邮件功能
30
+
31
  import { variableEnabledFieldMap } from '@/constants/chat';
32
  import i18n from '@/locales/config';
33
 
 
90
  Crawler = 'Crawler',
91
  Invoke = 'Invoke',
92
  Template = 'Template',
93
+ Email = 'Email',
94
  }
95
 
96
  export const CommonOperatorList = Object.values(Operator).filter(
 
131
  [Operator.Crawler]: CrawlerIcon,
132
  [Operator.Invoke]: InvokeIcon,
133
  [Operator.Template]: TemplateIcon,
134
+ [Operator.Email]: EmailIcon,
135
  };
136
 
137
  export const operatorMap: Record<
 
264
  [Operator.Template]: {
265
  backgroundColor: '#dee0e2',
266
  },
267
+ [Operator.Email]: { backgroundColor: '#e6f7ff' },
268
  };
269
 
270
  export const componentMenuList = [
 
364
  {
365
  name: Operator.Invoke,
366
  },
367
+ {
368
+ name: Operator.Email,
369
+ },
370
  ];
371
 
372
  const initialQueryBaseValues = {
 
589
  parameters: [],
590
  };
591
 
592
+ export const initialEmailValues = {
593
+ smtp_server: '',
594
+ smtp_port: 587,
595
+ email: '',
596
+ password: '',
597
+ sender_name: '',
598
+ to_email: '',
599
+ cc_email: '',
600
+ subject: '',
601
+ content: '',
602
+ };
603
+
604
  export const CategorizeAnchorPointPositions = [
605
  { top: 1, right: 34 },
606
  { top: 8, right: 18 },
 
681
  [Operator.Note]: [],
682
  [Operator.Invoke]: [Operator.Begin],
683
  [Operator.Template]: [Operator.Begin, Operator.Relevant],
684
+ [Operator.Email]: [Operator.Begin],
685
  };
686
 
687
  export const NodeMap = {
 
718
  [Operator.Crawler]: 'ragNode',
719
  [Operator.Invoke]: 'invokeNode',
720
  [Operator.Template]: 'templateNode',
721
+ [Operator.Email]: 'emailNode',
722
  };
723
 
724
  export const LanguageOptions = [
web/src/pages/flow/flow-drawer/index.tsx CHANGED
@@ -81,6 +81,7 @@ const FormMap = {
81
  [Operator.Concentrator]: () => <></>,
82
  [Operator.Note]: () => <></>,
83
  [Operator.Template]: TemplateForm,
 
84
  };
85
 
86
  const EmptyContent = () => <div></div>;
 
81
  [Operator.Concentrator]: () => <></>,
82
  [Operator.Note]: () => <></>,
83
  [Operator.Template]: TemplateForm,
84
+ [Operator.Email]: EmailForm,
85
  };
86
 
87
  const EmptyContent = () => <div></div>;
web/src/pages/flow/form/email-form/index.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslate } from '@/hooks/common-hooks';
2
+ import { Form, Input } from 'antd';
3
+ import { IOperatorForm } from '../../interface';
4
+ import DynamicInputVariable from '../components/dynamic-input-variable';
5
+
6
+ const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => {
7
+ const { t } = useTranslate('flow');
8
+
9
+ return (
10
+ <Form
11
+ name="basic"
12
+ autoComplete="off"
13
+ form={form}
14
+ onValuesChange={onValuesChange}
15
+ layout={'vertical'}
16
+ >
17
+ <DynamicInputVariable nodeId={node?.id}></DynamicInputVariable>
18
+
19
+ {/* SMTP服务器配置 */}
20
+ <Form.Item label={t('smtpServer')} name={'smtp_server'}>
21
+ <Input placeholder="smtp.example.com" />
22
+ </Form.Item>
23
+ <Form.Item label={t('smtpPort')} name={'smtp_port'}>
24
+ <Input type="number" placeholder="587" />
25
+ </Form.Item>
26
+ <Form.Item label={t('senderEmail')} name={'email'}>
27
+ <Input placeholder="[email protected]" />
28
+ </Form.Item>
29
+ <Form.Item label={t('authCode')} name={'password'}>
30
+ <Input.Password placeholder="your_password" />
31
+ </Form.Item>
32
+ <Form.Item label={t('senderName')} name={'sender_name'}>
33
+ <Input placeholder="Sender Name" />
34
+ </Form.Item>
35
+
36
+ {/* 动态参数说明 */}
37
+ <div style={{ marginBottom: 24 }}>
38
+ <h4>{t('dynamicParameters')}</h4>
39
+ <div>{t('jsonFormatTip')}</div>
40
+ <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}>
41
+ {`{
42
+ "to_email": "[email protected]",
43
+ "cc_email": "[email protected]",
44
+ "subject": "Email Subject",
45
+ "content": "Email Content"
46
+ }`}
47
+ </pre>
48
+ </div>
49
+ </Form>
50
+ );
51
+ };
52
+
53
+ export default EmailForm;
web/src/pages/flow/hooks.tsx CHANGED
@@ -44,6 +44,7 @@ import {
44
  initialCrawlerValues,
45
  initialDeepLValues,
46
  initialDuckValues,
 
47
  initialExeSqlValues,
48
  initialGenerateValues,
49
  initialGithubValues,
@@ -141,6 +142,7 @@ export const useInitializeOperatorParams = () => {
141
  [Operator.Crawler]: initialCrawlerValues,
142
  [Operator.Invoke]: initialInvokeValues,
143
  [Operator.Template]: initialTemplateValues,
 
144
  };
145
  }, [llmId]);
146
 
 
44
  initialCrawlerValues,
45
  initialDeepLValues,
46
  initialDuckValues,
47
+ initialEmailValues,
48
  initialExeSqlValues,
49
  initialGenerateValues,
50
  initialGithubValues,
 
142
  [Operator.Crawler]: initialCrawlerValues,
143
  [Operator.Invoke]: initialInvokeValues,
144
  [Operator.Template]: initialTemplateValues,
145
+ [Operator.Email]: initialEmailValues,
146
  };
147
  }, [llmId]);
148