feat: add email templates for verification codes in English, Chinese, and Traditional Chinese

This commit is contained in:
wxg0103 2025-07-10 15:01:56 +08:00
parent 28a909e226
commit fe78de5d3c
6 changed files with 395 additions and 152 deletions

View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 500px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 16px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
Intelligent Knowledge Q&A System
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
Dear user:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;This is your dynamic verification code. Please fill it in within 30 minutes. To protect the security of your account, please do not provide this verification code to anyone.
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>Intelligent knowledge base project team</p>
<br />
<p>
Please do not reply to this system email<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 230px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 24px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
智能知识库问答系统
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
尊敬的用户:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;为您的动态验证码请于30分钟内填写为保障帐户安全请勿向任何人提供此验证码。
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>智能知识库项目组</p>
<br />
<p>
此为系统邮件,请勿回复<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 230px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 24px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
智慧知識庫問答系統
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
尊敬的用戶:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;為您的動態驗證碼請於30分鐘內填寫為保障帳戶安全請勿向任何人提供此驗證碼。
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>智慧知識庫專案組</p>
<br />
<p>
此為系統郵件,請勿回覆<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -30,8 +30,7 @@ class VolcanicEngineTTIModelGeneralParams(BaseForm):
class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential): class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
access_key = forms.PasswordInputField('Access Key ID', required=True) api_key = forms.PasswordInputField('Api key', required=True)
secret_key = forms.PasswordInputField('Secret Access Key', required=True)
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,
raise_exception=False): raise_exception=False):
@ -40,7 +39,7 @@ class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
raise AppApiException(ValidCode.valid_error.value, raise AppApiException(ValidCode.valid_error.value,
gettext('{model_type} Model type is not supported').format(model_type=model_type)) gettext('{model_type} Model type is not supported').format(model_type=model_type))
for key in ['access_key', 'secret_key']: for key in ['api_key']:
if key not in model_credential: if key not in model_credential:
if raise_exception: if raise_exception:
raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key))
@ -62,7 +61,7 @@ class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
return True return True
def encryption_dict(self, model: Dict[str, object]): def encryption_dict(self, model: Dict[str, object]):
return {**model, 'secret_key': super().encryption(model.get('secret_key', ''))} return {**model, 'api_key': super().encryption(model.get('api_key', ''))}
def get_model_params_setting_form(self, model_name): def get_model_params_setting_form(self, model_name):
return VolcanicEngineTTIModelGeneralParams() return VolcanicEngineTTIModelGeneralParams()

View File

