小黑马
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

### Type of change
- [√] New Feature (non-breaking change which adds functionality)
---------
Co-authored-by: Kevin Hu <[email protected]>
- agent/component/__init__.py +2 -0
- agent/component/email.py +138 -0
- web/src/assets/svg/email.svg +1 -0
- web/src/locales/en.ts +25 -0
- web/src/locales/zh.ts +24 -0
- web/src/pages/flow/canvas/index.tsx +2 -0
- web/src/pages/flow/canvas/node/email-node.tsx +78 -0
- web/src/pages/flow/canvas/node/index.less +77 -0
- web/src/pages/flow/constant.tsx +23 -0
- web/src/pages/flow/flow-drawer/index.tsx +1 -0
- web/src/pages/flow/form/email-form/index.tsx +53 -0
- web/src/pages/flow/hooks.tsx +2 -0
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 |
|