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