recommended_app_service.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import json
  2. import logging
  3. from os import path
  4. from typing import Optional
  5. import requests
  6. from flask import current_app
  7. from configs import dify_config
  8. from constants.languages import languages
  9. from extensions.ext_database import db
  10. from models.model import App, RecommendedApp
  11. from services.app_dsl_service import AppDslService
  12. logger = logging.getLogger(__name__)
  13. class RecommendedAppService:
  14. builtin_data: Optional[dict] = None
  15. @classmethod
  16. def get_recommended_apps_and_categories(cls, language: str) -> dict:
  17. """
  18. Get recommended apps and categories.
  19. :param language: language
  20. :return:
  21. """
  22. mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE
  23. if mode == "remote":
  24. try:
  25. result = cls._fetch_recommended_apps_from_dify_official(language)
  26. except Exception as e:
  27. logger.warning(f"fetch recommended apps from dify official failed: {e}, switch to built-in.")
  28. result = cls._fetch_recommended_apps_from_builtin(language)
  29. elif mode == "db":
  30. result = cls._fetch_recommended_apps_from_db(language)
  31. elif mode == "builtin":
  32. result = cls._fetch_recommended_apps_from_builtin(language)
  33. else:
  34. raise ValueError(f"invalid fetch recommended apps mode: {mode}")
  35. if not result.get("recommended_apps") and language != "en-US":
  36. result = cls._fetch_recommended_apps_from_builtin("en-US")
  37. return result
  38. @classmethod
  39. def _fetch_recommended_apps_from_db(cls, language: str) -> dict:
  40. """
  41. Fetch recommended apps from db.
  42. :param language: language
  43. :return:
  44. """
  45. recommended_apps = (
  46. db.session.query(RecommendedApp)
  47. .filter(RecommendedApp.is_listed == True, RecommendedApp.language == language)
  48. .all()
  49. )
  50. if len(recommended_apps) == 0:
  51. recommended_apps = (
  52. db.session.query(RecommendedApp)
  53. .filter(RecommendedApp.is_listed == True, RecommendedApp.language == languages[0])
  54. .all()
  55. )
  56. categories = set()
  57. recommended_apps_result = []
  58. for recommended_app in recommended_apps:
  59. app = recommended_app.app
  60. if not app or not app.is_public:
  61. continue
  62. site = app.site
  63. if not site:
  64. continue
  65. recommended_app_result = {
  66. "id": recommended_app.id,
  67. "app": {
  68. "id": app.id,
  69. "name": app.name,
  70. "mode": app.mode,
  71. "icon": app.icon,
  72. "icon_background": app.icon_background,
  73. },
  74. "app_id": recommended_app.app_id,
  75. "description": site.description,
  76. "copyright": site.copyright,
  77. "privacy_policy": site.privacy_policy,
  78. "custom_disclaimer": site.custom_disclaimer,
  79. "category": recommended_app.category,
  80. "position": recommended_app.position,
  81. "is_listed": recommended_app.is_listed,
  82. }
  83. recommended_apps_result.append(recommended_app_result)
  84. categories.add(recommended_app.category) # add category to categories
  85. return {"recommended_apps": recommended_apps_result, "categories": sorted(categories)}
  86. @classmethod
  87. def _fetch_recommended_apps_from_dify_official(cls, language: str) -> dict:
  88. """
  89. Fetch recommended apps from dify official.
  90. :param language: language
  91. :return:
  92. """
  93. domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
  94. url = f"{domain}/apps?language={language}"
  95. response = requests.get(url, timeout=(3, 10))
  96. if response.status_code != 200:
  97. raise ValueError(f"fetch recommended apps failed, status code: {response.status_code}")
  98. result = response.json()
  99. if "categories" in result:
  100. result["categories"] = sorted(result["categories"])
  101. return result
  102. @classmethod
  103. def _fetch_recommended_apps_from_builtin(cls, language: str) -> dict:
  104. """
  105. Fetch recommended apps from builtin.
  106. :param language: language
  107. :return:
  108. """
  109. builtin_data = cls._get_builtin_data()
  110. return builtin_data.get("recommended_apps", {}).get(language)
  111. @classmethod
  112. def get_recommend_app_detail(cls, app_id: str) -> Optional[dict]:
  113. """
  114. Get recommend app detail.
  115. :param app_id: app id
  116. :return:
  117. """
  118. mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE
  119. if mode == "remote":
  120. try:
  121. result = cls._fetch_recommended_app_detail_from_dify_official(app_id)
  122. except Exception as e:
  123. logger.warning(f"fetch recommended app detail from dify official failed: {e}, switch to built-in.")
  124. result = cls._fetch_recommended_app_detail_from_builtin(app_id)
  125. elif mode == "db":
  126. result = cls._fetch_recommended_app_detail_from_db(app_id)
  127. elif mode == "builtin":
  128. result = cls._fetch_recommended_app_detail_from_builtin(app_id)
  129. else:
  130. raise ValueError(f"invalid fetch recommended app detail mode: {mode}")
  131. return result
  132. @classmethod
  133. def _fetch_recommended_app_detail_from_dify_official(cls, app_id: str) -> Optional[dict]:
  134. """
  135. Fetch recommended app detail from dify official.
  136. :param app_id: App ID
  137. :return:
  138. """
  139. domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
  140. url = f"{domain}/apps/{app_id}"
  141. response = requests.get(url, timeout=(3, 10))
  142. if response.status_code != 200:
  143. return None
  144. return response.json()
  145. @classmethod
  146. def _fetch_recommended_app_detail_from_db(cls, app_id: str) -> Optional[dict]:
  147. """
  148. Fetch recommended app detail from db.
  149. :param app_id: App ID
  150. :return:
  151. """
  152. # is in public recommended list
  153. recommended_app = (
  154. db.session.query(RecommendedApp)
  155. .filter(RecommendedApp.is_listed == True, RecommendedApp.app_id == app_id)
  156. .first()
  157. )
  158. if not recommended_app:
  159. return None
  160. # get app detail
  161. app_model = db.session.query(App).filter(App.id == app_id).first()
  162. if not app_model or not app_model.is_public:
  163. return None
  164. return {
  165. "id": app_model.id,
  166. "name": app_model.name,
  167. "icon": app_model.icon,
  168. "icon_background": app_model.icon_background,
  169. "mode": app_model.mode,
  170. "export_data": AppDslService.export_dsl(app_model=app_model),
  171. }
  172. @classmethod
  173. def _fetch_recommended_app_detail_from_builtin(cls, app_id: str) -> Optional[dict]:
  174. """
  175. Fetch recommended app detail from builtin.
  176. :param app_id: App ID
  177. :return:
  178. """
  179. builtin_data = cls._get_builtin_data()
  180. return builtin_data.get("app_details", {}).get(app_id)
  181. @classmethod
  182. def _get_builtin_data(cls) -> dict:
  183. """
  184. Get builtin data.
  185. :return:
  186. """
  187. if cls.builtin_data:
  188. return cls.builtin_data
  189. root_path = current_app.root_path
  190. with open(path.join(root_path, "constants", "recommended_apps.json"), encoding="utf-8") as f:
  191. json_data = f.read()
  192. data = json.loads(json_data)
  193. cls.builtin_data = data
  194. return cls.builtin_data
  195. @classmethod
  196. def fetch_all_recommended_apps_and_export_datas(cls):
  197. """
  198. Fetch all recommended apps and export datas
  199. :return:
  200. """
  201. templates = {"recommended_apps": {}, "app_details": {}}
  202. for language in languages:
  203. try:
  204. result = cls._fetch_recommended_apps_from_dify_official(language)
  205. except Exception as e:
  206. logger.warning(f"fetch recommended apps from dify official failed: {e}, skip.")
  207. continue
  208. templates["recommended_apps"][language] = result
  209. for recommended_app in result.get("recommended_apps"):
  210. app_id = recommended_app.get("app_id")
  211. # get app detail
  212. app_detail = cls._fetch_recommended_app_detail_from_dify_official(app_id)
  213. if not app_detail:
  214. continue
  215. templates["app_details"][app_id] = app_detail
  216. return templates