Selaa lähdekoodia

Feat/chat custom disclaimer (#4306)

Patryk Garstecki 11 kuukautta sitten
vanhempi
commit
aa13d14019
43 muutettua tiedostoa jossa 350 lisäystä ja 159 poistoa
  1. 58 29
      api/constants/recommended_apps.json
  2. 6 0
      api/controllers/console/admin.py
  3. 2 0
      api/controllers/console/app/site.py
  4. 1 0
      api/controllers/console/explore/recommended_app.py
  5. 4 0
      api/controllers/console/workspace/tool_providers.py
  6. 1 0
      api/controllers/web/site.py
  7. 2 1
      api/core/tools/tool_manager.py
  8. 2 0
      api/fields/app_fields.py
  9. 45 0
      api/migrations/versions/5fda94355fce_custom_disclaimer.py
  10. 2 0
      api/models/model.py
  11. 2 0
      api/models/tools.py
  12. 1 0
      api/services/recommended_app_service.py
  13. 5 3
      api/services/tools_manage_service.py
  14. 45 38
      web/app/components/app/chat/index.tsx
  15. 12 3
      web/app/components/app/overview/settings/index.tsx
  16. 92 85
      web/app/components/base/chat/chat/chat-input.tsx
  17. 1 0
      web/app/components/share/chat/index.tsx
  18. 1 0
      web/app/components/share/chatbot/index.tsx
  19. 13 0
      web/app/components/tools/edit-custom-collection-modal/index.tsx
  20. 1 0
      web/app/components/tools/types.ts
  21. 3 0
      web/i18n/de-DE/app-overview.ts
  22. 2 0
      web/i18n/de-DE/tools.ts
  23. 3 0
      web/i18n/en-US/app-overview.ts
  24. 2 0
      web/i18n/en-US/tools.ts
  25. 3 0
      web/i18n/fr-FR/app-overview.ts
  26. 2 0
      web/i18n/fr-FR/tools.ts
  27. 3 0
      web/i18n/ja-JP/app-overview.ts
  28. 2 0
      web/i18n/ja-JP/tools.ts
  29. 3 0
      web/i18n/pl-PL/app-overview.ts
  30. 2 0
      web/i18n/pl-PL/tools.ts
  31. 3 0
      web/i18n/pt-BR/app-overview.ts
  32. 2 0
      web/i18n/pt-BR/tools.ts
  33. 3 0
      web/i18n/uk-UA/app-overview.ts
  34. 2 0
      web/i18n/uk-UA/tools.ts
  35. 3 0
      web/i18n/vi-VN/app-overview.ts
  36. 2 0
      web/i18n/vi-VN/tools.ts
  37. 3 0
      web/i18n/zh-Hans/app-overview.ts
  38. 2 0
      web/i18n/zh-Hans/tools.ts
  39. 3 0
      web/i18n/zh-Hant/app-overview.ts
  40. 2 0
      web/i18n/zh-Hant/tools.ts
  41. 1 0
      web/models/explore.ts
  42. 1 0
      web/models/share.ts
  43. 2 0
      web/types/app.ts

+ 58 - 29
api/constants/recommended_apps.json

@@ -24,7 +24,8 @@
                     "description": "Welcome to your personalized Investment Analysis Copilot service, where we delve into the depths of stock analysis to provide you with comprehensive insights. \n",
                     "is_listed": true,
                     "position": 0,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -40,7 +41,8 @@
                     "description": "Code interpreter, clarifying the syntax and semantics of the code.",
                     "is_listed": true,
                     "position": 13,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -56,7 +58,8 @@
                     "description": "Hello, I am your creative partner in bringing ideas to vivid life! I can assist you in creating stunning designs by leveraging abilities of DALL\u00b7E 3.  ",
                     "is_listed": true,
                     "position": 4,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -72,7 +75,8 @@
                     "description": "Fully SEO Optimized Article including FAQs",
                     "is_listed": true,
                     "position": 1,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -88,7 +92,8 @@
                     "description": "Generate Flat Style Image",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -104,7 +109,8 @@
                     "description": "A multilingual translator that provides translation capabilities in multiple languages. Input the text you need to translate and select the target language.",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -120,7 +126,8 @@
                     "description": "I am a YouTube Channel Data Analysis Copilot, I am here to provide expert data analysis tailored to your needs. ",
                     "is_listed": true,
                     "position": 2,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -136,7 +143,8 @@
                     "description": "Meeting minutes generator",
                     "is_listed": true,
                     "position": 0,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -152,7 +160,8 @@
                     "description": "Tell me the main elements, I will generate a cyberpunk style image for you. ",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -168,7 +177,8 @@
                     "description": "Write SQL from natural language by pasting in your schema with the request.Please describe your query requirements in natural language and select the target database type.",
                     "is_listed": true,
                     "position": 13,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -184,7 +194,8 @@
                     "description": "Welcome to your personalized travel service with Consultant! \ud83c\udf0d\u2708\ufe0f Ready to embark on a journey filled with adventure and relaxation? Let's dive into creating your unforgettable travel experience. ",
                     "is_listed": true,
                     "position": 3,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -200,7 +211,8 @@
                     "description": "I can answer your questions related to strategic marketing.",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -216,7 +228,8 @@
                     "description": "A simulated front-end interviewer that tests the skill level of front-end development through questioning.",
                     "is_listed": true,
                     "position": 19,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -232,7 +245,8 @@
                     "description": "I'm here to hear about your feature request about Dify and help you flesh it out further. What's on your mind?",
                     "is_listed": true,
                     "position": 6,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 }
             ]
         },
@@ -261,7 +275,8 @@
                     "description": "\u4e00\u4e2a\u6a21\u62df\u7684\u524d\u7aef\u9762\u8bd5\u5b98\uff0c\u901a\u8fc7\u63d0\u95ee\u7684\u65b9\u5f0f\u5bf9\u524d\u7aef\u5f00\u53d1\u7684\u6280\u80fd\u6c34\u5e73\u8fdb\u884c\u68c0\u9a8c\u3002",
                     "is_listed": true,
                     "position": 20,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -277,7 +292,8 @@
                     "description": "\u8f93\u5165\u76f8\u5173\u5143\u7d20\uff0c\u4e3a\u4f60\u751f\u6210\u6241\u5e73\u63d2\u753b\u98ce\u683c\u7684\u5c01\u9762\u56fe\u7247",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -293,7 +309,8 @@
                     "description": "\u4e00\u4e2a\u591a\u8bed\u8a00\u7ffb\u8bd1\u5668\uff0c\u63d0\u4f9b\u591a\u79cd\u8bed\u8a00\u7ffb\u8bd1\u80fd\u529b\uff0c\u8f93\u5165\u4f60\u9700\u8981\u7ffb\u8bd1\u7684\u6587\u672c\uff0c\u9009\u62e9\u76ee\u6807\u8bed\u8a00\u5373\u53ef\u3002\u63d0\u793a\u8bcd\u6765\u81ea\u5b9d\u7389\u3002",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -309,7 +326,8 @@
                     "description": "\u6211\u5c06\u5e2e\u52a9\u4f60\u628a\u81ea\u7136\u8bed\u8a00\u8f6c\u5316\u6210\u6307\u5b9a\u7684\u6570\u636e\u5e93\u67e5\u8be2 SQL \u8bed\u53e5\uff0c\u8bf7\u5728\u4e0b\u65b9\u8f93\u5165\u4f60\u9700\u8981\u67e5\u8be2\u7684\u6761\u4ef6\uff0c\u5e76\u9009\u62e9\u76ee\u6807\u6570\u636e\u5e93\u7c7b\u578b\u3002",
                     "is_listed": true,
                     "position": 12,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -325,7 +343,8 @@
                     "description": "\u9610\u660e\u4ee3\u7801\u7684\u8bed\u6cd5\u548c\u8bed\u4e49\u3002",
                     "is_listed": true,
                     "position": 2,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -341,7 +360,8 @@
                     "description": "\u8f93\u5165\u76f8\u5173\u5143\u7d20\uff0c\u4e3a\u4f60\u751f\u6210\u8d5b\u535a\u670b\u514b\u98ce\u683c\u7684\u63d2\u753b",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -357,7 +377,8 @@
                     "description": "\u6211\u662f\u4e00\u540dSEO\u4e13\u5bb6\uff0c\u53ef\u4ee5\u6839\u636e\u60a8\u63d0\u4f9b\u7684\u6807\u9898\u3001\u5173\u952e\u8bcd\u3001\u76f8\u5173\u4fe1\u606f\u6765\u6279\u91cf\u751f\u6210SEO\u6587\u7ae0\u3002",
                     "is_listed": true,
                     "position": 10,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -373,7 +394,8 @@
                     "description": "\u5e2e\u4f60\u91cd\u65b0\u7ec4\u7ec7\u548c\u8f93\u51fa\u6df7\u4e71\u590d\u6742\u7684\u4f1a\u8bae\u7eaa\u8981\u3002",
                     "is_listed": true,
                     "position": 6,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -389,7 +411,8 @@
                     "description": "\u6b22\u8fce\u4f7f\u7528\u60a8\u7684\u4e2a\u6027\u5316\u7f8e\u80a1\u6295\u8d44\u5206\u6790\u52a9\u624b\uff0c\u5728\u8fd9\u91cc\u6211\u4eec\u6df1\u5165\u7684\u8fdb\u884c\u80a1\u7968\u5206\u6790\uff0c\u4e3a\u60a8\u63d0\u4f9b\u5168\u9762\u7684\u6d1e\u5bdf\u3002",
                     "is_listed": true,
                     "position": 0,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -405,7 +428,8 @@
                     "description": "\u60a8\u597d\uff0c\u6211\u662f\u60a8\u7684\u521b\u610f\u4f19\u4f34\uff0c\u5c06\u5e2e\u52a9\u60a8\u5c06\u60f3\u6cd5\u751f\u52a8\u5730\u5b9e\u73b0\uff01\u6211\u53ef\u4ee5\u534f\u52a9\u60a8\u5229\u7528DALL\u00b7E 3\u7684\u80fd\u529b\u521b\u9020\u51fa\u4ee4\u4eba\u60ca\u53f9\u7684\u8bbe\u8ba1\u3002",
                     "is_listed": true,
                     "position": 4,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -421,7 +445,8 @@
                     "description": "\u7ffb\u8bd1\u4e13\u5bb6\uff1a\u63d0\u4f9b\u4e2d\u82f1\u6587\u4e92\u8bd1",
                     "is_listed": true,
                     "position": 4,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -437,7 +462,8 @@
                     "description": "\u60a8\u7684\u79c1\u4eba\u5b66\u4e60\u5bfc\u5e08\uff0c\u5e2e\u60a8\u5236\u5b9a\u5b66\u4e60\u8ba1\u5212\u5e76\u8f85\u5bfc",
                     "is_listed": true,
                     "position": 26,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -453,7 +479,8 @@
                     "description": "\u5e2e\u4f60\u64b0\u5199\u8bba\u6587\u6587\u732e\u7efc\u8ff0",
                     "is_listed": true,
                     "position": 7,
-                    "privacy_policy": "https://dify.ai"
+                    "privacy_policy": "https://dify.ai",
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -469,7 +496,8 @@
                     "description": "\u4f60\u597d\uff0c\u544a\u8bc9\u6211\u60a8\u60f3\u5206\u6790\u7684 YouTube \u9891\u9053\uff0c\u6211\u5c06\u4e3a\u60a8\u6574\u7406\u4e00\u4efd\u5b8c\u6574\u7684\u6570\u636e\u5206\u6790\u62a5\u544a\u3002",
                     "is_listed": true,
                     "position": 0,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 },
                 {
                     "app": {
@@ -485,7 +513,8 @@
                     "description": "\u6b22\u8fce\u4f7f\u7528\u60a8\u7684\u4e2a\u6027\u5316\u65c5\u884c\u670d\u52a1\u987e\u95ee\uff01\ud83c\udf0d\u2708\ufe0f \u51c6\u5907\u597d\u8e0f\u4e0a\u4e00\u6bb5\u5145\u6ee1\u5192\u9669\u4e0e\u653e\u677e\u7684\u65c5\u7a0b\u4e86\u5417\uff1f\u8ba9\u6211\u4eec\u4e00\u8d77\u6df1\u5165\u6253\u9020\u60a8\u96be\u5fd8\u7684\u65c5\u884c\u4f53\u9a8c\u5427\u3002",
                     "is_listed": true,
                     "position": 0,
-                    "privacy_policy": null
+                    "privacy_policy": null,
+                    "custom_disclaimer": null
                 }
             ]
         },

+ 6 - 0
api/controllers/console/admin.py

@@ -48,6 +48,7 @@ class InsertExploreAppListApi(Resource):
         parser.add_argument('desc', type=str, location='json')
         parser.add_argument('copyright', type=str, location='json')
         parser.add_argument('privacy_policy', type=str, location='json')
+        parser.add_argument('custom_disclaimer', type=str, location='json')
         parser.add_argument('language', type=supported_language, required=True, nullable=False, location='json')
         parser.add_argument('category', type=str, required=True, nullable=False, location='json')
         parser.add_argument('position', type=int, required=True, nullable=False, location='json')
@@ -62,6 +63,7 @@ class InsertExploreAppListApi(Resource):
             desc = args['desc'] if args['desc'] else ''
             copy_right = args['copyright'] if args['copyright'] else ''
             privacy_policy = args['privacy_policy'] if args['privacy_policy'] else ''
+            custom_disclaimer = args['custom_disclaimer'] if args['custom_disclaimer'] else ''
         else:
             desc = site.description if site.description else \
                 args['desc'] if args['desc'] else ''
@@ -69,6 +71,8 @@ class InsertExploreAppListApi(Resource):
                 args['copyright'] if args['copyright'] else ''
             privacy_policy = site.privacy_policy if site.privacy_policy else \
                 args['privacy_policy'] if args['privacy_policy']  else ''
+            custom_disclaimer = site.custom_disclaimer if site.custom_disclaimer else \
+                args['custom_disclaimer'] if args['custom_disclaimer'] else ''
 
         recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == args['app_id']).first()
 
@@ -78,6 +82,7 @@ class InsertExploreAppListApi(Resource):
                 description=desc,
                 copyright=copy_right,
                 privacy_policy=privacy_policy,
+                custom_disclaimer=custom_disclaimer,
                 language=args['language'],
                 category=args['category'],
                 position=args['position']
@@ -93,6 +98,7 @@ class InsertExploreAppListApi(Resource):
             recommended_app.description = desc
             recommended_app.copyright = copy_right
             recommended_app.privacy_policy = privacy_policy
+            recommended_app.custom_disclaimer = custom_disclaimer
             recommended_app.language = args['language']
             recommended_app.category = args['category']
             recommended_app.position = args['position']

+ 2 - 0
api/controllers/console/app/site.py

@@ -23,6 +23,7 @@ def parse_app_site_args():
     parser.add_argument('customize_domain', type=str, required=False, location='json')
     parser.add_argument('copyright', type=str, required=False, location='json')
     parser.add_argument('privacy_policy', type=str, required=False, location='json')
+    parser.add_argument('custom_disclaimer', type=str, required=False, location='json')
     parser.add_argument('customize_token_strategy', type=str, choices=['must', 'allow', 'not_allow'],
                         required=False,
                         location='json')
@@ -56,6 +57,7 @@ class AppSite(Resource):
             'customize_domain',
             'copyright',
             'privacy_policy',
+            'custom_disclaimer',
             'customize_token_strategy',
             'prompt_public'
         ]:

+ 1 - 0
api/controllers/console/explore/recommended_app.py

@@ -21,6 +21,7 @@ recommended_app_fields = {
     'description': fields.String(attribute='description'),
     'copyright': fields.String,
     'privacy_policy': fields.String,
+    'custom_disclaimer': fields.String,
     'category': fields.String,
     'position': fields.Integer,
     'is_listed': fields.Boolean

+ 4 - 0
api/controllers/console/workspace/tool_providers.py

@@ -116,6 +116,7 @@ class ToolApiProviderAddApi(Resource):
         parser.add_argument('provider', type=str, required=True, nullable=False, location='json')
         parser.add_argument('icon', type=dict, required=True, nullable=False, location='json')
         parser.add_argument('privacy_policy', type=str, required=False, nullable=True, location='json')
+        parser.add_argument('custom_disclaimer', type=str, required=False, nullable=True, location='json')
 
         args = parser.parse_args()
 
@@ -128,6 +129,7 @@ class ToolApiProviderAddApi(Resource):
             args['schema_type'],
             args['schema'],
             args.get('privacy_policy', ''),
+            args.get('custom_disclaimer', ''),
         )
 
 class ToolApiProviderGetRemoteSchemaApi(Resource):
@@ -186,6 +188,7 @@ class ToolApiProviderUpdateApi(Resource):
         parser.add_argument('original_provider', type=str, required=True, nullable=False, location='json')
         parser.add_argument('icon', type=dict, required=True, nullable=False, location='json')
         parser.add_argument('privacy_policy', type=str, required=True, nullable=True, location='json')
+        parser.add_argument('custom_disclaimer', type=str, required=True, nullable=True, location='json')
 
         args = parser.parse_args()
 
@@ -199,6 +202,7 @@ class ToolApiProviderUpdateApi(Resource):
             args['schema_type'],
             args['schema'],
             args['privacy_policy'],
+            args['custom_disclaimer'],
         )
 
 class ToolApiProviderDeleteApi(Resource):

+ 1 - 0
api/controllers/web/site.py

@@ -31,6 +31,7 @@ class AppSiteApi(WebApiResource):
         'description': fields.String,
         'copyright': fields.String,
         'privacy_policy': fields.String,
+        'custom_disclaimer': fields.String,
         'default_language': fields.String,
         'prompt_public': fields.Boolean
     }

+ 2 - 1
api/core/tools/tool_manager.py

@@ -487,7 +487,8 @@ class ToolManager:
             'icon': icon,
             'description': provider.description,
             'credentials': masked_credentials,
-            'privacy_policy': provider.privacy_policy
+            'privacy_policy': provider.privacy_policy,
+            'custom_disclaimer': provider.custom_disclaimer
         })
 
     @classmethod

