Compare commits
49 Commits
main
...
deploy/ent
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1b280d30e5 | ||
![]() |
bbc5ec8301 | ||
![]() |
4a51a72c1d | ||
![]() |
4b6adffa8e | ||
![]() |
c7fd73d330 | ||
![]() |
8a709e445a | ||
![]() |
f02b77b99f | ||
![]() |
abc625bcce | ||
![]() |
b6bc1f8bc4 | ||
![]() |
b8f9037cd3 | ||
![]() |
02606ba3c7 | ||
![]() |
79311d3fb5 | ||
![]() |
31086a1fbf | ||
![]() |
6ae5d052e5 | ||
![]() |
c794ecf101 | ||
![]() |
d887aae012 | ||
![]() |
1b1e96eff7 | ||
![]() |
eecd091063 | ||
![]() |
d38f2cb380 | ||
![]() |
56aaee5558 | ||
![]() |
d72b4752c9 | ||
![]() |
ea769c6483 | ||
![]() |
ec194fa3d4 | ||
![]() |
b877039859 | ||
![]() |
54634f26d2 | ||
![]() |
3bef91a2cd | ||
![]() |
7da45ba589 | ||
![]() |
e0232c67cc | ||
![]() |
1dc4a229d4 | ||
![]() |
0e0bada1f3 | ||
![]() |
5366a814f9 | ||
![]() |
f1240a22db | ||
![]() |
66f35c2b7e | ||
![]() |
766ee48531 | ||
![]() |
083045f45c | ||
![]() |
fe237802c9 | ||
![]() |
00b923651f | ||
![]() |
24fce3cc64 | ||
![]() |
8ba969f67d | ||
![]() |
6844d59371 | ||
![]() |
fe5529db85 | ||
![]() |
d89034d913 | ||
![]() |
360fbeb108 | ||
![]() |
e7c2fa1cfa | ||
![]() |
735f09d977 | ||
![]() |
f83a5e3e49 | ||
![]() |
01a8d4efcc | ||
![]() |
fdb1e649d4 | ||
![]() |
0856792a57 |
1
.github/workflows/build-push.yml
vendored
1
.github/workflows/build-push.yml
vendored
@ -5,6 +5,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
- "deploy/dev"
|
- "deploy/dev"
|
||||||
|
- "deploy/enterprise"
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
29
.github/workflows/deploy-enterprise.yml
vendored
Normal file
29
.github/workflows/deploy-enterprise.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Deploy Enterprise
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Build and Push API & Web"]
|
||||||
|
branches:
|
||||||
|
- "deploy/enterprise"
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.event.workflow_run.conclusion == 'success' &&
|
||||||
|
github.event.workflow_run.head_branch == 'deploy/enterprise'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Deploy to server
|
||||||
|
uses: appleboy/ssh-action@v0.1.8
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.ENTERPRISE_SSH_HOST }}
|
||||||
|
username: ${{ secrets.ENTERPRISE_SSH_USER }}
|
||||||
|
password: ${{ secrets.ENTERPRISE_SSH_PASSWORD }}
|
||||||
|
script: |
|
||||||
|
${{ vars.ENTERPRISE_SSH_SCRIPT || secrets.ENTERPRISE_SSH_SCRIPT }}
|
@ -39,6 +39,17 @@ def only_edition_cloud(view):
|
|||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def only_enterprise_edition(view):
|
||||||
|
@wraps(view)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
if not dify_config.ENTERPRISE_ENABLED:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
def only_edition_self_hosted(view):
|
def only_edition_self_hosted(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
|
@ -36,6 +36,14 @@ class LicenseModel(BaseModel):
|
|||||||
expired_at: str = ""
|
expired_at: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class BrandingModel(BaseModel):
|
||||||
|
enabled: bool = False
|
||||||
|
application_title: str = ""
|
||||||
|
login_page_logo: str = ""
|
||||||
|
workspace_logo: str = ""
|
||||||
|
favicon: str = ""
|
||||||
|
|
||||||
|
|
||||||
class FeatureModel(BaseModel):
|
class FeatureModel(BaseModel):
|
||||||
billing: BillingModel = BillingModel()
|
billing: BillingModel = BillingModel()
|
||||||
members: LimitationModel = LimitationModel(size=0, limit=1)
|
members: LimitationModel = LimitationModel(size=0, limit=1)
|
||||||
@ -47,6 +55,7 @@ class FeatureModel(BaseModel):
|
|||||||
can_replace_logo: bool = False
|
can_replace_logo: bool = False
|
||||||
model_load_balancing_enabled: bool = False
|
model_load_balancing_enabled: bool = False
|
||||||
dataset_operator_enabled: bool = False
|
dataset_operator_enabled: bool = False
|
||||||
|
webapp_copyright_enabled: bool = False
|
||||||
|
|
||||||
# pydantic configs
|
# pydantic configs
|
||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
@ -65,6 +74,7 @@ class SystemFeatureModel(BaseModel):
|
|||||||
is_allow_create_workspace: bool = False
|
is_allow_create_workspace: bool = False
|
||||||
is_email_setup: bool = False
|
is_email_setup: bool = False
|
||||||
license: LicenseModel = LicenseModel()
|
license: LicenseModel = LicenseModel()
|
||||||
|
branding: BrandingModel = BrandingModel()
|
||||||
|
|
||||||
|
|
||||||
class FeatureService:
|
class FeatureService:
|
||||||
@ -77,6 +87,9 @@ class FeatureService:
|
|||||||
if dify_config.BILLING_ENABLED and tenant_id:
|
if dify_config.BILLING_ENABLED and tenant_id:
|
||||||
cls._fulfill_params_from_billing_api(features, tenant_id)
|
cls._fulfill_params_from_billing_api(features, tenant_id)
|
||||||
|
|
||||||
|
if dify_config.ENTERPRISE_ENABLED:
|
||||||
|
features.webapp_copyright_enabled = True
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -87,7 +100,7 @@ class FeatureService:
|
|||||||
|
|
||||||
if dify_config.ENTERPRISE_ENABLED:
|
if dify_config.ENTERPRISE_ENABLED:
|
||||||
system_features.enable_web_sso_switch_component = True
|
system_features.enable_web_sso_switch_component = True
|
||||||
|
system_features.branding.enabled = True
|
||||||
cls._fulfill_params_from_enterprise(system_features)
|
cls._fulfill_params_from_enterprise(system_features)
|
||||||
|
|
||||||
return system_features
|
return system_features
|
||||||
@ -115,6 +128,9 @@ class FeatureService:
|
|||||||
features.billing.subscription.plan = billing_info["subscription"]["plan"]
|
features.billing.subscription.plan = billing_info["subscription"]["plan"]
|
||||||
features.billing.subscription.interval = billing_info["subscription"]["interval"]
|
features.billing.subscription.interval = billing_info["subscription"]["interval"]
|
||||||
|
|
||||||
|
if features.billing.subscription.plan != "sandbox":
|
||||||
|
features.webapp_copyright_enabled = True
|
||||||
|
|
||||||
if "members" in billing_info:
|
if "members" in billing_info:
|
||||||
features.members.size = billing_info["members"]["size"]
|
features.members.size = billing_info["members"]["size"]
|
||||||
features.members.limit = billing_info["members"]["limit"]
|
features.members.limit = billing_info["members"]["limit"]
|
||||||
@ -148,35 +164,41 @@ class FeatureService:
|
|||||||
def _fulfill_params_from_enterprise(cls, features):
|
def _fulfill_params_from_enterprise(cls, features):
|
||||||
enterprise_info = EnterpriseService.get_info()
|
enterprise_info = EnterpriseService.get_info()
|
||||||
|
|
||||||
if "sso_enforced_for_signin" in enterprise_info:
|
if "SSOEnforcedForSignin" in enterprise_info:
|
||||||
features.sso_enforced_for_signin = enterprise_info["sso_enforced_for_signin"]
|
features.sso_enforced_for_signin = enterprise_info["SSOEnforcedForSignin"]
|
||||||
|
|
||||||
if "sso_enforced_for_signin_protocol" in enterprise_info:
|
if "SSOEnforcedForSigninProtocol" in enterprise_info:
|
||||||
features.sso_enforced_for_signin_protocol = enterprise_info["sso_enforced_for_signin_protocol"]
|
features.sso_enforced_for_signin_protocol = enterprise_info["SSOEnforcedForSigninProtocol"]
|
||||||
|
|
||||||
if "sso_enforced_for_web" in enterprise_info:
|
if "SSOEnforcedForWeb" in enterprise_info:
|
||||||
features.sso_enforced_for_web = enterprise_info["sso_enforced_for_web"]
|
features.sso_enforced_for_web = enterprise_info["SSOEnforcedForWeb"]
|
||||||
|
|
||||||
if "sso_enforced_for_web_protocol" in enterprise_info:
|
if "SSOEnforcedForWebProtocol" in enterprise_info:
|
||||||
features.sso_enforced_for_web_protocol = enterprise_info["sso_enforced_for_web_protocol"]
|
features.sso_enforced_for_web_protocol = enterprise_info["SSOEnforcedForWebProtocol"]
|
||||||
|
|
||||||
if "enable_email_code_login" in enterprise_info:
|
if "EnableEmailCodeLogin" in enterprise_info:
|
||||||
features.enable_email_code_login = enterprise_info["enable_email_code_login"]
|
features.enable_email_code_login = enterprise_info["EnableEmailCodeLogin"]
|
||||||
|
|
||||||
if "enable_email_password_login" in enterprise_info:
|
if "EnableEmailPasswordLogin" in enterprise_info:
|
||||||
features.enable_email_password_login = enterprise_info["enable_email_password_login"]
|
features.enable_email_password_login = enterprise_info["EnableEmailPasswordLogin"]
|
||||||
|
|
||||||
if "is_allow_register" in enterprise_info:
|
if "IsAllowRegister" in enterprise_info:
|
||||||
features.is_allow_register = enterprise_info["is_allow_register"]
|
features.is_allow_register = enterprise_info["IsAllowRegister"]
|
||||||
|
|
||||||
if "is_allow_create_workspace" in enterprise_info:
|
if "IsAllowCreateWorkspace" in enterprise_info:
|
||||||
features.is_allow_create_workspace = enterprise_info["is_allow_create_workspace"]
|
features.is_allow_create_workspace = enterprise_info["IsAllowCreateWorkspace"]
|
||||||
|
|
||||||
if "license" in enterprise_info:
|
if "Branding" in enterprise_info:
|
||||||
license_info = enterprise_info["license"]
|
features.branding.application_title = enterprise_info["Branding"].get("applicationTitle", "")
|
||||||
|
features.branding.login_page_logo = enterprise_info["Branding"].get("loginPageLogo", "")
|
||||||
|
features.branding.workspace_logo = enterprise_info["Branding"].get("workspaceLogo", "")
|
||||||
|
features.branding.favicon = enterprise_info["Branding"].get("favicon", "")
|
||||||
|
|
||||||
|
if "License" in enterprise_info:
|
||||||
|
license_info = enterprise_info["License"]
|
||||||
|
|
||||||
if "status" in license_info:
|
if "status" in license_info:
|
||||||
features.license.status = LicenseStatus(license_info.get("status", LicenseStatus.INACTIVE))
|
features.license.status = LicenseStatus(license_info.get("status", LicenseStatus.INACTIVE))
|
||||||
|
|
||||||
if "expired_at" in license_info:
|
if "expired_at" in license_info:
|
||||||
features.license.expired_at = license_info["expired_at"]
|
features.license.expired_at = license_info["expiredAt"]
|
||||||
|
@ -6,6 +6,7 @@ from celery import shared_task # type: ignore
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from extensions.ext_mail import mail
|
from extensions.ext_mail import mail
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="mail")
|
@shared_task(queue="mail")
|
||||||
@ -25,10 +26,24 @@ def send_email_code_login_mail_task(language: str, to: str, code: str):
|
|||||||
# send email code login mail using different languages
|
# send email code login mail using different languages
|
||||||
try:
|
try:
|
||||||
if language == "zh-Hans":
|
if language == "zh-Hans":
|
||||||
html_content = render_template("email_code_login_mail_template_zh-CN.html", to=to, code=code)
|
template = "email_code_login_mail_template_zh-CN.html"
|
||||||
|
system_features = FeatureService.get_system_features()
|
||||||
|
if system_features.branding.enabled:
|
||||||
|
application_title = system_features.branding.application_title
|
||||||
|
template = "without-brand/email_code_login_mail_template_zh-CN.html"
|
||||||
|
html_content = render_template(template, to=to, code=code, application_title=application_title)
|
||||||
|
else:
|
||||||
|
html_content = render_template(template, to=to, code=code)
|
||||||
mail.send(to=to, subject="邮箱验证码", html=html_content)
|
mail.send(to=to, subject="邮箱验证码", html=html_content)
|
||||||
else:
|
else:
|
||||||
html_content = render_template("email_code_login_mail_template_en-US.html", to=to, code=code)
|
template = "email_code_login_mail_template_en-US.html"
|
||||||
|
system_features = FeatureService.get_system_features()
|
||||||
|
if system_features.branding.enabled:
|
||||||
|
application_title = system_features.branding.application_title
|
||||||
|
template = "without-brand/email_code_login_mail_template_en-US.html"
|
||||||
|
html_content = render_template(template, to=to, code=code, application_title=application_title)
|
||||||
|
else:
|
||||||
|
html_content = render_template(template, to=to, code=code)
|
||||||
mail.send(to=to, subject="Email Code", html=html_content)
|
mail.send(to=to, subject="Email Code", html=html_content)
|
||||||
|
|
||||||
end_at = time.perf_counter()
|
end_at = time.perf_counter()
|
||||||
|
@ -7,6 +7,7 @@ from flask import render_template
|
|||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from extensions.ext_mail import mail
|
from extensions.ext_mail import mail
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="mail")
|
@shared_task(queue="mail")
|
||||||
@ -33,23 +34,45 @@ def send_invite_member_mail_task(language: str, to: str, token: str, inviter_nam
|
|||||||
try:
|
try:
|
||||||
url = f"{dify_config.CONSOLE_WEB_URL}/activate?token={token}"
|
url = f"{dify_config.CONSOLE_WEB_URL}/activate?token={token}"
|
||||||
if language == "zh-Hans":
|
if language == "zh-Hans":
|
||||||
html_content = render_template(
|
template = "invite_member_mail_template_zh-CN.html"
|
||||||
"invite_member_mail_template_zh-CN.html",
|
system_features = FeatureService.get_system_features()
|
||||||
to=to,
|
if system_features.branding.enabled:
|
||||||
inviter_name=inviter_name,
|
application_title = system_features.branding.application_title
|
||||||
workspace_name=workspace_name,
|
template = "without-brand/invite_member_mail_template_zh-CN.html"
|
||||||
url=url,
|
html_content = render_template(
|
||||||
)
|
template,
|
||||||
mail.send(to=to, subject="立即加入 Dify 工作空间", html=html_content)
|
to=to,
|
||||||
|
inviter_name=inviter_name,
|
||||||
|
workspace_name=workspace_name,
|
||||||
|
url=url,
|
||||||
|
application_title=application_title,
|
||||||
|
)
|
||||||
|
mail.send(to=to, subject=f"立即加入 {application_title} 工作空间", html=html_content)
|
||||||
|
else:
|
||||||
|
html_content = render_template(
|
||||||
|
template, to=to, inviter_name=inviter_name, workspace_name=workspace_name, url=url
|
||||||
|
)
|
||||||
|
mail.send(to=to, subject="立即加入 Dify 工作空间", html=html_content)
|
||||||
else:
|
else:
|
||||||
html_content = render_template(
|
template = "invite_member_mail_template_en-US.html"
|
||||||
"invite_member_mail_template_en-US.html",
|
system_features = FeatureService.get_system_features()
|
||||||
to=to,
|
if system_features.branding.enabled:
|
||||||
inviter_name=inviter_name,
|
application_title = system_features.branding.application_title
|
||||||
workspace_name=workspace_name,
|
template = "without-brand/invite_member_mail_template_en-US.html"
|
||||||
url=url,
|
html_content = render_template(
|
||||||
)
|
template,
|
||||||
mail.send(to=to, subject="Join Dify Workspace Now", html=html_content)
|
to=to,
|
||||||
|
inviter_name=inviter_name,
|
||||||
|
workspace_name=workspace_name,
|
||||||
|
url=url,
|
||||||
|
application_title=application_title,
|
||||||
|
)
|
||||||
|
mail.send(to=to, subject=f"Join {application_title} Workspace Now", html=html_content)
|
||||||
|
else:
|
||||||
|
html_content = render_template(
|
||||||
|
template, to=to, inviter_name=inviter_name, workspace_name=workspace_name, url=url
|
||||||
|
)
|
||||||
|
mail.send(to=to, subject="Join Dify Workspace Now", html=html_content)
|
||||||
|
|
||||||
end_at = time.perf_counter()
|
end_at = time.perf_counter()
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -6,6 +6,7 @@ from celery import shared_task # type: ignore
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from extensions.ext_mail import mail
|
from extensions.ext_mail import mail
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="mail")
|
@shared_task(queue="mail")
|
||||||
@ -25,11 +26,27 @@ def send_reset_password_mail_task(language: str, to: str, code: str):
|
|||||||
# send reset password mail using different languages
|
# send reset password mail using different languages
|
||||||
try:
|
try:
|
||||||
if language == "zh-Hans":
|
if language == "zh-Hans":
|
||||||
html_content = render_template("reset_password_mail_template_zh-CN.html", to=to, code=code)
|
template = "reset_password_mail_template_zh-CN.html"
|
||||||
mail.send(to=to, subject="设置您的 Dify 密码", html=html_content)
|
system_features = FeatureService.get_system_features()
|
||||||
|
if system_features.branding.enabled:
|
||||||
|
application_title = system_features.branding.application_title
|
||||||
|
template = "without-brand/reset_password_mail_template_zh-CN.html"
|
||||||
|
html_content = render_template(template, to=to, code=code, application_title=application_title)
|
||||||
|
mail.send(to=to, subject=f"设置您的 {application_title} 密码", html=html_content)
|
||||||
|
else:
|
||||||
|
html_content = render_template(template, to=to, code=code)
|
||||||
|
mail.send(to=to, subject="设置您的 Dify 密码", html=html_content)
|
||||||
else:
|
else:
|
||||||
html_content = render_template("reset_password_mail_template_en-US.html", to=to, code=code)
|
template = "reset_password_mail_template_en-US.html"
|
||||||
mail.send(to=to, subject="Set Your Dify Password", html=html_content)
|
system_features = FeatureService.get_system_features()
|
||||||
|
if system_features.branding.enabled:
|
||||||
|
application_title = system_features.branding.application_title
|
||||||
|
template = "without-brand/reset_password_mail_template_en-US.html"
|
||||||
|
html_content = render_template(template, to=to, code=code, application_title=application_title)
|
||||||
|
mail.send(to=to, subject=f"Set Your {application_title} Password", html=html_content)
|
||||||
|
else:
|
||||||
|
html_content = render_template(template, to=to, code=code)
|
||||||
|
mail.send(to=to, subject="Set Your Dify Password", html=html_content)
|
||||||
|
|
||||||
end_at = time.perf_counter()
|
end_at = time.perf_counter()
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #101828;
|
||||||
|
background-color: #e9ebf0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 600px;
|
||||||
|
height: 360px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 36px 48px;
|
||||||
|
background-color: #fcfcfd;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
box-shadow: 0 2px 4px -2px rgba(9, 9, 11, 0.08);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 28.8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.code-content {
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<p class="title">Your login code for {{application_title}}</p>
|
||||||
|
<p class="description">Copy and paste this code, this code will only be valid for the next 5 minutes.</p>
|
||||||
|
<div class="code-content">
|
||||||
|
<span class="code">{{code}}</span>
|
||||||
|
</div>
|
||||||
|
<p class="tips">If you didn't request a login, don't worry. You can safely ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #101828;
|
||||||
|
background-color: #e9ebf0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 600px;
|
||||||
|
height: 360px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 36px 48px;
|
||||||
|
background-color: #fcfcfd;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
box-shadow: 0 2px 4px -2px rgba(9, 9, 11, 0.08);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 28.8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.code-content {
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<p class="title">{{application_title}} 的登录验证码</p>
|
||||||
|
<p class="description">复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。</p>
|
||||||
|
<div class="code-content">
|
||||||
|
<span class="code">{{code}}</span>
|
||||||
|
</div>
|
||||||
|
<p class="tips">如果您没有请求登录,请不要担心。您可以安全地忽略此电子邮件。</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #374151;
|
||||||
|
background-color: #E5E7EB;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #F3F4F6;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: #2970FF;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: #265DD4;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #777777;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<p>Dear {{ to }},</p>
|
||||||
|
<p>{{ inviter_name }} is pleased to invite you to join our workspace on {{application_title}}, a platform specifically designed for LLM application development. On {{application_title}}, you can explore, create, and collaborate to build and operate AI applications.</p>
|
||||||
|
<p>Click the button below to log in to {{application_title}} and join the workspace.</p>
|
||||||
|
<p style="text-align: center;"><a style="color: #fff; text-decoration: none" class="button" href="{{ url }}">Login Here</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Best regards,</p>
|
||||||
|
<p>{{application_title}} Team</p>
|
||||||
|
<p>Please do not reply directly to this email; it is automatically sent by the system.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #374151;
|
||||||
|
background-color: #E5E7EB;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #F3F4F6;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: #2970FF;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: #265DD4;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #777777;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<p>尊敬的 {{ to }},</p>
|
||||||
|
<p>{{ inviter_name }} 现邀请您加入我们在 {{application_title}} 的工作区,这是一个专为 LLM 应用开发而设计的平台。在 {{application_title}} 上,您可以探索、创造和合作,构建和运营 AI 应用。</p>
|
||||||
|
<p>点击下方按钮即可登录 {{application_title}} 并且加入空间。</p>
|
||||||
|
<p style="text-align: center;"><a style="color: #fff; text-decoration: none" class="button" href="{{ url }}">在此登录</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>此致,</p>
|
||||||
|
<p>{{application_title}} 团队</p>
|
||||||
|
<p>请不要直接回复此电子邮件;由系统自动发送。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #101828;
|
||||||
|
background-color: #e9ebf0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 600px;
|
||||||
|
height: 360px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 36px 48px;
|
||||||
|
background-color: #fcfcfd;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
box-shadow: 0 2px 4px -2px rgba(9, 9, 11, 0.08);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 28.8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.code-content {
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<p class="title">Set your {{application_title}} password</p>
|
||||||
|
<p class="description">Copy and paste this code, this code will only be valid for the next 5 minutes.</p>
|
||||||
|
<div class="code-content">
|
||||||
|
<span class="code">{{code}}</span>
|
||||||
|
</div>
|
||||||
|
<p class="tips">If you didn't request, don't worry. You can safely ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 16pt;
|
||||||
|
color: #101828;
|
||||||
|
background-color: #e9ebf0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 600px;
|
||||||
|
height: 360px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 36px 48px;
|
||||||
|
background-color: #fcfcfd;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
box-shadow: 0 2px 4px -2px rgba(9, 9, 11, 0.08);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.header img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 28.8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.code-content {
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
line-height: 16px;
|
||||||
|
color: #676f83;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<p class="title">设置您的 {{application_title}} 账户密码</p>
|
||||||
|
<p class="description">复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。</p>
|
||||||
|
<div class="code-content">
|
||||||
|
<span class="code">{{code}}</span>
|
||||||
|
</div>
|
||||||
|
<p class="tips">如果您没有请求,请不要担心。您可以安全地忽略此电子邮件。</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -15,17 +15,17 @@ import {
|
|||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
import { useContextSelector } from 'use-context-selector'
|
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { useStore } from '@/app/components/app/store'
|
import { useStore } from '@/app/components/app/store'
|
||||||
import AppSideBar from '@/app/components/app-sidebar'
|
import AppSideBar from '@/app/components/app-sidebar'
|
||||||
import type { NavIcon } from '@/app/components/app-sidebar/navLink'
|
import type { NavIcon } from '@/app/components/app-sidebar/navLink'
|
||||||
import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
|
import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
|
||||||
import AppContext, { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export type IAppDetailLayoutProps = {
|
export type IAppDetailLayoutProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@ -56,7 +56,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
icon: NavIcon
|
icon: NavIcon
|
||||||
selectedIcon: NavIcon
|
selectedIcon: NavIcon
|
||||||
}>>([])
|
}>>([])
|
||||||
const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
|
const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
|
||||||
const navs = [
|
const navs = [
|
||||||
@ -98,7 +98,11 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appDetail) {
|
if (appDetail) {
|
||||||
document.title = `${(appDetail.name || 'App')} - Dify`
|
if (systemFeatures.branding.enabled)
|
||||||
|
document.title = `${(appDetail.name || 'App')} - ${systemFeatures.branding.application_title}`
|
||||||
|
else
|
||||||
|
document.title = `${(appDetail.name || 'App')} - Dify`
|
||||||
|
|
||||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||||
const mode = isMobile ? 'collapse' : 'expand'
|
const mode = isMobile ? 'collapse' : 'expand'
|
||||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||||
@ -106,7 +110,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
// if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
// if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
||||||
// setAppSiderbarExpand('collapse')
|
// setAppSiderbarExpand('collapse')
|
||||||
}
|
}
|
||||||
}, [appDetail, isMobile])
|
}, [appDetail, isMobile, pathname, setAppSiderbarExpand, systemFeatures])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAppDetail()
|
setAppDetail()
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext, useContextSelector } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import AppCard from '@/app/components/app/overview/appCard'
|
import AppCard from '@/app/components/app/overview/appCard'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
@ -20,7 +20,7 @@ import { asyncRunSafe } from '@/utils'
|
|||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
import type { IAppCardProps } from '@/app/components/app/overview/appCard'
|
import type { IAppCardProps } from '@/app/components/app/overview/appCard'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import AppContext from '@/context/app-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export type ICardViewProps = {
|
export type ICardViewProps = {
|
||||||
appId: string
|
appId: string
|
||||||
@ -31,7 +31,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
|
|||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const appDetail = useAppStore(state => state.appDetail)
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||||
const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
const updateAppDetail = async () => {
|
const updateAppDetail = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
export type IAppDetail = {
|
export type IAppDetail = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@ -11,11 +13,13 @@ export type IAppDetail = {
|
|||||||
const AppDetail: FC<IAppDetail> = ({ children }) => {
|
const AppDetail: FC<IAppDetail> = ({ children }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
useDocumentTitle(t('common.menus.appDetail'))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCurrentWorkspaceDatasetOperator)
|
if (isCurrentWorkspaceDatasetOperator)
|
||||||
return router.replace('/datasets')
|
return router.replace('/datasets')
|
||||||
}, [isCurrentWorkspaceDatasetOperator])
|
}, [isCurrentWorkspaceDatasetOperator, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -85,7 +85,6 @@ const Apps = () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${t('common.menus.apps')} - Dify`
|
|
||||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||||
mutate()
|
mutate()
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useContextSelector } from 'use-context-selector'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
|
import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import style from '../list.module.css'
|
import style from '../list.module.css'
|
||||||
import Apps from './Apps'
|
import Apps from './Apps'
|
||||||
import AppContext from '@/context/app-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import { LicenseStatus } from '@/types/feature'
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
const AppList = () => {
|
const AppList = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
useDocumentTitle(t('common.menus.apps'))
|
||||||
return (
|
return (
|
||||||
<div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'>
|
<div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'>
|
||||||
<Apps />
|
<Apps />
|
||||||
{systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'>
|
{!systemFeatures.branding.enabled && <footer className='px-12 py-6 grow-0 shrink-0'>
|
||||||
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
|
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
|
||||||
<p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p>
|
<p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p>
|
||||||
<div className='flex items-center gap-2 mt-3'>
|
<div className='flex items-center gap-2 mt-3'>
|
||||||
|
@ -31,6 +31,7 @@ import { getLocaleOnClient } from '@/i18n'
|
|||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
export type IAppDetailLayoutProps = {
|
export type IAppDetailLayoutProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@ -186,11 +187,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return baseNavigation
|
return baseNavigation
|
||||||
}, [datasetRes?.provider, datasetId, t])
|
}, [datasetRes?.provider, datasetId, t])
|
||||||
|
useDocumentTitle(`${datasetRes?.name || 'Dataset'}`)
|
||||||
useEffect(() => {
|
|
||||||
if (datasetRes)
|
|
||||||
document.title = `${datasetRes.name || 'Dataset'} - Dify`
|
|
||||||
}, [datasetRes])
|
|
||||||
|
|
||||||
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
|
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
|
||||||
|
|
||||||
|
@ -29,9 +29,11 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
|||||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const Container = () => {
|
const Container = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext()
|
const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext()
|
||||||
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
|
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
|
||||||
@ -123,7 +125,7 @@ const Container = () => {
|
|||||||
{activeTab === 'dataset' && (
|
{activeTab === 'dataset' && (
|
||||||
<>
|
<>
|
||||||
<Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} includeAll={includeAll} />
|
<Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} includeAll={includeAll} />
|
||||||
<DatasetFooter />
|
{!systemFeatures.branding.enabled && <DatasetFooter />}
|
||||||
{showTagManagementModal && (
|
{showTagManagementModal && (
|
||||||
<TagManagementModal type='knowledge' show={showTagManagementModal} />
|
<TagManagementModal type='knowledge' show={showTagManagementModal} />
|
||||||
)}
|
)}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
import useSWRInfinite from 'swr/infinite'
|
||||||
import { debounce } from 'lodash-es'
|
import { debounce } from 'lodash-es'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import NewDatasetCard from './NewDatasetCard'
|
import NewDatasetCard from './NewDatasetCard'
|
||||||
import DatasetCard from './DatasetCard'
|
import DatasetCard from './DatasetCard'
|
||||||
import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets'
|
import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets'
|
||||||
@ -57,11 +56,8 @@ const Datasets = ({
|
|||||||
const loadingStateRef = useRef(false)
|
const loadingStateRef = useRef(false)
|
||||||
const anchorRef = useRef<HTMLAnchorElement>(null)
|
const anchorRef = useRef<HTMLAnchorElement>(null)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadingStateRef.current = isLoading
|
loadingStateRef.current = isLoading
|
||||||
document.title = `${t('dataset.knowledge')} - Dify`
|
|
||||||
}, [isLoading])
|
}, [isLoading])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -80,7 +76,7 @@ const Datasets = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
||||||
{ isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> }
|
{isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} />}
|
||||||
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
||||||
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
|
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
|
||||||
))}
|
))}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
|
'use client'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import Container from './Container'
|
import Container from './Container'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
const AppList = async () => {
|
const AppList = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
useDocumentTitle(t('common.menus.datasets'))
|
||||||
return <Container />
|
return <Container />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: 'Datasets - Dify',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppList
|
export default AppList
|
||||||
|
@ -6,7 +6,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
<div>
|
<div>
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
Service API of Dify authenticates using an `API-Key`.
|
Service API authenticates using an `API-Key`.
|
||||||
|
|
||||||
It is suggested that developers store the `API-Key` in the backend instead of sharing or storing it in the client side to avoid the leakage of the `API-Key`, which may lead to property loss.
|
It is suggested that developers store the `API-Key` in the backend instead of sharing or storing it in the client side to avoid the leakage of the `API-Key`, which may lead to property loss.
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
<div>
|
<div>
|
||||||
### 鉴权
|
### 鉴权
|
||||||
|
|
||||||
Dify Service API 使用 `API-Key` 进行鉴权。
|
Service API 使用 `API-Key` 进行鉴权。
|
||||||
|
|
||||||
建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。
|
建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import type { FC } from 'react'
|
'use client'
|
||||||
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import ExploreClient from '@/app/components/explore'
|
import ExploreClient from '@/app/components/explore'
|
||||||
export type IAppDetail = {
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppDetail: FC<IAppDetail> = ({ children }) => {
|
const ExploreLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
useDocumentTitle(t('common.menus.explore'))
|
||||||
return (
|
return (
|
||||||
<ExploreClient>
|
<ExploreClient>
|
||||||
{children}
|
{children}
|
||||||
@ -13,4 +15,4 @@ const AppDetail: FC<IAppDetail> = ({ children }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(AppDetail)
|
export default React.memo(ExploreLayout)
|
||||||
|
@ -30,9 +30,4 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: 'Dify',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Layout
|
export default Layout
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import ToolProviderList from '@/app/components/tools/provider-list'
|
import ToolProviderList from '@/app/components/tools/provider-list'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
const Layout: FC = () => {
|
const ToolsList: FC = () => {
|
||||||
const { t } = useTranslation()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||||
|
const { t } = useTranslation()
|
||||||
useEffect(() => {
|
useDocumentTitle(t('common.menus.tools'))
|
||||||
if (typeof window !== 'undefined')
|
|
||||||
document.title = `${t('tools.title')} - Dify`
|
|
||||||
if (isCurrentWorkspaceDatasetOperator)
|
|
||||||
return router.replace('/datasets')
|
|
||||||
}, [isCurrentWorkspaceDatasetOperator, router, t])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCurrentWorkspaceDatasetOperator)
|
if (isCurrentWorkspaceDatasetOperator)
|
||||||
@ -25,4 +19,4 @@ const Layout: FC = () => {
|
|||||||
|
|
||||||
return <ToolProviderList />
|
return <ToolProviderList />
|
||||||
}
|
}
|
||||||
export default React.memo(Layout)
|
export default React.memo(ToolsList)
|
||||||
|
@ -16,6 +16,7 @@ import { ToastContext } from '@/app/components/base/toast'
|
|||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const titleClassName = `
|
const titleClassName = `
|
||||||
system-sm-semibold text-text-secondary
|
system-sm-semibold text-text-secondary
|
||||||
@ -28,7 +29,7 @@ const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
|||||||
|
|
||||||
export default function AccountPage() {
|
export default function AccountPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { systemFeatures } = useAppContext()
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const { mutateUserProfile, userProfile, apps } = useAppContext()
|
const { mutateUserProfile, userProfile, apps } = useAppContext()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const [editNameModalVisible, setEditNameModalVisible] = useState(false)
|
const [editNameModalVisible, setEditNameModalVisible] = useState(false)
|
||||||
@ -133,7 +134,7 @@ export default function AccountPage() {
|
|||||||
<h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4>
|
<h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className='mb-8 p-6 rounded-xl flex items-center bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1'>
|
<div className='mb-8 p-6 rounded-xl flex items-center bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1'>
|
||||||
<AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={ mutateUserProfile } size={64} />
|
<AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={mutateUserProfile} size={64} />
|
||||||
<div className='ml-4'>
|
<div className='ml-4'>
|
||||||
<p className='system-xl-semibold text-text-primary'>{userProfile.name}</p>
|
<p className='system-xl-semibold text-text-primary'>{userProfile.name}</p>
|
||||||
<p className='system-xs-regular text-text-tertiary'>{userProfile.email}</p>
|
<p className='system-xs-regular text-text-tertiary'>{userProfile.email}</p>
|
||||||
|
@ -5,9 +5,11 @@ import { useRouter } from 'next/navigation'
|
|||||||
import Button from '../components/base/button'
|
import Button from '../components/base/button'
|
||||||
import Avatar from './avatar'
|
import Avatar from './avatar'
|
||||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const back = () => {
|
const back = () => {
|
||||||
@ -25,7 +27,7 @@ const Header = () => {
|
|||||||
<div className='flex items-center flex-shrink-0 gap-3'>
|
<div className='flex items-center flex-shrink-0 gap-3'>
|
||||||
<Button className='gap-2 py-2 px-3 system-sm-medium' onClick={back}>
|
<Button className='gap-2 py-2 px-3 system-sm-medium' onClick={back}>
|
||||||
<RiRobot2Line className='w-4 h-4' />
|
<RiRobot2Line className='w-4 h-4' />
|
||||||
<p>{t('common.account.studio')}</p>
|
<p>{!systemFeatures.branding.enabled && 'Dify '}{t('common.account.studio')}</p>
|
||||||
<RiArrowRightUpLine className='w-4 h-4' />
|
<RiArrowRightUpLine className='w-4 h-4' />
|
||||||
</Button>
|
</Button>
|
||||||
<div className='w-[1px] h-4 bg-divider-regular' />
|
<div className='w-[1px] h-4 bg-divider-regular' />
|
||||||
|
@ -32,9 +32,4 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: 'Dify',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Layout
|
export default Layout
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import AccountPage from './account-page'
|
import AccountPage from './account-page'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
export default function Account() {
|
export default function Account() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
useDocumentTitle(t('common.menus.account'))
|
||||||
return <div className='max-w-[640px] w-full mx-auto pt-12 px-6'>
|
return <div className='max-w-[640px] w-full mx-auto pt-12 px-6'>
|
||||||
<AccountPage />
|
<AccountPage />
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,8 +7,10 @@ import Button from '@/app/components/base/button'
|
|||||||
|
|
||||||
import { invitationCheck } from '@/service/common'
|
import { invitationCheck } from '@/service/common'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
const ActivateForm = () => {
|
const ActivateForm = () => {
|
||||||
|
useDocumentTitle('')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
'use client'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Header from '../signin/_header'
|
import Header from '../signin/_header'
|
||||||
import style from '../signin/page.module.css'
|
import style from '../signin/page.module.css'
|
||||||
import ActivateForm from './activateForm'
|
import ActivateForm from './activateForm'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const Activate = () => {
|
const Activate = () => {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
return (
|
return (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
style.background,
|
style.background,
|
||||||
@ -21,9 +24,9 @@ const Activate = () => {
|
|||||||
}>
|
}>
|
||||||
<Header />
|
<Header />
|
||||||
<ActivateForm />
|
<ActivateForm />
|
||||||
<div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
{!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
||||||
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react'
|
|||||||
import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react'
|
import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useContext, useContextSelector } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
@ -21,13 +21,14 @@ import type { AppIconType, AppSSO, Language } from '@/types/app'
|
|||||||
import { useToastContext } from '@/app/components/base/toast'
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
import { LanguagesSupported, languages } from '@/i18n/language'
|
import { LanguagesSupported, languages } from '@/i18n/language'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import AppContext, { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||||
import I18n from '@/context/i18n'
|
import I18n from '@/context/i18n'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export type ISettingsModalProps = {
|
export type ISettingsModalProps = {
|
||||||
isChat: boolean
|
isChat: boolean
|
||||||
@ -65,7 +66,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onSave,
|
onSave,
|
||||||
}) => {
|
}) => {
|
||||||
const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext()
|
||||||
const [isShowMore, setIsShowMore] = useState(false)
|
const [isShowMore, setIsShowMore] = useState(false)
|
||||||
@ -110,7 +111,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
: { type: 'emoji', icon, background: icon_background! },
|
: { type: 'emoji', icon, background: icon_background! },
|
||||||
)
|
)
|
||||||
|
|
||||||
const { enableBilling, plan } = useProviderContext()
|
const { enableBilling, plan, webappCopyrightEnabled } = useProviderContext()
|
||||||
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
|
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
|
||||||
const isFreePlan = plan.type === 'sandbox'
|
const isFreePlan = plan.type === 'sandbox'
|
||||||
const handlePlanClick = useCallback(() => {
|
const handlePlanClick = useCallback(() => {
|
||||||
@ -177,7 +178,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
chat_color_theme: inputInfo.chatColorTheme,
|
chat_color_theme: inputInfo.chatColorTheme,
|
||||||
chat_color_theme_inverted: inputInfo.chatColorThemeInverted,
|
chat_color_theme_inverted: inputInfo.chatColorThemeInverted,
|
||||||
prompt_public: false,
|
prompt_public: false,
|
||||||
copyright: isFreePlan
|
copyright: !webappCopyrightEnabled
|
||||||
? ''
|
? ''
|
||||||
: inputInfo.copyrightSwitchValue
|
: inputInfo.copyrightSwitchValue
|
||||||
? inputInfo.copyright
|
? inputInfo.copyright
|
||||||
@ -354,7 +355,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.entry`)}</div>
|
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.entry`)}</div>
|
||||||
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p>
|
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p>
|
||||||
</div>
|
</div>
|
||||||
<RiArrowRightSLine className='shrink-0 ml-1 w-4 h-4 text-text-secondary'/>
|
<RiArrowRightSLine className='shrink-0 ml-1 w-4 h-4 text-text-secondary' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* more settings */}
|
{/* more settings */}
|
||||||
@ -380,14 +381,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
disabled={!isFreePlan}
|
disabled={webappCopyrightEnabled}
|
||||||
popupContent={
|
popupContent={
|
||||||
<div className='w-[260px]'>{t(`${prefixSettings}.more.copyrightTooltip`)}</div>
|
<div className='w-[180px]'>{t(`${prefixSettings}.more.copyrightTooltip`)}</div>
|
||||||
}
|
}
|
||||||
asChild={false}
|
asChild={false}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
disabled={isFreePlan}
|
disabled={!webappCopyrightEnabled}
|
||||||
defaultValue={inputInfo.copyrightSwitchValue}
|
defaultValue={inputInfo.copyrightSwitchValue}
|
||||||
onChange={v => setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
|
onChange={v => setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
|
||||||
/>
|
/>
|
||||||
@ -439,20 +440,22 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
|||||||
<Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
|
<Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal >
|
</Modal >
|
||||||
{showAppIconPicker && (
|
{
|
||||||
<AppIconPicker
|
showAppIconPicker && (
|
||||||
onSelect={(payload) => {
|
<AppIconPicker
|
||||||
setAppIcon(payload)
|
onSelect={(payload) => {
|
||||||
setShowAppIconPicker(false)
|
setAppIcon(payload)
|
||||||
}}
|
setShowAppIconPicker(false)
|
||||||
onClose={() => {
|
}}
|
||||||
setAppIcon(icon_type === 'image'
|
onClose={() => {
|
||||||
? { type: 'image', url: icon_url!, fileId: icon }
|
setAppIcon(icon_type === 'image'
|
||||||
: { type: 'emoji', icon, background: icon_background! })
|
? { type: 'image', url: icon_url!, fileId: icon }
|
||||||
setShowAppIconPicker(false)
|
: { type: 'emoji', icon, background: icon_background! })
|
||||||
}}
|
setShowAppIconPicker(false)
|
||||||
/>
|
}}
|
||||||
)}
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -11,10 +11,12 @@ import { useLocalStorageState } from 'ahooks'
|
|||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import type {
|
import type {
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
|
ChatItem,
|
||||||
Feedback,
|
Feedback,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { CONVERSATION_ID_INFO } from '../constants'
|
import { CONVERSATION_ID_INFO } from '../constants'
|
||||||
import { getPrevChatList, getProcessedInputsFromUrlParams } from '../utils'
|
import { buildChatItemTree, getProcessedInputsFromUrlParams } from '../utils'
|
||||||
|
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||||
import {
|
import {
|
||||||
fetchAppInfo,
|
fetchAppInfo,
|
||||||
fetchAppMeta,
|
fetchAppMeta,
|
||||||
@ -32,6 +34,33 @@ import { useToastContext } from '@/app/components/base/toast'
|
|||||||
import { changeLanguage } from '@/i18n/i18next-config'
|
import { changeLanguage } from '@/i18n/i18next-config'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||||
|
|
||||||
|
function getFormattedChatList(messages: any[]) {
|
||||||
|
const newChatList: ChatItem[] = []
|
||||||
|
messages.forEach((item) => {
|
||||||
|
const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || []
|
||||||
|
newChatList.push({
|
||||||
|
id: `question-${item.id}`,
|
||||||
|
content: item.query,
|
||||||
|
isAnswer: false,
|
||||||
|
message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
parentMessageId: item.parent_message_id || undefined,
|
||||||
|
})
|
||||||
|
const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || []
|
||||||
|
newChatList.push({
|
||||||
|
id: item.id,
|
||||||
|
content: item.answer,
|
||||||
|
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
|
||||||
|
feedback: item.feedback,
|
||||||
|
isAnswer: true,
|
||||||
|
citation: item.retriever_resources,
|
||||||
|
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
parentMessageId: `question-${item.id}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return newChatList
|
||||||
|
}
|
||||||
|
|
||||||
export const useEmbeddedChatbot = () => {
|
export const useEmbeddedChatbot = () => {
|
||||||
const isInstalledApp = false
|
const isInstalledApp = false
|
||||||
@ -77,7 +106,7 @@ export const useEmbeddedChatbot = () => {
|
|||||||
|
|
||||||
const appPrevChatList = useMemo(
|
const appPrevChatList = useMemo(
|
||||||
() => (currentConversationId && appChatListData?.data.length)
|
() => (currentConversationId && appChatListData?.data.length)
|
||||||
? getPrevChatList(appChatListData.data)
|
? buildChatItemTree(getFormattedChatList(appChatListData.data))
|
||||||
: [],
|
: [],
|
||||||
[appChatListData, currentConversationId],
|
[appChatListData, currentConversationId],
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import { useSelector } from '@/context/app-context'
|
import { useSelector } from '@/context/app-context'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
type LogoSiteProps = {
|
type LogoSiteProps = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -10,13 +11,17 @@ type LogoSiteProps = {
|
|||||||
const LogoSite: FC<LogoSiteProps> = ({
|
const LogoSite: FC<LogoSiteProps> = ({
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const { theme } = useSelector((s) => {
|
const { theme } = useSelector((s) => {
|
||||||
return {
|
return {
|
||||||
theme: s.theme,
|
theme: s.theme,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png`
|
let src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png`
|
||||||
|
if (systemFeatures.branding.enabled)
|
||||||
|
src = systemFeatures.branding.workspace_logo
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
|
@ -67,6 +67,7 @@ export type CurrentPlanInfoBackend = {
|
|||||||
can_replace_logo: boolean
|
can_replace_logo: boolean
|
||||||
model_load_balancing_enabled: boolean
|
model_load_balancing_enabled: boolean
|
||||||
dataset_operator_enabled: boolean
|
dataset_operator_enabled: boolean
|
||||||
|
webapp_copyright_enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubscriptionItem = {
|
export type SubscriptionItem = {
|
||||||
|
@ -15,7 +15,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
|||||||
### 鉴权
|
### 鉴权
|
||||||
|
|
||||||
|
|
||||||
Dify Service API 使用 `API-Key` 进行鉴权。
|
Service API 使用 `API-Key` 进行鉴权。
|
||||||
<i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i>
|
<i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i>
|
||||||
所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示:
|
所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示:
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
|
|||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
Dify Service API 使用 `API-Key` 进行鉴权。
|
Service API 使用 `API-Key` 进行鉴权。
|
||||||
<i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i>
|
<i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i>
|
||||||
所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示:
|
所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示:
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import ExploreContext from '@/context/explore-context'
|
import ExploreContext from '@/context/explore-context'
|
||||||
import Sidebar from '@/app/components/explore/sidebar'
|
import Sidebar from '@/app/components/explore/sidebar'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
@ -16,7 +15,6 @@ export type IExploreProps = {
|
|||||||
const Explore: FC<IExploreProps> = ({
|
const Explore: FC<IExploreProps> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
|
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
|
||||||
const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||||
@ -24,7 +22,6 @@ const Explore: FC<IExploreProps> = ({
|
|||||||
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
|
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${t('explore.title')} - Dify`;
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
|
||||||
if (!accounts)
|
if (!accounts)
|
||||||
|
@ -20,6 +20,7 @@ import { useModalContext } from '@/context/modal-context'
|
|||||||
import { LanguagesSupported } from '@/i18n/language'
|
import { LanguagesSupported } from '@/i18n/language'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { Plan } from '@/app/components/billing/type'
|
import { Plan } from '@/app/components/billing/type'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export type IAppSelector = {
|
export type IAppSelector = {
|
||||||
isMobile: boolean
|
isMobile: boolean
|
||||||
@ -32,6 +33,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
|||||||
`
|
`
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [aboutVisible, setAboutVisible] = useState(false)
|
const [aboutVisible, setAboutVisible] = useState(false)
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
const { locale } = useContext(I18n)
|
const { locale } = useContext(I18n)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -122,78 +124,80 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
|||||||
<div>{t('common.userProfile.settings')}</div>
|
<div>{t('common.userProfile.settings')}</div>
|
||||||
</div>}
|
</div>}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{canEmailSupport && <Menu.Item>
|
{!systemFeatures.branding.enabled && <>
|
||||||
{({ active }) => <a
|
{canEmailSupport && <Menu.Item>
|
||||||
className={classNames(itemClassName, 'group justify-between',
|
{({ active }) => <a
|
||||||
active && 'bg-state-base-hover',
|
className={classNames(itemClassName, 'group justify-between',
|
||||||
)}
|
|
||||||
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
|
|
||||||
target='_blank' rel='noopener noreferrer'>
|
|
||||||
<div>{t('common.userProfile.emailSupport')}</div>
|
|
||||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
|
||||||
</a>}
|
|
||||||
</Menu.Item>}
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => <Link
|
|
||||||
className={classNames(itemClassName, 'group justify-between',
|
|
||||||
active && 'bg-state-base-hover',
|
|
||||||
)}
|
|
||||||
href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
|
|
||||||
target='_blank' rel='noopener noreferrer'>
|
|
||||||
<div>{t('common.userProfile.communityFeedback')}</div>
|
|
||||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
|
||||||
</Link>}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => <Link
|
|
||||||
className={classNames(itemClassName, 'group justify-between',
|
|
||||||
active && 'bg-state-base-hover',
|
|
||||||
)}
|
|
||||||
href='https://discord.gg/5AEfbxcd9k'
|
|
||||||
target='_blank' rel='noopener noreferrer'>
|
|
||||||
<div>{t('common.userProfile.community')}</div>
|
|
||||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
|
||||||
</Link>}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => <Link
|
|
||||||
className={classNames(itemClassName, 'group justify-between',
|
|
||||||
active && 'bg-state-base-hover',
|
|
||||||
)}
|
|
||||||
href={
|
|
||||||
locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
|
|
||||||
}
|
|
||||||
target='_blank' rel='noopener noreferrer'>
|
|
||||||
<div>{t('common.userProfile.helpCenter')}</div>
|
|
||||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
|
||||||
</Link>}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => <Link
|
|
||||||
className={classNames(itemClassName, 'group justify-between',
|
|
||||||
active && 'bg-state-base-hover',
|
|
||||||
)}
|
|
||||||
href='https://roadmap.dify.ai'
|
|
||||||
target='_blank' rel='noopener noreferrer'>
|
|
||||||
<div>{t('common.userProfile.roadmap')}</div>
|
|
||||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
|
||||||
</Link>}
|
|
||||||
</Menu.Item>
|
|
||||||
{
|
|
||||||
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => <div className={classNames(itemClassName, 'justify-between',
|
|
||||||
active && 'bg-state-base-hover',
|
active && 'bg-state-base-hover',
|
||||||
)} onClick={() => setAboutVisible(true)}>
|
)}
|
||||||
<div>{t('common.userProfile.about')}</div>
|
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
|
||||||
<div className='flex items-center'>
|
target='_blank' rel='noopener noreferrer'>
|
||||||
<div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
|
<div>{t('common.userProfile.emailSupport')}</div>
|
||||||
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
|
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||||
</div>
|
</a>}
|
||||||
</div>}
|
</Menu.Item>}
|
||||||
</Menu.Item>
|
<Menu.Item>
|
||||||
)
|
{({ active }) => <Link
|
||||||
}
|
className={classNames(itemClassName, 'group justify-between',
|
||||||
|
active && 'bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
|
||||||
|
target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div>{t('common.userProfile.communityFeedback')}</div>
|
||||||
|
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||||
|
</Link>}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => <Link
|
||||||
|
className={classNames(itemClassName, 'group justify-between',
|
||||||
|
active && 'bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
href='https://discord.gg/5AEfbxcd9k'
|
||||||
|
target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div>{t('common.userProfile.community')}</div>
|
||||||
|
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||||
|
</Link>}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => <Link
|
||||||
|
className={classNames(itemClassName, 'group justify-between',
|
||||||
|
active && 'bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
href={
|
||||||
|
locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
|
||||||
|
}
|
||||||
|
target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div>{t('common.userProfile.helpCenter')}</div>
|
||||||
|
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||||
|
</Link>}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => <Link
|
||||||
|
className={classNames(itemClassName, 'group justify-between',
|
||||||
|
active && 'bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
href='https://roadmap.dify.ai'
|
||||||
|
target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div>{t('common.userProfile.roadmap')}</div>
|
||||||
|
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||||
|
</Link>}
|
||||||
|
</Menu.Item>
|
||||||
|
{
|
||||||
|
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => <div className={classNames(itemClassName, 'justify-between',
|
||||||
|
active && 'bg-state-base-hover',
|
||||||
|
)} onClick={() => setAboutVisible(true)}>
|
||||||
|
<div>{t('common.userProfile.about')}</div>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
|
||||||
|
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</Menu.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => <div className='p-1' onClick={() => handleLogout()}>
|
{({ active }) => <div className='p-1' onClick={() => handleLogout()}>
|
||||||
|
@ -21,6 +21,7 @@ import { Plan } from '@/app/components/billing/type'
|
|||||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||||
import { NUM_INFINITE } from '@/app/components/billing/config'
|
import { NUM_INFINITE } from '@/app/components/billing/config'
|
||||||
import { LanguagesSupported } from '@/i18n/language'
|
import { LanguagesSupported } from '@/i18n/language'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
const MembersPage = () => {
|
const MembersPage = () => {
|
||||||
@ -34,7 +35,8 @@ const MembersPage = () => {
|
|||||||
}
|
}
|
||||||
const { locale } = useContext(I18n)
|
const { locale } = useContext(I18n)
|
||||||
|
|
||||||
const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext()
|
const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager } = useAppContext()
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers)
|
const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers)
|
||||||
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
||||||
const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([])
|
const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([])
|
||||||
|
@ -4,7 +4,6 @@ import Link from 'next/link'
|
|||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||||
import { Bars3Icon } from '@heroicons/react/20/solid'
|
import { Bars3Icon } from '@heroicons/react/20/solid'
|
||||||
import { useContextSelector } from 'use-context-selector'
|
|
||||||
import HeaderBillingBtn from '../billing/header-billing-btn'
|
import HeaderBillingBtn from '../billing/header-billing-btn'
|
||||||
import AccountDropdown from './account-dropdown'
|
import AccountDropdown from './account-dropdown'
|
||||||
import AppNav from './app-nav'
|
import AppNav from './app-nav'
|
||||||
@ -15,12 +14,13 @@ import ToolsNav from './tools-nav'
|
|||||||
import GithubStar from './github-star'
|
import GithubStar from './github-star'
|
||||||
import LicenseNav from './license-env'
|
import LicenseNav from './license-env'
|
||||||
import { WorkspaceProvider } from '@/context/workspace-context'
|
import { WorkspaceProvider } from '@/context/workspace-context'
|
||||||
import AppContext, { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { LicenseStatus } from '@/types/feature'
|
import { LicenseStatus } from '@/types/feature'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const navClassName = `
|
const navClassName = `
|
||||||
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
|
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
|
||||||
@ -30,7 +30,7 @@ const navClassName = `
|
|||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||||
const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const selectedSegment = useSelectedLayoutSegment()
|
const selectedSegment = useSelectedLayoutSegment()
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints()
|
||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile
|
||||||
|
@ -5,14 +5,15 @@ import { LicenseStatus } from '@/types/feature'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContextSelector } from 'use-context-selector'
|
import { useContextSelector } from 'use-context-selector'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const LicenseNav = () => {
|
const LicenseNav = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const systemFeatures = useContextSelector(AppContext, s => s.systemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
|
if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
|
||||||
const expiredAt = systemFeatures.license?.expired_at
|
const expiredAt = systemFeatures.license?.expired_at
|
||||||
const count = dayjs(expiredAt).diff(dayjs(), 'days')
|
const count = dayjs(expiredAt).diff(dayjs(), 'day')
|
||||||
return <div className='px-2 py-1 mr-4 rounded-full bg-util-colors-orange-orange-50 border-util-colors-orange-orange-100 system-xs-medium text-util-colors-orange-orange-600'>
|
return <div className='px-2 py-1 mr-4 rounded-full bg-util-colors-orange-orange-50 border-util-colors-orange-orange-100 system-xs-medium text-util-colors-orange-orange-600'>
|
||||||
{count <= 1 && <span>{t('common.license.expiring', { count })}</span>}
|
{count <= 1 && <span>{t('common.license.expiring', { count })}</span>}
|
||||||
{count > 1 && <span>{t('common.license.expiring_plural', { count })}</span>}
|
{count > 1 && <span>{t('common.license.expiring_plural', { count })}</span>}
|
||||||
|
@ -17,9 +17,11 @@ import ProviderCard from '@/app/components/tools/provider/card'
|
|||||||
import ProviderDetail from '@/app/components/tools/provider/detail'
|
import ProviderDetail from '@/app/components/tools/provider/detail'
|
||||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||||
import { fetchCollectionList } from '@/service/tools'
|
import { fetchCollectionList } from '@/service/tools'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const ProviderList = () => {
|
const ProviderList = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useTabSearchParams({
|
const [activeTab, setActiveTab] = useTabSearchParams({
|
||||||
defaultTab: 'builtin',
|
defaultTab: 'builtin',
|
||||||
@ -98,7 +100,7 @@ const ProviderList = () => {
|
|||||||
'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0',
|
'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0',
|
||||||
currentProvider && 'pr-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
currentProvider && 'pr-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
)}>
|
)}>
|
||||||
{activeTab === 'builtin' && <ContributeCard />}
|
{activeTab === 'builtin' && !systemFeatures.branding.enabled && <ContributeCard />}
|
||||||
{activeTab === 'api' && <CustomCreateCard onRefreshData={getProviderList} />}
|
{activeTab === 'api' && <CustomCreateCard onRefreshData={getProviderList} />}
|
||||||
{filteredCollectionList.map(collection => (
|
{filteredCollectionList.map(collection => (
|
||||||
<ProviderCard
|
<ProviderCard
|
||||||
|
@ -21,8 +21,8 @@ const Contribute: FC = () => {
|
|||||||
>
|
>
|
||||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||||
<div className='relative shrink-0 flex items-center'>
|
<div className='relative shrink-0 flex items-center'>
|
||||||
<div className='z-10 flex p-3 rounded-[10px] bg-white border-[0.5px] border-primary-100 shadow-md'><RiHammerFill className='w-4 h-4 text-primary-600'/></div>
|
<div className='z-10 flex p-3 rounded-[10px] bg-white border-[0.5px] border-primary-100 shadow-md'><RiHammerFill className='w-4 h-4 text-primary-600' /></div>
|
||||||
<div className='-translate-x-2 flex p-3 rounded-[10px] bg-[#FEF6FB] border-[0.5px] border-[#FCE7F6] shadow-md'><Heart02 className='w-4 h-4 text-[#EE46BC]'/></div>
|
<div className='-translate-x-2 flex p-3 rounded-[10px] bg-[#FEF6FB] border-[0.5px] border-[#FCE7F6] shadow-md'><Heart02 className='w-4 h-4 text-[#EE46BC]' /></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mb-3 px-[14px] text-[15px] leading-5 font-semibold'>
|
<div className='mb-3 px-[14px] text-[15px] leading-5 font-semibold'>
|
||||||
|
@ -6,10 +6,14 @@ import Header from '../signin/_header'
|
|||||||
import style from '../signin/page.module.css'
|
import style from '../signin/page.module.css'
|
||||||
import ForgotPasswordForm from './ForgotPasswordForm'
|
import ForgotPasswordForm from './ForgotPasswordForm'
|
||||||
import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm'
|
import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const ForgotPassword = () => {
|
const ForgotPassword = () => {
|
||||||
|
useDocumentTitle('')
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const token = searchParams.get('token')
|
const token = searchParams.get('token')
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
@ -27,9 +31,9 @@ const ForgotPassword = () => {
|
|||||||
}>
|
}>
|
||||||
<Header />
|
<Header />
|
||||||
{token ? <ChangePasswordForm /> : <ForgotPasswordForm />}
|
{token ? <ChangePasswordForm /> : <ForgotPasswordForm />}
|
||||||
<div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
{!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
||||||
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,10 @@ import Loading from '../components/base/loading'
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { fetchInitValidateStatus, initValidate } from '@/service/common'
|
import { fetchInitValidateStatus, initValidate } from '@/service/common'
|
||||||
import type { InitValidateStatusResponse } from '@/models/common'
|
import type { InitValidateStatusResponse } from '@/models/common'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
const InitPasswordPopup = () => {
|
const InitPasswordPopup = () => {
|
||||||
|
useDocumentTitle('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [validated, setValidated] = useState(false)
|
const [validated, setValidated] = useState(false)
|
||||||
|
@ -15,6 +15,7 @@ import Button from '@/app/components/base/button'
|
|||||||
|
|
||||||
import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
|
import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
|
||||||
import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
|
import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ const accountFormSchema = z.object({
|
|||||||
type AccountFormValues = z.infer<typeof accountFormSchema>
|
type AccountFormValues = z.infer<typeof accountFormSchema>
|
||||||
|
|
||||||
const InstallForm = () => {
|
const InstallForm = () => {
|
||||||
|
useDocumentTitle('')
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [showPassword, setShowPassword] = React.useState(false)
|
const [showPassword, setShowPassword] = React.useState(false)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
'use client'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Header from '../signin/_header'
|
import Header from '../signin/_header'
|
||||||
import style from '../signin/page.module.css'
|
import style from '../signin/page.module.css'
|
||||||
import InstallForm from './installForm'
|
import InstallForm from './installForm'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const Install = () => {
|
const Install = () => {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
return (
|
return (
|
||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
style.background,
|
style.background,
|
||||||
@ -21,9 +24,9 @@ const Install = () => {
|
|||||||
}>
|
}>
|
||||||
<Header />
|
<Header />
|
||||||
<InstallForm />
|
<InstallForm />
|
||||||
<div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
{!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-gray-500'>
|
||||||
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Viewport } from 'next'
|
import type { Metadata, Viewport } from 'next'
|
||||||
import I18nServer from './components/i18n-server'
|
import I18nServer from './components/i18n-server'
|
||||||
import BrowserInitor from './components/browser-initor'
|
import BrowserInitor from './components/browser-initor'
|
||||||
import SentryInitor from './components/sentry-initor'
|
import SentryInitor from './components/sentry-initor'
|
||||||
@ -6,10 +6,7 @@ import { getLocaleOnServer } from '@/i18n/server'
|
|||||||
import { TanstackQueryIniter } from '@/context/query-client'
|
import { TanstackQueryIniter } from '@/context/query-client'
|
||||||
import './styles/globals.css'
|
import './styles/globals.css'
|
||||||
import './styles/markdown.scss'
|
import './styles/markdown.scss'
|
||||||
|
import GlobalPublicStoreProvider from '@/context/global-public-context'
|
||||||
export const metadata = {
|
|
||||||
title: 'Dify',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
width: 'device-width',
|
width: 'device-width',
|
||||||
@ -18,6 +15,10 @@ export const viewport: Viewport = {
|
|||||||
viewportFit: 'cover',
|
viewportFit: 'cover',
|
||||||
userScalable: false,
|
userScalable: false,
|
||||||
}
|
}
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: ' ',
|
||||||
|
icons: 'data:',
|
||||||
|
}
|
||||||
|
|
||||||
const LocaleLayout = ({
|
const LocaleLayout = ({
|
||||||
children,
|
children,
|
||||||
@ -50,7 +51,11 @@ const LocaleLayout = ({
|
|||||||
<BrowserInitor>
|
<BrowserInitor>
|
||||||
<SentryInitor>
|
<SentryInitor>
|
||||||
<TanstackQueryIniter>
|
<TanstackQueryIniter>
|
||||||
<I18nServer>{children}</I18nServer>
|
<I18nServer>
|
||||||
|
<GlobalPublicStoreProvider>
|
||||||
|
{children}
|
||||||
|
</GlobalPublicStoreProvider>
|
||||||
|
</I18nServer>
|
||||||
</TanstackQueryIniter>
|
</TanstackQueryIniter>
|
||||||
</SentryInitor>
|
</SentryInitor>
|
||||||
</BrowserInitor>
|
</BrowserInitor>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
'use client'
|
||||||
import Header from '../signin/_header'
|
import Header from '../signin/_header'
|
||||||
import style from '../signin/page.module.css'
|
import style from '../signin/page.module.css'
|
||||||
|
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
export default async function SignInLayout({ children }: any) {
|
export default function SignInLayout({ children }: any) {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
return <>
|
return <>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
style.background,
|
style.background,
|
||||||
@ -30,9 +33,9 @@ export default async function SignInLayout({ children }: any) {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='px-8 py-6 system-xs-regular text-text-tertiary'>
|
{!systemFeatures.branding.enabled && <div className='px-8 py-6 system-xs-regular text-text-tertiary'>
|
||||||
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -12,9 +12,11 @@ import Input from '@/app/components/base/input'
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { sendResetPasswordCode } from '@/service/common'
|
import { sendResetPasswordCode } from '@/service/common'
|
||||||
import I18NContext from '@/context/i18n'
|
import I18NContext from '@/context/i18n'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
export default function CheckCode() {
|
export default function CheckCode() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
useDocumentTitle('')
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
|
34
web/app/signin/LoginLogo.tsx
Normal file
34
web/app/signin/LoginLogo.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import classNames from '@/utils/classnames'
|
||||||
|
import { useSelector } from '@/context/app-context'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
|
type LoginLogoProps = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginLogo: FC<LoginLogoProps> = ({
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
const { theme } = useSelector((s) => {
|
||||||
|
return {
|
||||||
|
theme: s.theme,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png`
|
||||||
|
if (systemFeatures.branding.enabled)
|
||||||
|
src = systemFeatures.branding.login_page_logo
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
className={classNames('block w-auto h-10', className)}
|
||||||
|
alt='logo'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginLogo
|
@ -1,17 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
|
import LoginLogo from './LoginLogo'
|
||||||
import Select from '@/app/components/base/select/locale'
|
import Select from '@/app/components/base/select/locale'
|
||||||
import { languages } from '@/i18n/language'
|
import { languages } from '@/i18n/language'
|
||||||
import { type Locale } from '@/i18n'
|
import { type Locale } from '@/i18n'
|
||||||
import I18n from '@/context/i18n'
|
import I18n from '@/context/i18n'
|
||||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { locale, setLocaleOnClient } = useContext(I18n)
|
const { locale, setLocaleOnClient } = useContext(I18n)
|
||||||
|
|
||||||
return <div className='flex items-center justify-between p-6 w-full'>
|
return <div className='flex items-center justify-between p-6 w-full'>
|
||||||
<LogoSite />
|
<LoginLogo />
|
||||||
<Select
|
<Select
|
||||||
value={locale}
|
value={locale}
|
||||||
items={languages.filter(item => item.supported)}
|
items={languages.filter(item => item.supported)}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
'use client'
|
||||||
import Header from './_header'
|
import Header from './_header'
|
||||||
import style from './page.module.css'
|
import style from './page.module.css'
|
||||||
|
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
|
|
||||||
export default async function SignInLayout({ children }: any) {
|
export default function SignInLayout({ children }: any) {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
useDocumentTitle('')
|
||||||
return <>
|
return <>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
style.background,
|
style.background,
|
||||||
@ -30,9 +35,9 @@ export default async function SignInLayout({ children }: any) {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='px-8 py-6 system-xs-regular text-text-tertiary'>
|
{systemFeatures.branding.enabled === false && <div className='px-8 py-6 system-xs-regular text-text-tertiary'>
|
||||||
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved.
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -9,10 +9,11 @@ import MailAndPasswordAuth from './components/mail-and-password-auth'
|
|||||||
import SocialAuth from './components/social-auth'
|
import SocialAuth from './components/social-auth'
|
||||||
import SSOAuth from './components/sso-auth'
|
import SSOAuth from './components/sso-auth'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { getSystemFeatures, invitationCheck } from '@/service/common'
|
import { invitationCheck } from '@/service/common'
|
||||||
import { LicenseStatus, defaultSystemFeatures } from '@/types/feature'
|
import { LicenseStatus } from '@/types/feature'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
const NormalForm = () => {
|
const NormalForm = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -23,7 +24,7 @@ const NormalForm = () => {
|
|||||||
const message = decodeURIComponent(searchParams.get('message') || '')
|
const message = decodeURIComponent(searchParams.get('message') || '')
|
||||||
const invite_token = decodeURIComponent(searchParams.get('invite_token') || '')
|
const invite_token = decodeURIComponent(searchParams.get('invite_token') || '')
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [systemFeatures, setSystemFeatures] = useState(defaultSystemFeatures)
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const [authType, updateAuthType] = useState<'code' | 'password'>('password')
|
const [authType, updateAuthType] = useState<'code' | 'password'>('password')
|
||||||
const [showORLine, setShowORLine] = useState(false)
|
const [showORLine, setShowORLine] = useState(false)
|
||||||
const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false)
|
const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false)
|
||||||
@ -46,12 +47,9 @@ const NormalForm = () => {
|
|||||||
message,
|
message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const features = await getSystemFeatures()
|
setAllMethodsAreDisabled(!systemFeatures.enable_social_oauth_login && !systemFeatures.enable_email_code_login && !systemFeatures.enable_email_password_login && !systemFeatures.sso_enforced_for_signin)
|
||||||
const allFeatures = { ...defaultSystemFeatures, ...features }
|
setShowORLine((systemFeatures.enable_social_oauth_login || systemFeatures.sso_enforced_for_signin) && (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login))
|
||||||
setSystemFeatures(allFeatures)
|
updateAuthType(systemFeatures.enable_email_password_login ? 'password' : 'code')
|
||||||
setAllMethodsAreDisabled(!allFeatures.enable_social_oauth_login && !allFeatures.enable_email_code_login && !allFeatures.enable_email_password_login && !allFeatures.sso_enforced_for_signin)
|
|
||||||
setShowORLine((allFeatures.enable_social_oauth_login || allFeatures.sso_enforced_for_signin) && (allFeatures.enable_email_code_login || allFeatures.enable_email_password_login))
|
|
||||||
updateAuthType(allFeatures.enable_email_password_login ? 'password' : 'code')
|
|
||||||
if (isInviteLink) {
|
if (isInviteLink) {
|
||||||
const checkRes = await invitationCheck({
|
const checkRes = await invitationCheck({
|
||||||
url: '/activate/check',
|
url: '/activate/check',
|
||||||
@ -65,10 +63,9 @@ const NormalForm = () => {
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
setAllMethodsAreDisabled(true)
|
setAllMethodsAreDisabled(true)
|
||||||
setSystemFeatures(defaultSystemFeatures)
|
|
||||||
}
|
}
|
||||||
finally { setIsLoading(false) }
|
finally { setIsLoading(false) }
|
||||||
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink])
|
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, systemFeatures])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init()
|
init()
|
||||||
}, [init])
|
}, [init])
|
||||||
@ -83,7 +80,7 @@ const NormalForm = () => {
|
|||||||
<Loading type='area' />
|
<Loading type='area' />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
if (systemFeatures.license?.status === LicenseStatus.LOST) {
|
if (systemFeatures.license.status === LicenseStatus.LOST) {
|
||||||
return <div className='w-full mx-auto mt-8'>
|
return <div className='w-full mx-auto mt-8'>
|
||||||
<div className='bg-white'>
|
<div className='bg-white'>
|
||||||
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
||||||
@ -97,7 +94,7 @@ const NormalForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
if (systemFeatures.license?.status === LicenseStatus.EXPIRED) {
|
if (systemFeatures.license.status === LicenseStatus.EXPIRED) {
|
||||||
return <div className='w-full mx-auto mt-8'>
|
return <div className='w-full mx-auto mt-8'>
|
||||||
<div className='bg-white'>
|
<div className='bg-white'>
|
||||||
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
||||||
@ -111,7 +108,7 @@ const NormalForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
if (systemFeatures.license?.status === LicenseStatus.INACTIVE) {
|
if (systemFeatures.license.status === LicenseStatus.INACTIVE) {
|
||||||
return <div className='w-full mx-auto mt-8'>
|
return <div className='w-full mx-auto mt-8'>
|
||||||
<div className='bg-white'>
|
<div className='bg-white'>
|
||||||
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
||||||
@ -132,11 +129,11 @@ const NormalForm = () => {
|
|||||||
{isInviteLink
|
{isInviteLink
|
||||||
? <div className="w-full mx-auto">
|
? <div className="w-full mx-auto">
|
||||||
<h2 className="title-4xl-semi-bold text-text-primary">{t('login.join')}{workspaceName}</h2>
|
<h2 className="title-4xl-semi-bold text-text-primary">{t('login.join')}{workspaceName}</h2>
|
||||||
<p className='mt-2 body-md-regular text-text-tertiary'>{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}</p>
|
{!systemFeatures.branding.enabled && <p className='mt-2 body-md-regular text-text-tertiary'>{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}</p>}
|
||||||
</div>
|
</div>
|
||||||
: <div className="w-full mx-auto">
|
: <div className="w-full mx-auto">
|
||||||
<h2 className="title-4xl-semi-bold text-text-primary">{t('login.pageTitle')}</h2>
|
<h2 className="title-4xl-semi-bold text-text-primary">{t('login.pageTitle')}</h2>
|
||||||
<p className='mt-2 body-md-regular text-text-tertiary'>{t('login.welcome')}</p>
|
{!systemFeatures.branding.enabled && <p className='mt-2 body-md-regular text-text-tertiary'>{t('login.welcome')}</p>}
|
||||||
</div>}
|
</div>}
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
<div className="flex flex-col gap-3 mt-6">
|
<div className="flex flex-col gap-3 mt-6">
|
||||||
@ -184,29 +181,31 @@ const NormalForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
<div className="w-full block mt-2 system-xs-regular text-text-tertiary">
|
{!systemFeatures.branding.enabled && <>
|
||||||
{t('login.tosDesc')}
|
<div className="w-full block mt-2 system-xs-regular text-text-tertiary">
|
||||||
|
{t('login.tosDesc')}
|
||||||
<Link
|
|
||||||
className='system-xs-medium text-text-secondary hover:underline'
|
<Link
|
||||||
target='_blank' rel='noopener noreferrer'
|
className='system-xs-medium text-text-secondary hover:underline'
|
||||||
href='https://dify.ai/terms'
|
target='_blank' rel='noopener noreferrer'
|
||||||
>{t('login.tos')}</Link>
|
href='https://dify.ai/terms'
|
||||||
&
|
>{t('login.tos')}</Link>
|
||||||
<Link
|
&
|
||||||
className='system-xs-medium text-text-secondary hover:underline'
|
<Link
|
||||||
target='_blank' rel='noopener noreferrer'
|
className='system-xs-medium text-text-secondary hover:underline'
|
||||||
href='https://dify.ai/privacy'
|
target='_blank' rel='noopener noreferrer'
|
||||||
>{t('login.pp')}</Link>
|
href='https://dify.ai/privacy'
|
||||||
</div>
|
>{t('login.pp')}</Link>
|
||||||
{IS_CE_EDITION && <div className="w-hull block mt-2 system-xs-regular text-text-tertiary">
|
</div>
|
||||||
{t('login.goToInit')}
|
{IS_CE_EDITION && <div className="w-hull block mt-2 system-xs-regular text-text-tertiary">
|
||||||
|
{t('login.goToInit')}
|
||||||
<Link
|
|
||||||
className='system-xs-medium text-text-secondary hover:underline'
|
<Link
|
||||||
href='/install'
|
className='system-xs-medium text-text-secondary hover:underline'
|
||||||
>{t('login.setAdminAccount')}</Link>
|
href='/install'
|
||||||
</div>}
|
>{t('login.setAdminAccount')}</Link>
|
||||||
|
</div>}
|
||||||
|
</>}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,19 +6,16 @@ import { createContext, useContext, useContextSelector } from 'use-context-selec
|
|||||||
import type { FC, ReactNode } from 'react'
|
import type { FC, ReactNode } from 'react'
|
||||||
import { fetchAppList } from '@/service/apps'
|
import { fetchAppList } from '@/service/apps'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile, getSystemFeatures } from '@/service/common'
|
import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from '@/service/common'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
import { Theme } from '@/types/app'
|
import { Theme } from '@/types/app'
|
||||||
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
|
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
|
||||||
import MaintenanceNotice from '@/app/components/header/maintenance-notice'
|
import MaintenanceNotice from '@/app/components/header/maintenance-notice'
|
||||||
import type { SystemFeatures } from '@/types/feature'
|
|
||||||
import { defaultSystemFeatures } from '@/types/feature'
|
|
||||||
|
|
||||||
export type AppContextValue = {
|
export type AppContextValue = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
setTheme: (theme: Theme) => void
|
setTheme: (theme: Theme) => void
|
||||||
apps: App[]
|
apps: App[]
|
||||||
systemFeatures: SystemFeatures
|
|
||||||
mutateApps: VoidFunction
|
mutateApps: VoidFunction
|
||||||
userProfile: UserProfileResponse
|
userProfile: UserProfileResponse
|
||||||
mutateUserProfile: VoidFunction
|
mutateUserProfile: VoidFunction
|
||||||
@ -57,7 +54,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = {
|
|||||||
|
|
||||||
const AppContext = createContext<AppContextValue>({
|
const AppContext = createContext<AppContextValue>({
|
||||||
theme: Theme.light,
|
theme: Theme.light,
|
||||||
systemFeatures: defaultSystemFeatures,
|
|
||||||
setTheme: () => { },
|
setTheme: () => { },
|
||||||
apps: [],
|
apps: [],
|
||||||
mutateApps: () => { },
|
mutateApps: () => { },
|
||||||
@ -96,10 +92,6 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
|
|||||||
const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile)
|
const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile)
|
||||||
const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace)
|
const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace)
|
||||||
|
|
||||||
const { data: systemFeatures } = useSWR({ url: '/console/system-features' }, getSystemFeatures, {
|
|
||||||
fallbackData: defaultSystemFeatures,
|
|
||||||
})
|
|
||||||
|
|
||||||
const [userProfile, setUserProfile] = useState<UserProfileResponse>()
|
const [userProfile, setUserProfile] = useState<UserProfileResponse>()
|
||||||
const [langeniusVersionInfo, setLangeniusVersionInfo] = useState<LangGeniusVersionResponse>(initialLangeniusVersionInfo)
|
const [langeniusVersionInfo, setLangeniusVersionInfo] = useState<LangGeniusVersionResponse>(initialLangeniusVersionInfo)
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo)
|
const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo)
|
||||||
@ -146,7 +138,6 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
|
|||||||
theme,
|
theme,
|
||||||
setTheme: handleSetTheme,
|
setTheme: handleSetTheme,
|
||||||
apps: appList.data,
|
apps: appList.data,
|
||||||
systemFeatures: { ...defaultSystemFeatures, ...systemFeatures },
|
|
||||||
mutateApps,
|
mutateApps,
|
||||||
userProfile,
|
userProfile,
|
||||||
mutateUserProfile,
|
mutateUserProfile,
|
||||||
|
37
web/context/global-public-context.tsx
Normal file
37
web/context/global-public-context.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use client'
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import type { SystemFeatures } from '@/types/feature'
|
||||||
|
import { defaultSystemFeatures } from '@/types/feature'
|
||||||
|
import { getSystemFeatures } from '@/service/common'
|
||||||
|
import Loading from '@/app/components/base/loading'
|
||||||
|
|
||||||
|
type GlobalPublicStore = {
|
||||||
|
systemFeatures: SystemFeatures
|
||||||
|
setSystemFeatures: (systemFeatures: SystemFeatures) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGlobalPublicStore = create<GlobalPublicStore>(set => ({
|
||||||
|
systemFeatures: defaultSystemFeatures,
|
||||||
|
setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { isPending, data } = useQuery({
|
||||||
|
queryKey: ['systemFeatures'],
|
||||||
|
queryFn: getSystemFeatures,
|
||||||
|
})
|
||||||
|
const { setSystemFeatures } = useGlobalPublicStore()
|
||||||
|
useEffect(() => {
|
||||||
|
if (data)
|
||||||
|
setSystemFeatures({ ...defaultSystemFeatures, ...data })
|
||||||
|
}, [data, setSystemFeatures])
|
||||||
|
if (isPending)
|
||||||
|
return <div className='w-screen h-screen flex items-center justify-center'><Loading /></div>
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
|
export default GlobalPublicStoreProvider
|
@ -35,6 +35,7 @@ type ProviderContextState = {
|
|||||||
enableReplaceWebAppLogo: boolean
|
enableReplaceWebAppLogo: boolean
|
||||||
modelLoadBalancingEnabled: boolean
|
modelLoadBalancingEnabled: boolean
|
||||||
datasetOperatorEnabled: boolean
|
datasetOperatorEnabled: boolean
|
||||||
|
webappCopyrightEnabled: boolean
|
||||||
}
|
}
|
||||||
const ProviderContext = createContext<ProviderContextState>({
|
const ProviderContext = createContext<ProviderContextState>({
|
||||||
modelProviders: [],
|
modelProviders: [],
|
||||||
@ -64,6 +65,7 @@ const ProviderContext = createContext<ProviderContextState>({
|
|||||||
enableReplaceWebAppLogo: false,
|
enableReplaceWebAppLogo: false,
|
||||||
modelLoadBalancingEnabled: false,
|
modelLoadBalancingEnabled: false,
|
||||||
datasetOperatorEnabled: false,
|
datasetOperatorEnabled: false,
|
||||||
|
webappCopyrightEnabled: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useProviderContext = () => useContext(ProviderContext)
|
export const useProviderContext = () => useContext(ProviderContext)
|
||||||
@ -91,6 +93,7 @@ export const ProviderContextProvider = ({
|
|||||||
const [enableReplaceWebAppLogo, setEnableReplaceWebAppLogo] = useState(false)
|
const [enableReplaceWebAppLogo, setEnableReplaceWebAppLogo] = useState(false)
|
||||||
const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false)
|
const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false)
|
||||||
const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false)
|
const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false)
|
||||||
|
const [webappCopyrightEnabled, setWebappCopyrightEnabled] = useState(false)
|
||||||
|
|
||||||
const fetchPlan = async () => {
|
const fetchPlan = async () => {
|
||||||
const data = await fetchCurrentPlanInfo()
|
const data = await fetchCurrentPlanInfo()
|
||||||
@ -105,6 +108,8 @@ export const ProviderContextProvider = ({
|
|||||||
setModelLoadBalancingEnabled(true)
|
setModelLoadBalancingEnabled(true)
|
||||||
if (data.dataset_operator_enabled)
|
if (data.dataset_operator_enabled)
|
||||||
setDatasetOperatorEnabled(true)
|
setDatasetOperatorEnabled(true)
|
||||||
|
if (data.webapp_copyright_enabled)
|
||||||
|
setWebappCopyrightEnabled(true)
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlan()
|
fetchPlan()
|
||||||
@ -123,6 +128,7 @@ export const ProviderContextProvider = ({
|
|||||||
enableReplaceWebAppLogo,
|
enableReplaceWebAppLogo,
|
||||||
modelLoadBalancingEnabled,
|
modelLoadBalancingEnabled,
|
||||||
datasetOperatorEnabled,
|
datasetOperatorEnabled,
|
||||||
|
webappCopyrightEnabled,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</ProviderContext.Provider>
|
</ProviderContext.Provider>
|
||||||
|
18
web/hooks/use-document-title.ts
Normal file
18
web/hooks/use-document-title.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
import { useLayoutEffect } from 'react'
|
||||||
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
|
|
||||||
|
export default function useDocumentTitle(title: string) {
|
||||||
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const prefix = title ? `${title} - ` : ''
|
||||||
|
if (systemFeatures.branding.enabled) {
|
||||||
|
document.title = `${prefix}${systemFeatures.branding.application_title}`
|
||||||
|
const faviconEle = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement
|
||||||
|
faviconEle.href = systemFeatures.branding.favicon
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.title = `${prefix}Dify`
|
||||||
|
}
|
||||||
|
}, [systemFeatures, title])
|
||||||
|
}
|
@ -131,6 +131,8 @@ const translation = {
|
|||||||
status: 'beta',
|
status: 'beta',
|
||||||
explore: 'Explore',
|
explore: 'Explore',
|
||||||
apps: 'Studio',
|
apps: 'Studio',
|
||||||
|
appDetail: 'App Detail',
|
||||||
|
account: 'Account',
|
||||||
plugins: 'Plugins',
|
plugins: 'Plugins',
|
||||||
pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.',
|
pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.',
|
||||||
datasets: 'Knowledge',
|
datasets: 'Knowledge',
|
||||||
@ -167,7 +169,7 @@ const translation = {
|
|||||||
account: {
|
account: {
|
||||||
account: 'Account',
|
account: 'Account',
|
||||||
myAccount: 'My Account',
|
myAccount: 'My Account',
|
||||||
studio: 'Dify Studio',
|
studio: 'Studio',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
@ -179,8 +181,8 @@ const translation = {
|
|||||||
newPassword: 'New password',
|
newPassword: 'New password',
|
||||||
confirmPassword: 'Confirm password',
|
confirmPassword: 'Confirm password',
|
||||||
notEqual: 'Two passwords are different.',
|
notEqual: 'Two passwords are different.',
|
||||||
langGeniusAccount: 'Dify account',
|
langGeniusAccount: 'Account\'s data',
|
||||||
langGeniusAccountTip: 'Your Dify account and associated user data.',
|
langGeniusAccountTip: 'The user data of your account.',
|
||||||
editName: 'Edit Name',
|
editName: 'Edit Name',
|
||||||
showAppLength: 'Show {{length}} apps',
|
showAppLength: 'Show {{length}} apps',
|
||||||
delete: 'Delete Account',
|
delete: 'Delete Account',
|
||||||
|
@ -16,7 +16,7 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: 'Explore Apps by Dify',
|
title: 'Explore Apps',
|
||||||
description: 'Use these template apps instantly or customize your own apps based on the templates.',
|
description: 'Use these template apps instantly or customize your own apps based on the templates.',
|
||||||
allCategories: 'Recommended',
|
allCategories: 'Recommended',
|
||||||
},
|
},
|
||||||
|
@ -131,6 +131,8 @@ const translation = {
|
|||||||
status: 'ベータ版',
|
status: 'ベータ版',
|
||||||
explore: '探索',
|
explore: '探索',
|
||||||
apps: 'スタジオ',
|
apps: 'スタジオ',
|
||||||
|
appDetail: 'アプリの詳細',
|
||||||
|
account: 'アカウント',
|
||||||
plugins: 'プラグイン',
|
plugins: 'プラグイン',
|
||||||
pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。',
|
pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。',
|
||||||
datasets: 'ナレッジ',
|
datasets: 'ナレッジ',
|
||||||
@ -176,8 +178,8 @@ const translation = {
|
|||||||
newPassword: '新しいパスワード',
|
newPassword: '新しいパスワード',
|
||||||
confirmPassword: 'パスワードを確認',
|
confirmPassword: 'パスワードを確認',
|
||||||
notEqual: '2つのパスワードが異なります。',
|
notEqual: '2つのパスワードが異なります。',
|
||||||
langGeniusAccount: 'Difyアカウント',
|
langGeniusAccount: 'アカウント関連データ',
|
||||||
langGeniusAccountTip: 'Difyアカウントと関連するユーザーデータ。',
|
langGeniusAccountTip: 'アカウントに関連するユーザーデータ。',
|
||||||
editName: '名前を編集',
|
editName: '名前を編集',
|
||||||
showAppLength: '{{length}}アプリを表示',
|
showAppLength: '{{length}}アプリを表示',
|
||||||
delete: 'アカウントを削除',
|
delete: 'アカウントを削除',
|
||||||
@ -185,7 +187,7 @@ const translation = {
|
|||||||
deleteConfirmTip: '確認のため、登録したメールから次の内容をに送信してください ',
|
deleteConfirmTip: '確認のため、登録したメールから次の内容をに送信してください ',
|
||||||
account: 'アカウント',
|
account: 'アカウント',
|
||||||
myAccount: 'マイアカウント',
|
myAccount: 'マイアカウント',
|
||||||
studio: 'Difyスタジオ',
|
studio: 'スタジオ',
|
||||||
deletePrivacyLinkTip: 'お客様のデータの取り扱い方法の詳細については、当社の',
|
deletePrivacyLinkTip: 'お客様のデータの取り扱い方法の詳細については、当社の',
|
||||||
deletePrivacyLink: 'プライバシーポリシー。',
|
deletePrivacyLink: 'プライバシーポリシー。',
|
||||||
deleteSuccessTip: 'アカウントの削除が完了するまでに時間が必要です。すべて完了しましたら、メールでお知らせします。',
|
deleteSuccessTip: 'アカウントの削除が完了するまでに時間が必要です。すべて完了しましたら、メールでお知らせします。',
|
||||||
|
@ -16,7 +16,7 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: 'Difyによるアプリの探索',
|
title: 'アプリを探索',
|
||||||
description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。',
|
description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。',
|
||||||
allCategories: '推奨',
|
allCategories: '推奨',
|
||||||
},
|
},
|
||||||
|
@ -131,6 +131,8 @@ const translation = {
|
|||||||
status: 'beta',
|
status: 'beta',
|
||||||
explore: '探索',
|
explore: '探索',
|
||||||
apps: '工作室',
|
apps: '工作室',
|
||||||
|
appDetail: '应用详情',
|
||||||
|
account: '账户',
|
||||||
plugins: '插件',
|
plugins: '插件',
|
||||||
pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。',
|
pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。',
|
||||||
datasets: '知识库',
|
datasets: '知识库',
|
||||||
@ -167,7 +169,7 @@ const translation = {
|
|||||||
account: {
|
account: {
|
||||||
account: '账户',
|
account: '账户',
|
||||||
myAccount: '我的账户',
|
myAccount: '我的账户',
|
||||||
studio: 'Dify 工作室',
|
studio: '工作室',
|
||||||
avatar: '头像',
|
avatar: '头像',
|
||||||
name: '用户名',
|
name: '用户名',
|
||||||
email: '邮箱',
|
email: '邮箱',
|
||||||
@ -179,8 +181,8 @@ const translation = {
|
|||||||
newPassword: '新密码',
|
newPassword: '新密码',
|
||||||
notEqual: '两个密码不相同',
|
notEqual: '两个密码不相同',
|
||||||
confirmPassword: '确认密码',
|
confirmPassword: '确认密码',
|
||||||
langGeniusAccount: 'Dify 账号',
|
langGeniusAccount: '账号关联数据',
|
||||||
langGeniusAccountTip: '您的 Dify 账号和相关的用户数据。',
|
langGeniusAccountTip: '您的账号相关的用户数据。',
|
||||||
editName: '编辑名字',
|
editName: '编辑名字',
|
||||||
showAppLength: '显示 {{length}} 个应用',
|
showAppLength: '显示 {{length}} 个应用',
|
||||||
delete: '删除账户',
|
delete: '删除账户',
|
||||||
|
@ -16,7 +16,7 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: '探索 Dify 的应用',
|
title: '探索应用',
|
||||||
description: '使用这些模板应用程序,或根据模板自定义您自己的应用程序。',
|
description: '使用这些模板应用程序,或根据模板自定义您自己的应用程序。',
|
||||||
allCategories: '推荐',
|
allCategories: '推荐',
|
||||||
},
|
},
|
||||||
|
@ -127,6 +127,8 @@ const translation = {
|
|||||||
status: 'beta',
|
status: 'beta',
|
||||||
explore: '探索',
|
explore: '探索',
|
||||||
apps: '工作室',
|
apps: '工作室',
|
||||||
|
appDetail: '應用詳情',
|
||||||
|
account: '我的帳戶',
|
||||||
plugins: '外掛',
|
plugins: '外掛',
|
||||||
pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。',
|
pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。',
|
||||||
datasets: '知識庫',
|
datasets: '知識庫',
|
||||||
@ -172,8 +174,8 @@ const translation = {
|
|||||||
newPassword: '新密碼',
|
newPassword: '新密碼',
|
||||||
notEqual: '兩個密碼不相同',
|
notEqual: '兩個密碼不相同',
|
||||||
confirmPassword: '確認密碼',
|
confirmPassword: '確認密碼',
|
||||||
langGeniusAccount: 'Dify 賬號',
|
langGeniusAccount: '賬號数据',
|
||||||
langGeniusAccountTip: '您的 Dify 賬號和相關的使用者資料。',
|
langGeniusAccountTip: '您的賬號和相關的使用者資料。',
|
||||||
editName: '編輯名字',
|
editName: '編輯名字',
|
||||||
showAppLength: '顯示 {{length}} 個應用',
|
showAppLength: '顯示 {{length}} 個應用',
|
||||||
delete: '刪除帳戶',
|
delete: '刪除帳戶',
|
||||||
@ -181,7 +183,7 @@ const translation = {
|
|||||||
deleteConfirmTip: '請將以下內容從您的註冊電子郵件發送至 ',
|
deleteConfirmTip: '請將以下內容從您的註冊電子郵件發送至 ',
|
||||||
account: '帳戶',
|
account: '帳戶',
|
||||||
myAccount: '我的帳戶',
|
myAccount: '我的帳戶',
|
||||||
studio: 'Dify 工作室',
|
studio: '工作室',
|
||||||
deletePrivacyLinkTip: '有關我們如何處理您的數據的更多資訊,請參閱我們的',
|
deletePrivacyLinkTip: '有關我們如何處理您的數據的更多資訊,請參閱我們的',
|
||||||
deletePrivacyLink: '隱私策略。',
|
deletePrivacyLink: '隱私策略。',
|
||||||
deleteSuccessTip: '您的帳戶需要時間才能完成刪除。完成後,我們會給您發送電子郵件。',
|
deleteSuccessTip: '您的帳戶需要時間才能完成刪除。完成後,我們會給您發送電子郵件。',
|
||||||
|
@ -16,7 +16,7 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
title: '探索 Dify 的應用',
|
title: '探索應用',
|
||||||
description: '使用這些模板應用程式,或根據模板自定義您自己的應用程式。',
|
description: '使用這些模板應用程式,或根據模板自定義您自己的應用程式。',
|
||||||
allCategories: '推薦',
|
allCategories: '推薦',
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ import type { SystemFeatures } from '@/types/feature'
|
|||||||
|
|
||||||
type LoginSuccess = {
|
type LoginSuccess = {
|
||||||
result: 'success'
|
result: 'success'
|
||||||
data: { access_token: string;refresh_token: string }
|
data: { access_token: string; refresh_token: string }
|
||||||
}
|
}
|
||||||
type LoginFail = {
|
type LoginFail = {
|
||||||
result: 'fail'
|
result: 'fail'
|
||||||
@ -331,20 +331,20 @@ export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => {
|
|||||||
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
||||||
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
||||||
|
|
||||||
export const emailLoginWithCode = (data: { email: string;code: string;token: string }) =>
|
export const emailLoginWithCode = (data: { email: string; code: string; token: string }) =>
|
||||||
post<LoginResponse>('/email-code-login/validity', { body: data })
|
post<LoginResponse>('/email-code-login/validity', { body: data })
|
||||||
|
|
||||||
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
||||||
post<CommonResponse & { data: string;message?: string ;code?: string }>('/forgot-password', { body: { email, language } })
|
post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } })
|
||||||
|
|
||||||
export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) =>
|
export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) =>
|
||||||
post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
|
post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
|
||||||
|
|
||||||
export const sendDeleteAccountCode = () =>
|
export const sendDeleteAccountCode = () =>
|
||||||
get<CommonResponse & { data: string }>('/account/delete/verify')
|
get<CommonResponse & { data: string }>('/account/delete/verify')
|
||||||
|
|
||||||
export const verifyDeleteAccountCode = (body: { code: string;token: string }) =>
|
export const verifyDeleteAccountCode = (body: { code: string; token: string }) =>
|
||||||
post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
|
post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
|
||||||
|
|
||||||
export const submitDeleteAccountFeedback = (body: { feedback: string;email: string }) =>
|
export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }) =>
|
||||||
post<CommonResponse>('/account/delete/feedback', { body })
|
post<CommonResponse>('/account/delete/feedback', { body })
|
||||||
|
@ -31,6 +31,13 @@ export type SystemFeatures = {
|
|||||||
is_allow_register: boolean
|
is_allow_register: boolean
|
||||||
is_email_setup: boolean
|
is_email_setup: boolean
|
||||||
license: License
|
license: License
|
||||||
|
branding: {
|
||||||
|
enabled: boolean
|
||||||
|
login_page_logo: string
|
||||||
|
workspace_logo: string
|
||||||
|
favicon: string
|
||||||
|
application_title: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultSystemFeatures: SystemFeatures = {
|
export const defaultSystemFeatures: SystemFeatures = {
|
||||||
@ -49,4 +56,11 @@ export const defaultSystemFeatures: SystemFeatures = {
|
|||||||
status: LicenseStatus.NONE,
|
status: LicenseStatus.NONE,
|
||||||
expired_at: '',
|
expired_at: '',
|
||||||
},
|
},
|
||||||
|
branding: {
|
||||||
|
enabled: false,
|
||||||
|
login_page_logo: '',
|
||||||
|
workspace_logo: '',
|
||||||
|
favicon: '',
|
||||||
|
application_title: 'test title',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user