@ -7,122 +7,21 @@ pip install asyncio
pip install websockets pip install websockets
''' '''
import datetime
import hashlib
import hmac
import json
import logging
import sys
from typing import Dict from typing import Dict
import requests
from common.utils.logger import maxkb_logger
from models_provider.base_model_provider import MaxKBBaseModel from models_provider.base_model_provider import MaxKBBaseModel
from models_provider.impl.base_tti import BaseTextToImage from models_provider.impl.base_tti import BaseTextToImage
method = 'POST' from volcenginesdkarkruntime import Ark
host = 'visual.volcengineapi.com'
region = 'cn-north-1'
endpoint = 'https://visual.volcengineapi.com'
service = 'cv'
req_key_dict = {
'general_v1.4': 'high_aes_general_v14',
'general_v2.0': 'high_aes_general_v20',
'general_v2.0_L': 'high_aes_general_v20_L',
'anime_v1.3': 'high_aes',
'anime_v1.3.1': 'high_aes',
}
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(key.encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'request')
return kSigning
def formatQuery(parameters):
request_parameters_init = ''
for key in sorted(parameters):
request_parameters_init += key + '=' + parameters[key] + '&'
request_parameters = request_parameters_init[:-1]
return request_parameters
def signV4Request(access_key, secret_key, service, req_query, req_body):
if access_key is None or secret_key is None:
maxkb_logger.info('No access key is available.')
sys.exit()
t = datetime.datetime.utcnow()
current_date = t.strftime('%Y%m%dT%H%M%SZ')
# current_date = '20210818T095729Z'
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
canonical_uri = '/'
canonical_querystring = req_query
signed_headers = 'content-type;host;x-content-sha256;x-date'
payload_hash = hashlib.sha256(req_body.encode('utf-8')).hexdigest()
content_type = 'application/json'
canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + \
'\n' + 'x-content-sha256:' + payload_hash + \
'\n' + 'x-date:' + current_date + '\n'
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + \
'\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
algorithm = 'HMAC-SHA256'
credential_scope = datestamp + '/' + region + '/' + service + '/' + 'request'
string_to_sign = algorithm + '\n' + current_date + '\n' + credential_scope + '\n' + hashlib.sha256(
canonical_request.encode('utf-8')).hexdigest()
signing_key = getSignatureKey(secret_key, datestamp, region, service)
signature = hmac.new(signing_key, (string_to_sign).encode(
'utf-8'), hashlib.sha256).hexdigest()
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + \
credential_scope + ', ' + 'SignedHeaders=' + \
signed_headers + ', ' + 'Signature=' + signature
headers = {'X-Date': current_date,
'Authorization': authorization_header,
'X-Content-Sha256': payload_hash,
'Content-Type': content_type
}
# ************* SEND THE REQUEST *************
request_url = endpoint + '?' + canonical_querystring
maxkb_logger.info('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')
maxkb_logger.info('Request URL = ' + request_url)
try:
r = requests.post(request_url, headers=headers, data=req_body)
except Exception as err:
maxkb_logger.info(f'error occurred: {err}')
raise
else:
maxkb_logger.info('\nRESPONSE++++++++++++++++++++++++++++++++++++')
maxkb_logger.info(f'Response code: {r.status_code}\n')
# 使用 replace 方法将 \u0026 替换为 &
resp_str = r.text.replace("\\u0026", "&")
if r.status_code != 200:
raise Exception(f'Error: {resp_str}')
return json.loads(resp_str)['data']['image_urls']
class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage): class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage):
access_key: str api_key: str
secret_key: str
model_version: str model_version: str
params: dict params: dict
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.access_key = kwargs.get('access_key') self.api_key = kwargs.get('api_key')
self.secret_key = kwargs.get('secret_key')
self.model_version = kwargs.get('model_version') self.model_version = kwargs.get('model_version')
self.params = kwargs.get('params') self.params = kwargs.get('params')
@ -138,33 +37,25 @@ class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage):
optional_params['params'][key] = value optional_params['params'][key] = value
return VolcanicEngineTextToImage( return VolcanicEngineTextToImage(
model_version=model_name, model_version=model_name,
access_key=model_credential.get('access_key'), api_key=model_credential.get('api_key'),
secret_key=model_credential.get('secret_key'),
**optional_params **optional_params
) )
def check_auth(self): def check_auth(self):
res = self.generate_image('生成一张小猫图片') return True
def generate_image(self, prompt: str, negative_prompt: str = None): def generate_image(self, prompt: str, negative_prompt: str = None):
# 请求Query按照接口文档中填入即可 client = Ark(
query_params = { # 此为默认路径,您可根据业务所在地域进行配置
'Action': 'CVProcess', base_url="https://ark.cn-beijing.volces.com/api/v3",
'Version': '2022-08-31', # 从环境变量中获取您的 API Key。此为默认方式您可根据需要进行修改
} api_key=self.api_key,
formatted_query = formatQuery(query_params) )
size = self.params.pop('size', '512*512').split('*') file_urls = []
body_params = { imagesResponse = client.images.generate(
"req_key": req_key_dict[self.model_version], model=self.model_version,
"prompt": prompt, prompt=prompt,
"model_version": self.model_version,
"return_url": True,
"width": int(size[0]),
"height": int(size[1]),
**self.params **self.params
} )
formatted_body = json.dumps(body_params) file_urls.append(imagesResponse.data[0].url)
return signV4Request(self.access_key, self.secret_key, service, formatted_query, formatted_body) return file_urls
def is_cache_model(self):
return False

View File

@ -55,23 +55,8 @@ model_info_list = [
ModelTypeConst.TTS, ModelTypeConst.TTS,
volcanic_engine_tts_model_credential, VolcanicEngineTextToSpeech volcanic_engine_tts_model_credential, VolcanicEngineTextToSpeech
), ),
ModelInfo('general_v2.0', ModelInfo('doubao-seedream-3-0-t2i-250415',
_('Universal 2.0-Vincent Diagram'), _(''),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('general_v2.0_L',
_('Universal 2.0Pro-Vincent Chart'),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('general_v1.4',
_('Universal 1.4-Vincent Chart'),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('anime_v1.3.1',
_('Animation 1.3.1-Vincent Picture'),
ModelTypeConst.TTI, ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
), ),
@ -105,7 +90,9 @@ class VolcanicEngineModelProvider(IModelProvider):
return model_info_manage return model_info_manage
def get_model_provide_info(self): def get_model_provide_info(self):
return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'), icon=get_file_content( return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'),
os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'volcanic_engine_model_provider', icon=get_file_content(
os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl',
'volcanic_engine_model_provider',
'icon', 'icon',
'volcanic_engine_icon_svg'))) 'volcanic_engine_icon_svg')))