+ 2 - 0
api/fields/app_fields.py

@@ -113,6 +113,7 @@ site_fields = {
     'customize_domain': fields.String,
     'copyright': fields.String,
     'privacy_policy': fields.String,
+    'custom_disclaimer': fields.String,
     'customize_token_strategy': fields.String,
     'prompt_public': fields.Boolean,
     'app_base_url': fields.String,
@@ -146,6 +147,7 @@ app_site_fields = {
     'customize_domain': fields.String,
     'copyright': fields.String,
     'privacy_policy': fields.String,
+    'custom_disclaimer': fields.String,
     'customize_token_strategy': fields.String,
     'prompt_public': fields.Boolean
 }

+ 45 - 0
api/migrations/versions/5fda94355fce_custom_disclaimer.py

@@ -0,0 +1,45 @@
+"""Custom Disclaimer
+
+Revision ID: 5fda94355fce
+Revises: 47cc7df8c4f3
+Create Date: 2024-05-10 20:04:45.806549
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+import models as models
+
+# revision identifiers, used by Alembic.
+revision = '5fda94355fce'
+down_revision = '47cc7df8c4f3'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('recommended_apps', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('custom_disclaimer', sa.String(length=255), nullable=False))
+
+    with op.batch_alter_table('sites', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('custom_disclaimer', sa.String(length=255), nullable=True))
+
+    with op.batch_alter_table('tool_api_providers', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('custom_disclaimer', sa.String(length=255), nullable=True))
+
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    with op.batch_alter_table('tool_api_providers', schema=None) as batch_op:
+        batch_op.drop_column('custom_disclaimer')
+
+    with op.batch_alter_table('sites', schema=None) as batch_op:
+        batch_op.drop_column('custom_disclaimer')
+
+    with op.batch_alter_table('recommended_apps', schema=None) as batch_op:
+        batch_op.drop_column('custom_disclaimer')
+
+    # ### end Alembic commands ###

+ 2 - 0
api/models/model.py

@@ -435,6 +435,7 @@ class RecommendedApp(db.Model):
     description = db.Column(db.JSON, nullable=False)
     copyright = db.Column(db.String(255), nullable=False)
     privacy_policy = db.Column(db.String(255), nullable=False)
+    custom_disclaimer = db.Column(db.String(255), nullable=False)
     category = db.Column(db.String(255), nullable=False)
     position = db.Column(db.Integer, nullable=False, default=0)
     is_listed = db.Column(db.Boolean, nullable=False, default=True)
@@ -1042,6 +1043,7 @@ class Site(db.Model):
     default_language = db.Column(db.String(255), nullable=False)
     copyright = db.Column(db.String(255))
     privacy_policy = db.Column(db.String(255))
+    custom_disclaimer = db.Column(db.String(255))
     customize_domain = db.Column(db.String(255))
     customize_token_strategy = db.Column(db.String(255), nullable=False)
     prompt_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))

+ 2 - 0
api/models/tools.py

@@ -107,6 +107,8 @@ class ApiToolProvider(db.Model):
     credentials_str = db.Column(db.Text, nullable=False)
     # privacy policy
     privacy_policy = db.Column(db.String(255), nullable=True)
+    # custom_disclaimer
+    custom_disclaimer = db.Column(db.String(255), nullable=True)
 
     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)'))

+ 1 - 0
api/services/recommended_app_service.py

@@ -86,6 +86,7 @@ class RecommendedAppService:
                 'description': site.description,
                 'copyright': site.copyright,
                 'privacy_policy': site.privacy_policy,
+                'custom_disclaimer': site.custom_disclaimer,
                 'category': recommended_app.category,
                 'position': recommended_app.position,
                 'is_listed': recommended_app.is_listed

+ 5 - 3
api/services/tools_manage_service.py

@@ -177,7 +177,7 @@ class ToolManageService:
     @staticmethod
     def create_api_tool_provider(
         user_id: str, tenant_id: str, provider_name: str, icon: dict, credentials: dict,
-        schema_type: str, schema: str, privacy_policy: str
+        schema_type: str, schema: str, privacy_policy: str, custom_disclaimer: str
     ):
         """
             create api tool provider
@@ -213,7 +213,8 @@ class ToolManageService:
             schema_type_str=schema_type,
             tools_str=json.dumps(jsonable_encoder(tool_bundles)),
             credentials_str={},
-            privacy_policy=privacy_policy
+            privacy_policy=privacy_policy,
+            custom_disclaimer=custom_disclaimer
         )
 
         if 'auth_type' not in credentials:
