mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 01:49:02 +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 .invoke import Invoke, InvokeParam
|
||||
from .template import Template, TemplateParam
|
||||
from .email import Email, EmailParam
|
||||
|
||||
|
||||
|
||||
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',
|
||||
templateDescription:
|
||||
'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: {
|
||||
profile: 'All rights reserved @ React',
|
||||
|
@ -1029,6 +1029,30 @@ export default {
|
||||
testRun: '试运行',
|
||||
template: '模板转换',
|
||||
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: {
|
||||
profile: 'All rights reserved @ React',
|
||||
|
@ -25,6 +25,7 @@ import styles from './index.less';
|
||||
import { RagNode } from './node';
|
||||
import { BeginNode } from './node/begin-node';
|
||||
import { CategorizeNode } from './node/categorize-node';
|
||||
import { EmailNode } from './node/email-node';
|
||||
import { GenerateNode } from './node/generate-node';
|
||||
import { InvokeNode } from './node/invoke-node';
|
||||
import { KeywordNode } from './node/keyword-node';
|
||||
@ -52,6 +53,7 @@ const nodeTypes = {
|
||||
keywordNode: KeywordNode,
|
||||
invokeNode: InvokeNode,
|
||||
templateNode: TemplateNode,
|
||||
emailNode: EmailNode,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.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 DeepLIcon } from '@/assets/svg/deepl.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 GithubIcon } from '@/assets/svg/github.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 YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
||||
|
||||
// 邮件功能
|
||||
|
||||
import { variableEnabledFieldMap } from '@/constants/chat';
|
||||
import i18n from '@/locales/config';
|
||||
|
||||
@ -87,6 +90,7 @@ export enum Operator {
|
||||
Crawler = 'Crawler',
|
||||
Invoke = 'Invoke',
|
||||
Template = 'Template',
|
||||
Email = 'Email',
|
||||
}
|
||||
|
||||
export const CommonOperatorList = Object.values(Operator).filter(
|
||||
@ -127,6 +131,7 @@ export const operatorIconMap = {
|
||||
[Operator.Crawler]: CrawlerIcon,
|
||||
[Operator.Invoke]: InvokeIcon,
|
||||
[Operator.Template]: TemplateIcon,
|
||||
[Operator.Email]: EmailIcon,
|
||||
};
|
||||
|
||||
export const operatorMap: Record<
|
||||
@ -259,6 +264,7 @@ export const operatorMap: Record<
|
||||
[Operator.Template]: {
|
||||
backgroundColor: '#dee0e2',
|
||||
},
|
||||
[Operator.Email]: { backgroundColor: '#e6f7ff' },
|
||||
};
|
||||
|
||||
export const componentMenuList = [
|
||||
@ -358,6 +364,9 @@ export const componentMenuList = [
|
||||
{
|
||||
name: Operator.Invoke,
|
||||
},
|
||||
{
|
||||
name: Operator.Email,
|
||||
},
|
||||
];
|
||||
|
||||
const initialQueryBaseValues = {
|
||||
@ -580,6 +589,18 @@ export const initialTemplateValues = {
|
||||
parameters: [],
|
||||
};
|
||||
|
||||
export const initialEmailValues = {
|
||||
smtp_server: '',
|
||||
smtp_port: 587,
|
||||
email: '',
|
||||
password: '',
|
||||
sender_name: '',
|
||||
to_email: '',
|
||||
cc_email: '',
|
||||
subject: '',
|
||||
content: '',
|
||||
};
|
||||
|
||||
export const CategorizeAnchorPointPositions = [
|
||||
{ top: 1, right: 34 },
|
||||
{ top: 8, right: 18 },
|
||||
@ -660,6 +681,7 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.Note]: [],
|
||||
[Operator.Invoke]: [Operator.Begin],
|
||||
[Operator.Template]: [Operator.Begin, Operator.Relevant],
|
||||
[Operator.Email]: [Operator.Begin],
|
||||
};
|
||||
|
||||
export const NodeMap = {
|
||||
@ -696,6 +718,7 @@ export const NodeMap = {
|
||||
[Operator.Crawler]: 'ragNode',
|
||||
[Operator.Invoke]: 'invokeNode',
|
||||
[Operator.Template]: 'templateNode',
|
||||
[Operator.Email]: 'emailNode',
|
||||
};
|
||||
|
||||
export const LanguageOptions = [
|
||||
|
@ -81,6 +81,7 @@ const FormMap = {
|
||||
[Operator.Concentrator]: () => <></>,
|
||||
[Operator.Note]: () => <></>,
|
||||
[Operator.Template]: TemplateForm,
|
||||
[Operator.Email]: EmailForm,
|
||||
};
|
||||
|
||||
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,
|
||||
initialDeepLValues,
|
||||
initialDuckValues,
|
||||
initialEmailValues,
|
||||
initialExeSqlValues,
|
||||
initialGenerateValues,
|
||||
initialGithubValues,
|
||||
@ -141,6 +142,7 @@ export const useInitializeOperatorParams = () => {
|
||||
[Operator.Crawler]: initialCrawlerValues,
|
||||
[Operator.Invoke]: initialInvokeValues,
|
||||
[Operator.Template]: initialTemplateValues,
|
||||
[Operator.Email]: initialEmailValues,
|
||||
};
|
||||
}, [llmId]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user