recommended_app_service.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 = db.session.query(RecommendedApp).filter(
  46. RecommendedApp.is_listed == True,
  47. RecommendedApp.language == language
  48. ).all()
  49. if len(recommended_apps) == 0:
  50. recommended_apps = db.session.query(RecommendedApp).filter(
  51. RecommendedApp.is_listed == True,
  52. RecommendedApp.language == languages[0]
  53. ).all()
  54. categories = set()
  55. recommended_apps_result = []
  56. for recommended_app in recommended_apps:
  57. app = recommended_app.app
  58. if not app or not app.is_public:
  59. continue
  60. site = app.site
  61. if not site:
  62. continue
  63. recommended_app_result = {
  64. 'id': recommended_app.id,
  65. 'app': {
  66. 'id': app.id,
  67. 'name': app.name,
  68. 'mode': app.mode,
  69. 'icon': app.icon,
  70. 'icon_background': app.icon_background
  71. },
  72. 'app_id': recommended_app.app_id,
  73. 'description': site.description,
  74. 'copyright': site.copyright,
  75. 'privacy_policy': site.privacy_policy,
  76. 'custom_disclaimer': site.custom_disclaimer,
  77. 'category': recommended_app.category,
  78. 'position': recommended_app.position,
  79. 'is_listed': recommended_app.is_listed
  80. }
  81. recommended_apps_result.append(recommended_app_result)
  82. categories.add(recommended_app.category) # add category to categories
  83. return {'recommended_apps': recommended_apps_result, 'categories': sorted(categories)}
  84. @classmethod
  85. def _fetch_recommended_apps_from_dify_official(cls, language: str) -> dict:
  86. """
  87. Fetch recommended apps from dify official.
  88. :param language: language
  89. :return:
  90. """
  91. domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
  92. url = f'{domain}/apps?language={language}'
  93. response = requests.get(url, timeout=(3, 10))
  94. if response.status_code != 200:
  95. raise ValueError(f'fetch recommended apps failed, status code: {response.status_code}')
  96. result = response.json()
  97. if "categories" in result:
  98. result["categories"] = sorted(result["categories"])
  99. return result
  100. @classmethod
  101. def _fetch_recommended_apps_from_builtin(cls, language: str) -> dict:
  102. """
  103. Fetch recommended apps from builtin.
  104. :param language: language
  105. :return:
  106. """
  107. builtin_data = cls._get_builtin_data()
  108. return builtin_data.get('recommended_apps', {}).get(language)
  109. @classmethod
  110. def get_recommend_app_detail(cls, app_id: str) -> Optional[dict]:
  111. """
  112. Get recommend app detail.
  113. :param app_id: app id
  114. :return:
  115. """
  116. mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE
  117. if mode == 'remote':
  118. try:
  119. result = cls._fetch_recommended_app_detail_from_dify_official(app_id)
  120. except Exception as e:
  121. logger.warning(f'fetch recommended app detail from dify official failed: {e}, switch to built-in.')
  122. result = cls._fetch_recommended_app_detail_from_builtin(app_id)
  123. elif mode == 'db':
  124. result = cls._fetch_recommended_app_detail_from_db(app_id)
  125. elif mode == 'builtin':
  126. result = cls._fetch_recommended_app_detail_from_builtin(app_id)
  127. else:
  128. raise ValueError(f'invalid fetch recommended app detail mode: {mode}')
  129. return result
  130. @classmethod
  131. def _fetch_recommended_app_detail_from_dify_official(cls, app_id: str) -> Optional[dict]:
  132. """
  133. Fetch recommended app detail from dify official.
  134. :param app_id: App ID
  135. :return:
  136. """
  137. domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
  138. url = f'{domain}/apps/{app_id}'
  139. response = requests.get(url, timeout=(3, 10))
  140. if response.status_code != 200:
  141. return None
  142. return response.json()
  143. @classmethod
  144. def _fetch_recommended_app_detail_from_db(cls, app_id: str) -> Optional[dict]:
  145. """
  146. Fetch recommended app detail from db.
  147. :param app_id: App ID
  148. :return:
  149. """
  150. # is in public recommended list
  151. recommended_app = db.session.query(RecommendedApp).filter(
  152. RecommendedApp.is_listed == True,
  153. RecommendedApp.app_id == app_id
  154. ).first()
  155. if not recommended_app:
  156. return None
  157. # get app detail
  158. app_model = db.session.query(App).filter(App.id == app_id).first()
  159. if not app_model or not app_model.is_public:
  160. return None
  161. return {
  162. 'id': app_model.id,
  163. 'name': app_model.name,
  164. 'icon': app_model.icon,
  165. 'icon_background': app_model.icon_background,
  166. 'mode': app_model.mode,
  167. 'export_data': AppDslService.export_dsl(app_model=app_model)
  168. }
  169. @classmethod
  170. def _fetch_recommended_app_detail_from_builtin(cls, app_id: str) -> Optional[dict]:
  171. """
  172. Fetch recommended app detail from builtin.
  173. :param app_id: App ID
  174. :return:
  175. """
  176. builtin_data = cls._get_builtin_data()
  177. return builtin_data.get('app_details', {}).get(app_id)
  178. @classmethod
  179. def _get_builtin_data(cls) -> dict:
  180. """
  181. Get builtin data.
  182. :return:
  183. """
  184. if cls.builtin_data:
  185. return cls.builtin_data
  186. root_path = current_app.root_path
  187. with open(path.join(root_path, 'constants', 'recommended_apps.json'), encoding='utf-8') as f:
  188. json_data = f.read()
  189. data = json.loads(json_data)
  190. cls.builtin_data = data
  191. return cls.builtin_data
  192. @classmethod
  193. def fetch_all_recommended_apps_and_export_datas(cls):
  194. """
  195. Fetch all recommended apps and export datas
  196. :return:
  197. """
  198. templates = {
  199. "recommended_apps": {},
  200. "app_details": {}
  201. }
  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