Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
5bb84193
Unverified
Commit
5bb84193
authored
Dec 18, 2023
by
zxhlyh
Committed by
GitHub
Dec 18, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: custom webapp logo (#1766)
parent
65fd4b39
Changes
40
Hide whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
888 additions
and
24 deletions
+888
-24
workspace.py
api/controllers/console/workspace/workspace.py
+62
-1
wraps.py
api/controllers/console/wraps.py
+2
-0
image_preview.py
api/controllers/files/image_preview.py
+23
-0
site.py
api/controllers/web/site.py
+11
-0
upload_file_parser.py
api/core/file/upload_file_parser.py
+1
-1
88072f0caa04_add_custom_config_in_tenant.py
...ions/versions/88072f0caa04_add_custom_config_in_tenant.py
+32
-0
account.py
api/models/account.py
+11
-0
account_service.py
api/services/account_service.py
+6
-0
file_service.py
api/services/file_service.py
+20
-2
workspace_service.py
api/services/workspace_service.py
+9
-1
colors.svg
...omponents/base/icons/assets/vender/line/editor/colors.svg
+5
-0
message-dots-circle.svg
...assets/vender/solid/communication/message-dots-circle.svg
+5
-0
colors.svg
...mponents/base/icons/assets/vender/solid/editor/colors.svg
+9
-0
Colors.json
.../components/base/icons/src/vender/line/editor/Colors.json
+39
-0
Colors.tsx
...p/components/base/icons/src/vender/line/editor/Colors.tsx
+16
-0
index.ts
...app/components/base/icons/src/vender/line/editor/index.ts
+1
-0
MessageDotsCircle.json
...ons/src/vender/solid/communication/MessageDotsCircle.json
+38
-0
MessageDotsCircle.tsx
...cons/src/vender/solid/communication/MessageDotsCircle.tsx
+16
-0
index.ts
...onents/base/icons/src/vender/solid/communication/index.ts
+1
-0
Colors.json
...components/base/icons/src/vender/solid/editor/Colors.json
+62
-0
Colors.tsx
.../components/base/icons/src/vender/solid/editor/Colors.tsx
+16
-0
index.ts
...pp/components/base/icons/src/vender/solid/editor/index.ts
+1
-0
utils.ts
web/app/components/base/image-uploader/utils.ts
+3
-3
index.tsx
web/app/components/custom/custom-app-header-brand/index.tsx
+70
-0
style.module.css
...omponents/custom/custom-app-header-brand/style.module.css
+3
-0
index.tsx
web/app/components/custom/custom-page/index.tsx
+52
-0
index.tsx
web/app/components/custom/custom-web-app-brand/index.tsx
+234
-0
style.module.css
...p/components/custom/custom-web-app-brand/style.module.css
+3
-0
style.module.css
web/app/components/custom/style.module.css
+6
-0
index.tsx
web/app/components/header/account-setting/index.tsx
+11
-0
index.tsx
web/app/components/share/chat/index.tsx
+4
-1
index.tsx
web/app/components/share/chat/welcome/index.tsx
+19
-6
index.tsx
web/app/components/share/chatbot/index.tsx
+4
-1
index.tsx
web/app/components/share/chatbot/welcome/index.tsx
+19
-6
i18next-config.ts
web/i18n/i18next-config.ts
+4
-0
custom.en.ts
web/i18n/lang/custom.en.ts
+30
-0
custom.zh.ts
web/i18n/lang/custom.zh.ts
+30
-0
common.ts
web/models/common.ts
+4
-0
base.ts
web/service/base.ts
+2
-2
common.ts
web/service/common.ts
+4
-0
No files found.
api/controllers/console/workspace/workspace.py
View file @
5bb84193
...
@@ -10,12 +10,15 @@ from controllers.console import api
...
@@ -10,12 +10,15 @@ from controllers.console import api
from
controllers.console.admin
import
admin_required
from
controllers.console.admin
import
admin_required
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.error
import
AccountNotLinkTenantError
from
controllers.console.error
import
AccountNotLinkTenantError
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
,
cloud_edition_billing_resource_check
from
controllers.console.datasets.error
import
NoFileUploadedError
,
TooManyFilesError
,
FileTooLargeError
,
UnsupportedFileTypeError
from
libs.helper
import
TimestampField
from
libs.helper
import
TimestampField
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
models.account
import
Tenant
from
models.account
import
Tenant
import
services
from
services.account_service
import
TenantService
from
services.account_service
import
TenantService
from
services.workspace_service
import
WorkspaceService
from
services.workspace_service
import
WorkspaceService
from
services.file_service
import
FileService
provider_fields
=
{
provider_fields
=
{
'provider_name'
:
fields
.
String
,
'provider_name'
:
fields
.
String
,
...
@@ -34,6 +37,7 @@ tenant_fields = {
...
@@ -34,6 +37,7 @@ tenant_fields = {
'providers'
:
fields
.
List
(
fields
.
Nested
(
provider_fields
)),
'providers'
:
fields
.
List
(
fields
.
Nested
(
provider_fields
)),
'in_trial'
:
fields
.
Boolean
,
'in_trial'
:
fields
.
Boolean
,
'trial_end_reason'
:
fields
.
String
,
'trial_end_reason'
:
fields
.
String
,
'custom_config'
:
fields
.
Raw
(
attribute
=
'custom_config'
),
}
}
tenants_fields
=
{
tenants_fields
=
{
...
@@ -130,6 +134,61 @@ class SwitchWorkspaceApi(Resource):
...
@@ -130,6 +134,61 @@ class SwitchWorkspaceApi(Resource):
new_tenant
=
db
.
session
.
query
(
Tenant
)
.
get
(
args
[
'tenant_id'
])
# Get new tenant
new_tenant
=
db
.
session
.
query
(
Tenant
)
.
get
(
args
[
'tenant_id'
])
# Get new tenant
return
{
'result'
:
'success'
,
'new_tenant'
:
marshal
(
WorkspaceService
.
get_tenant_info
(
new_tenant
),
tenant_fields
)}
return
{
'result'
:
'success'
,
'new_tenant'
:
marshal
(
WorkspaceService
.
get_tenant_info
(
new_tenant
),
tenant_fields
)}
class
CustomConfigWorkspaceApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
cloud_edition_billing_resource_check
(
'workspace_custom'
)
def
post
(
self
):
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'remove_webapp_brand'
,
type
=
bool
,
location
=
'json'
)
parser
.
add_argument
(
'replace_webapp_logo'
,
type
=
str
,
location
=
'json'
)
args
=
parser
.
parse_args
()
custom_config_dict
=
{
'remove_webapp_brand'
:
args
[
'remove_webapp_brand'
],
'replace_webapp_logo'
:
args
[
'replace_webapp_logo'
],
}
tenant
=
db
.
session
.
query
(
Tenant
)
.
filter
(
Tenant
.
id
==
current_user
.
current_tenant_id
)
.
one_or_404
()
tenant
.
custom_config_dict
=
custom_config_dict
db
.
session
.
commit
()
return
{
'result'
:
'success'
,
'tenant'
:
marshal
(
WorkspaceService
.
get_tenant_info
(
tenant
),
tenant_fields
)}
class
WebappLogoWorkspaceApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
cloud_edition_billing_resource_check
(
'workspace_custom'
)
def
post
(
self
):
# get file from request
file
=
request
.
files
[
'file'
]
# check file
if
'file'
not
in
request
.
files
:
raise
NoFileUploadedError
()
if
len
(
request
.
files
)
>
1
:
raise
TooManyFilesError
()
extension
=
file
.
filename
.
split
(
'.'
)[
-
1
]
if
extension
.
lower
()
not
in
[
'svg'
,
'png'
]:
raise
UnsupportedFileTypeError
()
try
:
upload_file
=
FileService
.
upload_file
(
file
,
current_user
,
True
)
except
services
.
errors
.
file
.
FileTooLargeError
as
file_too_large_error
:
raise
FileTooLargeError
(
file_too_large_error
.
description
)
except
services
.
errors
.
file
.
UnsupportedFileTypeError
:
raise
UnsupportedFileTypeError
()
return
{
'id'
:
upload_file
.
id
},
201
api
.
add_resource
(
TenantListApi
,
'/workspaces'
)
# GET for getting all tenants
api
.
add_resource
(
TenantListApi
,
'/workspaces'
)
# GET for getting all tenants
...
@@ -137,3 +196,5 @@ api.add_resource(WorkspaceListApi, '/all-workspaces') # GET for getting all ten
...
@@ -137,3 +196,5 @@ api.add_resource(WorkspaceListApi, '/all-workspaces') # GET for getting all ten
api
.
add_resource
(
TenantApi
,
'/workspaces/current'
,
endpoint
=
'workspaces_current'
)
# GET for getting current tenant info
api
.
add_resource
(
TenantApi
,
'/workspaces/current'
,
endpoint
=
'workspaces_current'
)
# GET for getting current tenant info
api
.
add_resource
(
TenantApi
,
'/info'
,
endpoint
=
'info'
)
# Deprecated
api
.
add_resource
(
TenantApi
,
'/info'
,
endpoint
=
'info'
)
# Deprecated
api
.
add_resource
(
SwitchWorkspaceApi
,
'/workspaces/switch'
)
# POST for switching tenant
api
.
add_resource
(
SwitchWorkspaceApi
,
'/workspaces/switch'
)
# POST for switching tenant
api
.
add_resource
(
CustomConfigWorkspaceApi
,
'/workspaces/custom-config'
)
api
.
add_resource
(
WebappLogoWorkspaceApi
,
'/workspaces/custom-config/webapp-logo/upload'
)
api/controllers/console/wraps.py
View file @
5bb84193
...
@@ -63,6 +63,8 @@ def cloud_edition_billing_resource_check(resource: str,
...
@@ -63,6 +63,8 @@ def cloud_edition_billing_resource_check(resource: str,
abort
(
403
,
error_msg
)
abort
(
403
,
error_msg
)
elif
resource
==
'vector_space'
and
0
<
vector_space
[
'limit'
]
<=
vector_space
[
'size'
]:
elif
resource
==
'vector_space'
and
0
<
vector_space
[
'limit'
]
<=
vector_space
[
'size'
]:
abort
(
403
,
error_msg
)
abort
(
403
,
error_msg
)
elif
resource
==
'workspace_custom'
and
not
billing_info
[
'can_replace_logo'
]:
abort
(
403
,
error_msg
)
elif
resource
==
'annotation'
and
0
<
annotation_quota_limit
[
'limit'
]
<=
annotation_quota_limit
[
'size'
]:
elif
resource
==
'annotation'
and
0
<
annotation_quota_limit
[
'limit'
]
<=
annotation_quota_limit
[
'size'
]:
abort
(
403
,
error_msg
)
abort
(
403
,
error_msg
)
else
:
else
:
...
...
api/controllers/files/image_preview.py
View file @
5bb84193
from
flask
import
request
,
Response
from
flask
import
request
,
Response
from
flask_restful
import
Resource
from
flask_restful
import
Resource
from
werkzeug.exceptions
import
NotFound
import
services
import
services
from
controllers.files
import
api
from
controllers.files
import
api
from
libs.exception
import
BaseHTTPException
from
libs.exception
import
BaseHTTPException
from
services.file_service
import
FileService
from
services.file_service
import
FileService
from
services.account_service
import
TenantService
class
ImagePreviewApi
(
Resource
):
class
ImagePreviewApi
(
Resource
):
...
@@ -29,9 +31,30 @@ class ImagePreviewApi(Resource):
...
@@ -29,9 +31,30 @@ class ImagePreviewApi(Resource):
raise
UnsupportedFileTypeError
()
raise
UnsupportedFileTypeError
()
return
Response
(
generator
,
mimetype
=
mimetype
)
return
Response
(
generator
,
mimetype
=
mimetype
)
class
WorkspaceWebappLogoApi
(
Resource
):
def
get
(
self
,
workspace_id
):
workspace_id
=
str
(
workspace_id
)
custom_config
=
TenantService
.
get_custom_config
(
workspace_id
)
webapp_logo_file_id
=
custom_config
.
get
(
'replace_webapp_logo'
)
if
custom_config
is
not
None
else
None
if
not
webapp_logo_file_id
:
raise
NotFound
(
f
'webapp logo is not found'
)
try
:
generator
,
mimetype
=
FileService
.
get_public_image_preview
(
webapp_logo_file_id
,
)
except
services
.
errors
.
file
.
UnsupportedFileTypeError
:
raise
UnsupportedFileTypeError
()
return
Response
(
generator
,
mimetype
=
mimetype
)
api
.
add_resource
(
ImagePreviewApi
,
'/files/<uuid:file_id>/image-preview'
)
api
.
add_resource
(
ImagePreviewApi
,
'/files/<uuid:file_id>/image-preview'
)
api
.
add_resource
(
WorkspaceWebappLogoApi
,
'/files/workspaces/<uuid:workspace_id>/webapp-logo'
)
class
UnsupportedFileTypeError
(
BaseHTTPException
):
class
UnsupportedFileTypeError
(
BaseHTTPException
):
...
...
api/controllers/web/site.py
View file @
5bb84193
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
os
import
os
from
flask_restful
import
fields
,
marshal_with
from
flask_restful
import
fields
,
marshal_with
from
flask
import
current_app
from
werkzeug.exceptions
import
Forbidden
from
werkzeug.exceptions
import
Forbidden
from
controllers.web
import
api
from
controllers.web
import
api
...
@@ -43,6 +44,7 @@ class AppSiteApi(WebApiResource):
...
@@ -43,6 +44,7 @@ class AppSiteApi(WebApiResource):
'model_config'
:
fields
.
Nested
(
model_config_fields
,
allow_null
=
True
),
'model_config'
:
fields
.
Nested
(
model_config_fields
,
allow_null
=
True
),
'plan'
:
fields
.
String
,
'plan'
:
fields
.
String
,
'can_replace_logo'
:
fields
.
Boolean
,
'can_replace_logo'
:
fields
.
Boolean
,
'custom_config'
:
fields
.
Raw
(
attribute
=
'custom_config'
),
}
}
@
marshal_with
(
app_fields
)
@
marshal_with
(
app_fields
)
...
@@ -80,6 +82,15 @@ class AppSiteInfo:
...
@@ -80,6 +82,15 @@ class AppSiteInfo:
self
.
plan
=
tenant
.
plan
self
.
plan
=
tenant
.
plan
self
.
can_replace_logo
=
can_replace_logo
self
.
can_replace_logo
=
can_replace_logo
if
can_replace_logo
:
base_url
=
current_app
.
config
.
get
(
'FILES_URL'
)
remove_webapp_brand
=
tenant
.
custom_config_dict
.
get
(
'remove_webapp_brand'
,
False
)
replace_webapp_logo
=
f
'{base_url}/files/workspaces/{tenant.id}/webapp-logo'
if
tenant
.
custom_config_dict
[
'replace_webapp_logo'
]
else
None
self
.
custom_config
=
{
'remove_webapp_brand'
:
remove_webapp_brand
,
'replace_webapp_logo'
:
replace_webapp_logo
,
}
if
app
.
enable_site
and
site
.
prompt_public
:
if
app
.
enable_site
and
site
.
prompt_public
:
app_model_config
=
app
.
app_model_config
app_model_config
=
app
.
app_model_config
self
.
model_config
=
app_model_config
self
.
model_config
=
app_model_config
api/core/file/upload_file_parser.py
View file @
5bb84193
...
@@ -10,7 +10,7 @@ from flask import current_app
...
@@ -10,7 +10,7 @@ from flask import current_app
from
extensions.ext_storage
import
storage
from
extensions.ext_storage
import
storage
SUPPORT_EXTENSIONS
=
[
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
]
SUPPORT_EXTENSIONS
=
[
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
,
'svg'
]
class
UploadFileParser
:
class
UploadFileParser
:
...
...
api/migrations/versions/88072f0caa04_add_custom_config_in_tenant.py
0 → 100644
View file @
5bb84193
"""add custom config in tenant
Revision ID: 88072f0caa04
Revises: fca025d3b60f
Create Date: 2023-12-14 07:36:50.705362
"""
from
alembic
import
op
import
sqlalchemy
as
sa
# revision identifiers, used by Alembic.
revision
=
'88072f0caa04'
down_revision
=
'246ba09cbbdb'
branch_labels
=
None
depends_on
=
None
def
upgrade
():
# ### commands auto generated by Alembic - please adjust! ###
with
op
.
batch_alter_table
(
'tenants'
,
schema
=
None
)
as
batch_op
:
batch_op
.
add_column
(
sa
.
Column
(
'custom_config'
,
sa
.
Text
(),
nullable
=
True
))
# ### end Alembic commands ###
def
downgrade
():
# ### commands auto generated by Alembic - please adjust! ###
with
op
.
batch_alter_table
(
'tenants'
,
schema
=
None
)
as
batch_op
:
batch_op
.
drop_column
(
'custom_config'
)
# ### end Alembic commands ###
api/models/account.py
View file @
5bb84193
import
json
import
enum
import
enum
from
math
import
e
from
typing
import
List
from
typing
import
List
from
flask_login
import
UserMixin
from
flask_login
import
UserMixin
...
@@ -112,6 +114,7 @@ class Tenant(db.Model):
...
@@ -112,6 +114,7 @@ class Tenant(db.Model):
encrypt_public_key
=
db
.
Column
(
db
.
Text
)
encrypt_public_key
=
db
.
Column
(
db
.
Text
)
plan
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
server_default
=
db
.
text
(
"'basic'::character varying"
))
plan
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
server_default
=
db
.
text
(
"'basic'::character varying"
))
status
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
server_default
=
db
.
text
(
"'normal'::character varying"
))
status
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
,
server_default
=
db
.
text
(
"'normal'::character varying"
))
custom_config
=
db
.
Column
(
db
.
Text
)
created_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
created_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
updated_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
updated_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
...
@@ -121,6 +124,14 @@ class Tenant(db.Model):
...
@@ -121,6 +124,14 @@ class Tenant(db.Model):
Account
.
id
==
TenantAccountJoin
.
account_id
,
Account
.
id
==
TenantAccountJoin
.
account_id
,
TenantAccountJoin
.
tenant_id
==
self
.
id
TenantAccountJoin
.
tenant_id
==
self
.
id
)
.
all
()
)
.
all
()
@
property
def
custom_config_dict
(
self
)
->
dict
:
return
json
.
loads
(
self
.
custom_config
)
if
self
.
custom_config
else
None
@
custom_config_dict
.
setter
def
custom_config_dict
(
self
,
value
:
dict
):
self
.
custom_config
=
json
.
dumps
(
value
)
class
TenantAccountJoinRole
(
enum
.
Enum
):
class
TenantAccountJoinRole
(
enum
.
Enum
):
...
...
api/services/account_service.py
View file @
5bb84193
...
@@ -412,6 +412,12 @@ class TenantService:
...
@@ -412,6 +412,12 @@ class TenantService:
db
.
session
.
delete
(
tenant
)
db
.
session
.
delete
(
tenant
)
db
.
session
.
commit
()
db
.
session
.
commit
()
@
staticmethod
def
get_custom_config
(
tenant_id
:
str
)
->
None
:
tenant
=
db
.
session
.
query
(
Tenant
)
.
filter
(
Tenant
.
id
==
tenant_id
)
.
one_or_404
()
return
tenant
.
custom_config_dict
class
RegisterService
:
class
RegisterService
:
...
...
api/services/file_service.py
View file @
5bb84193
...
@@ -17,8 +17,8 @@ from models.model import UploadFile, EndUser
...
@@ -17,8 +17,8 @@ from models.model import UploadFile, EndUser
from
services.errors.file
import
FileTooLargeError
,
UnsupportedFileTypeError
from
services.errors.file
import
FileTooLargeError
,
UnsupportedFileTypeError
ALLOWED_EXTENSIONS
=
[
'txt'
,
'markdown'
,
'md'
,
'pdf'
,
'html'
,
'htm'
,
'xlsx'
,
'docx'
,
'csv'
,
ALLOWED_EXTENSIONS
=
[
'txt'
,
'markdown'
,
'md'
,
'pdf'
,
'html'
,
'htm'
,
'xlsx'
,
'docx'
,
'csv'
,
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
]
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
,
'svg'
]
IMAGE_EXTENSIONS
=
[
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
]
IMAGE_EXTENSIONS
=
[
'jpg'
,
'jpeg'
,
'png'
,
'webp'
,
'gif'
,
'svg'
]
PREVIEW_WORDS_LIMIT
=
3000
PREVIEW_WORDS_LIMIT
=
3000
...
@@ -154,3 +154,21 @@ class FileService:
...
@@ -154,3 +154,21 @@ class FileService:
generator
=
storage
.
load
(
upload_file
.
key
,
stream
=
True
)
generator
=
storage
.
load
(
upload_file
.
key
,
stream
=
True
)
return
generator
,
upload_file
.
mime_type
return
generator
,
upload_file
.
mime_type
@
staticmethod
def
get_public_image_preview
(
file_id
:
str
)
->
str
:
upload_file
=
db
.
session
.
query
(
UploadFile
)
\
.
filter
(
UploadFile
.
id
==
file_id
)
\
.
first
()
if
not
upload_file
:
raise
NotFound
(
"File not found or signature is invalid"
)
# extract text from file
extension
=
upload_file
.
extension
if
extension
.
lower
()
not
in
IMAGE_EXTENSIONS
:
raise
UnsupportedFileTypeError
()
generator
=
storage
.
load
(
upload_file
.
key
)
return
generator
,
upload_file
.
mime_type
api/services/workspace_service.py
View file @
5bb84193
from
flask_login
import
current_user
from
flask_login
import
current_user
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
models.account
import
Tenant
,
TenantAccountJoin
from
models.account
import
Tenant
,
TenantAccountJoin
,
TenantAccountJoinRole
from
models.provider
import
Provider
from
models.provider
import
Provider
from
services.billing_service
import
BillingService
from
services.account_service
import
TenantService
class
WorkspaceService
:
class
WorkspaceService
:
@
classmethod
@
classmethod
...
@@ -28,6 +31,11 @@ class WorkspaceService:
...
@@ -28,6 +31,11 @@ class WorkspaceService:
)
.
first
()
)
.
first
()
tenant_info
[
'role'
]
=
tenant_account_join
.
role
tenant_info
[
'role'
]
=
tenant_account_join
.
role
billing_info
=
BillingService
.
get_info
(
tenant_info
[
'id'
])
if
billing_info
[
'can_replace_logo'
]
and
TenantService
.
has_roles
(
tenant
,
[
TenantAccountJoinRole
.
OWNER
,
TenantAccountJoinRole
.
ADMIN
]):
tenant_info
[
'custom_config'
]
=
tenant
.
custom_config_dict
# Get providers
# Get providers
providers
=
db
.
session
.
query
(
Provider
)
.
filter
(
providers
=
db
.
session
.
query
(
Provider
)
.
filter
(
Provider
.
tenant_id
==
tenant
.
id
Provider
.
tenant_id
==
tenant
.
id
...
...
web/app/components/base/icons/assets/vender/line/editor/colors.svg
0 → 100644
View file @
5bb84193
<svg
width=
"24"
height=
"24"
viewBox=
"0 0 24 24"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"colors"
>
<path
id=
"Icon"
d=
"M12 20.4722C13.0615 21.4223 14.4633 22 16 22C19.3137 22 22 19.3137 22 16C22 13.2331 20.1271 10.9036 17.5798 10.2102M6.42018 10.2102C3.87293 10.9036 2 13.2331 2 16C2 19.3137 4.68629 22 8 22C11.3137 22 14 19.3137 14 16C14 15.2195 13.851 14.4738 13.5798 13.7898M18 8C18 11.3137 15.3137 14 12 14C8.68629 14 6 11.3137 6 8C6 4.68629 8.68629 2 12 2C15.3137 2 18 4.68629 18 8Z"
stroke=
"black"
stroke-width=
"2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/communication/message-dots-circle.svg
0 → 100644
View file @
5bb84193
<svg
width=
"24"
height=
"24"
viewBox=
"0 0 24 24"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"message-dots-circle"
>
<path
id=
"Solid"
fill-rule=
"evenodd"
clip-rule=
"evenodd"
d=
"M12 2C6.47715 2 2 6.47715 2 12C2 13.3283 2.25952 14.5985 2.73156 15.7608C2.77419 15.8658 2.79872 15.9264 2.81552 15.9711L2.82063 15.9849L2.82 15.9897C2.815 16.0266 2.80672 16.0769 2.79071 16.173L2.19294 19.7596C2.16612 19.9202 2.13611 20.0999 2.12433 20.256C2.11148 20.4261 2.10701 20.6969 2.22973 20.983C2.38144 21.3367 2.6633 21.6186 3.017 21.7703C3.30312 21.893 3.57386 21.8885 3.74404 21.8757C3.90013 21.8639 4.07985 21.8339 4.24049 21.8071L7.82705 21.2093C7.92309 21.1933 7.97339 21.185 8.0103 21.18L8.01505 21.1794L8.02887 21.1845C8.07362 21.2013 8.13423 21.2258 8.23921 21.2684C9.4015 21.7405 10.6717 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM6 12C6 11.1716 6.67157 10.5 7.5 10.5C8.32843 10.5 9 11.1716 9 12C9 12.8284 8.32843 13.5 7.5 13.5C6.67157 13.5 6 12.8284 6 12ZM10.5 12C10.5 11.1716 11.1716 10.5 12 10.5C12.8284 10.5 13.5 11.1716 13.5 12C13.5 12.8284 12.8284 13.5 12 13.5C11.1716 13.5 10.5 12.8284 10.5 12ZM16.5 10.5C15.6716 10.5 15 11.1716 15 12C15 12.8284 15.6716 13.5 16.5 13.5C17.3284 13.5 18 12.8284 18 12C18 11.1716 17.3284 10.5 16.5 10.5Z"
fill=
"black"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/editor/colors.svg
0 → 100644
View file @
5bb84193
<svg
width=
"24"
height=
"24"
viewBox=
"0 0 24 24"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"colors"
>
<g
id=
"Solid"
>
<path
d=
"M13.4494 13.2298C12.9854 13.3409 12.5002 13.3999 12 13.3999C10.2804 13.3999 8.72326 12.6997 7.59953 11.5677C6.4872 10.4471 5.8 8.90382 5.8 7.20007C5.8 3.77586 8.57584 1 12 1C15.4241 1 18.2 3.77586 18.2 7.20007C18.2 8.44569 17.8327 9.60551 17.2005 10.5771C16.3665 11.8588 15.0715 12.8131 13.5506 13.2047C13.517 13.2133 13.4833 13.2217 13.4494 13.2298Z"
fill=
"black"
/>
<path
d=
"M15.1476 14.7743C16.6646 14.1431 17.9513 13.0695 18.8465 11.7146C19.0004 11.4817 19.0773 11.3652 19.1762 11.3066C19.2615 11.2561 19.3659 11.2312 19.4648 11.2379C19.5795 11.2457 19.6773 11.3015 19.8728 11.4133C21.7413 12.4817 23 14.4946 23 16.7999C23 20.2241 20.2242 23 16.8 23C15.9123 23 15.0689 22.8139 14.3059 22.4782C14.0549 22.3678 13.9294 22.3126 13.8502 22.2049C13.7822 22.1126 13.7468 21.9922 13.7539 21.8777C13.7622 21.7444 13.8565 21.6018 14.045 21.3167C14.8373 20.1184 15.3234 18.6997 15.3917 17.1723C15.3969 17.0566 15.3996 16.9402 15.4 16.8233L15.4 16.7999C15.4 16.1888 15.333 15.5926 15.2057 15.0185C15.1876 14.9366 15.1682 14.8552 15.1476 14.7743Z"
fill=
"black"
/>
<path
d=
"M4.12723 11.4133C4.32273 11.3015 4.42049 11.2457 4.53516 11.2379C4.63414 11.2312 4.73848 11.2561 4.82382 11.3066C4.92269 11.3652 4.99964 11.4817 5.15355 11.7146C6.62074 13.9352 9.13929 15.4001 12 15.4001C12.4146 15.4001 12.822 15.3694 13.2201 15.31L13.2263 15.3357C13.3398 15.8045 13.4 16.2947 13.4 16.7999L13.4 16.8214C13.3997 16.9056 13.3977 16.9895 13.3941 17.0728C13.2513 20.3704 10.5327 23 7.2 23C3.77584 23 1 20.2241 1 16.7999C1 14.4946 2.25869 12.4817 4.12723 11.4133Z"
fill=
"black"
/>
</g>
</g>
</svg>
web/app/components/base/icons/src/vender/line/editor/Colors.json
0 → 100644
View file @
5bb84193
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"24"
,
"height"
:
"24"
,
"viewBox"
:
"0 0 24 24"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"colors"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon"
,
"d"
:
"M12 20.4722C13.0615 21.4223 14.4633 22 16 22C19.3137 22 22 19.3137 22 16C22 13.2331 20.1271 10.9036 17.5798 10.2102M6.42018 10.2102C3.87293 10.9036 2 13.2331 2 16C2 19.3137 4.68629 22 8 22C11.3137 22 14 19.3137 14 16C14 15.2195 13.851 14.4738 13.5798 13.7898M18 8C18 11.3137 15.3137 14 12 14C8.68629 14 6 11.3137 6 8C6 4.68629 8.68629 2 12 2C15.3137 2 18 4.68629 18 8Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"2"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"Colors"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/editor/Colors.tsx
0 → 100644
View file @
5bb84193
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Colors.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'Colors'
export
default
Icon
web/app/components/base/icons/src/vender/line/editor/index.ts
View file @
5bb84193
export
{
default
as
BezierCurve03
}
from
'./BezierCurve03'
export
{
default
as
BezierCurve03
}
from
'./BezierCurve03'
export
{
default
as
Colors
}
from
'./Colors'
export
{
default
as
TypeSquare
}
from
'./TypeSquare'
export
{
default
as
TypeSquare
}
from
'./TypeSquare'
web/app/components/base/icons/src/vender/solid/communication/MessageDotsCircle.json
0 → 100644
View file @
5bb84193
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"24"
,
"height"
:
"24"
,
"viewBox"
:
"0 0 24 24"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"message-dots-circle"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"fill-rule"
:
"evenodd"
,
"clip-rule"
:
"evenodd"
,
"d"
:
"M12 2C6.47715 2 2 6.47715 2 12C2 13.3283 2.25952 14.5985 2.73156 15.7608C2.77419 15.8658 2.79872 15.9264 2.81552 15.9711L2.82063 15.9849L2.82 15.9897C2.815 16.0266 2.80672 16.0769 2.79071 16.173L2.19294 19.7596C2.16612 19.9202 2.13611 20.0999 2.12433 20.256C2.11148 20.4261 2.10701 20.6969 2.22973 20.983C2.38144 21.3367 2.6633 21.6186 3.017 21.7703C3.30312 21.893 3.57386 21.8885 3.74404 21.8757C3.90013 21.8639 4.07985 21.8339 4.24049 21.8071L7.82705 21.2093C7.92309 21.1933 7.97339 21.185 8.0103 21.18L8.01505 21.1794L8.02887 21.1845C8.07362 21.2013 8.13423 21.2258 8.23921 21.2684C9.4015 21.7405 10.6717 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM6 12C6 11.1716 6.67157 10.5 7.5 10.5C8.32843 10.5 9 11.1716 9 12C9 12.8284 8.32843 13.5 7.5 13.5C6.67157 13.5 6 12.8284 6 12ZM10.5 12C10.5 11.1716 11.1716 10.5 12 10.5C12.8284 10.5 13.5 11.1716 13.5 12C13.5 12.8284 12.8284 13.5 12 13.5C11.1716 13.5 10.5 12.8284 10.5 12ZM16.5 10.5C15.6716 10.5 15 11.1716 15 12C15 12.8284 15.6716 13.5 16.5 13.5C17.3284 13.5 18 12.8284 18 12C18 11.1716 17.3284 10.5 16.5 10.5Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"MessageDotsCircle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/communication/MessageDotsCircle.tsx
0 → 100644
View file @
5bb84193
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./MessageDotsCircle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'MessageDotsCircle'
export
default
Icon
web/app/components/base/icons/src/vender/solid/communication/index.ts
View file @
5bb84193
export
{
default
as
MessageDotsCircle
}
from
'./MessageDotsCircle'
export
{
default
as
MessageFast
}
from
'./MessageFast'
export
{
default
as
MessageFast
}
from
'./MessageFast'
web/app/components/base/icons/src/vender/solid/editor/Colors.json
0 → 100644
View file @
5bb84193
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"24"
,
"height"
:
"24"
,
"viewBox"
:
"0 0 24 24"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"colors"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"Solid"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M13.4494 13.2298C12.9854 13.3409 12.5002 13.3999 12 13.3999C10.2804 13.3999 8.72326 12.6997 7.59953 11.5677C6.4872 10.4471 5.8 8.90382 5.8 7.20007C5.8 3.77586 8.57584 1 12 1C15.4241 1 18.2 3.77586 18.2 7.20007C18.2 8.44569 17.8327 9.60551 17.2005 10.5771C16.3665 11.8588 15.0715 12.8131 13.5506 13.2047C13.517 13.2133 13.4833 13.2217 13.4494 13.2298Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M15.1476 14.7743C16.6646 14.1431 17.9513 13.0695 18.8465 11.7146C19.0004 11.4817 19.0773 11.3652 19.1762 11.3066C19.2615 11.2561 19.3659 11.2312 19.4648 11.2379C19.5795 11.2457 19.6773 11.3015 19.8728 11.4133C21.7413 12.4817 23 14.4946 23 16.7999C23 20.2241 20.2242 23 16.8 23C15.9123 23 15.0689 22.8139 14.3059 22.4782C14.0549 22.3678 13.9294 22.3126 13.8502 22.2049C13.7822 22.1126 13.7468 21.9922 13.7539 21.8777C13.7622 21.7444 13.8565 21.6018 14.045 21.3167C14.8373 20.1184 15.3234 18.6997 15.3917 17.1723C15.3969 17.0566 15.3996 16.9402 15.4 16.8233L15.4 16.7999C15.4 16.1888 15.333 15.5926 15.2057 15.0185C15.1876 14.9366 15.1682 14.8552 15.1476 14.7743Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M4.12723 11.4133C4.32273 11.3015 4.42049 11.2457 4.53516 11.2379C4.63414 11.2312 4.73848 11.2561 4.82382 11.3066C4.92269 11.3652 4.99964 11.4817 5.15355 11.7146C6.62074 13.9352 9.13929 15.4001 12 15.4001C12.4146 15.4001 12.822 15.3694 13.2201 15.31L13.2263 15.3357C13.3398 15.8045 13.4 16.2947 13.4 16.7999L13.4 16.8214C13.3997 16.9056 13.3977 16.9895 13.3941 17.0728C13.2513 20.3704 10.5327 23 7.2 23C3.77584 23 1 20.2241 1 16.7999C1 14.4946 2.25869 12.4817 4.12723 11.4133Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"Colors"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/editor/Colors.tsx
0 → 100644
View file @
5bb84193
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Colors.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'Colors'
export
default
Icon
web/app/components/base/icons/src/vender/solid/editor/index.ts
View file @
5bb84193
export
{
default
as
Brush01
}
from
'./Brush01'
export
{
default
as
Brush01
}
from
'./Brush01'
export
{
default
as
Citations
}
from
'./Citations'
export
{
default
as
Citations
}
from
'./Citations'
export
{
default
as
Colors
}
from
'./Colors'
export
{
default
as
Paragraph
}
from
'./Paragraph'
export
{
default
as
Paragraph
}
from
'./Paragraph'
export
{
default
as
TypeSquare
}
from
'./TypeSquare'
export
{
default
as
TypeSquare
}
from
'./TypeSquare'
web/app/components/base/image-uploader/utils.ts
View file @
5bb84193
...
@@ -6,13 +6,13 @@ type ImageUploadParams = {
...
@@ -6,13 +6,13 @@ type ImageUploadParams = {
onSuccessCallback
:
(
res
:
{
id
:
string
})
=>
void
onSuccessCallback
:
(
res
:
{
id
:
string
})
=>
void
onErrorCallback
:
()
=>
void
onErrorCallback
:
()
=>
void
}
}
type
ImageUpload
=
(
v
:
ImageUploadParams
,
isPublic
?:
boolean
)
=>
void
type
ImageUpload
=
(
v
:
ImageUploadParams
,
isPublic
?:
boolean
,
url
?:
string
)
=>
void
export
const
imageUpload
:
ImageUpload
=
({
export
const
imageUpload
:
ImageUpload
=
({
file
,
file
,
onProgressCallback
,
onProgressCallback
,
onSuccessCallback
,
onSuccessCallback
,
onErrorCallback
,
onErrorCallback
,
},
isPublic
)
=>
{
},
isPublic
,
url
)
=>
{
const
formData
=
new
FormData
()
const
formData
=
new
FormData
()
formData
.
append
(
'file'
,
file
)
formData
.
append
(
'file'
,
file
)
const
onProgress
=
(
e
:
ProgressEvent
)
=>
{
const
onProgress
=
(
e
:
ProgressEvent
)
=>
{
...
@@ -26,7 +26,7 @@ export const imageUpload: ImageUpload = ({
...
@@ -26,7 +26,7 @@ export const imageUpload: ImageUpload = ({
xhr
:
new
XMLHttpRequest
(),
xhr
:
new
XMLHttpRequest
(),
data
:
formData
,
data
:
formData
,
onprogress
:
onProgress
,
onprogress
:
onProgress
,
},
isPublic
)
},
isPublic
,
url
)
.
then
((
res
:
{
id
:
string
})
=>
{
.
then
((
res
:
{
id
:
string
})
=>
{
onSuccessCallback
(
res
)
onSuccessCallback
(
res
)
})
})
...
...
web/app/components/custom/custom-app-header-brand/index.tsx
0 → 100644
View file @
5bb84193
import
{
useTranslation
}
from
'react-i18next'
import
s
from
'./style.module.css'
import
Button
from
'@/app/components/base/button'
import
{
Grid01
}
from
'@/app/components/base/icons/src/vender/solid/layout'
import
{
Container
,
Database01
}
from
'@/app/components/base/icons/src/vender/line/development'
import
{
ImagePlus
}
from
'@/app/components/base/icons/src/vender/line/images'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
Plan
}
from
'@/app/components/billing/type'
const
CustomAppHeaderBrand
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
plan
}
=
useProviderContext
()
return
(
<
div
className=
'py-3'
>
<
div
className=
'mb-2 text-sm font-medium text-gray-900'
>
{
t
(
'custom.app.title'
)
}
</
div
>
<
div
className=
'relative mb-4 rounded-xl bg-gray-100 border-[0.5px] border-black/[0.08] shadow-xs'
>
<
div
className=
{
`${s.mask} absolute inset-0 rounded-xl`
}
></
div
>
<
div
className=
'flex items-center pl-5 h-14 rounded-t-xl'
>
<
div
className=
'relative flex items-center mr-[199px] w-[120px] h-10 bg-[rgba(217,45,32,0.12)]'
>
<
div
className=
'ml-[1px] mr-[3px] w-[34px] h-[34px] border-8 border-black/[0.16] rounded-full'
></
div
>
<
div
className=
'text-[13px] font-bold text-black/[0.24]'
>
YOUR LOGO
</
div
>
<
div
className=
'absolute top-0 bottom-0 left-0.5 w-[0.5px] bg-[#F97066] opacity-50'
></
div
>
<
div
className=
'absolute top-0 bottom-0 right-0.5 w-[0.5px] bg-[#F97066] opacity-50'
></
div
>
<
div
className=
'absolute left-0 right-0 top-0.5 h-[0.5px] bg-[#F97066] opacity-50'
></
div
>
<
div
className=
'absolute left-0 right-0 bottom-0.5 h-[0.5px] bg-[#F97066] opacity-50'
></
div
>
</
div
>
<
div
className=
'flex items-center mr-3 px-3 h-7 rounded-xl bg-white shadow-xs'
>
<
Grid01
className=
'shrink-0 mr-2 w-4 h-4 text-[#155eef]'
/>
<
div
className=
'w-12 h-1.5 rounded-[5px] bg-[#155eef] opacity-80'
></
div
>
</
div
>
<
div
className=
'flex items-center mr-3 px-3 h-7'
>
<
Container
className=
'shrink-0 mr-2 w-4 h-4 text-gray-500'
/>
<
div
className=
'w-[50px] h-1.5 rounded-[5px] bg-gray-300'
></
div
>
</
div
>
<
div
className=
'flex items-center px-3 h-7'
>
<
Database01
className=
'shrink-0 mr-2 w-4 h-4 text-gray-500'
/>
<
div
className=
'w-14 h-1.5 rounded-[5px] bg-gray-300 opacity-80'
></
div
>
</
div
>
</
div
>
<
div
className=
'h-8 border-t border-t-gray-200 rounded-b-xl'
></
div
>
</
div
>
<
div
className=
'flex items-center mb-2'
>
<
Button
className=
{
`
!h-8 !px-3 bg-white !text-[13px]
${plan.type === Plan.sandbox ? 'opacity-40' : ''}
`
}
disabled=
{
plan
.
type
===
Plan
.
sandbox
}
>
<
ImagePlus
className=
'mr-2 w-4 h-4'
/>
{
t
(
'custom.upload'
)
}
</
Button
>
<
div
className=
'mx-2 h-5 w-[1px] bg-black/5'
></
div
>
<
Button
className=
{
`
!h-8 !px-3 bg-white !text-[13px]
${plan.type === Plan.sandbox ? 'opacity-40' : ''}
`
}
disabled=
{
plan
.
type
===
Plan
.
sandbox
}
>
{
t
(
'custom.restore'
)
}
</
Button
>
</
div
>
<
div
className=
'text-xs text-gray-500'
>
{
t
(
'custom.app.changeLogoTip'
)
}
</
div
>
</
div
>
)
}
export
default
CustomAppHeaderBrand
web/app/components/custom/custom-app-header-brand/style.module.css
0 → 100644
View file @
5bb84193
.mask
{
background
:
linear-gradient
(
95deg
,
rgba
(
255
,
255
,
255
,
0.00
)
43.9%
,
rgba
(
255
,
255
,
255
,
0.80
)
95.76%
);
;
}
\ No newline at end of file
web/app/components/custom/custom-page/index.tsx
0 → 100644
View file @
5bb84193
import
{
useTranslation
}
from
'react-i18next'
import
CustomWebAppBrand
from
'../custom-web-app-brand'
import
CustomAppHeaderBrand
from
'../custom-app-header-brand'
import
s
from
'../style.module.css'
import
GridMask
from
'@/app/components/base/grid-mask'
import
UpgradeBtn
from
'@/app/components/billing/upgrade-btn'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
Plan
}
from
'@/app/components/billing/type'
import
{
contactSalesUrl
}
from
'@/app/components/billing/config'
const
CustomPage
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
plan
}
=
useProviderContext
()
return
(
<
div
className=
'flex flex-col'
>
{
plan
.
type
===
Plan
.
sandbox
&&
(
<
GridMask
canvasClassName=
'!rounded-xl'
>
<
div
className=
'flex justify-between mb-1 px-6 py-5 h-[88px] shadow-md rounded-xl border-[0.5px] border-gray-200'
>
<
div
className=
{
`${s.textGradient} leading-[24px] text-base font-semibold`
}
>
<
div
>
{
t
(
'custom.upgradeTip.prefix'
)
}
</
div
>
<
div
>
{
t
(
'custom.upgradeTip.suffix'
)
}
</
div
>
</
div
>
<
UpgradeBtn
/>
</
div
>
</
GridMask
>
)
}
<
CustomWebAppBrand
/>
{
plan
.
type
===
Plan
.
sandbox
&&
(
<>
<
div
className=
'my-2 h-[0.5px] bg-gray-100'
></
div
>
<
CustomAppHeaderBrand
/>
</>
)
}
{
(
plan
.
type
===
Plan
.
professional
||
plan
.
type
===
Plan
.
team
)
&&
(
<
div
className=
'absolute bottom-0 h-[50px] leading-[50px] text-xs text-gray-500'
>
{
t
(
'custom.customize.prefix'
)
}
<
a
className=
'text-[#155EEF]'
href=
{
contactSalesUrl
}
target=
'_blank'
>
{
t
(
'custom.customize.contactUs'
)
}
</
a
>
{
t
(
'custom.customize.suffix'
)
}
</
div
>
)
}
</
div
>
)
}
export
default
CustomPage
web/app/components/custom/custom-web-app-brand/index.tsx
0 → 100644
View file @
5bb84193
import
type
{
ChangeEvent
}
from
'react'
import
{
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
s
from
'./style.module.css'
import
LogoSite
from
'@/app/components/base/logo/logo-site'
import
Switch
from
'@/app/components/base/switch'
import
Button
from
'@/app/components/base/button'
import
{
Loading02
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
MessageDotsCircle
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
{
ImagePlus
}
from
'@/app/components/base/icons/src/vender/line/images'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
Plan
}
from
'@/app/components/billing/type'
import
{
imageUpload
}
from
'@/app/components/base/image-uploader/utils'
import
type
{}
from
'@/app/components/base/image-uploader/utils'
import
{
useToastContext
}
from
'@/app/components/base/toast'
import
{
updateCurrentWorkspace
,
}
from
'@/service/common'
import
{
useAppContext
}
from
'@/context/app-context'
import
{
API_PREFIX
}
from
'@/config'
const
ALLOW_FILE_EXTENSIONS
=
[
'svg'
,
'png'
]
const
CustomWebAppBrand
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useToastContext
()
const
{
plan
}
=
useProviderContext
()
const
{
currentWorkspace
,
mutateCurrentWorkspace
,
isCurrentWorkspaceManager
,
}
=
useAppContext
()
const
[
fileId
,
setFileId
]
=
useState
(
''
)
const
[
uploadProgress
,
setUploadProgress
]
=
useState
(
0
)
const
isSandbox
=
plan
.
type
===
Plan
.
sandbox
const
uploading
=
uploadProgress
>
0
&&
uploadProgress
<
100
const
webappLogo
=
currentWorkspace
.
custom_config
?.
replace_webapp_logo
||
''
const
webappBrandRemoved
=
currentWorkspace
.
custom_config
?.
remove_webapp_brand
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
file
=
e
.
target
.
files
?.[
0
]
if
(
!
file
)
return
if
(
file
.
size
>
5
*
1024
*
1024
)
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerLimit'
,
{
size
:
5
})
})
return
}
imageUpload
({
file
,
onProgressCallback
:
(
progress
)
=>
{
setUploadProgress
(
progress
)
},
onSuccessCallback
:
(
res
)
=>
{
setUploadProgress
(
100
)
setFileId
(
res
.
id
)
},
onErrorCallback
:
()
=>
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerUploadError'
)
})
setUploadProgress
(
-
1
)
},
},
false
,
'/workspaces/custom-config/webapp-logo/upload'
)
}
const
handleApply
=
async
()
=>
{
await
updateCurrentWorkspace
({
url
:
'/workspaces/custom-config'
,
body
:
{
remove_webapp_brand
:
webappBrandRemoved
,
replace_webapp_logo
:
fileId
,
},
})
mutateCurrentWorkspace
()
setFileId
(
''
)
}
const
handleRestore
=
async
()
=>
{
await
updateCurrentWorkspace
({
url
:
'/workspaces/custom-config'
,
body
:
{
remove_webapp_brand
:
false
,
replace_webapp_logo
:
null
,
},
})
mutateCurrentWorkspace
()
}
const
handleSwitch
=
async
(
checked
:
boolean
)
=>
{
await
updateCurrentWorkspace
({
url
:
'/workspaces/custom-config'
,
body
:
{
remove_webapp_brand
:
checked
,
replace_webapp_logo
:
webappLogo
,
},
})
mutateCurrentWorkspace
()
}
const
handleCancel
=
()
=>
{
setFileId
(
''
)
setUploadProgress
(
0
)
}
return
(
<
div
className=
'py-4'
>
<
div
className=
'mb-2 text-sm font-medium text-gray-900'
>
{
t
(
'custom.webapp.title'
)
}
</
div
>
<
div
className=
'relative mb-4 pl-4 pb-6 pr-[119px] rounded-xl border-[0.5px] border-black/[0.08] shadow-xs bg-gray-50 overflow-hidden'
>
<
div
className=
{
`${s.mask} absolute top-0 left-0 w-full -bottom-2 z-10`
}
></
div
>
<
div
className=
'flex items-center -mt-2 mb-4 p-6 bg-white rounded-xl'
>
<
div
className=
'flex items-center px-4 w-[125px] h-9 rounded-lg bg-primary-600 border-[0.5px] border-primary-700 shadow-xs'
>
<
MessageDotsCircle
className=
'shrink-0 mr-2 w-4 h-4 text-white'
/>
<
div
className=
'grow h-2 rounded-sm bg-white opacity-50'
/>
</
div
>
</
div
>
<
div
className=
'flex items-center h-5 justify-between'
>
<
div
className=
'w-[369px] h-1.5 rounded-sm bg-gray-200 opacity-80'
/>
{
!
webappBrandRemoved
&&
(
<
div
className=
'flex items-center text-[10px] font-medium text-gray-400'
>
POWERED BY
{
webappLogo
?
<
img
key=
{
webappLogo
}
src=
{
`${API_PREFIX.slice(0, -12)}/files/workspaces/${currentWorkspace.id}/webapp-logo`
}
alt=
'logo'
className=
'ml-2 block w-auto h-5'
/>
:
<
LogoSite
className=
'ml-2 !h-5'
/>
}
</
div
>
)
}
</
div
>
</
div
>
<
div
className=
'flex items-center justify-between mb-2 px-4 h-14 rounded-xl border-[0.5px] border-gray-200 bg-gray-50 text-sm font-medium text-gray-900'
>
{
t
(
'custom.webapp.removeBrand'
)
}
<
Switch
size=
'l'
defaultValue=
{
webappBrandRemoved
}
disabled=
{
isSandbox
||
!
isCurrentWorkspaceManager
}
onChange=
{
handleSwitch
}
/>
</
div
>
<
div
className=
{
`
flex items-center justify-between px-4 py-3 rounded-xl border-[0.5px] border-gray-200 bg-gray-50
${webappBrandRemoved && 'opacity-30'}
`
}
>
<
div
>
<
div
className=
'leading-5 text-sm font-medium text-gray-900'
>
{
t
(
'custom.webapp.changeLogo'
)
}
</
div
>
<
div
className=
'leading-[18px] text-xs text-gray-500'
>
{
t
(
'custom.webapp.changeLogoTip'
)
}
</
div
>
</
div
>
<
div
className=
'flex items-center'
>
{
!
uploading
&&
(
<
Button
className=
{
`
relative mr-2 !h-8 !px-3 bg-white !text-[13px]
${isSandbox ? 'opacity-40' : ''}
`
}
disabled=
{
isSandbox
||
webappBrandRemoved
||
!
isCurrentWorkspaceManager
}
>
<
ImagePlus
className=
'mr-2 w-4 h-4'
/>
{
(
webappLogo
||
fileId
)
?
t
(
'custom.change'
)
:
t
(
'custom.upload'
)
}
<
input
className=
{
`
absolute block inset-0 opacity-0 text-[0] w-full
${(isSandbox || webappBrandRemoved) ? 'cursor-not-allowed' : 'cursor-pointer'}
`
}
onClick=
{
e
=>
(
e
.
target
as
HTMLInputElement
).
value
=
''
}
type=
'file'
accept=
{
ALLOW_FILE_EXTENSIONS
.
map
(
ext
=>
`.${ext}`
).
join
(
','
)
}
onChange=
{
handleChange
}
disabled=
{
isSandbox
||
webappBrandRemoved
||
!
isCurrentWorkspaceManager
}
/>
</
Button
>
)
}
{
uploading
&&
(
<
Button
className=
'relative mr-2 !h-8 !px-3 bg-white !text-[13px] opacity-40'
disabled=
{
true
}
>
<
Loading02
className=
'animate-spin mr-2 w-4 h-4'
/>
{
t
(
'custom.uploading'
)
}
</
Button
>
)
}
{
fileId
&&
(
<>
<
Button
type=
'primary'
className=
'mr-2 !h-8 !px-3 !py-0 !text-[13px]'
onClick=
{
handleApply
}
disabled=
{
webappBrandRemoved
||
!
isCurrentWorkspaceManager
}
>
{
t
(
'custom.apply'
)
}
</
Button
>
<
Button
className=
'mr-2 !h-8 !px-3 !text-[13px] bg-white'
onClick=
{
handleCancel
}
disabled=
{
webappBrandRemoved
||
!
isCurrentWorkspaceManager
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
</>
)
}
<
div
className=
'mr-2 h-5 w-[1px] bg-black/5'
></
div
>
<
Button
className=
{
`
!h-8 !px-3 bg-white !text-[13px]
${isSandbox ? 'opacity-40' : ''}
`
}
disabled=
{
isSandbox
||
(
!
webappLogo
&&
!
webappBrandRemoved
)
||
webappBrandRemoved
||
!
isCurrentWorkspaceManager
}
onClick=
{
handleRestore
}
>
{
t
(
'custom.restore'
)
}
</
Button
>
</
div
>
</
div
>
{
uploadProgress
===
-
1
&&
(
<
div
className=
'mt-2 text-xs text-[#D92D20]'
>
{
t
(
'custom.uploadedFail'
)
}
</
div
>
)
}
</
div
>
)
}
export
default
CustomWebAppBrand
web/app/components/custom/custom-web-app-brand/style.module.css
0 → 100644
View file @
5bb84193
.mask
{
background
:
linear-gradient
(
273deg
,
rgba
(
255
,
255
,
255
,
0.00
)
51.75%
,
rgba
(
255
,
255
,
255
,
0.80
)
115.32%
);
}
\ No newline at end of file
web/app/components/custom/style.module.css
0 → 100644
View file @
5bb84193
.textGradient
{
background
:
linear-gradient
(
92deg
,
#2250F2
-29.55%
,
#0EBCF3
75.22%
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
}
\ No newline at end of file
web/app/components/header/account-setting/index.tsx
View file @
5bb84193
...
@@ -14,6 +14,7 @@ import DataSourcePage from './data-source-page'
...
@@ -14,6 +14,7 @@ import DataSourcePage from './data-source-page'
import
ModelPage
from
'./model-page'
import
ModelPage
from
'./model-page'
import
s
from
'./index.module.css'
import
s
from
'./index.module.css'
import
BillingPage
from
'@/app/components/billing/billing-page'
import
BillingPage
from
'@/app/components/billing/billing-page'
import
CustomPage
from
'@/app/components/custom/custom-page'
import
Modal
from
'@/app/components/base/modal'
import
Modal
from
'@/app/components/base/modal'
import
{
import
{
Database03
,
Database03
,
...
@@ -26,8 +27,11 @@ import { User01 as User01Solid, Users01 as Users01Solid } from '@/app/components
...
@@ -26,8 +27,11 @@ import { User01 as User01Solid, Users01 as Users01Solid } from '@/app/components
import
{
Globe01
}
from
'@/app/components/base/icons/src/vender/line/mapsAndTravel'
import
{
Globe01
}
from
'@/app/components/base/icons/src/vender/line/mapsAndTravel'
import
{
AtSign
,
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
AtSign
,
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
CubeOutline
}
from
'@/app/components/base/icons/src/vender/line/shapes'
import
{
CubeOutline
}
from
'@/app/components/base/icons/src/vender/line/shapes'
import
{
Colors
}
from
'@/app/components/base/icons/src/vender/line/editor'
import
{
Colors
as
ColorsSolid
}
from
'@/app/components/base/icons/src/vender/solid/editor'
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
{
IS_CE_EDITION
}
from
'@/config'
const
iconClassName
=
`
const
iconClassName
=
`
w-4 h-4 ml-3 mr-2
w-4 h-4 ml-3 mr-2
...
@@ -96,6 +100,12 @@ export default function AccountSetting({
...
@@ -96,6 +100,12 @@ export default function AccountSetting({
icon
:
<
Webhooks
className=
{
iconClassName
}
/>,
icon
:
<
Webhooks
className=
{
iconClassName
}
/>,
activeIcon
:
<
Webhooks
className=
{
iconClassName
}
/>,
activeIcon
:
<
Webhooks
className=
{
iconClassName
}
/>,
},
},
{
key
:
IS_CE_EDITION
?
false
:
'custom'
,
name
:
t
(
'custom.custom'
),
icon
:
<
Colors
className=
{
iconClassName
}
/>,
activeIcon
:
<
ColorsSolid
className=
{
iconClassName
}
/>,
},
].
filter
(
item
=>
!!
item
.
key
)
as
GroupItem
[]
].
filter
(
item
=>
!!
item
.
key
)
as
GroupItem
[]
})()
})()
...
@@ -206,6 +216,7 @@ export default function AccountSetting({
...
@@ -206,6 +216,7 @@ export default function AccountSetting({
{
activeMenu
===
'data-source'
&&
<
DataSourcePage
/>
}
{
activeMenu
===
'data-source'
&&
<
DataSourcePage
/>
}
{
activeMenu
===
'plugin'
&&
<
PluginPage
/>
}
{
activeMenu
===
'plugin'
&&
<
PluginPage
/>
}
{
activeMenu
===
'api-based-extension'
&&
<
ApiBasedExtensionPage
/>
}
{
activeMenu
===
'api-based-extension'
&&
<
ApiBasedExtensionPage
/>
}
{
activeMenu
===
'custom'
&&
<
CustomPage
/>
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/app/components/share/chat/index.tsx
View file @
5bb84193
...
@@ -71,6 +71,7 @@ const Main: FC<IMainProps> = ({
...
@@ -71,6 +71,7 @@ const Main: FC<IMainProps> = ({
const
[
inited
,
setInited
]
=
useState
<
boolean
>
(
false
)
const
[
inited
,
setInited
]
=
useState
<
boolean
>
(
false
)
const
[
plan
,
setPlan
]
=
useState
<
string
>
(
'basic'
)
// basic/plus/pro
const
[
plan
,
setPlan
]
=
useState
<
string
>
(
'basic'
)
// basic/plus/pro
const
[
canReplaceLogo
,
setCanReplaceLogo
]
=
useState
<
boolean
>
(
false
)
const
[
canReplaceLogo
,
setCanReplaceLogo
]
=
useState
<
boolean
>
(
false
)
const
[
customConfig
,
setCustomConfig
]
=
useState
<
any
>
(
null
)
// in mobile, show sidebar by click button
// in mobile, show sidebar by click button
const
[
isShowSidebar
,
{
setTrue
:
showSidebar
,
setFalse
:
hideSidebar
}]
=
useBoolean
(
false
)
const
[
isShowSidebar
,
{
setTrue
:
showSidebar
,
setFalse
:
hideSidebar
}]
=
useBoolean
(
false
)
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
...
@@ -364,10 +365,11 @@ const Main: FC<IMainProps> = ({
...
@@ -364,10 +365,11 @@ const Main: FC<IMainProps> = ({
(
async
()
=>
{
(
async
()
=>
{
try
{
try
{
const
[
appData
,
conversationData
,
appParams
]:
any
=
await
fetchInitData
()
const
[
appData
,
conversationData
,
appParams
]:
any
=
await
fetchInitData
()
const
{
app_id
:
appId
,
site
:
siteInfo
,
plan
,
can_replace_logo
}:
any
=
appData
const
{
app_id
:
appId
,
site
:
siteInfo
,
plan
,
can_replace_logo
,
custom_config
}:
any
=
appData
setAppId
(
appId
)
setAppId
(
appId
)
setPlan
(
plan
)
setPlan
(
plan
)
setCanReplaceLogo
(
can_replace_logo
)
setCanReplaceLogo
(
can_replace_logo
)
setCustomConfig
(
custom_config
)
const
tempIsPublicVersion
=
siteInfo
.
prompt_public
const
tempIsPublicVersion
=
siteInfo
.
prompt_public
setIsPublicVersion
(
tempIsPublicVersion
)
setIsPublicVersion
(
tempIsPublicVersion
)
const
prompt_template
=
''
const
prompt_template
=
''
...
@@ -752,6 +754,7 @@ const Main: FC<IMainProps> = ({
...
@@ -752,6 +754,7 @@ const Main: FC<IMainProps> = ({
onInputsChange=
{
setCurrInputs
}
onInputsChange=
{
setCurrInputs
}
plan=
{
plan
}
plan=
{
plan
}
canReplaceLogo=
{
canReplaceLogo
}
canReplaceLogo=
{
canReplaceLogo
}
customConfig=
{
customConfig
}
></
ConfigSence
>
></
ConfigSence
>
{
{
...
...
web/app/components/share/chat/welcome/index.tsx
View file @
5bb84193
...
@@ -27,6 +27,10 @@ export type IWelcomeProps = {
...
@@ -27,6 +27,10 @@ export type IWelcomeProps = {
onInputsChange
:
(
inputs
:
Record
<
string
,
any
>
)
=>
void
onInputsChange
:
(
inputs
:
Record
<
string
,
any
>
)
=>
void
plan
?:
string
plan
?:
string
canReplaceLogo
?:
boolean
canReplaceLogo
?:
boolean
customConfig
?:
{
remove_webapp_brand
?:
boolean
replace_webapp_logo
?:
string
}
}
}
const
Welcome
:
FC
<
IWelcomeProps
>
=
({
const
Welcome
:
FC
<
IWelcomeProps
>
=
({
...
@@ -34,13 +38,12 @@ const Welcome: FC<IWelcomeProps> = ({
...
@@ -34,13 +38,12 @@ const Welcome: FC<IWelcomeProps> = ({
hasSetInputs
,
hasSetInputs
,
isPublicVersion
,
isPublicVersion
,
siteInfo
,
siteInfo
,
plan
,
promptConfig
,
promptConfig
,
onStartChat
,
onStartChat
,
canEidtInpus
,
canEidtInpus
,
savedInputs
,
savedInputs
,
onInputsChange
,
onInputsChange
,
c
anReplaceLogo
,
c
ustomConfig
,
})
=>
{
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
hasVar
=
promptConfig
.
prompt_variables
.
length
>
0
const
hasVar
=
promptConfig
.
prompt_variables
.
length
>
0
...
@@ -352,10 +355,20 @@ const Welcome: FC<IWelcomeProps> = ({
...
@@ -352,10 +355,20 @@ const Welcome: FC<IWelcomeProps> = ({
</
div
>
</
div
>
:
<
div
>
:
<
div
>
</
div
>
}
</
div
>
}
{
!
canReplaceLogo
&&
<
a
className=
'flex items-center pr-3 space-x-3'
href=
"https://dify.ai/"
target=
"_blank"
>
{
<
span
className=
'uppercase'
>
{
t
(
'share.chat.powerBy'
)
}
</
span
>
customConfig
?.
remove_webapp_brand
<
FootLogo
/>
?
null
</
a
>
}
:
(
<
a
className=
'flex items-center pr-3 space-x-3'
href=
"https://dify.ai/"
target=
"_blank"
>
<
span
className=
'uppercase'
>
{
t
(
'share.chat.powerBy'
)
}
</
span
>
{
customConfig
?.
replace_webapp_logo
?
<
img
src=
{
customConfig
?.
replace_webapp_logo
}
alt=
'logo'
className=
'block w-auto h-5'
/>
:
<
FootLogo
/>
}
</
a
>
)
}
</
div
>
</
div
>
)
}
)
}
</
div
>
</
div
>
...
...
web/app/components/share/chatbot/index.tsx
View file @
5bb84193
...
@@ -55,6 +55,7 @@ const Main: FC<IMainProps> = ({
...
@@ -55,6 +55,7 @@ const Main: FC<IMainProps> = ({
const
[
inited
,
setInited
]
=
useState
<
boolean
>
(
false
)
const
[
inited
,
setInited
]
=
useState
<
boolean
>
(
false
)
const
[
plan
,
setPlan
]
=
useState
<
string
>
(
'basic'
)
// basic/plus/pro
const
[
plan
,
setPlan
]
=
useState
<
string
>
(
'basic'
)
// basic/plus/pro
const
[
canReplaceLogo
,
setCanReplaceLogo
]
=
useState
<
boolean
>
(
false
)
const
[
canReplaceLogo
,
setCanReplaceLogo
]
=
useState
<
boolean
>
(
false
)
const
[
customConfig
,
setCustomConfig
]
=
useState
<
any
>
(
null
)
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
siteInfo
?.
title
)
{
if
(
siteInfo
?.
title
)
{
...
@@ -283,10 +284,11 @@ const Main: FC<IMainProps> = ({
...
@@ -283,10 +284,11 @@ const Main: FC<IMainProps> = ({
(
async
()
=>
{
(
async
()
=>
{
try
{
try
{
const
[
appData
,
conversationData
,
appParams
]:
any
=
await
fetchInitData
()
const
[
appData
,
conversationData
,
appParams
]:
any
=
await
fetchInitData
()
const
{
app_id
:
appId
,
site
:
siteInfo
,
plan
,
can_replace_logo
}:
any
=
appData
const
{
app_id
:
appId
,
site
:
siteInfo
,
plan
,
can_replace_logo
,
custom_config
}:
any
=
appData
setAppId
(
appId
)
setAppId
(
appId
)
setPlan
(
plan
)
setPlan
(
plan
)
setCanReplaceLogo
(
can_replace_logo
)
setCanReplaceLogo
(
can_replace_logo
)
setCustomConfig
(
custom_config
)
const
tempIsPublicVersion
=
siteInfo
.
prompt_public
const
tempIsPublicVersion
=
siteInfo
.
prompt_public
setIsPublicVersion
(
tempIsPublicVersion
)
setIsPublicVersion
(
tempIsPublicVersion
)
const
prompt_template
=
''
const
prompt_template
=
''
...
@@ -592,6 +594,7 @@ const Main: FC<IMainProps> = ({
...
@@ -592,6 +594,7 @@ const Main: FC<IMainProps> = ({
onInputsChange=
{
setCurrInputs
}
onInputsChange=
{
setCurrInputs
}
plan=
{
plan
}
plan=
{
plan
}
canReplaceLogo=
{
canReplaceLogo
}
canReplaceLogo=
{
canReplaceLogo
}
customConfig=
{
customConfig
}
></
ConfigScene
>
></
ConfigScene
>
{
{
shouldReload
&&
(
shouldReload
&&
(
...
...
web/app/components/share/chatbot/welcome/index.tsx
View file @
5bb84193
...
@@ -27,6 +27,10 @@ export type IWelcomeProps = {
...
@@ -27,6 +27,10 @@ export type IWelcomeProps = {
onInputsChange
:
(
inputs
:
Record
<
string
,
any
>
)
=>
void
onInputsChange
:
(
inputs
:
Record
<
string
,
any
>
)
=>
void
plan
:
string
plan
:
string
canReplaceLogo
?:
boolean
canReplaceLogo
?:
boolean
customConfig
?:
{
remove_webapp_brand
?:
boolean
replace_webapp_logo
?:
string
}
}
}
const
Welcome
:
FC
<
IWelcomeProps
>
=
({
const
Welcome
:
FC
<
IWelcomeProps
>
=
({
...
@@ -34,13 +38,12 @@ const Welcome: FC<IWelcomeProps> = ({
...
@@ -34,13 +38,12 @@ const Welcome: FC<IWelcomeProps> = ({
hasSetInputs
,
hasSetInputs
,
isPublicVersion
,
isPublicVersion
,
siteInfo
,
siteInfo
,
plan
,
promptConfig
,
promptConfig
,
onStartChat
,
onStartChat
,
canEditInputs
,
canEditInputs
,
savedInputs
,
savedInputs
,
onInputsChange
,
onInputsChange
,
c
anReplaceLogo
,
c
ustomConfig
,
})
=>
{
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
hasVar
=
promptConfig
.
prompt_variables
.
length
>
0
const
hasVar
=
promptConfig
.
prompt_variables
.
length
>
0
...
@@ -353,10 +356,20 @@ const Welcome: FC<IWelcomeProps> = ({
...
@@ -353,10 +356,20 @@ const Welcome: FC<IWelcomeProps> = ({
</
div
>
</
div
>
:
<
div
>
:
<
div
>
</
div
>
}
</
div
>
}
{
!
canReplaceLogo
&&
<
a
className=
'flex items-center pr-3 space-x-3'
href=
"https://dify.ai/"
target=
"_blank"
>
{
<
span
className=
'uppercase'
>
{
t
(
'share.chat.powerBy'
)
}
</
span
>
customConfig
?.
remove_webapp_brand
<
FootLogo
/>
?
null
</
a
>
}
:
(
<
a
className=
'flex items-center pr-3 space-x-3'
href=
"https://dify.ai/"
target=
"_blank"
>
<
span
className=
'uppercase'
>
{
t
(
'share.chat.powerBy'
)
}
</
span
>
{
customConfig
?.
replace_webapp_logo
?
<
img
src=
{
customConfig
?.
replace_webapp_logo
}
alt=
'logo'
className=
'block w-auto h-5'
/>
:
<
FootLogo
/>
}
</
a
>
)
}
</
div
>
</
div
>
)
}
)
}
</
div
>
</
div
>
...
...
web/i18n/i18next-config.ts
View file @
5bb84193
...
@@ -37,6 +37,8 @@ import exploreEn from './lang/explore.en'
...
@@ -37,6 +37,8 @@ import exploreEn from './lang/explore.en'
import
exploreZh
from
'./lang/explore.zh'
import
exploreZh
from
'./lang/explore.zh'
import
billingEn
from
'./lang/billing.en'
import
billingEn
from
'./lang/billing.en'
import
billingZh
from
'./lang/billing.zh'
import
billingZh
from
'./lang/billing.zh'
import
customEn
from
'./lang/custom.en'
import
customZh
from
'./lang/custom.zh'
const
resources
=
{
const
resources
=
{
'en'
:
{
'en'
:
{
...
@@ -62,6 +64,7 @@ const resources = {
...
@@ -62,6 +64,7 @@ const resources = {
explore
:
exploreEn
,
explore
:
exploreEn
,
// billing
// billing
billing
:
billingEn
,
billing
:
billingEn
,
custom
:
customEn
,
},
},
},
},
'zh-Hans'
:
{
'zh-Hans'
:
{
...
@@ -86,6 +89,7 @@ const resources = {
...
@@ -86,6 +89,7 @@ const resources = {
datasetCreation
:
datasetCreationZh
,
datasetCreation
:
datasetCreationZh
,
explore
:
exploreZh
,
explore
:
exploreZh
,
billing
:
billingZh
,
billing
:
billingZh
,
custom
:
customZh
,
},
},
},
},
}
}
...
...
web/i18n/lang/custom.en.ts
0 → 100644
View file @
5bb84193
const
translation
=
{
custom
:
'Customization'
,
upgradeTip
:
{
prefix
:
'Upgrade your plan to'
,
suffix
:
'customize your brand.'
,
},
webapp
:
{
title
:
'Customize web app brand'
,
removeBrand
:
'Remove Powered by Dify'
,
changeLogo
:
'Change Powered by Brand Image'
,
changeLogoTip
:
'SVG or PNG format with a minimum size of 40x40px'
,
},
app
:
{
title
:
'Customize app header brand'
,
changeLogoTip
:
'SVG or PNG format with a minimum size of 80x80px'
,
},
upload
:
'Upload'
,
uploading
:
'Uploading'
,
uploadedFail
:
'Image upload failed, please re-upload.'
,
change
:
'Change'
,
apply
:
'Apply'
,
restore
:
'Restore Defaults'
,
customize
:
{
contactUs
:
' contact us '
,
prefix
:
'To customize the brand logo within the app, please'
,
suffix
:
'to upgrade to the Enterprise edition.'
,
},
}
export
default
translation
web/i18n/lang/custom.zh.ts
0 → 100644
View file @
5bb84193
const
translation
=
{
custom
:
'定制'
,
upgradeTip
:
{
prefix
:
'升级您的计划以'
,
suffix
:
'定制您的品牌。'
,
},
webapp
:
{
title
:
'定制 web app 品牌'
,
removeBrand
:
'移除 Powered by Dify'
,
changeLogo
:
'更改 Powered by Brand 图片'
,
changeLogoTip
:
'SVG 或 PNG 格式,最小尺寸为 40x40px'
,
},
app
:
{
title
:
'定制应用品牌'
,
changeLogoTip
:
'SVG 或 PNG 格式,最小尺寸为 80x80px'
,
},
upload
:
'上传'
,
uploading
:
'上传中'
,
uploadedFail
:
'图片上传失败,请重新上传。'
,
change
:
'更改'
,
apply
:
'应用'
,
restore
:
'恢复默认'
,
customize
:
{
contactUs
:
'联系我们'
,
prefix
:
'如需在 Dify 内自定义品牌图标,请'
,
suffix
:
'升级至企业版。'
,
},
}
export
default
translation
web/models/common.ts
View file @
5bb84193
...
@@ -123,6 +123,10 @@ export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
...
@@ -123,6 +123,10 @@ export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & {
providers
:
Provider
[]
providers
:
Provider
[]
in_trail
:
boolean
in_trail
:
boolean
trial_end_reason
?:
string
trial_end_reason
?:
string
custom_config
?:
{
remove_webapp_brand
?:
boolean
replace_webapp_logo
?:
string
}
}
}
export
type
DataSourceNotionPage
=
{
export
type
DataSourceNotionPage
=
{
...
...
web/service/base.ts
View file @
5bb84193
...
@@ -297,7 +297,7 @@ const baseFetch = <T>(
...
@@ -297,7 +297,7 @@ const baseFetch = <T>(
])
as
Promise
<
T
>
])
as
Promise
<
T
>
}
}
export
const
upload
=
(
options
:
any
,
isPublicAPI
?:
boolean
):
Promise
<
any
>
=>
{
export
const
upload
=
(
options
:
any
,
isPublicAPI
?:
boolean
,
url
?:
string
):
Promise
<
any
>
=>
{
const
urlPrefix
=
isPublicAPI
?
PUBLIC_API_PREFIX
:
API_PREFIX
const
urlPrefix
=
isPublicAPI
?
PUBLIC_API_PREFIX
:
API_PREFIX
let
token
=
''
let
token
=
''
if
(
isPublicAPI
)
{
if
(
isPublicAPI
)
{
...
@@ -318,7 +318,7 @@ export const upload = (options: any, isPublicAPI?: boolean): Promise<any> => {
...
@@ -318,7 +318,7 @@ export const upload = (options: any, isPublicAPI?: boolean): Promise<any> => {
}
}
const
defaultOptions
=
{
const
defaultOptions
=
{
method
:
'POST'
,
method
:
'POST'
,
url
:
`
${
urlPrefix
}
/files/upload`
,
url
:
url
?
`
${
urlPrefix
}${
url
}
`
:
`
${
urlPrefix
}
/files/upload`
,
headers
:
{
headers
:
{
Authorization
:
`Bearer
${
token
}
`
,
Authorization
:
`Bearer
${
token
}
`
,
},
},
...
...
web/service/common.ts
View file @
5bb84193
...
@@ -103,6 +103,10 @@ export const fetchCurrentWorkspace: Fetcher<ICurrentWorkspace, { url: string; pa
...
@@ -103,6 +103,10 @@ export const fetchCurrentWorkspace: Fetcher<ICurrentWorkspace, { url: string; pa
return
get
<
ICurrentWorkspace
>
(
url
,
{
params
})
return
get
<
ICurrentWorkspace
>
(
url
,
{
params
})
}
}
export
const
updateCurrentWorkspace
:
Fetcher
<
ICurrentWorkspace
,
{
url
:
string
;
body
:
Record
<
string
,
any
>
}
>
=
({
url
,
body
})
=>
{
return
post
<
ICurrentWorkspace
>
(
url
,
{
body
})
}
export
const
fetchWorkspaces
:
Fetcher
<
{
workspaces
:
IWorkspace
[]
},
{
url
:
string
;
params
:
Record
<
string
,
any
>
}
>
=
({
url
,
params
})
=>
{
export
const
fetchWorkspaces
:
Fetcher
<
{
workspaces
:
IWorkspace
[]
},
{
url
:
string
;
params
:
Record
<
string
,
any
>
}
>
=
({
url
,
params
})
=>
{
return
get
<
{
workspaces
:
IWorkspace
[]
}
>
(
url
,
{
params
})
return
get
<
{
workspaces
:
IWorkspace
[]
}
>
(
url
,
{
params
})
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment