Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Refactor the service of retrieval the recommend app #9302

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Empty file.
64 changes: 64 additions & 0 deletions api/services/recommend_app/buildin/buildin_retrieval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import json
from os import path
from pathlib import Path
from typing import Optional

from flask import current_app

from services.recommend_app.recommend_app_base import RecommendAppRetrievalBase
from services.recommend_app.recommend_app_type import RecommendAppType


class BuildInRecommendAppRetrieval(RecommendAppRetrievalBase):
"""
Retrieval recommended app from buildin, the location is constants/recommended_apps.json
"""

builtin_data: Optional[dict] = None

def get_type(self) -> str:
return RecommendAppType.BUILDIN

def get_recommended_apps_and_categories(self, language: str) -> dict:
result = self.fetch_recommended_apps_from_builtin(language)
return result

def get_recommend_app_detail(self, app_id: str):
result = self.fetch_recommended_app_detail_from_builtin(app_id)
return result

@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
cls.builtin_data = json.loads(
Path(path.join(root_path, "constants", "recommended_apps.json")).read_text(encoding="utf-8")
)

return cls.builtin_data

@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()
return builtin_data.get("recommended_apps", {}).get(language)

@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()
return builtin_data.get("app_details", {}).get(app_id)
Empty file.
111 changes: 111 additions & 0 deletions api/services/recommend_app/database/database_retrieval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from typing import Optional

from constants.languages import languages
from extensions.ext_database import db
from models.model import App, RecommendedApp
from services.app_dsl_service import AppDslService
from services.recommend_app.recommend_app_base import RecommendAppRetrievalBase
from services.recommend_app.recommend_app_type import RecommendAppType


class DatabaseRecommendAppRetrieval(RecommendAppRetrievalBase):
"""
Retrieval recommended app from database
"""

def get_recommended_apps_and_categories(self, language: str) -> dict:
result = self.fetch_recommended_apps_from_db(language)
return result

def get_recommend_app_detail(self, app_id: str):
result = self.fetch_recommended_app_detail_from_db(app_id)
return result

def get_type(self) -> str:
return RecommendAppType.DATABASE

@classmethod
def fetch_recommended_apps_from_db(cls, language: str) -> dict:
"""
Fetch recommended apps from db.
:param language: language
:return:
"""
recommended_apps = (
db.session.query(RecommendedApp)
.filter(RecommendedApp.is_listed == True, RecommendedApp.language == language)
.all()
)

if len(recommended_apps) == 0:
recommended_apps = (
db.session.query(RecommendedApp)
.filter(RecommendedApp.is_listed == True, RecommendedApp.language == languages[0])
.all()
)

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 = {
"id": recommended_app.id,
"app": {
"id": app.id,
"name": app.name,
"mode": app.mode,
"icon": app.icon,
"icon_background": app.icon_background,
},
"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,
}
recommended_apps_result.append(recommended_app_result)

categories.add(recommended_app.category)

return {"recommended_apps": recommended_apps_result, "categories": sorted(categories)}

@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
recommended_app = (
db.session.query(RecommendedApp)
.filter(RecommendedApp.is_listed == True, RecommendedApp.app_id == app_id)
.first()
)

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 {
"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),
}
17 changes: 17 additions & 0 deletions api/services/recommend_app/recommend_app_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from abc import ABC, abstractmethod


class RecommendAppRetrievalBase(ABC):
"""Interface for recommend app retrieval."""

@abstractmethod
def get_recommended_apps_and_categories(self, language: str) -> dict:
raise NotImplementedError

@abstractmethod
def get_recommend_app_detail(self, app_id: str):
raise NotImplementedError

@abstractmethod
def get_type(self) -> str:
raise NotImplementedError
23 changes: 23 additions & 0 deletions api/services/recommend_app/recommend_app_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from services.recommend_app.buildin.buildin_retrieval import BuildInRecommendAppRetrieval
from services.recommend_app.database.database_retrieval import DatabaseRecommendAppRetrieval
from services.recommend_app.recommend_app_base import RecommendAppRetrievalBase
from services.recommend_app.recommend_app_type import RecommendAppType
from services.recommend_app.remote.remote_retrieval import RemoteRecommendAppRetrieval


class RecommendAppRetrievalFactory:
@staticmethod
def get_recommend_app_factory(mode: str) -> type[RecommendAppRetrievalBase]:
match mode:
case RecommendAppType.REMOTE:
return RemoteRecommendAppRetrieval
case RecommendAppType.DATABASE:
return DatabaseRecommendAppRetrieval
case RecommendAppType.BUILDIN:
return BuildInRecommendAppRetrieval
case _:
raise ValueError(f"invalid fetch recommended apps mode: {mode}")

@staticmethod
def get_buildin_recommend_app_retrieval():
return BuildInRecommendAppRetrieval
7 changes: 7 additions & 0 deletions api/services/recommend_app/recommend_app_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import Enum


class RecommendAppType(str, Enum):
REMOTE = "remote"
BUILDIN = "builtin"
DATABASE = "db"
Empty file.
71 changes: 71 additions & 0 deletions api/services/recommend_app/remote/remote_retrieval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import logging
from typing import Optional

import requests

from configs import dify_config
from services.recommend_app.buildin.buildin_retrieval import BuildInRecommendAppRetrieval
from services.recommend_app.recommend_app_base import RecommendAppRetrievalBase
from services.recommend_app.recommend_app_type import RecommendAppType

logger = logging.getLogger(__name__)


class RemoteRecommendAppRetrieval(RecommendAppRetrievalBase):
"""
Retrieval recommended app from dify official
"""

def get_recommend_app_detail(self, app_id: str):
try:
result = self.fetch_recommended_app_detail_from_dify_official(app_id)
except Exception as e:
logger.warning(f"fetch recommended app detail from dify official failed: {e}, switch to built-in.")
result = BuildInRecommendAppRetrieval.fetch_recommended_app_detail_from_builtin(app_id)
return result

def get_recommended_apps_and_categories(self, language: str) -> dict:
try:
result = self.fetch_recommended_apps_from_dify_official(language)
except Exception as e:
logger.warning(f"fetch recommended apps from dify official failed: {e}, switch to built-in.")
result = BuildInRecommendAppRetrieval.fetch_recommended_apps_from_builtin(language)
return result

def get_type(self) -> str:
return RecommendAppType.REMOTE

@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:
"""
domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
url = f"{domain}/apps/{app_id}"
response = requests.get(url, timeout=(3, 10))
if response.status_code != 200:
return None

return response.json()

@classmethod
def fetch_recommended_apps_from_dify_official(cls, language: str) -> dict:
"""
Fetch recommended apps from dify official.
:param language: language
:return:
"""
domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN
url = f"{domain}/apps?language={language}"
response = requests.get(url, timeout=(3, 10))
if response.status_code != 200:
raise ValueError(f"fetch recommended apps failed, status code: {response.status_code}")

result = response.json()

if "categories" in result:
result["categories"] = sorted(result["categories"])

return result
Loading