Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
1b761988
Commit
1b761988
authored
Jun 30, 2023
by
John Wang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add activate link return and add activate mail send
parent
f029b188
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
306 additions
and
14 deletions
+306
-14
.env.example
api/.env.example
+5
-0
app.py
api/app.py
+2
-1
config.py
api/config.py
+5
-0
__init__.py
api/controllers/console/__init__.py
+1
-1
activate.py
api/controllers/console/auth/activate.py
+75
-0
error.py
api/controllers/console/error.py
+6
-0
members.py
api/controllers/console/workspace/members.py
+13
-3
ext_mail.py
api/extensions/ext_mail.py
+61
-0
requirements.txt
api/requirements.txt
+3
-2
account_service.py
api/services/account_service.py
+77
-7
mail_invite_member_task.py
api/tasks/mail_invite_member_task.py
+52
-0
docker-compose.yaml
docker/docker-compose.yaml
+6
-0
No files found.
api/.env.example
View file @
1b761988
...
@@ -79,6 +79,11 @@ WEAVIATE_BATCH_SIZE=100
...
@@ -79,6 +79,11 @@ WEAVIATE_BATCH_SIZE=100
QDRANT_URL=path:storage/qdrant
QDRANT_URL=path:storage/qdrant
QDRANT_API_KEY=your-qdrant-api-key
QDRANT_API_KEY=your-qdrant-api-key
# Mail configuration, support: resend
MAIL_TYPE=
MAIL_DEFAULT_SEND_FROM=no-reply <no-reply@dify.ai>
RESEND_API_KEY=
# Sentry configuration
# Sentry configuration
SENTRY_DSN=
SENTRY_DSN=
...
...
api/app.py
View file @
1b761988
...
@@ -15,7 +15,7 @@ import flask_login
...
@@ -15,7 +15,7 @@ import flask_login
from
flask_cors
import
CORS
from
flask_cors
import
CORS
from
extensions
import
ext_session
,
ext_celery
,
ext_sentry
,
ext_redis
,
ext_login
,
ext_migrate
,
\
from
extensions
import
ext_session
,
ext_celery
,
ext_sentry
,
ext_redis
,
ext_login
,
ext_migrate
,
\
ext_database
,
ext_storage
ext_database
,
ext_storage
,
ext_mail
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
extensions.ext_login
import
login_manager
from
extensions.ext_login
import
login_manager
...
@@ -83,6 +83,7 @@ def initialize_extensions(app):
...
@@ -83,6 +83,7 @@ def initialize_extensions(app):
ext_celery
.
init_app
(
app
)
ext_celery
.
init_app
(
app
)
ext_session
.
init_app
(
app
)
ext_session
.
init_app
(
app
)
ext_login
.
init_app
(
app
)
ext_login
.
init_app
(
app
)
ext_mail
.
init_app
(
app
)
ext_sentry
.
init_app
(
app
)
ext_sentry
.
init_app
(
app
)
...
...
api/config.py
View file @
1b761988
...
@@ -151,6 +151,11 @@ class Config:
...
@@ -151,6 +151,11 @@ class Config:
self
.
WEB_API_CORS_ALLOW_ORIGINS
=
get_cors_allow_origins
(
self
.
WEB_API_CORS_ALLOW_ORIGINS
=
get_cors_allow_origins
(
'WEB_API_CORS_ALLOW_ORIGINS'
,
'*'
)
'WEB_API_CORS_ALLOW_ORIGINS'
,
'*'
)
# mail settings
self
.
MAIL_TYPE
=
get_env
(
'MAIL_TYPE'
)
self
.
MAIL_DEFAULT_SEND_FROM
=
get_env
(
'MAIL_DEFAULT_SEND_FROM'
)
self
.
RESEND_API_KEY
=
get_env
(
'RESEND_API_KEY'
)
# sentry settings
# sentry settings
self
.
SENTRY_DSN
=
get_env
(
'SENTRY_DSN'
)
self
.
SENTRY_DSN
=
get_env
(
'SENTRY_DSN'
)
self
.
SENTRY_TRACES_SAMPLE_RATE
=
float
(
get_env
(
'SENTRY_TRACES_SAMPLE_RATE'
))
self
.
SENTRY_TRACES_SAMPLE_RATE
=
float
(
get_env
(
'SENTRY_TRACES_SAMPLE_RATE'
))
...
...
api/controllers/console/__init__.py
View file @
1b761988
...
@@ -12,7 +12,7 @@ from . import setup, version, apikey, admin
...
@@ -12,7 +12,7 @@ from . import setup, version, apikey, admin
from
.app
import
app
,
site
,
completion
,
model_config
,
statistic
,
conversation
,
message
,
generator
from
.app
import
app
,
site
,
completion
,
model_config
,
statistic
,
conversation
,
message
,
generator
# Import auth controllers
# Import auth controllers
from
.auth
import
login
,
oauth
,
data_source_oauth
from
.auth
import
login
,
oauth
,
data_source_oauth
,
activate
# Import datasets controllers
# Import datasets controllers
from
.datasets
import
datasets
,
datasets_document
,
datasets_segments
,
file
,
hit_testing
,
data_source
from
.datasets
import
datasets
,
datasets_document
,
datasets_segments
,
file
,
hit_testing
,
data_source
...
...
api/controllers/console/auth/activate.py
0 → 100644
View file @
1b761988
import
base64
import
secrets
from
datetime
import
datetime
from
flask_restful
import
Resource
,
reqparse
from
controllers.console
import
api
from
controllers.console.error
import
AlreadyActivateError
from
extensions.ext_database
import
db
from
libs.helper
import
email
,
str_len
,
supported_language
,
timezone
from
libs.password
import
valid_password
,
hash_password
from
models.account
import
AccountStatus
,
Tenant
from
services.account_service
import
RegisterService
class
ActivateCheckApi
(
Resource
):
def
get
(
self
):
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'workspace_id'
,
type
=
str
,
required
=
True
,
nullable
=
False
,
location
=
'args'
)
parser
.
add_argument
(
'email'
,
type
=
email
,
required
=
True
,
nullable
=
False
,
location
=
'args'
)
parser
.
add_argument
(
'token'
,
type
=
str
,
required
=
True
,
nullable
=
False
,
location
=
'args'
)
args
=
parser
.
parse_args
()
account
=
RegisterService
.
get_account_if_token_valid
(
args
[
'workspace_id'
],
args
[
'email'
],
args
[
'token'
])
tenant
=
db
.
session
.
query
(
Tenant
)
.
filter
(
Tenant
.
id
==
args
[
'workspace_id'
],
Tenant
.
status
==
'normal'
)
.
first
()
return
{
'is_valid'
:
account
is
not
None
,
'workspace_name'
:
tenant
.
name
}
class
ActivateApi
(
Resource
):
def
post
(
self
):
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'workspace_id'
,
type
=
str
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'email'
,
type
=
email
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'token'
,
type
=
str
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'name'
,
type
=
str_len
(
30
),
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'password'
,
type
=
valid_password
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'interface_language'
,
type
=
supported_language
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
parser
.
add_argument
(
'timezone'
,
type
=
timezone
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
args
=
parser
.
parse_args
()
account
=
RegisterService
.
get_account_if_token_valid
(
args
[
'workspace_id'
],
args
[
'email'
],
args
[
'token'
])
if
account
is
None
:
raise
AlreadyActivateError
()
RegisterService
.
revoke_token
(
args
[
'workspace_id'
],
args
[
'email'
],
args
[
'token'
])
account
.
name
=
args
[
'name'
]
# generate password salt
salt
=
secrets
.
token_bytes
(
16
)
base64_salt
=
base64
.
b64encode
(
salt
)
.
decode
()
# encrypt password with salt
password_hashed
=
hash_password
(
args
[
'password'
],
salt
)
base64_password_hashed
=
base64
.
b64encode
(
password_hashed
)
.
decode
()
account
.
password
=
base64_password_hashed
account
.
password_salt
=
base64_salt
account
.
interface_language
=
args
[
'interface_language'
]
account
.
timezone
=
args
[
'timezone'
]
account
.
interface_theme
=
'light'
account
.
status
=
AccountStatus
.
ACTIVE
.
value
account
.
initialized_at
=
datetime
.
utcnow
()
db
.
session
.
commit
()
return
{
'result'
:
'success'
}
api
.
add_resource
(
ActivateCheckApi
,
'/activate/check'
)
api
.
add_resource
(
ActivateApi
,
'/activate'
)
api/controllers/console/error.py
View file @
1b761988
...
@@ -18,3 +18,9 @@ class AccountNotLinkTenantError(BaseHTTPException):
...
@@ -18,3 +18,9 @@ class AccountNotLinkTenantError(BaseHTTPException):
error_code
=
'account_not_link_tenant'
error_code
=
'account_not_link_tenant'
description
=
"Account not link tenant."
description
=
"Account not link tenant."
code
=
403
code
=
403
class
AlreadyActivateError
(
BaseHTTPException
):
error_code
=
'already_activate'
description
=
"Auth Token is invalid or account already activated, please check again."
code
=
403
api/controllers/console/workspace/members.py
View file @
1b761988
# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
from
flask
import
current_app
from
flask_login
import
login_required
,
current_user
from
flask_login
import
login_required
,
current_user
from
flask_restful
import
Resource
,
reqparse
,
marshal_with
,
abort
,
fields
,
marshal
from
flask_restful
import
Resource
,
reqparse
,
marshal_with
,
abort
,
fields
,
marshal
...
@@ -60,7 +60,8 @@ class MemberInviteEmailApi(Resource):
...
@@ -60,7 +60,8 @@ class MemberInviteEmailApi(Resource):
inviter
=
current_user
inviter
=
current_user
try
:
try
:
RegisterService
.
invite_new_member
(
inviter
.
current_tenant
,
invitee_email
,
role
=
invitee_role
,
inviter
=
inviter
)
token
=
RegisterService
.
invite_new_member
(
inviter
.
current_tenant
,
invitee_email
,
role
=
invitee_role
,
inviter
=
inviter
)
account
=
db
.
session
.
query
(
Account
,
TenantAccountJoin
.
role
)
.
join
(
account
=
db
.
session
.
query
(
Account
,
TenantAccountJoin
.
role
)
.
join
(
TenantAccountJoin
,
Account
.
id
==
TenantAccountJoin
.
account_id
TenantAccountJoin
,
Account
.
id
==
TenantAccountJoin
.
account_id
)
.
filter
(
Account
.
email
==
args
[
'email'
])
.
first
()
)
.
filter
(
Account
.
email
==
args
[
'email'
])
.
first
()
...
@@ -78,7 +79,16 @@ class MemberInviteEmailApi(Resource):
...
@@ -78,7 +79,16 @@ class MemberInviteEmailApi(Resource):
# todo:413
# todo:413
return
{
'result'
:
'success'
,
'account'
:
account
},
201
return
{
'result'
:
'success'
,
'account'
:
account
,
'invite_url'
:
'{}/activate?workspace_id={}&email={}&token={}'
.
format
(
current_app
.
config
.
get
(
"CONSOLE_URL"
),
str
(
current_user
.
current_tenant_id
),
invitee_email
,
token
)
},
201
class
MemberCancelInviteApi
(
Resource
):
class
MemberCancelInviteApi
(
Resource
):
...
...
api/extensions/ext_mail.py
0 → 100644
View file @
1b761988
from
typing
import
Optional
import
resend
from
flask
import
Flask
class
Mail
:
def
__init__
(
self
):
self
.
_client
=
None
self
.
_default_send_from
=
None
def
is_inited
(
self
)
->
bool
:
return
self
.
_client
is
not
None
def
init_app
(
self
,
app
:
Flask
):
if
app
.
config
.
get
(
'MAIL_TYPE'
):
if
app
.
config
.
get
(
'MAIL_DEFAULT_SEND_FROM'
):
self
.
_default_send_from
=
app
.
config
.
get
(
'MAIL_DEFAULT_SEND_FROM'
)
if
app
.
config
.
get
(
'MAIL_TYPE'
)
==
'resend'
:
api_key
=
app
.
config
.
get
(
'RESEND_API_KEY'
)
if
not
api_key
:
raise
ValueError
(
'RESEND_API_KEY is not set'
)
resend
.
api_key
=
api_key
self
.
_client
=
resend
.
Emails
else
:
raise
ValueError
(
'Unsupported mail type {}'
.
format
(
app
.
config
.
get
(
'MAIL_TYPE'
)))
def
send
(
self
,
to
:
str
,
subject
:
str
,
html
:
str
,
from_
:
Optional
[
str
]
=
None
):
if
not
self
.
_client
:
raise
ValueError
(
'Mail client is not initialized'
)
if
not
from_
and
self
.
_default_send_from
:
from_
=
self
.
_default_send_from
if
not
from_
:
raise
ValueError
(
'mail from is not set'
)
if
not
to
:
raise
ValueError
(
'mail to is not set'
)
if
not
subject
:
raise
ValueError
(
'mail subject is not set'
)
if
not
html
:
raise
ValueError
(
'mail html is not set'
)
self
.
_client
.
send
({
"from"
:
from_
,
"to"
:
to
,
"subject"
:
subject
,
"html"
:
html
})
def
init_app
(
app
:
Flask
):
mail
.
init_app
(
app
)
mail
=
Mail
()
api/requirements.txt
View file @
1b761988
...
@@ -21,7 +21,7 @@ Authlib==1.2.0
...
@@ -21,7 +21,7 @@ Authlib==1.2.0
boto3~=1.26.123
boto3~=1.26.123
tenacity==8.2.2
tenacity==8.2.2
cachetools~=5.3.0
cachetools~=5.3.0
weaviate-client~=3.
16.2
weaviate-client~=3.
21.0
qdrant_client~=1.1.6
qdrant_client~=1.1.6
mailchimp-transactional~=1.0.50
mailchimp-transactional~=1.0.50
scikit-learn==1.2.2
scikit-learn==1.2.2
...
@@ -32,4 +32,5 @@ redis~=4.5.4
...
@@ -32,4 +32,5 @@ redis~=4.5.4
openpyxl==3.1.2
openpyxl==3.1.2
chardet~=5.1.0
chardet~=5.1.0
docx2txt==0.8
docx2txt==0.8
pypdfium2==4.16.0
pypdfium2==4.16.0
\ No newline at end of file
resend~=0.5.1
\ No newline at end of file
api/services/account_service.py
View file @
1b761988
...
@@ -2,13 +2,16 @@
...
@@ -2,13 +2,16 @@
import
base64
import
base64
import
logging
import
logging
import
secrets
import
secrets
import
uuid
from
datetime
import
datetime
from
datetime
import
datetime
from
hashlib
import
sha256
from
typing
import
Optional
from
typing
import
Optional
from
flask
import
session
from
flask
import
session
from
sqlalchemy
import
func
from
sqlalchemy
import
func
from
events.tenant_event
import
tenant_was_created
from
events.tenant_event
import
tenant_was_created
from
extensions.ext_redis
import
redis_client
from
services.errors.account
import
AccountLoginError
,
CurrentPasswordIncorrectError
,
LinkAccountIntegrateError
,
\
from
services.errors.account
import
AccountLoginError
,
CurrentPasswordIncorrectError
,
LinkAccountIntegrateError
,
\
TenantNotFound
,
AccountNotLinkTenantError
,
InvalidActionError
,
CannotOperateSelfError
,
MemberNotInTenantError
,
\
TenantNotFound
,
AccountNotLinkTenantError
,
InvalidActionError
,
CannotOperateSelfError
,
MemberNotInTenantError
,
\
RoleAlreadyAssignedError
,
NoPermissionError
,
AccountRegisterError
,
AccountAlreadyInTenantError
RoleAlreadyAssignedError
,
NoPermissionError
,
AccountRegisterError
,
AccountAlreadyInTenantError
...
@@ -16,6 +19,7 @@ from libs.helper import get_remote_ip
...
@@ -16,6 +19,7 @@ from libs.helper import get_remote_ip
from
libs.password
import
compare_password
,
hash_password
from
libs.password
import
compare_password
,
hash_password
from
libs.rsa
import
generate_key_pair
from
libs.rsa
import
generate_key_pair
from
models.account
import
*
from
models.account
import
*
from
tasks.mail_invite_member_task
import
send_invite_member_mail_task
class
AccountService
:
class
AccountService
:
...
@@ -332,8 +336,8 @@ class TenantService:
...
@@ -332,8 +336,8 @@ class TenantService:
class
RegisterService
:
class
RegisterService
:
@
static
method
@
class
method
def
register
(
email
,
name
,
password
:
str
=
None
,
open_id
:
str
=
None
,
provider
:
str
=
None
)
->
Account
:
def
register
(
cls
,
email
,
name
,
password
:
str
=
None
,
open_id
:
str
=
None
,
provider
:
str
=
None
)
->
Account
:
db
.
session
.
begin_nested
()
db
.
session
.
begin_nested
()
"""Register account"""
"""Register account"""
try
:
try
:
...
@@ -359,9 +363,9 @@ class RegisterService:
...
@@ -359,9 +363,9 @@ class RegisterService:
return
account
return
account
@
static
method
@
class
method
def
invite_new_member
(
tenant
:
Tenant
,
email
:
str
,
role
:
str
=
'normal'
,
def
invite_new_member
(
cls
,
tenant
:
Tenant
,
email
:
str
,
role
:
str
=
'normal'
,
inviter
:
Account
=
None
)
->
TenantAccountJoin
:
inviter
:
Account
=
None
)
->
str
:
"""Invite new member"""
"""Invite new member"""
account
=
Account
.
query
.
filter_by
(
email
=
email
)
.
first
()
account
=
Account
.
query
.
filter_by
(
email
=
email
)
.
first
()
...
@@ -380,5 +384,71 @@ class RegisterService:
...
@@ -380,5 +384,71 @@ class RegisterService:
if
ta
:
if
ta
:
raise
AccountAlreadyInTenantError
(
"Account already in tenant."
)
raise
AccountAlreadyInTenantError
(
"Account already in tenant."
)
ta
=
TenantService
.
create_tenant_member
(
tenant
,
account
,
role
)
TenantService
.
create_tenant_member
(
tenant
,
account
,
role
)
return
ta
token
=
cls
.
generate_invite_token
(
tenant
,
account
)
# send email
send_invite_member_mail_task
.
delay
(
to
=
email
,
token
=
cls
.
generate_invite_token
(
tenant
,
account
),
inviter_name
=
inviter
.
name
if
inviter
else
'Dify'
,
workspace_id
=
tenant
.
id
,
workspace_name
=
tenant
.
name
,
)
return
token
@
classmethod
def
generate_invite_token
(
cls
,
tenant
:
Tenant
,
account
:
Account
)
->
str
:
token
=
str
(
uuid
.
uuid4
())
email_hash
=
sha256
(
account
.
email
.
encode
())
.
hexdigest
()
cache_key
=
'member_invite_token:{}, {}:{}'
.
format
(
str
(
tenant
.
id
),
email_hash
,
token
)
redis_client
.
setex
(
cache_key
,
600
,
str
(
account
.
id
))
return
token
@
classmethod
def
revoke_token
(
cls
,
workspace_id
:
str
,
email
:
str
,
token
:
str
):
email_hash
=
sha256
(
email
.
encode
())
.
hexdigest
()
cache_key
=
'member_invite_token:{}, {}:{}'
.
format
(
workspace_id
,
email_hash
,
token
)
redis_client
.
delete
(
cache_key
)
@
classmethod
def
get_account_if_token_valid
(
cls
,
workspace_id
:
str
,
email
:
str
,
token
:
str
)
->
Optional
[
Account
]:
tenant
=
db
.
session
.
query
(
Tenant
)
.
filter
(
Tenant
.
id
==
workspace_id
,
Tenant
.
status
==
'normal'
)
.
first
()
if
not
tenant
:
return
None
tenant_account
=
db
.
session
.
query
(
Account
,
TenantAccountJoin
.
role
)
.
join
(
TenantAccountJoin
,
Account
.
id
==
TenantAccountJoin
.
account_id
)
.
filter
(
Account
.
email
==
email
,
TenantAccountJoin
.
tenant_id
==
tenant
.
id
)
.
first
()
if
not
tenant_account
:
return
None
account_id
=
cls
.
_get_account_id_by_invite_token
(
workspace_id
,
email
,
token
)
if
not
account_id
:
return
None
account
=
tenant_account
[
0
]
if
not
account
:
return
None
if
account_id
!=
str
(
account
.
id
):
return
None
return
account
@
classmethod
def
_get_account_id_by_invite_token
(
cls
,
workspace_id
:
str
,
email
:
str
,
token
:
str
)
->
Optional
[
str
]:
email_hash
=
sha256
(
email
.
encode
())
.
hexdigest
()
cache_key
=
'member_invite_token:{}, {}:{}'
.
format
(
workspace_id
,
email_hash
,
token
)
account_id
=
redis_client
.
get
(
cache_key
)
if
not
account_id
:
return
None
return
account_id
.
decode
(
'utf-8'
)
api/tasks/mail_invite_member_task.py
0 → 100644
View file @
1b761988
import
logging
import
time
import
click
from
celery
import
shared_task
from
flask
import
current_app
from
extensions.ext_mail
import
mail
@
shared_task
def
send_invite_member_mail_task
(
to
:
str
,
token
:
str
,
inviter_name
:
str
,
workspace_id
:
str
,
workspace_name
:
str
):
"""
Async Send invite member mail
:param to
:param token
:param inviter_name
:param workspace_id
:param workspace_name
Usage: send_invite_member_mail_task.delay(to, token, inviter_name, workspace_id, workspace_name)
"""
if
not
mail
.
is_inited
():
return
logging
.
info
(
click
.
style
(
'Start send invite member mail to {} in workspace {}'
.
format
(
to
,
workspace_name
),
fg
=
'green'
))
start_at
=
time
.
perf_counter
()
try
:
mail
.
send
(
to
=
to
,
subject
=
"{} invited you to join {}"
.
format
(
inviter_name
,
workspace_name
),
html
=
"""<p>Hi there,</p>
<p>{inviter_name} invited you to join {workspace_name}.</p>
<p>Click <a href="{url}">here</a> to join.</p>
<p>Thanks,</p>
<p>Dify Team</p>"""
.
format
(
inviter_name
=
inviter_name
,
workspace_name
=
workspace_name
,
url
=
'{}/activate?workspace_id={}&email={}&token={}'
.
format
(
current_app
.
config
.
get
(
"CONSOLE_URL"
),
workspace_id
,
to
,
token
)
)
)
end_at
=
time
.
perf_counter
()
logging
.
info
(
click
.
style
(
'Send invite member mail to {} succeeded: latency: {}'
.
format
(
to
,
end_at
-
start_at
),
fg
=
'green'
))
except
Exception
:
logging
.
exception
(
"Send invite member mail to {} failed"
.
format
(
to
))
docker/docker-compose.yaml
View file @
1b761988
...
@@ -93,6 +93,12 @@ services:
...
@@ -93,6 +93,12 @@ services:
QDRANT_URL
:
'
https://your-qdrant-cluster-url.qdrant.tech/'
QDRANT_URL
:
'
https://your-qdrant-cluster-url.qdrant.tech/'
# The Qdrant API key.
# The Qdrant API key.
QDRANT_API_KEY
:
'
ak-difyai'
QDRANT_API_KEY
:
'
ak-difyai'
# Mail configuration, support: resend
MAIL_TYPE
:
'
'
# default send from email address, if not specified
MAIL_DEFAULT_SEND_FROM
:
'
YOUR
EMAIL
FROM
(eg:
no-reply
<no-reply@dify.ai>)'
# the api-key for resend (https://resend.com)
RESEND_API_KEY
:
'
'
# The DSN for Sentry error reporting. If not set, Sentry error reporting will be disabled.
# The DSN for Sentry error reporting. If not set, Sentry error reporting will be disabled.
SENTRY_DSN
:
'
'
SENTRY_DSN
:
'
'
# The sample rate for Sentry events. Default: `1.0`
# The sample rate for Sentry events. Default: `1.0`
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment