| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | import json | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | from os import path | 
					
						
							| 
									
										
										
										
											2024-09-12 15:50:49 +08:00
										 |  |  | from pathlib import Path | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | from typing import Optional | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import requests | 
					
						
							| 
									
										
										
										
											2024-07-23 16:48:21 +08:00
										 |  |  | from flask import current_app | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 12:25:38 +08:00
										 |  |  | from configs import dify_config | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | from constants.languages import languages | 
					
						
							|  |  |  | from extensions.ext_database import db | 
					
						
							|  |  |  | from models.model import App, RecommendedApp | 
					
						
							| 
									
										
										
										
											2024-07-15 16:23:40 +08:00
										 |  |  | from services.app_dsl_service import AppDslService | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger(__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RecommendedAppService: | 
					
						
							|  |  |  |     builtin_data: Optional[dict] = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_recommended_apps_and_categories(cls, language: str) -> dict: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Get recommended apps and categories. | 
					
						
							|  |  |  |         :param language: language | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-07-12 12:25:38 +08:00
										 |  |  |         mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         if mode == "remote": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 result = cls._fetch_recommended_apps_from_dify_official(language) | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 logger.warning(f"fetch recommended apps from dify official failed: {e}, switch to built-in.") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |                 result = cls._fetch_recommended_apps_from_builtin(language) | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         elif mode == "db": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             result = cls._fetch_recommended_apps_from_db(language) | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         elif mode == "builtin": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             result = cls._fetch_recommended_apps_from_builtin(language) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             raise ValueError(f"invalid fetch recommended apps mode: {mode}") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         if not result.get("recommended_apps") and language != "en-US": | 
					
						
							|  |  |  |             result = cls._fetch_recommended_apps_from_builtin("en-US") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_apps_from_db(cls, language: str) -> dict: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended apps from db. | 
					
						
							|  |  |  |         :param language: language | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         recommended_apps = ( | 
					
						
							|  |  |  |             db.session.query(RecommendedApp) | 
					
						
							|  |  |  |             .filter(RecommendedApp.is_listed == True, RecommendedApp.language == language) | 
					
						
							|  |  |  |             .all() | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if len(recommended_apps) == 0: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             recommended_apps = ( | 
					
						
							|  |  |  |                 db.session.query(RecommendedApp) | 
					
						
							|  |  |  |                 .filter(RecommendedApp.is_listed == True, RecommendedApp.language == languages[0]) | 
					
						
							|  |  |  |                 .all() | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         categories = set() | 
					
						
							|  |  |  |         recommended_apps_result = [] | 
					
						
							|  |  |  |         for recommended_app in recommended_apps: | 
					
						
							|  |  |  |             app = recommended_app.app | 
					
						
							|  |  |  |             if not app or not app.is_public: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             site = app.site | 
					
						
							|  |  |  |             if not site: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             recommended_app_result = { | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 "id": recommended_app.id, | 
					
						
							|  |  |  |                 "app": { | 
					
						
							|  |  |  |                     "id": app.id, | 
					
						
							|  |  |  |                     "name": app.name, | 
					
						
							|  |  |  |                     "mode": app.mode, | 
					
						
							|  |  |  |                     "icon": app.icon, | 
					
						
							|  |  |  |                     "icon_background": app.icon_background, | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 "app_id": recommended_app.app_id, | 
					
						
							|  |  |  |                 "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, | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |             recommended_apps_result.append(recommended_app_result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             categories.add(recommended_app.category)  # add category to categories | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         return {"recommended_apps": recommended_apps_result, "categories": sorted(categories)} | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_apps_from_dify_official(cls, language: str) -> dict: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended apps from dify official. | 
					
						
							|  |  |  |         :param language: language | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-07-12 12:25:38 +08:00
										 |  |  |         domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         url = f"{domain}/apps?language={language}" | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |         response = requests.get(url, timeout=(3, 10)) | 
					
						
							|  |  |  |         if response.status_code != 200: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             raise ValueError(f"fetch recommended apps failed, status code: {response.status_code}") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-07 17:06:47 +08:00
										 |  |  |         result = response.json() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "categories" in result: | 
					
						
							|  |  |  |             result["categories"] = sorted(result["categories"]) | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-07 17:06:47 +08:00
										 |  |  |         return result | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_apps_from_builtin(cls, language: str) -> dict: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended apps from builtin. | 
					
						
							|  |  |  |         :param language: language | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         builtin_data = cls._get_builtin_data() | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         return builtin_data.get("recommended_apps", {}).get(language) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_recommend_app_detail(cls, app_id: str) -> Optional[dict]: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Get recommend app detail. | 
					
						
							|  |  |  |         :param app_id: app id | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-07-12 12:25:38 +08:00
										 |  |  |         mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         if mode == "remote": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 result = cls._fetch_recommended_app_detail_from_dify_official(app_id) | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 logger.warning(f"fetch recommended app detail from dify official failed: {e}, switch to built-in.") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |                 result = cls._fetch_recommended_app_detail_from_builtin(app_id) | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         elif mode == "db": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             result = cls._fetch_recommended_app_detail_from_db(app_id) | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         elif mode == "builtin": | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |             result = cls._fetch_recommended_app_detail_from_builtin(app_id) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             raise ValueError(f"invalid fetch recommended app detail mode: {mode}") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_app_detail_from_dify_official(cls, app_id: str) -> Optional[dict]: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended app detail from dify official. | 
					
						
							|  |  |  |         :param app_id: App ID | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-07-12 12:25:38 +08:00
										 |  |  |         domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         url = f"{domain}/apps/{app_id}" | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |         response = requests.get(url, timeout=(3, 10)) | 
					
						
							|  |  |  |         if response.status_code != 200: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return response.json() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_app_detail_from_db(cls, app_id: str) -> Optional[dict]: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended app detail from db. | 
					
						
							|  |  |  |         :param app_id: App ID | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # is in public recommended list | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         recommended_app = ( | 
					
						
							|  |  |  |             db.session.query(RecommendedApp) | 
					
						
							|  |  |  |             .filter(RecommendedApp.is_listed == True, RecommendedApp.app_id == app_id) | 
					
						
							|  |  |  |             .first() | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if not recommended_app: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # get app detail | 
					
						
							|  |  |  |         app_model = db.session.query(App).filter(App.id == app_id).first() | 
					
						
							|  |  |  |         if not app_model or not app_model.is_public: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             "id": app_model.id, | 
					
						
							|  |  |  |             "name": app_model.name, | 
					
						
							|  |  |  |             "icon": app_model.icon, | 
					
						
							|  |  |  |             "icon_background": app_model.icon_background, | 
					
						
							|  |  |  |             "mode": app_model.mode, | 
					
						
							|  |  |  |             "export_data": AppDslService.export_dsl(app_model=app_model), | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _fetch_recommended_app_detail_from_builtin(cls, app_id: str) -> Optional[dict]: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch recommended app detail from builtin. | 
					
						
							|  |  |  |         :param app_id: App ID | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         builtin_data = cls._get_builtin_data() | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         return builtin_data.get("app_details", {}).get(app_id) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _get_builtin_data(cls) -> dict: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Get builtin data. | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if cls.builtin_data: | 
					
						
							|  |  |  |             return cls.builtin_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         root_path = current_app.root_path | 
					
						
							| 
									
										
										
										
											2024-09-12 15:50:49 +08:00
										 |  |  |         cls.builtin_data = json.loads( | 
					
						
							|  |  |  |             Path(path.join(root_path, "constants", "recommended_apps.json")).read_text(encoding="utf-8") | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return cls.builtin_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def fetch_all_recommended_apps_and_export_datas(cls): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Fetch all recommended apps and export datas | 
					
						
							|  |  |  |         :return: | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |         templates = {"recommended_apps": {}, "app_details": {}} | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |         for language in languages: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 result = cls._fetch_recommended_apps_from_dify_official(language) | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 logger.warning(f"fetch recommended apps from dify official failed: {e}, skip.") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             templates["recommended_apps"][language] = result | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |             for recommended_app in result.get("recommended_apps"): | 
					
						
							|  |  |  |                 app_id = recommended_app.get("app_id") | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 # get app detail | 
					
						
							|  |  |  |                 app_detail = cls._fetch_recommended_app_detail_from_dify_official(app_id) | 
					
						
							|  |  |  |                 if not app_detail: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:43:57 +08:00
										 |  |  |                 templates["app_details"][app_id] = app_detail | 
					
						
							| 
									
										
										
										
											2024-04-08 18:51:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return templates |