@@ -364,7 +365,7 @@ class ToolManageService:
     @staticmethod
     def update_api_tool_provider(
         user_id: str, tenant_id: str, provider_name: str, original_provider: str, icon: dict, credentials: dict, 
-        schema_type: str, schema: str, privacy_policy: str
+        schema_type: str, schema: str, privacy_policy: str, custom_disclaimer: str
     ):
         """
             update api tool provider
@@ -394,6 +395,7 @@ class ToolManageService:
         provider.schema_type_str = ApiProviderSchemaType.OPENAPI.value
         provider.tools_str = json.dumps(jsonable_encoder(tool_bundles))
         provider.privacy_policy = privacy_policy
+        provider.custom_disclaimer = custom_disclaimer
 
         if 'auth_type' not in credentials:
             raise ValueError('auth_type is required')

+ 45 - 38
web/app/components/app/chat/index.tsx

@@ -67,6 +67,7 @@ export type IChatProps = {
   visionConfig?: VisionSettings
   supportAnnotation?: boolean
   allToolIcons?: Record<string, string | Emoji>
+  customDisclaimer?: string
 }
 
 const Chat: FC<IChatProps> = ({
@@ -102,6 +103,7 @@ const Chat: FC<IChatProps> = ({
   supportAnnotation,
   onChatListChange,
   allToolIcons,
+  customDisclaimer,
 }) => {
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
@@ -358,44 +360,46 @@ const Chat: FC<IChatProps> = ({
               </div>
             </div>
           )}
-          <div className={cn('p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto', isDragActive && 'border-primary-600')}>
-            {visionConfig?.enabled && (
-              <>
-                <div className='absolute bottom-2 left-2 flex items-center'>
-                  <ChatImageUploader
-                    settings={visionConfig}
-                    onUpload={onUpload}
-                    disabled={files.length >= visionConfig.number_limits}
-                  />
-                  <div className='mx-1 w-[1px] h-4 bg-black/5' />
-                </div>
-                <div className='pl-[52px]'>
-                  <ImageList
-                    list={files}
-                    onRemove={onRemove}
-                    onReUpload={onReUpload}
-                    onImageLinkLoadSuccess={onImageLinkLoadSuccess}
-                    onImageLinkLoadError={onImageLinkLoadError}
-                  />
-                </div>
-              </>
-            )}
-            <Textarea
-              className={`
-                block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
-                ${visionConfig?.enabled && 'pl-12'}
-              `}
-              value={query}
-              onChange={handleContentChange}
-              onKeyUp={handleKeyUp}
-              onKeyDown={handleKeyDown}
-              onPaste={onPaste}
-              onDragEnter={onDragEnter}
-              onDragLeave={onDragLeave}
-              onDragOver={onDragOver}
-              onDrop={onDrop}
-              autoSize
-            />
+          <div className='relative'>
+            <div className={cn('relative p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto', isDragActive && 'border-primary-600')}>
+              {visionConfig?.enabled && (
+                <>
+                  <div className='absolute bottom-2 left-2 flex items-center'>
+                    <ChatImageUploader
+                      settings={visionConfig}
+                      onUpload={onUpload}
+                      disabled={files.length >= visionConfig.number_limits}
+                    />
+                    <div className='mx-1 w-[1px] h-4 bg-black/5' />
+                  </div>
+                  <div className='pl-[52px]'>
+                    <ImageList
+                      list={files}
+                      onRemove={onRemove}
+                      onReUpload={onReUpload}
+                      onImageLinkLoadSuccess={onImageLinkLoadSuccess}
+                      onImageLinkLoadError={onImageLinkLoadError}
+                    />
+                  </div>
+                </>
+              )}
+              <Textarea
+                className={`
+                  block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
+                  ${visionConfig?.enabled && 'pl-12'}
+                `}
+                value={query}
+                onChange={handleContentChange}
+                onKeyUp={handleKeyUp}
+                onKeyDown={handleKeyDown}
+                onPaste={onPaste}
+                onDragEnter={onDragEnter}
+                onDragLeave={onDragLeave}
+                onDragOver={onDragOver}
+                onDrop={onDrop}
+                autoSize
+              />
+            </div>
             <div className="absolute bottom-2 right-2 flex items-center h-8">
               <div className={`${s.count} mr-4 h-5 leading-5 text-sm bg-gray-50 text-gray-500`}>{query.trim().length}</div>
               {
@@ -440,6 +444,9 @@ const Chat: FC<IChatProps> = ({
               />
             )}
           </div>
+          {customDisclaimer && <div className='text-xs text-gray-500 mt-1 text-center'>
+            {customDisclaimer}
+          </div>}
         </div>
       )}
     </div>

+ 12 - 3
web/app/components/app/overview/settings/index.tsx

@@ -31,6 +31,7 @@ export type ConfigParams = {
   prompt_public: boolean
   copyright: string
   privacy_policy: string
+  custom_disclaimer: string
   icon: string
   icon_background: string
 }
@@ -46,8 +47,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
   const { notify } = useToastContext()
   const [isShowMore, setIsShowMore] = useState(false)
   const { icon, icon_background } = appInfo
-  const { title, description, copyright, privacy_policy, default_language } = appInfo.site
-  const [inputInfo, setInputInfo] = useState({ title, desc: description, copyright, privacyPolicy: privacy_policy })
+  const { title, description, copyright, privacy_policy, custom_disclaimer, default_language } = appInfo.site
+  const [inputInfo, setInputInfo] = useState({ title, desc: description, copyright, privacyPolicy: privacy_policy, customDisclaimer: custom_disclaimer })
   const [language, setLanguage] = useState(default_language)
   const [saveLoading, setSaveLoading] = useState(false)
   const { t } = useTranslation()
@@ -56,7 +57,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
   const [emoji, setEmoji] = useState({ icon, icon_background })
 
   useEffect(() => {
-    setInputInfo({ title, desc: description, copyright, privacyPolicy: privacy_policy })
+    setInputInfo({ title, desc: description, copyright, privacyPolicy: privacy_policy, customDisclaimer: custom_disclaimer })
     setLanguage(default_language)
     setEmoji({ icon, icon_background })
   }, [appInfo])
@@ -81,6 +82,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
       prompt_public: false,
       copyright: inputInfo.copyright,
       privacy_policy: inputInfo.privacyPolicy,
+      custom_disclaimer: inputInfo.customDisclaimer,
       icon: emoji.icon,
       icon_background: emoji.icon_background,
     }
@@ -161,6 +163,13 @@ const SettingsModal: FC<ISettingsModalProps> = ({
             onChange={onChange('privacyPolicy')}
             placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string}
           />
+          <div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.customDisclaimer`)}</div>
+          <p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p>
+          <input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
+            value={inputInfo.customDisclaimer}
+            onChange={onChange('customDisclaimer')}
+            placeholder={t(`${prefixSettings}.more.customDisclaimerPlaceholder`) as string}
+          />
         </>}
         <div className='mt-10 flex justify-end'>
           <Button className='mr-2 flex-shrink-0 !text-sm' onClick={onHide}>{t('common.operation.cancel')}</Button>

+ 92 - 85
web/app/components/base/chat/chat/chat-input.tsx

@@ -14,6 +14,7 @@ import type {
   VisionConfig,
 } from '../types'
 import { TransferMethod } from '../types'
+import { useChatWithHistoryContext } from '../chat-with-history/context'
 import TooltipPlus from '@/app/components/base/tooltip-plus'
 import { ToastContext } from '@/app/components/base/toast'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
@@ -40,6 +41,7 @@ const ChatInput: FC<ChatInputProps> = ({
   speechToTextConfig,
   onSend,
 }) => {
+  const { appData } = useChatWithHistoryContext()
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const [voiceInputShow, setVoiceInputShow] = useState(false)
@@ -127,101 +129,106 @@ const ChatInput: FC<ChatInputProps> = ({
   )
 
   return (
-    <div className='relative'>
-      <div
-        className={`
-          p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
-          ${isDragActive && 'border-primary-600'}
-        `}
-      >
-        {
-          visionConfig?.enabled && (
-            <>
-              <div className='absolute bottom-2 left-2 flex items-center'>
-                <ChatImageUploader
-                  settings={visionConfig}
-                  onUpload={onUpload}
-                  disabled={files.length >= visionConfig.number_limits}
-                />
-                <div className='mx-1 w-[1px] h-4 bg-black/5' />
-              </div>
-              <div className='pl-[52px]'>
-                <ImageList
-                  list={files}
-                  onRemove={onRemove}
-                  onReUpload={onReUpload}
-                  onImageLinkLoadSuccess={onImageLinkLoadSuccess}
-                  onImageLinkLoadError={onImageLinkLoadError}
-                />
-              </div>
-            </>
-          )
-        }
-        <Textarea
+    <>
+      <div className='relative'>
+        <div
           className={`
-            block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
-            ${visionConfig?.enabled && 'pl-12'}
+            p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
+            ${isDragActive && 'border-primary-600'} mb-2
           `}
-          value={query}
-          onChange={handleContentChange}
-          onKeyUp={handleKeyUp}
-          onKeyDown={handleKeyDown}
-          onPaste={onPaste}
-          onDragEnter={onDragEnter}
-          onDragLeave={onDragLeave}
-          onDragOver={onDragOver}
-          onDrop={onDrop}
-          autoSize
-        />
-        <div className='absolute bottom-[7px] right-2 flex items-center h-8'>
-          <div className='flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'>
-            {query.trim().length}
-          </div>
+        >
           {
-            query
-              ? (
-                <div className='flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
-                  <XCircle className='w-4 h-4 text-[#98A2B3]' />
+            visionConfig?.enabled && (
+              <>
+                <div className='absolute bottom-2 left-2 flex items-center'>
+                  <ChatImageUploader
+                    settings={visionConfig}
+                    onUpload={onUpload}
+                    disabled={files.length >= visionConfig.number_limits}
+                  />
+                  <div className='mx-1 w-[1px] h-4 bg-black/5' />
                 </div>
-              )
-              : speechToTextConfig?.enabled
+                <div className='pl-[52px]'>
+                  <ImageList
+                    list={files}
+                    onRemove={onRemove}
+                    onReUpload={onReUpload}
+                    onImageLinkLoadSuccess={onImageLinkLoadSuccess}
+                    onImageLinkLoadError={onImageLinkLoadError}
+                  />
+                </div>
+              </>
+            )
+          }
+          <Textarea
+            className={`
+              block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
+              ${visionConfig?.enabled && 'pl-12'}
+            `}
+            value={query}
+            onChange={handleContentChange}
+            onKeyUp={handleKeyUp}
+            onKeyDown={handleKeyDown}
+            onPaste={onPaste}
+            onDragEnter={onDragEnter}
+            onDragLeave={onDragLeave}
+            onDragOver={onDragOver}
+            onDrop={onDrop}
+            autoSize
+          />
+          <div className='absolute bottom-[7px] right-2 flex items-center h-8'>
+            <div className='flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'>
+              {query.trim().length}
+            </div>
+            {
+              query
                 ? (
-                  <div
-                    className='group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
-                    onClick={handleVoiceInputShow}
-                  >
-                    <Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
-                    <Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
+                  <div className='flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
+                    <XCircle className='w-4 h-4 text-[#98A2B3]' />
                   </div>
                 )
-                : null
+                : speechToTextConfig?.enabled
+                  ? (
+                    <div
+                      className='group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
+                      onClick={handleVoiceInputShow}
+                    >
+                      <Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
+                      <Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
+                    </div>
+                  )
+                  : null
+            }
+            <div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
+            {isMobile
+              ? sendBtn
+              : (
+                <TooltipPlus
+                  popupContent={
+                    <div>
+                      <div>{t('common.operation.send')} Enter</div>
+                      <div>{t('common.operation.lineBreak')} Shift Enter</div>
+                    </div>
+                  }
+                >
+                  {sendBtn}
+                </TooltipPlus>
+              )}
+          </div>
+          {
+            voiceInputShow && (
+              <VoiceInput
+                onCancel={() => setVoiceInputShow(false)}
+                onConverted={text => setQuery(text)}
+              />
+            )
           }
-          <div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
-          {isMobile
-            ? sendBtn
-            : (
-              <TooltipPlus
-                popupContent={
-                  <div>
-                    <div>{t('common.operation.send')} Enter</div>
-                    <div>{t('common.operation.lineBreak')} Shift Enter</div>
-                  </div>
-                }
-              >
-                {sendBtn}
-              </TooltipPlus>
-            )}
         </div>
-        {
-          voiceInputShow && (
-            <VoiceInput
-              onCancel={() => setVoiceInputShow(false)}
-              onConverted={text => setQuery(text)}
-            />
-          )
-        }
       </div>
-    </div>
+      {appData?.site?.custom_disclaimer && <div className='text-xs text-gray-500 mt-1 text-center'>
+        {appData.site.custom_disclaimer}
+      </div>}
+    </>
   )
 }
 

+ 1 - 0
web/app/components/share/chat/index.tsx

@@ -929,6 +929,7 @@ const Main: FC<IMainProps> = ({
                       image_file_size_limit: fileUploadConfigResponse ? fileUploadConfigResponse.image_file_size_limit : visionConfig.image_file_size_limit,
                     }}
                     allToolIcons={appMeta?.tool_icons || {}}
+                    customDisclaimer={siteInfo.custom_disclaimer}
                   />
                 </div>
               </div>)

+ 1 - 0
web/app/components/share/chatbot/index.tsx

@@ -800,6 +800,7 @@ const Main: FC<IMainProps> = ({
                     answerIcon={<LogoAvatar className='relative shrink-0' />}
                     visionConfig={visionConfig}
                     allToolIcons={appMeta?.tool_icons || {}}
+                    customDisclaimer={siteInfo.custom_disclaimer}
                   />
                 </div>
               </div>)

+ 13 - 0
web/app/components/tools/edit-custom-collection-modal/index.tsx

@@ -267,6 +267,19 @@ const EditCustomCollectionModal: FC<Props> = ({
                   className='w-full h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} />
               </div>
 
+              <div>
+                <div className={fieldNameClassNames}>{t('tools.createTool.customDisclaimer')}</div>
+                <input
+                  value={customCollection.custom_disclaimer}
+                  onChange={(e) => {
+                    const newCollection = produce(customCollection, (draft) => {
+                      draft.custom_disclaimer = e.target.value
+                    })
+                    setCustomCollection(newCollection)
+                  }}
+                  className='w-full h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('tools.createTool.customDisclaimerPlaceholder') || ''} />
+              </div>
+
             </div>
             <div className={cn(isEdit ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-gray-50 border-t border-black/5')} >
               {

+ 1 - 0
web/app/components/tools/types.ts

@@ -89,6 +89,7 @@ export type CustomCollectionBackend = {
   schema_type: string
   schema: string
   privacy_policy: string
+  custom_disclaimer: string
   tools?: ParamItem[]
 }
 

+ 3 - 0
web/i18n/de-DE/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Datenschutzrichtlinie',
           privacyPolicyPlaceholder: 'Geben Sie den Link zur Datenschutzrichtlinie ein',
           privacyPolicyTip: 'Hilft Besuchern zu verstehen, welche Daten die Anwendung sammelt, siehe Difys <privacyPolicyLink>Datenschutzrichtlinie</privacyPolicyLink>.',
+          customDisclaimer: 'Benutzerdefinierte Haftungsausschluss',
+          customDisclaimerPlaceholder: 'Geben Sie den benutzerdefinierten Haftungsausschluss-Text ein',
+          customDisclaimerTip: 'Der ben userdefinierte Haftungsausschluss-Text wird auf der Clientseite angezeigt und bietet zusätzliche Informationen über die Anwendung',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/de-DE/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'Datenschutzrichtlinie',
     privacyPolicyPlaceholder: 'Bitte Datenschutzrichtlinie eingeben',
+    customDisclaimer: 'Benutzer Haftungsausschluss',
+    customDisclaimerPlaceholder: 'Bitte benutzerdefinierten Haftungsausschluss eingeben',
   },
   test: {
     title: 'Test',

+ 3 - 0
web/i18n/en-US/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Privacy Policy',
           privacyPolicyPlaceholder: 'Enter the privacy policy link',
           privacyPolicyTip: 'Helps visitors understand the data the application collects, see Dify\'s <privacyPolicyLink>Privacy Policy</privacyPolicyLink>.',
+          customDisclaimer: 'Custom Disclaimer',
+          customDisclaimerPlaceholder: 'Enter the custom disclaimer text',
+          customDisclaimerTip: 'Custom disclaimer text will be displayed on the client side, providing additional information about the application',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/en-US/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'Privacy policy',
     privacyPolicyPlaceholder: 'Please enter privacy policy',
+    customDisclaimer: 'Custom disclaimer',
+    customDisclaimerPlaceholder: 'Please enter custom disclaimer',
   },
   test: {
     title: 'Test',

+ 3 - 0
web/i18n/fr-FR/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Politique de confidentialité',
           privacyPolicyPlaceholder: 'Entrez le lien de la politique de confidentialité',
           privacyPolicyTip: 'Aide les visiteurs à comprendre les données collectées par l\'application, voir la <privacyPolicyLink>Politique de confidentialité</privacyPolicyLink> de Dify.',
+          customDisclaimer: 'Clause de non-responsabilité personnalisée',
+          customDisclaimerPlaceholder: 'Entrez le texte de la clause de non-responsabilité personnalisée',
+          customDisclaimerTip: 'Le texte de la clause de non-responsabilité personnalisée sera affiché côté client, fournissant des informations supplémentaires sur l\'application',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/fr-FR/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'Politique de confidentialité',
     privacyPolicyPlaceholder: 'Veuillez entrer la politique de confidentialité',
+    customDisclaimer: 'Clause de non-responsabilité personnalisée',
+    customDisclaimerPlaceholder: 'Entrez le texte de la clause de non-responsabilité personnalisée',
   },
   test: {
     title: 'Test',

+ 3 - 0
web/i18n/ja-JP/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'プライバシーポリシー',
           privacyPolicyPlaceholder: 'プライバシーポリシーリンクを入力してください',
           privacyPolicyTip: '訪問者がアプリケーションが収集するデータを理解し、Difyの<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照できるようにします。',
+          customDisclaimer: 'カスタム免責事項',
+          customDisclaimerPlaceholder: '免責事項を入力してください',
+          customDisclaimerTip: 'アプリケーションの使用に関する免責事項を提供します。',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/ja-JP/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'プライバシーポリシー',
     privacyPolicyPlaceholder: 'プライバシーポリシーを入力してください',
+    customDisclaimer: 'カスタム免責事項',
+    customDisclaimerPlaceholder: 'カスタム免責事項を入力してください',
   },
   test: {
     title: 'テスト',

+ 3 - 0
web/i18n/pl-PL/app-overview.ts

@@ -55,6 +55,9 @@ const translation = {
           privacyPolicyPlaceholder: 'Wprowadź link do polityki prywatności',
           privacyPolicyTip:
             'Pomaga odwiedzającym zrozumieć, jakie dane zbiera aplikacja, zobacz <privacyPolicyLink>Politykę prywatności Dify</privacyPolicyLink>.',
+          customDisclaimer: 'Oświadczenie o ochronie danych',
+          customDisclaimerPlaceholder: 'Wprowadź oświadczenie o ochronie danych',
+          customDisclaimerTip: 'Niestandardowy tekst oświadczenia będzie wyświetlany po stronie klienta, dostarczając dodatkowych informacji o aplikacji.',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/pl-PL/tools.ts

@@ -73,6 +73,8 @@ const translation = {
     },
     privacyPolicy: 'Polityka prywatności',
     privacyPolicyPlaceholder: 'Proszę wprowadzić politykę prywatności',
+    customDisclaimer: 'Oświadczenie niestandardowe',
+    customDisclaimerPlaceholder: 'Proszę wprowadzić oświadczenie niestandardowe',
   },
   test: {
     title: 'Test',

+ 3 - 0
web/i18n/pt-BR/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Política de Privacidade',
           privacyPolicyPlaceholder: 'Insira o link da política de privacidade',
           privacyPolicyTip: 'Ajuda os visitantes a entender os dados coletados pelo aplicativo, consulte a <privacyPolicyLink>Política de Privacidade</privacyPolicyLink> do Dify.',
+          customDisclaimer: 'Aviso Legal Personalizado',
+          customDisclaimerPlaceholder: 'Insira o texto do aviso legal',
+          customDisclaimerTip: 'O texto do aviso legal personalizado será exibido no lado do cliente, fornecendo informações adicionais sobre o aplicativo',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/pt-BR/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'Política de Privacidade',
     privacyPolicyPlaceholder: 'Digite a política de privacidade',
+    customDisclaimer: 'Aviso Personalizado',
+    customDisclaimerPlaceholder: 'Digite o aviso personalizado',
   },
   test: {
     title: 'Testar',

+ 3 - 0
web/i18n/uk-UA/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Політика конфіденційності',
           privacyPolicyPlaceholder: 'Введіть посилання на політику конфіденційності',
           privacyPolicyTip: 'Допомагає відвідувачам зрозуміти дані, зібрані додатком, див. <privacyPolicyLink>Політику конфіденційності</privacyPolicyLink> Dify.',
+          customDisclaimer: 'Відмова від відповідальності',
+          customDisclaimerPlaceholder: 'Введіть відмову від відповідальності',
+          customDisclaimerTip: 'Відображається на клієнтському боці, щоб визначити відповідальність за використання додатка',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/uk-UA/tools.ts

@@ -70,6 +70,8 @@ const translation = {
     },
     privacyPolicy: 'Політика конфіденційності',
     privacyPolicyPlaceholder: 'Введіть політику конфіденційності',
+    customDisclaimer: 'Власний відомості',
+    customDisclaimerPlaceholder: 'Введіть власні відомості',
   },
 
   test: {

+ 3 - 0
web/i18n/vi-VN/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: 'Chính sách bảo mật',
           privacyPolicyPlaceholder: 'Nhập liên kết chính sách bảo mật',
           privacyPolicyTip: 'Giúp khách truy cập hiểu được dữ liệu mà ứng dụng thu thập, xem <privacyPolicyLink>Chính sách bảo mật</privacyPolicyLink> của Dify.',
+          customDisclaimer: 'Tùy chỉnh từ chối trách nhiệm',
+          customDisclaimerPlaceholder: 'Nhập liên kết từ chối trách nhiệm',
+          customDisclaimerTip: 'Liên kết này sẽ được hiển thị ở phía máy khách, cung cấp thông tin về trách nhiệm của ứng dụng',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/vi-VN/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: 'Chính sách bảo mật',
     privacyPolicyPlaceholder: 'Vui lòng nhập chính sách bảo mật',
+    customDisclaimer: 'Tuyên bố Tùy chỉnh',
+    customDisclaimerPlaceholder: 'Vui lòng nhập tuyên bố tùy chỉnh',
   },
   test: {
     title: 'Kiểm tra',

+ 3 - 0
web/i18n/zh-Hans/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: '隐私政策',
           privacyPolicyPlaceholder: '请输入隐私政策链接',
           privacyPolicyTip: '帮助访问者了解该应用收集的数据,可参考 Dify 的<privacyPolicyLink>隐私政策</privacyPolicyLink>。',
+          customDisclaimer: '自定义免责声明',
+          customDisclaimerPlaceholder: '请输入免责声明',
+          customDisclaimerTip: '在应用中展示免责声明,可用于告知用户 AI 的局限性。',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/zh-Hans/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: '隐私协议',
     privacyPolicyPlaceholder: '请输入隐私协议',
+    customDisclaimer: '自定义免责声明',
+    customDisclaimerPlaceholder: '请输入自定义免责声明',
   },
   test: {
     title: '测试',

+ 3 - 0
web/i18n/zh-Hant/app-overview.ts

@@ -50,6 +50,9 @@ const translation = {
           privacyPolicy: '隱私政策',
           privacyPolicyPlaceholder: '請輸入隱私政策連結',
           privacyPolicyTip: '幫助訪問者瞭解該應用收集的資料,可參考 Dify 的<privacyPolicyLink>隱私政策</privacyPolicyLink>。',
+          customDisclaimer: '自定義免責聲明',
+          customDisclaimerPlaceholder: '請輸入免責聲明',
+          customDisclaimerTip: '客製化的免責聲明文字將在客戶端顯示,提供有關應用程式的額外資訊。',
         },
       },
       embedded: {

+ 2 - 0
web/i18n/zh-Hant/tools.ts

@@ -71,6 +71,8 @@ const translation = {
     },
     privacyPolicy: '隱私協議',
     privacyPolicyPlaceholder: '請輸入隱私協議',
+    customDisclaimer: '自定義免責聲明',
+    customDisclaimerPlaceholder: '請輸入自定義免責聲明',
   },
   test: {
     title: '測試',

+ 1 - 0
web/models/explore.ts

@@ -16,6 +16,7 @@ export type App = {
   description: string
   copyright: string
   privacy_policy: string | null
+  custom_disclaimer: string | null
   category: AppCategory
   position: number
   is_listed: boolean

+ 1 - 0
web/models/share.ts

@@ -18,6 +18,7 @@ export type SiteInfo = {
   prompt_public?: boolean
   copyright?: string
   privacy_policy?: string
+  custom_disclaimer?: string
 }
 
 export type AppMeta = {

+ 2 - 0
web/types/app.ts

@@ -269,6 +269,8 @@ export type SiteConfig = {
   copyright: string
   /** Privacy Policy */
   privacy_policy: string
+  /** Custom Disclaimer */
+  custom_disclaimer: string
 
   icon: string
   icon_background: string