mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 12:49:03 +08:00
Email sending tool (#3837)
### 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 <kevinhu.sh@gmail.com>
This commit is contained in:
parent
285bc58364
commit
efae7afd62
@ -31,6 +31,8 @@ from .akshare import AkShare, AkShareParam
|
|||||||
from .crawler import Crawler, CrawlerParam
|
from .crawler import Crawler, CrawlerParam
|
||||||
from .invoke import Invoke, InvokeParam
|
from .invoke import Invoke, InvokeParam
|
||||||
from .template import Template, TemplateParam
|
from .template import Template, TemplateParam
|
||||||
|
from .email import Email, EmailParam
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def component_class(class_name):
|
def component_class(class_name):
|
||||||
|
138
agent/component/email.py
Normal file
138
agent/component/email.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
|
import json
|
||||||
|
import smtplib
|
||||||
|
import logging
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.header import Header
|
||||||
|
from email.utils import formataddr
|
||||||
|
from agent.component.base import ComponentBase, ComponentParamBase
|
||||||
|
|
||||||
|
class EmailParam(ComponentParamBase):
|
||||||
|
"""
|
||||||
|
Define the Email component parameters.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
# Fixed configuration parameters
|
||||||
|
self.smtp_server = "" # SMTP server address
|
||||||
|
self.smtp_port = 465 # SMTP port
|
||||||
|
self.email = "" # Sender email
|
||||||
|
self.password = "" # Email authorization code
|
||||||
|
self.sender_name = "" # Sender name
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
# Check required parameters
|
||||||
|
self.check_empty(self.smtp_server, "SMTP Server")
|
||||||
|
self.check_empty(self.email, "Email")
|
||||||
|
self.check_empty(self.password, "Password")
|
||||||
|
self.check_empty(self.sender_name, "Sender Name")
|
||||||
|
|
||||||
|
class Email(ComponentBase, ABC):
|
||||||
|
component_name = "Email"
|
||||||
|
|
||||||
|
def _run(self, history, **kwargs):
|
||||||
|
# Get upstream component output and parse JSON
|
||||||
|
ans = self.get_input()
|
||||||
|
content = "".join(ans["content"]) if "content" in ans else ""
|
||||||
|
if not content:
|
||||||
|
return Email.be_output("No content to send")
|
||||||
|
|
||||||
|
success = False
|
||||||
|
try:
|
||||||
|
# Parse JSON string passed from upstream
|
||||||
|
email_data = json.loads(content)
|
||||||
|
|
||||||
|
# Validate required fields
|
||||||
|
if "to_email" not in email_data:
|
||||||
|
return Email.be_output("Missing required field: to_email")
|
||||||
|
|
||||||
|
# Create email object
|
||||||
|
msg = MIMEMultipart('alternative')
|
||||||
|
|
||||||
|
# Properly handle sender name encoding
|
||||||
|
msg['From'] = formataddr((str(Header(self._param.sender_name,'utf-8')), self._param.email))
|
||||||
|
msg['To'] = email_data["to_email"]
|
||||||
|
if "cc_email" in email_data and email_data["cc_email"]:
|
||||||
|
msg['Cc'] = email_data["cc_email"]
|
||||||
|
msg['Subject'] = Header(email_data.get("subject", "No Subject"), 'utf-8').encode()
|
||||||
|
|
||||||
|
# Use content from email_data or default content
|
||||||
|
email_content = email_data.get("content", "No content provided")
|
||||||
|
# msg.attach(MIMEText(email_content, 'plain', 'utf-8'))
|
||||||
|
msg.attach(MIMEText(email_content, 'html', 'utf-8'))
|
||||||
|
|
||||||
|
# Connect to SMTP server and send
|
||||||
|
logging.info(f"Connecting to SMTP server {self._param.smtp_server}:{self._param.smtp_port}")
|
||||||
|
|
||||||
|
context = smtplib.ssl.create_default_context()
|
||||||
|
with smtplib.SMTP_SSL(self._param.smtp_server, self._param.smtp_port, context=context) as server:
|
||||||
|
# Login
|
||||||
|
logging.info(f"Attempting to login with email: {self._param.email}")
|
||||||
|
server.login(self._param.email, self._param.password)
|
||||||
|
|
||||||
|
# Get all recipient list
|
||||||
|
recipients = [email_data["to_email"]]
|
||||||
|
if "cc_email" in email_data and email_data["cc_email"]:
|
||||||
|
recipients.extend(email_data["cc_email"].split(','))
|
||||||
|
|
||||||
|
# Send email
|
||||||
|
logging.info(f"Sending email to recipients: {recipients}")
|
||||||
|
try:
|
||||||
|
server.send_message(msg, self._param.email, recipients)
|
||||||
|
success = True
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error during send_message: {str(e)}")
|
||||||
|
# Try alternative method
|
||||||
|
server.sendmail(self._param.email, recipients, msg.as_string())
|
||||||
|
success = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
server.quit()
|
||||||
|
except Exception as e:
|
||||||
|
# Ignore errors when closing connection
|
||||||
|
logging.warning(f"Non-fatal error during connection close: {str(e)}")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return Email.be_output("Email sent successfully")
|
||||||
|
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
error_msg = "Invalid JSON format in input"
|
||||||
|
logging.error(error_msg)
|
||||||
|
return Email.be_output(error_msg)
|
||||||
|
|
||||||
|
except smtplib.SMTPAuthenticationError:
|
||||||
|
error_msg = "SMTP Authentication failed. Please check your email and authorization code."
|
||||||
|
logging.error(error_msg)
|
||||||
|
return Email.be_output(f"Failed to send email: {error_msg}")
|
||||||
|
|
||||||
|
except smtplib.SMTPConnectError:
|
||||||
|
error_msg = f"Failed to connect to SMTP server {self._param.smtp_server}:{self._param.smtp_port}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
return Email.be_output(f"Failed to send email: {error_msg}")
|
||||||
|
|
||||||
|
except smtplib.SMTPException as e:
|
||||||
|
error_msg = f"SMTP error occurred: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
return Email.be_output(f"Failed to send email: {error_msg}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Unexpected error: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
return Email.be_output(f"Failed to send email: {error_msg}")
|
1
web/src/assets/svg/email.svg
Normal file
1
web/src/assets/svg/email.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg t="1733148906323" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2383" width="200" height="200"><path d="M883.36 784H49.76a32 32 0 0 1-32-32V176a32 32 0 0 1 32-32h832a32 32 0 0 1 32 32l1.76 576a32 32 0 0 1-32.16 32z" fill="#FAEFDE" p-id="2384"></path><path d="M913.76 204.32l-448 320L17.76 208v-16c0-17.6 14.4-48 32-48h832c17.6 0 32 26.72 32 44.32z" fill="#FFF7F0" p-id="2385"></path><path d="M897.76 784h-864c-8.8 0-16-3.52-16-12.32V768a64 64 0 0 1 64-64h769.6a64 64 0 0 1 64 64v3.68c0 8.8-8.8 12.32-17.6 12.32z" fill="#EFD8BE" p-id="2386"></path><path d="M816.8 752m-192 0a192 192 0 1 0 384 0 192 192 0 1 0-384 0Z" fill="#72CAAF" p-id="2387"></path><path d="M48 140.32h833.76a32 32 0 0 1 32 32 35.36 35.36 0 0 1-32 35.68h-832A36.8 36.8 0 0 1 16 172.32a32 32 0 0 1 32-32z" fill="#FFFFFF" p-id="2388"></path><path d="M144.8 674.72a16 16 0 0 0-16 16v32a16 16 0 1 0 32 0v-32a16 16 0 0 0-16-16zM64.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16zM224.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16zM304.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16zM384.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16zM464.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16zM544.8 674.72a16 16 0 0 0-16 16v32a16 16 0 0 0 32 0v-32a16 16 0 0 0-16-16z" fill="#8D6C9F" p-id="2389"></path><path d="M928.8 576.96V178.72a48 48 0 0 0-48-48h-832a48 48 0 0 0-48 48v576a48 48 0 0 0 48 48h566.24a208 208 0 1 0 313.76-225.76z m-880-414.24h832a16 16 0 0 1 16 16v19.52l-413.12 299.2a32 32 0 0 1-37.6 0L32.8 198.24v-19.52a16 16 0 0 1 16-16z m0 608a16 16 0 0 1-16-16v-86.88L314.08 496a16 16 0 0 0-16-27.36L32.8 630.24V237.76l394.56 285.6a64 64 0 0 0 75.04 0l394.4-285.6V560a208 208 0 0 0-131.2-9.6l-133.28-81.28a16 16 0 0 0-21.92 5.28 16 16 0 0 0 5.28 21.6l112 67.84A208 208 0 0 0 608.8 752a183.68 183.68 0 0 0 0.96 18.72z m768 157.28a176 176 0 0 1-168.64-125.28 161.76 161.76 0 0 1-6.24-32 147.2 147.2 0 0 1-1.12-18.72 176 176 0 0 1 120.16-166.88 181.6 181.6 0 0 1 46.88-9.12h8.96a174.72 174.72 0 0 1 80 19.2 164.8 164.8 0 0 1 32 20.96 176 176 0 0 1-112 311.84z" fill="#8D6C9F" p-id="2390"></path><path d="M860.16 660.64a16 16 0 0 0-22.56 22.56l52.64 52.8h-57.44a112 112 0 0 0-112 112 16 16 0 0 0 32 0 80 80 0 0 1 80-80h57.44l-52.64 52.64a16 16 0 1 0 22.56 22.56l80-80a16 16 0 0 0 0-22.56z" fill="#F9EFDE" p-id="2391"></path></svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -1050,6 +1050,31 @@ When you want to search the given knowledge base at first place, set a higher pa
|
|||||||
template: 'Template',
|
template: 'Template',
|
||||||
templateDescription:
|
templateDescription:
|
||||||
'This component is used for typesetting the outputs of various components.',
|
'This component is used for typesetting the outputs of various components.',
|
||||||
|
emailComponent: 'Email',
|
||||||
|
emailDescription: 'Send email to specified address',
|
||||||
|
smtpServer: 'SMTP Server',
|
||||||
|
smtpPort: 'SMTP Port',
|
||||||
|
senderEmail: 'Sender Email',
|
||||||
|
authCode: 'Authorization Code',
|
||||||
|
senderName: 'Sender Name',
|
||||||
|
toEmail: 'Recipient Email',
|
||||||
|
ccEmail: 'CC Email',
|
||||||
|
emailSubject: 'Subject',
|
||||||
|
emailContent: 'Content',
|
||||||
|
smtpServerRequired: 'Please input SMTP server address',
|
||||||
|
senderEmailRequired: 'Please input sender email',
|
||||||
|
authCodeRequired: 'Please input authorization code',
|
||||||
|
toEmailRequired: 'Please input recipient email',
|
||||||
|
emailContentRequired: 'Please input email content',
|
||||||
|
emailSentSuccess: 'Email sent successfully',
|
||||||
|
emailSentFailed: 'Failed to send email',
|
||||||
|
dynamicParameters: 'Dynamic Parameters',
|
||||||
|
jsonFormatTip:
|
||||||
|
'Upstream component should provide JSON string in following format:',
|
||||||
|
toEmailTip: 'to_email: Recipient email (Required)',
|
||||||
|
ccEmailTip: 'cc_email: CC email (Optional)',
|
||||||
|
subjectTip: 'subject: Email subject (Optional)',
|
||||||
|
contentTip: 'content: Email content (Optional)',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -1029,6 +1029,30 @@ export default {
|
|||||||
testRun: '试运行',
|
testRun: '试运行',
|
||||||
template: '模板转换',
|
template: '模板转换',
|
||||||
templateDescription: '该组件用于排版各种组件的输出。',
|
templateDescription: '该组件用于排版各种组件的输出。',
|
||||||
|
emailComponent: '邮件',
|
||||||
|
emailDescription: '发送邮件到指定邮箱',
|
||||||
|
smtpServer: 'SMTP服务器',
|
||||||
|
smtpPort: 'SMTP端口',
|
||||||
|
senderEmail: '发件人邮箱',
|
||||||
|
authCode: '授权码',
|
||||||
|
senderName: '发件人名称',
|
||||||
|
toEmail: '收件人邮箱',
|
||||||
|
ccEmail: '抄送邮箱',
|
||||||
|
emailSubject: '邮件主题',
|
||||||
|
emailContent: '邮件内容',
|
||||||
|
smtpServerRequired: '请输入SMTP服务器地址',
|
||||||
|
senderEmailRequired: '请输入发件人邮箱',
|
||||||
|
authCodeRequired: '请输入授权码',
|
||||||
|
toEmailRequired: '请输入收件人邮箱',
|
||||||
|
emailContentRequired: '请输入邮件内容',
|
||||||
|
emailSentSuccess: '邮件发送成功',
|
||||||
|
emailSentFailed: '邮件发送失败',
|
||||||
|
dynamicParameters: '动态参数说明',
|
||||||
|
jsonFormatTip: '上游组件需要传入以下格式的JSON字符串:',
|
||||||
|
toEmailTip: 'to_email: 收件人邮箱(必填)',
|
||||||
|
ccEmailTip: 'cc_email: 抄送邮箱(可选)',
|
||||||
|
subjectTip: 'subject: 邮件主题(可选)',
|
||||||
|
contentTip: 'content: 邮件内容(可选)',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -25,6 +25,7 @@ import styles from './index.less';
|
|||||||
import { RagNode } from './node';
|
import { RagNode } from './node';
|
||||||
import { BeginNode } from './node/begin-node';
|
import { BeginNode } from './node/begin-node';
|
||||||
import { CategorizeNode } from './node/categorize-node';
|
import { CategorizeNode } from './node/categorize-node';
|
||||||
|
import { EmailNode } from './node/email-node';
|
||||||
import { GenerateNode } from './node/generate-node';
|
import { GenerateNode } from './node/generate-node';
|
||||||
import { InvokeNode } from './node/invoke-node';
|
import { InvokeNode } from './node/invoke-node';
|
||||||
import { KeywordNode } from './node/keyword-node';
|
import { KeywordNode } from './node/keyword-node';
|
||||||
@ -52,6 +53,7 @@ const nodeTypes = {
|
|||||||
keywordNode: KeywordNode,
|
keywordNode: KeywordNode,
|
||||||
invokeNode: InvokeNode,
|
invokeNode: InvokeNode,
|
||||||
templateNode: TemplateNode,
|
templateNode: TemplateNode,
|
||||||
|
emailNode: EmailNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
|
78
web/src/pages/flow/canvas/node/email-node.tsx
Normal file
78
web/src/pages/flow/canvas/node/email-node.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Flex } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Handle, NodeProps, Position } from 'reactflow';
|
||||||
|
import { NodeData } from '../../interface';
|
||||||
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
|
import styles from './index.less';
|
||||||
|
import NodeHeader from './node-header';
|
||||||
|
|
||||||
|
export function EmailNode({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
isConnectable = true,
|
||||||
|
selected,
|
||||||
|
}: NodeProps<NodeData>) {
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={classNames(styles.ragNode, {
|
||||||
|
[styles.selectedNode]: selected,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Handle
|
||||||
|
id="c"
|
||||||
|
type="source"
|
||||||
|
position={Position.Left}
|
||||||
|
isConnectable={isConnectable}
|
||||||
|
className={styles.handle}
|
||||||
|
style={LeftHandleStyle}
|
||||||
|
></Handle>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
isConnectable={isConnectable}
|
||||||
|
className={styles.handle}
|
||||||
|
style={RightHandleStyle}
|
||||||
|
id="b"
|
||||||
|
></Handle>
|
||||||
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
|
|
||||||
|
<Flex vertical gap={8} className={styles.emailNodeContainer}>
|
||||||
|
<div
|
||||||
|
className={styles.emailConfig}
|
||||||
|
onClick={() => setShowDetails(!showDetails)}
|
||||||
|
>
|
||||||
|
<div className={styles.configItem}>
|
||||||
|
<span className={styles.configLabel}>SMTP:</span>
|
||||||
|
<span className={styles.configValue}>{data.form?.smtp_server}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.configItem}>
|
||||||
|
<span className={styles.configLabel}>Port:</span>
|
||||||
|
<span className={styles.configValue}>{data.form?.smtp_port}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.configItem}>
|
||||||
|
<span className={styles.configLabel}>From:</span>
|
||||||
|
<span className={styles.configValue}>{data.form?.email}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.expandIcon}>{showDetails ? '▼' : '▶'}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showDetails && (
|
||||||
|
<div className={styles.jsonExample}>
|
||||||
|
<div className={styles.jsonTitle}>Expected Input JSON:</div>
|
||||||
|
<pre className={styles.jsonContent}>
|
||||||
|
{`{
|
||||||
|
"to_email": "...",
|
||||||
|
"cc_email": "...",
|
||||||
|
"subject": "...",
|
||||||
|
"content": "..."
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -193,3 +193,80 @@
|
|||||||
.conditionLine;
|
.conditionLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emailNodeContainer {
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.emailConfig {
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.configItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configLabel {
|
||||||
|
color: #666;
|
||||||
|
width: 45px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configValue {
|
||||||
|
color: #333;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandIcon {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsonExample {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
|
||||||
|
.jsonTitle {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsonContent {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.sv
|
|||||||
import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
|
import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
|
||||||
import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg';
|
import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg';
|
||||||
import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
|
import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
|
||||||
|
import { ReactComponent as EmailIcon } from '@/assets/svg/email.svg';
|
||||||
import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
|
import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
|
||||||
import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
|
import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
|
||||||
import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
|
import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
|
||||||
@ -25,6 +26,8 @@ import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
|
|||||||
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
||||||
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
||||||
|
|
||||||
|
// 邮件功能
|
||||||
|
|
||||||
import { variableEnabledFieldMap } from '@/constants/chat';
|
import { variableEnabledFieldMap } from '@/constants/chat';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
|
|
||||||
@ -87,6 +90,7 @@ export enum Operator {
|
|||||||
Crawler = 'Crawler',
|
Crawler = 'Crawler',
|
||||||
Invoke = 'Invoke',
|
Invoke = 'Invoke',
|
||||||
Template = 'Template',
|
Template = 'Template',
|
||||||
|
Email = 'Email',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommonOperatorList = Object.values(Operator).filter(
|
export const CommonOperatorList = Object.values(Operator).filter(
|
||||||
@ -127,6 +131,7 @@ export const operatorIconMap = {
|
|||||||
[Operator.Crawler]: CrawlerIcon,
|
[Operator.Crawler]: CrawlerIcon,
|
||||||
[Operator.Invoke]: InvokeIcon,
|
[Operator.Invoke]: InvokeIcon,
|
||||||
[Operator.Template]: TemplateIcon,
|
[Operator.Template]: TemplateIcon,
|
||||||
|
[Operator.Email]: EmailIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const operatorMap: Record<
|
export const operatorMap: Record<
|
||||||
@ -259,6 +264,7 @@ export const operatorMap: Record<
|
|||||||
[Operator.Template]: {
|
[Operator.Template]: {
|
||||||
backgroundColor: '#dee0e2',
|
backgroundColor: '#dee0e2',
|
||||||
},
|
},
|
||||||
|
[Operator.Email]: { backgroundColor: '#e6f7ff' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMenuList = [
|
export const componentMenuList = [
|
||||||
@ -358,6 +364,9 @@ export const componentMenuList = [
|
|||||||
{
|
{
|
||||||
name: Operator.Invoke,
|
name: Operator.Invoke,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: Operator.Email,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialQueryBaseValues = {
|
const initialQueryBaseValues = {
|
||||||
@ -580,6 +589,18 @@ export const initialTemplateValues = {
|
|||||||
parameters: [],
|
parameters: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialEmailValues = {
|
||||||
|
smtp_server: '',
|
||||||
|
smtp_port: 587,
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
sender_name: '',
|
||||||
|
to_email: '',
|
||||||
|
cc_email: '',
|
||||||
|
subject: '',
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
|
||||||
export const CategorizeAnchorPointPositions = [
|
export const CategorizeAnchorPointPositions = [
|
||||||
{ top: 1, right: 34 },
|
{ top: 1, right: 34 },
|
||||||
{ top: 8, right: 18 },
|
{ top: 8, right: 18 },
|
||||||
@ -660,6 +681,7 @@ export const RestrictedUpstreamMap = {
|
|||||||
[Operator.Note]: [],
|
[Operator.Note]: [],
|
||||||
[Operator.Invoke]: [Operator.Begin],
|
[Operator.Invoke]: [Operator.Begin],
|
||||||
[Operator.Template]: [Operator.Begin, Operator.Relevant],
|
[Operator.Template]: [Operator.Begin, Operator.Relevant],
|
||||||
|
[Operator.Email]: [Operator.Begin],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NodeMap = {
|
export const NodeMap = {
|
||||||
@ -696,6 +718,7 @@ export const NodeMap = {
|
|||||||
[Operator.Crawler]: 'ragNode',
|
[Operator.Crawler]: 'ragNode',
|
||||||
[Operator.Invoke]: 'invokeNode',
|
[Operator.Invoke]: 'invokeNode',
|
||||||
[Operator.Template]: 'templateNode',
|
[Operator.Template]: 'templateNode',
|
||||||
|
[Operator.Email]: 'emailNode',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LanguageOptions = [
|
export const LanguageOptions = [
|
||||||
|
@ -81,6 +81,7 @@ const FormMap = {
|
|||||||
[Operator.Concentrator]: () => <></>,
|
[Operator.Concentrator]: () => <></>,
|
||||||
[Operator.Note]: () => <></>,
|
[Operator.Note]: () => <></>,
|
||||||
[Operator.Template]: TemplateForm,
|
[Operator.Template]: TemplateForm,
|
||||||
|
[Operator.Email]: EmailForm,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptyContent = () => <div></div>;
|
const EmptyContent = () => <div></div>;
|
||||||
|
53
web/src/pages/flow/form/email-form/index.tsx
Normal file
53
web/src/pages/flow/form/email-form/index.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { Form, Input } from 'antd';
|
||||||
|
import { IOperatorForm } from '../../interface';
|
||||||
|
import DynamicInputVariable from '../components/dynamic-input-variable';
|
||||||
|
|
||||||
|
const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||||
|
const { t } = useTranslate('flow');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
autoComplete="off"
|
||||||
|
form={form}
|
||||||
|
onValuesChange={onValuesChange}
|
||||||
|
layout={'vertical'}
|
||||||
|
>
|
||||||
|
<DynamicInputVariable nodeId={node?.id}></DynamicInputVariable>
|
||||||
|
|
||||||
|
{/* SMTP服务器配置 */}
|
||||||
|
<Form.Item label={t('smtpServer')} name={'smtp_server'}>
|
||||||
|
<Input placeholder="smtp.example.com" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('smtpPort')} name={'smtp_port'}>
|
||||||
|
<Input type="number" placeholder="587" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('senderEmail')} name={'email'}>
|
||||||
|
<Input placeholder="sender@example.com" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('authCode')} name={'password'}>
|
||||||
|
<Input.Password placeholder="your_password" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('senderName')} name={'sender_name'}>
|
||||||
|
<Input placeholder="Sender Name" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* 动态参数说明 */}
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
<h4>{t('dynamicParameters')}</h4>
|
||||||
|
<div>{t('jsonFormatTip')}</div>
|
||||||
|
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}>
|
||||||
|
{`{
|
||||||
|
"to_email": "recipient@example.com",
|
||||||
|
"cc_email": "cc@example.com",
|
||||||
|
"subject": "Email Subject",
|
||||||
|
"content": "Email Content"
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailForm;
|
@ -44,6 +44,7 @@ import {
|
|||||||
initialCrawlerValues,
|
initialCrawlerValues,
|
||||||
initialDeepLValues,
|
initialDeepLValues,
|
||||||
initialDuckValues,
|
initialDuckValues,
|
||||||
|
initialEmailValues,
|
||||||
initialExeSqlValues,
|
initialExeSqlValues,
|
||||||
initialGenerateValues,
|
initialGenerateValues,
|
||||||
initialGithubValues,
|
initialGithubValues,
|
||||||
@ -141,6 +142,7 @@ export const useInitializeOperatorParams = () => {
|
|||||||
[Operator.Crawler]: initialCrawlerValues,
|
[Operator.Crawler]: initialCrawlerValues,
|
||||||
[Operator.Invoke]: initialInvokeValues,
|
[Operator.Invoke]: initialInvokeValues,
|
||||||
[Operator.Template]: initialTemplateValues,
|
[Operator.Template]: initialTemplateValues,
|
||||||
|
[Operator.Email]: initialEmailValues,
|
||||||
};
|
};
|
||||||
}, [llmId]);
|
}, [llmId]);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user