recommended_app_service.py 8.5 KB

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