浏览代码

refactor: Migrate part of the console basic API module to Flask-RESTX (#24732)

Signed-off-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: -LAN- <laipz8200@outlook.com>
tags/1.9.0
Guangdong Liu 1 个月前
父节点
当前提交
b51c724a94
没有帐户链接到提交者的电子邮件

+ 39
- 18
api/controllers/console/__init__.py 查看文件

from flask import Blueprint from flask import Blueprint
from flask_restx import Namespace


from libs.external_api import ExternalApi from libs.external_api import ExternalApi


from .remote_files import RemoteFileInfoApi, RemoteFileUploadApi from .remote_files import RemoteFileInfoApi, RemoteFileUploadApi


bp = Blueprint("console", __name__, url_prefix="/console/api") bp = Blueprint("console", __name__, url_prefix="/console/api")
api = ExternalApi(bp)

api = ExternalApi(
bp,
version="1.0",
title="Console API",
description="Console management APIs for app configuration, monitoring, and administration",
)

# Create namespace
console_ns = Namespace("console", description="Console management API operations", path="/")


# File # File
api.add_resource(FileApi, "/files/upload") api.add_resource(FileApi, "/files/upload")
api.add_resource(AppImportCheckDependenciesApi, "/apps/imports/<string:app_id>/check-dependencies") api.add_resource(AppImportCheckDependenciesApi, "/apps/imports/<string:app_id>/check-dependencies")


# Import other controllers # Import other controllers
from . import admin, apikey, extension, feature, ping, setup, version # pyright: ignore[reportUnusedImport]
from . import (
admin, # pyright: ignore[reportUnusedImport]
apikey, # pyright: ignore[reportUnusedImport]
extension, # pyright: ignore[reportUnusedImport]
feature, # pyright: ignore[reportUnusedImport]
init_validate, # pyright: ignore[reportUnusedImport]
ping, # pyright: ignore[reportUnusedImport]
setup, # pyright: ignore[reportUnusedImport]
version, # pyright: ignore[reportUnusedImport]
)


# Import app controllers # Import app controllers
from .app import ( from .app import (
saved_message, # pyright: ignore[reportUnusedImport] saved_message, # pyright: ignore[reportUnusedImport]
) )


# Import tag controllers
from .tag import tags # pyright: ignore[reportUnusedImport]

# Import workspace controllers
from .workspace import (
account, # pyright: ignore[reportUnusedImport]
agent_providers, # pyright: ignore[reportUnusedImport]
endpoint, # pyright: ignore[reportUnusedImport]
load_balancing_config, # pyright: ignore[reportUnusedImport]
members, # pyright: ignore[reportUnusedImport]
model_providers, # pyright: ignore[reportUnusedImport]
models, # pyright: ignore[reportUnusedImport]
plugin, # pyright: ignore[reportUnusedImport]
tool_providers, # pyright: ignore[reportUnusedImport]
workspace, # pyright: ignore[reportUnusedImport]
)

# Explore Audio # Explore Audio
api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio") api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio")
api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text") api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text")
InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop" InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop"
) )


# Import tag controllers
from .tag import tags # pyright: ignore[reportUnusedImport]

# Import workspace controllers
from .workspace import (
account, # pyright: ignore[reportUnusedImport]
agent_providers, # pyright: ignore[reportUnusedImport]
endpoint, # pyright: ignore[reportUnusedImport]
load_balancing_config, # pyright: ignore[reportUnusedImport]
members, # pyright: ignore[reportUnusedImport]
model_providers, # pyright: ignore[reportUnusedImport]
models, # pyright: ignore[reportUnusedImport]
plugin, # pyright: ignore[reportUnusedImport]
tool_providers, # pyright: ignore[reportUnusedImport]
workspace, # pyright: ignore[reportUnusedImport]
)
api.add_namespace(console_ns)

+ 28
- 6
api/controllers/console/admin.py 查看文件

from typing import ParamSpec, TypeVar from typing import ParamSpec, TypeVar


from flask import request from flask import request
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound, Unauthorized from werkzeug.exceptions import NotFound, Unauthorized
R = TypeVar("R") R = TypeVar("R")
from configs import dify_config from configs import dify_config
from constants.languages import supported_language from constants.languages import supported_language
from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.wraps import only_edition_cloud from controllers.console.wraps import only_edition_cloud
from extensions.ext_database import db from extensions.ext_database import db
from models.model import App, InstalledApp, RecommendedApp from models.model import App, InstalledApp, RecommendedApp
return decorated return decorated




@console_ns.route("/admin/insert-explore-apps")
class InsertExploreAppListApi(Resource): class InsertExploreAppListApi(Resource):
@api.doc("insert_explore_app")
@api.doc(description="Insert or update an app in the explore list")
@api.expect(
api.model(
"InsertExploreAppRequest",
{
"app_id": fields.String(required=True, description="Application ID"),
"desc": fields.String(description="App description"),
"copyright": fields.String(description="Copyright information"),
"privacy_policy": fields.String(description="Privacy policy"),
"custom_disclaimer": fields.String(description="Custom disclaimer"),
"language": fields.String(required=True, description="Language code"),
"category": fields.String(required=True, description="App category"),
"position": fields.Integer(required=True, description="Display position"),
},
)
)
@api.response(200, "App updated successfully")
@api.response(201, "App inserted successfully")
@api.response(404, "App not found")
@only_edition_cloud @only_edition_cloud
@admin_required @admin_required
def post(self): def post(self):
return {"result": "success"}, 200 return {"result": "success"}, 200




@console_ns.route("/admin/insert-explore-apps/<uuid:app_id>")
class InsertExploreAppApi(Resource): class InsertExploreAppApi(Resource):
@api.doc("delete_explore_app")
@api.doc(description="Remove an app from the explore list")
@api.doc(params={"app_id": "Application ID to remove"})
@api.response(204, "App removed successfully")
@only_edition_cloud @only_edition_cloud
@admin_required @admin_required
def delete(self, app_id): def delete(self, app_id):
db.session.commit() db.session.commit()


return {"result": "success"}, 204 return {"result": "success"}, 204


api.add_resource(InsertExploreAppListApi, "/admin/insert-explore-apps")
api.add_resource(InsertExploreAppApi, "/admin/insert-explore-apps/<uuid:app_id>")

+ 55
- 7
api/controllers/console/apikey.py 查看文件

from models.dataset import Dataset from models.dataset import Dataset
from models.model import ApiToken, App from models.model import ApiToken, App


from . import api
from . import api, console_ns
from .wraps import account_initialization_required, setup_required from .wraps import account_initialization_required, setup_required


api_key_fields = { api_key_fields = {
return {"result": "success"}, 204 return {"result": "success"}, 204




@console_ns.route("/apps/<uuid:resource_id>/api-keys")
class AppApiKeyListResource(BaseApiKeyListResource): class AppApiKeyListResource(BaseApiKeyListResource):
@api.doc("get_app_api_keys")
@api.doc(description="Get all API keys for an app")
@api.doc(params={"resource_id": "App ID"})
@api.response(200, "Success", api_key_list)
def get(self, resource_id):
"""Get all API keys for an app"""
return super().get(resource_id)

@api.doc("create_app_api_key")
@api.doc(description="Create a new API key for an app")
@api.doc(params={"resource_id": "App ID"})
@api.response(201, "API key created successfully", api_key_fields)
@api.response(400, "Maximum keys exceeded")
def post(self, resource_id):
"""Create a new API key for an app"""
return super().post(resource_id)

def after_request(self, resp): def after_request(self, resp):
resp.headers["Access-Control-Allow-Origin"] = "*" resp.headers["Access-Control-Allow-Origin"] = "*"
resp.headers["Access-Control-Allow-Credentials"] = "true" resp.headers["Access-Control-Allow-Credentials"] = "true"
token_prefix = "app-" token_prefix = "app-"




@console_ns.route("/apps/<uuid:resource_id>/api-keys/<uuid:api_key_id>")
class AppApiKeyResource(BaseApiKeyResource): class AppApiKeyResource(BaseApiKeyResource):
@api.doc("delete_app_api_key")
@api.doc(description="Delete an API key for an app")
@api.doc(params={"resource_id": "App ID", "api_key_id": "API key ID"})
@api.response(204, "API key deleted successfully")
def delete(self, resource_id, api_key_id):
"""Delete an API key for an app"""
return super().delete(resource_id, api_key_id)

def after_request(self, resp): def after_request(self, resp):
resp.headers["Access-Control-Allow-Origin"] = "*" resp.headers["Access-Control-Allow-Origin"] = "*"
resp.headers["Access-Control-Allow-Credentials"] = "true" resp.headers["Access-Control-Allow-Credentials"] = "true"
resource_id_field = "app_id" resource_id_field = "app_id"




@console_ns.route("/datasets/<uuid:resource_id>/api-keys")
class DatasetApiKeyListResource(BaseApiKeyListResource): class DatasetApiKeyListResource(BaseApiKeyListResource):
@api.doc("get_dataset_api_keys")
@api.doc(description="Get all API keys for a dataset")
@api.doc(params={"resource_id": "Dataset ID"})
@api.response(200, "Success", api_key_list)
def get(self, resource_id):
"""Get all API keys for a dataset"""
return super().get(resource_id)

@api.doc("create_dataset_api_key")
@api.doc(description="Create a new API key for a dataset")
@api.doc(params={"resource_id": "Dataset ID"})
@api.response(201, "API key created successfully", api_key_fields)
@api.response(400, "Maximum keys exceeded")
def post(self, resource_id):
"""Create a new API key for a dataset"""
return super().post(resource_id)

def after_request(self, resp): def after_request(self, resp):
resp.headers["Access-Control-Allow-Origin"] = "*" resp.headers["Access-Control-Allow-Origin"] = "*"
resp.headers["Access-Control-Allow-Credentials"] = "true" resp.headers["Access-Control-Allow-Credentials"] = "true"
token_prefix = "ds-" token_prefix = "ds-"




@console_ns.route("/datasets/<uuid:resource_id>/api-keys/<uuid:api_key_id>")
class DatasetApiKeyResource(BaseApiKeyResource): class DatasetApiKeyResource(BaseApiKeyResource):
@api.doc("delete_dataset_api_key")
@api.doc(description="Delete an API key for a dataset")
@api.doc(params={"resource_id": "Dataset ID", "api_key_id": "API key ID"})
@api.response(204, "API key deleted successfully")
def delete(self, resource_id, api_key_id):
"""Delete an API key for a dataset"""
return super().delete(resource_id, api_key_id)

def after_request(self, resp): def after_request(self, resp):
resp.headers["Access-Control-Allow-Origin"] = "*" resp.headers["Access-Control-Allow-Origin"] = "*"
resp.headers["Access-Control-Allow-Credentials"] = "true" resp.headers["Access-Control-Allow-Credentials"] = "true"
resource_type = "dataset" resource_type = "dataset"
resource_model = Dataset resource_model = Dataset
resource_id_field = "dataset_id" resource_id_field = "dataset_id"


api.add_resource(AppApiKeyListResource, "/apps/<uuid:resource_id>/api-keys")
api.add_resource(AppApiKeyResource, "/apps/<uuid:resource_id>/api-keys/<uuid:api_key_id>")
api.add_resource(DatasetApiKeyListResource, "/datasets/<uuid:resource_id>/api-keys")
api.add_resource(DatasetApiKeyResource, "/datasets/<uuid:resource_id>/api-keys/<uuid:api_key_id>")

+ 57
- 21
api/controllers/console/auth/activate.py 查看文件

from flask import request from flask import request
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse


from constants.languages import supported_language from constants.languages import supported_language
from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.error import AlreadyActivateError from controllers.console.error import AlreadyActivateError
from extensions.ext_database import db from extensions.ext_database import db
from libs.datetime_utils import naive_utc_now from libs.datetime_utils import naive_utc_now
from models.account import AccountStatus from models.account import AccountStatus
from services.account_service import AccountService, RegisterService from services.account_service import AccountService, RegisterService


active_check_parser = reqparse.RequestParser()
active_check_parser.add_argument(
"workspace_id", type=str, required=False, nullable=True, location="args", help="Workspace ID"
)
active_check_parser.add_argument(
"email", type=email, required=False, nullable=True, location="args", help="Email address"
)
active_check_parser.add_argument(
"token", type=str, required=True, nullable=False, location="args", help="Activation token"
)



@console_ns.route("/activate/check")
class ActivateCheckApi(Resource): class ActivateCheckApi(Resource):
@api.doc("check_activation_token")
@api.doc(description="Check if activation token is valid")
@api.expect(active_check_parser)
@api.response(
200,
"Success",
api.model(
"ActivationCheckResponse",
{
"is_valid": fields.Boolean(description="Whether token is valid"),
"data": fields.Raw(description="Activation data if valid"),
},
),
)
def get(self): def get(self):
parser = reqparse.RequestParser()
parser.add_argument("workspace_id", type=str, required=False, nullable=True, location="args")
parser.add_argument("email", type=email, required=False, nullable=True, location="args")
parser.add_argument("token", type=str, required=True, nullable=False, location="args")
args = parser.parse_args()
args = active_check_parser.parse_args()


workspaceId = args["workspace_id"] workspaceId = args["workspace_id"]
reg_email = args["email"] reg_email = args["email"]
return {"is_valid": False} return {"is_valid": False}




active_parser = reqparse.RequestParser()
active_parser.add_argument("workspace_id", type=str, required=False, nullable=True, location="json")
active_parser.add_argument("email", type=email, required=False, nullable=True, location="json")
active_parser.add_argument("token", type=str, required=True, nullable=False, location="json")
active_parser.add_argument("name", type=StrLen(30), required=True, nullable=False, location="json")
active_parser.add_argument(
"interface_language", type=supported_language, required=True, nullable=False, location="json"
)
active_parser.add_argument("timezone", type=timezone, required=True, nullable=False, location="json")


@console_ns.route("/activate")
class ActivateApi(Resource): class ActivateApi(Resource):
@api.doc("activate_account")
@api.doc(description="Activate account with invitation token")
@api.expect(active_parser)
@api.response(
200,
"Account activated successfully",
api.model(
"ActivationResponse",
{
"result": fields.String(description="Operation result"),
"data": fields.Raw(description="Login token data"),
},
),
)
@api.response(400, "Already activated or invalid token")
def post(self): def post(self):
parser = reqparse.RequestParser()
parser.add_argument("workspace_id", type=str, required=False, nullable=True, location="json")
parser.add_argument("email", type=email, required=False, nullable=True, location="json")
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
parser.add_argument("name", type=StrLen(30), required=True, nullable=False, location="json")
parser.add_argument(
"interface_language", type=supported_language, required=True, nullable=False, location="json"
)
parser.add_argument("timezone", type=timezone, required=True, nullable=False, location="json")
args = parser.parse_args()
args = active_parser.parse_args()


invitation = RegisterService.get_invitation_if_token_valid(args["workspace_id"], args["email"], args["token"]) invitation = RegisterService.get_invitation_if_token_valid(args["workspace_id"], args["email"], args["token"])
if invitation is None: if invitation is None:
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request)) token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))


return {"result": "success", "data": token_pair.model_dump()} return {"result": "success", "data": token_pair.model_dump()}


api.add_resource(ActivateCheckApi, "/activate/check")
api.add_resource(ActivateApi, "/activate")

+ 50
- 8
api/controllers/console/auth/data_source_oauth.py 查看文件

import requests import requests
from flask import current_app, redirect, request from flask import current_app, redirect, request
from flask_login import current_user from flask_login import current_user
from flask_restx import Resource
from flask_restx import Resource, fields
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden


from configs import dify_config from configs import dify_config
from controllers.console import api
from controllers.console import api, console_ns
from libs.login import login_required from libs.login import login_required
from libs.oauth_data_source import NotionOAuth from libs.oauth_data_source import NotionOAuth


return OAUTH_PROVIDERS return OAUTH_PROVIDERS




@console_ns.route("/oauth/data-source/<string:provider>")
class OAuthDataSource(Resource): class OAuthDataSource(Resource):
@api.doc("oauth_data_source")
@api.doc(description="Get OAuth authorization URL for data source provider")
@api.doc(params={"provider": "Data source provider name (notion)"})
@api.response(
200,
"Authorization URL or internal setup success",
api.model(
"OAuthDataSourceResponse",
{"data": fields.Raw(description="Authorization URL or 'internal' for internal setup")},
),
)
@api.response(400, "Invalid provider")
@api.response(403, "Admin privileges required")
def get(self, provider: str): def get(self, provider: str):
# The role of the current user in the table must be admin or owner # The role of the current user in the table must be admin or owner
if not current_user.is_admin_or_owner: if not current_user.is_admin_or_owner:
return {"data": auth_url}, 200 return {"data": auth_url}, 200




@console_ns.route("/oauth/data-source/callback/<string:provider>")
class OAuthDataSourceCallback(Resource): class OAuthDataSourceCallback(Resource):
@api.doc("oauth_data_source_callback")
@api.doc(description="Handle OAuth callback from data source provider")
@api.doc(
params={
"provider": "Data source provider name (notion)",
"code": "Authorization code from OAuth provider",
"error": "Error message from OAuth provider",
}
)
@api.response(302, "Redirect to console with result")
@api.response(400, "Invalid provider")
def get(self, provider: str): def get(self, provider: str):
OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers() OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers()
with current_app.app_context(): with current_app.app_context():
return redirect(f"{dify_config.CONSOLE_WEB_URL}?type=notion&error=Access denied") return redirect(f"{dify_config.CONSOLE_WEB_URL}?type=notion&error=Access denied")




@console_ns.route("/oauth/data-source/binding/<string:provider>")
class OAuthDataSourceBinding(Resource): class OAuthDataSourceBinding(Resource):
@api.doc("oauth_data_source_binding")
@api.doc(description="Bind OAuth data source with authorization code")
@api.doc(
params={"provider": "Data source provider name (notion)", "code": "Authorization code from OAuth provider"}
)
@api.response(
200,
"Data source binding success",
api.model("OAuthDataSourceBindingResponse", {"result": fields.String(description="Operation result")}),
)
@api.response(400, "Invalid provider or code")
def get(self, provider: str): def get(self, provider: str):
OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers() OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers()
with current_app.app_context(): with current_app.app_context():
return {"result": "success"}, 200 return {"result": "success"}, 200




@console_ns.route("/oauth/data-source/<string:provider>/<uuid:binding_id>/sync")
class OAuthDataSourceSync(Resource): class OAuthDataSourceSync(Resource):
@api.doc("oauth_data_source_sync")
@api.doc(description="Sync data from OAuth data source")
@api.doc(params={"provider": "Data source provider name (notion)", "binding_id": "Data source binding ID"})
@api.response(
200,
"Data source sync success",
api.model("OAuthDataSourceSyncResponse", {"result": fields.String(description="Operation result")}),
)
@api.response(400, "Invalid provider or sync failed")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
return {"error": "OAuth data source process failed"}, 400 return {"error": "OAuth data source process failed"}, 400


return {"result": "success"}, 200 return {"result": "success"}, 200


api.add_resource(OAuthDataSource, "/oauth/data-source/<string:provider>")
api.add_resource(OAuthDataSourceCallback, "/oauth/data-source/callback/<string:provider>")
api.add_resource(OAuthDataSourceBinding, "/oauth/data-source/binding/<string:provider>")
api.add_resource(OAuthDataSourceSync, "/oauth/data-source/<string:provider>/<uuid:binding_id>/sync")

+ 72
- 7
api/controllers/console/auth/forgot_password.py 查看文件

import secrets import secrets


from flask import request from flask import request
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session


from constants.languages import languages from constants.languages import languages
from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.auth.error import ( from controllers.console.auth.error import (
EmailCodeError, EmailCodeError,
EmailPasswordResetLimitError, EmailPasswordResetLimitError,
from services.feature_service import FeatureService from services.feature_service import FeatureService




@console_ns.route("/forgot-password")
class ForgotPasswordSendEmailApi(Resource): class ForgotPasswordSendEmailApi(Resource):
@api.doc("send_forgot_password_email")
@api.doc(description="Send password reset email")
@api.expect(
api.model(
"ForgotPasswordEmailRequest",
{
"email": fields.String(required=True, description="Email address"),
"language": fields.String(description="Language for email (zh-Hans/en-US)"),
},
)
)
@api.response(
200,
"Email sent successfully",
api.model(
"ForgotPasswordEmailResponse",
{
"result": fields.String(description="Operation result"),
"data": fields.String(description="Reset token"),
"code": fields.String(description="Error code if account not found"),
},
),
)
@api.response(400, "Invalid email or rate limit exceeded")
@setup_required @setup_required
@email_password_login_enabled @email_password_login_enabled
def post(self): def post(self):
return {"result": "success", "data": token} return {"result": "success", "data": token}




@console_ns.route("/forgot-password/validity")
class ForgotPasswordCheckApi(Resource): class ForgotPasswordCheckApi(Resource):
@api.doc("check_forgot_password_code")
@api.doc(description="Verify password reset code")
@api.expect(
api.model(
"ForgotPasswordCheckRequest",
{
"email": fields.String(required=True, description="Email address"),
"code": fields.String(required=True, description="Verification code"),
"token": fields.String(required=True, description="Reset token"),
},
)
)
@api.response(
200,
"Code verified successfully",
api.model(
"ForgotPasswordCheckResponse",
{
"is_valid": fields.Boolean(description="Whether code is valid"),
"email": fields.String(description="Email address"),
"token": fields.String(description="New reset token"),
},
),
)
@api.response(400, "Invalid code or token")
@setup_required @setup_required
@email_password_login_enabled @email_password_login_enabled
def post(self): def post(self):
return {"is_valid": True, "email": token_data.get("email"), "token": new_token} return {"is_valid": True, "email": token_data.get("email"), "token": new_token}




@console_ns.route("/forgot-password/resets")
class ForgotPasswordResetApi(Resource): class ForgotPasswordResetApi(Resource):
@api.doc("reset_password")
@api.doc(description="Reset password with verification token")
@api.expect(
api.model(
"ForgotPasswordResetRequest",
{
"token": fields.String(required=True, description="Verification token"),
"new_password": fields.String(required=True, description="New password"),
"password_confirm": fields.String(required=True, description="Password confirmation"),
},
)
)
@api.response(
200,
"Password reset successfully",
api.model("ForgotPasswordResetResponse", {"result": fields.String(description="Operation result")}),
)
@api.response(400, "Invalid token or password mismatch")
@setup_required @setup_required
@email_password_login_enabled @email_password_login_enabled
def post(self): def post(self):
pass pass
except AccountRegisterError: except AccountRegisterError:
raise AccountInFreezeError() raise AccountInFreezeError()


api.add_resource(ForgotPasswordSendEmailApi, "/forgot-password")
api.add_resource(ForgotPasswordCheckApi, "/forgot-password/validity")
api.add_resource(ForgotPasswordResetApi, "/forgot-password/resets")

+ 19
- 5
api/controllers/console/auth/oauth.py 查看文件

from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkSpaceNotFoundError from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkSpaceNotFoundError
from services.feature_service import FeatureService from services.feature_service import FeatureService


from .. import api
from .. import api, console_ns


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)


return OAUTH_PROVIDERS return OAUTH_PROVIDERS




@console_ns.route("/oauth/login/<provider>")
class OAuthLogin(Resource): class OAuthLogin(Resource):
@api.doc("oauth_login")
@api.doc(description="Initiate OAuth login process")
@api.doc(params={"provider": "OAuth provider name (github/google)", "invite_token": "Optional invitation token"})
@api.response(302, "Redirect to OAuth authorization URL")
@api.response(400, "Invalid provider")
def get(self, provider: str): def get(self, provider: str):
invite_token = request.args.get("invite_token") or None invite_token = request.args.get("invite_token") or None
OAUTH_PROVIDERS = get_oauth_providers() OAUTH_PROVIDERS = get_oauth_providers()
return redirect(auth_url) return redirect(auth_url)




@console_ns.route("/oauth/authorize/<provider>")
class OAuthCallback(Resource): class OAuthCallback(Resource):
@api.doc("oauth_callback")
@api.doc(description="Handle OAuth callback and complete login process")
@api.doc(
params={
"provider": "OAuth provider name (github/google)",
"code": "Authorization code from OAuth provider",
"state": "Optional state parameter (used for invite token)",
}
)
@api.response(302, "Redirect to console with access token")
@api.response(400, "OAuth process failed")
def get(self, provider: str): def get(self, provider: str):
OAUTH_PROVIDERS = get_oauth_providers() OAUTH_PROVIDERS = get_oauth_providers()
with current_app.app_context(): with current_app.app_context():
AccountService.link_account_integrate(provider, user_info.id, account) AccountService.link_account_integrate(provider, user_info.id, account)


return account return account


api.add_resource(OAuthLogin, "/oauth/login/<provider>")
api.add_resource(OAuthCallback, "/oauth/authorize/<provider>")

+ 56
- 8
api/controllers/console/extension.py 查看文件

from flask_login import current_user from flask_login import current_user
from flask_restx import Resource, marshal_with, reqparse
from flask_restx import Resource, fields, marshal_with, reqparse


from constants import HIDDEN_VALUE from constants import HIDDEN_VALUE
from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from fields.api_based_extension_fields import api_based_extension_fields from fields.api_based_extension_fields import api_based_extension_fields
from libs.login import login_required from libs.login import login_required
from services.code_based_extension_service import CodeBasedExtensionService from services.code_based_extension_service import CodeBasedExtensionService




@console_ns.route("/code-based-extension")
class CodeBasedExtensionAPI(Resource): class CodeBasedExtensionAPI(Resource):
@api.doc("get_code_based_extension")
@api.doc(description="Get code-based extension data by module name")
@api.expect(
api.parser().add_argument("module", type=str, required=True, location="args", help="Extension module name")
)
@api.response(
200,
"Success",
api.model(
"CodeBasedExtensionResponse",
{"module": fields.String(description="Module name"), "data": fields.Raw(description="Extension data")},
),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
return {"module": args["module"], "data": CodeBasedExtensionService.get_code_based_extension(args["module"])} return {"module": args["module"], "data": CodeBasedExtensionService.get_code_based_extension(args["module"])}




@console_ns.route("/api-based-extension")
class APIBasedExtensionAPI(Resource): class APIBasedExtensionAPI(Resource):
@api.doc("get_api_based_extensions")
@api.doc(description="Get all API-based extensions for current tenant")
@api.response(200, "Success", fields.List(fields.Nested(api_based_extension_fields)))
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
tenant_id = current_user.current_tenant_id tenant_id = current_user.current_tenant_id
return APIBasedExtensionService.get_all_by_tenant_id(tenant_id) return APIBasedExtensionService.get_all_by_tenant_id(tenant_id)


@api.doc("create_api_based_extension")
@api.doc(description="Create a new API-based extension")
@api.expect(
api.model(
"CreateAPIBasedExtensionRequest",
{
"name": fields.String(required=True, description="Extension name"),
"api_endpoint": fields.String(required=True, description="API endpoint URL"),
"api_key": fields.String(required=True, description="API key for authentication"),
},
)
)
@api.response(201, "Extension created successfully", api_based_extension_fields)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
return APIBasedExtensionService.save(extension_data) return APIBasedExtensionService.save(extension_data)




@console_ns.route("/api-based-extension/<uuid:id>")
class APIBasedExtensionDetailAPI(Resource): class APIBasedExtensionDetailAPI(Resource):
@api.doc("get_api_based_extension")
@api.doc(description="Get API-based extension by ID")
@api.doc(params={"id": "Extension ID"})
@api.response(200, "Success", api_based_extension_fields)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required


return APIBasedExtensionService.get_with_tenant_id(tenant_id, api_based_extension_id) return APIBasedExtensionService.get_with_tenant_id(tenant_id, api_based_extension_id)


@api.doc("update_api_based_extension")
@api.doc(description="Update API-based extension")
@api.doc(params={"id": "Extension ID"})
@api.expect(
api.model(
"UpdateAPIBasedExtensionRequest",
{
"name": fields.String(required=True, description="Extension name"),
"api_endpoint": fields.String(required=True, description="API endpoint URL"),
"api_key": fields.String(required=True, description="API key for authentication"),
},
)
)
@api.response(200, "Extension updated successfully", api_based_extension_fields)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required


return APIBasedExtensionService.save(extension_data_from_db) return APIBasedExtensionService.save(extension_data_from_db)


@api.doc("delete_api_based_extension")
@api.doc(description="Delete API-based extension")
@api.doc(params={"id": "Extension ID"})
@api.response(204, "Extension deleted successfully")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
APIBasedExtensionService.delete(extension_data_from_db) APIBasedExtensionService.delete(extension_data_from_db)


return {"result": "success"}, 204 return {"result": "success"}, 204


api.add_resource(CodeBasedExtensionAPI, "/code-based-extension")

api.add_resource(APIBasedExtensionAPI, "/api-based-extension")
api.add_resource(APIBasedExtensionDetailAPI, "/api-based-extension/<uuid:id>")

+ 20
- 6
api/controllers/console/feature.py 查看文件

from flask_login import current_user from flask_login import current_user
from flask_restx import Resource
from flask_restx import Resource, fields


from libs.login import login_required from libs.login import login_required
from services.feature_service import FeatureService from services.feature_service import FeatureService


from . import api
from . import api, console_ns
from .wraps import account_initialization_required, cloud_utm_record, setup_required from .wraps import account_initialization_required, cloud_utm_record, setup_required




@console_ns.route("/features")
class FeatureApi(Resource): class FeatureApi(Resource):
@api.doc("get_tenant_features")
@api.doc(description="Get feature configuration for current tenant")
@api.response(
200,
"Success",
api.model("FeatureResponse", {"features": fields.Raw(description="Feature configuration object")}),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@cloud_utm_record @cloud_utm_record
def get(self): def get(self):
"""Get feature configuration for current tenant"""
return FeatureService.get_features(current_user.current_tenant_id).model_dump() return FeatureService.get_features(current_user.current_tenant_id).model_dump()




@console_ns.route("/system-features")
class SystemFeatureApi(Resource): class SystemFeatureApi(Resource):
@api.doc("get_system_features")
@api.doc(description="Get system-wide feature configuration")
@api.response(
200,
"Success",
api.model("SystemFeatureResponse", {"features": fields.Raw(description="System feature configuration object")}),
)
def get(self): def get(self):
"""Get system-wide feature configuration"""
return FeatureService.get_system_features().model_dump() return FeatureService.get_system_features().model_dump()


api.add_resource(FeatureApi, "/features")
api.add_resource(SystemFeatureApi, "/system-features")

+ 29
- 5
api/controllers/console/init_validate.py 查看文件

import os import os


from flask import session from flask import session
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session


from models.model import DifySetup from models.model import DifySetup
from services.account_service import TenantService from services.account_service import TenantService


from . import api
from . import api, console_ns
from .error import AlreadySetupError, InitValidateFailedError from .error import AlreadySetupError, InitValidateFailedError
from .wraps import only_edition_self_hosted from .wraps import only_edition_self_hosted




@console_ns.route("/init")
class InitValidateAPI(Resource): class InitValidateAPI(Resource):
@api.doc("get_init_status")
@api.doc(description="Get initialization validation status")
@api.response(
200,
"Success",
model=api.model(
"InitStatusResponse",
{"status": fields.String(description="Initialization status", enum=["finished", "not_started"])},
),
)
def get(self): def get(self):
"""Get initialization validation status"""
init_status = get_init_validate_status() init_status = get_init_validate_status()
if init_status: if init_status:
return {"status": "finished"} return {"status": "finished"}
return {"status": "not_started"} return {"status": "not_started"}


@api.doc("validate_init_password")
@api.doc(description="Validate initialization password for self-hosted edition")
@api.expect(
api.model(
"InitValidateRequest",
{"password": fields.String(required=True, description="Initialization password", max_length=30)},
)
)
@api.response(
201,
"Success",
model=api.model("InitValidateResponse", {"result": fields.String(description="Operation result")}),
)
@api.response(400, "Already setup or validation failed")
@only_edition_self_hosted @only_edition_self_hosted
def post(self): def post(self):
"""Validate initialization password"""
# is tenant created # is tenant created
tenant_count = TenantService.get_tenant_count() tenant_count = TenantService.get_tenant_count()
if tenant_count > 0: if tenant_count > 0:
return db_session.execute(select(DifySetup)).scalar_one_or_none() return db_session.execute(select(DifySetup)).scalar_one_or_none()


return True return True


api.add_resource(InitValidateAPI, "/init")

+ 11
- 8
api/controllers/console/ping.py 查看文件

from flask_restx import Resource
from flask_restx import Resource, fields


from controllers.console import api
from . import api, console_ns




@console_ns.route("/ping")
class PingApi(Resource): class PingApi(Resource):
@api.doc("health_check")
@api.doc(description="Health check endpoint for connection testing")
@api.response(
200,
"Success",
api.model("PingResponse", {"result": fields.String(description="Health check result", example="pong")}),
)
def get(self): def get(self):
"""
For connection health check
"""
"""Health check endpoint for connection testing"""
return {"result": "pong"} return {"result": "pong"}


api.add_resource(PingApi, "/ping")

+ 36
- 6
api/controllers/console/setup.py 查看文件

from flask import request from flask import request
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse


from configs import dify_config from configs import dify_config
from libs.helper import StrLen, email, extract_remote_ip from libs.helper import StrLen, email, extract_remote_ip
from models.model import DifySetup, db from models.model import DifySetup, db
from services.account_service import RegisterService, TenantService from services.account_service import RegisterService, TenantService


from . import api
from . import api, console_ns
from .error import AlreadySetupError, NotInitValidateError from .error import AlreadySetupError, NotInitValidateError
from .init_validate import get_init_validate_status from .init_validate import get_init_validate_status
from .wraps import only_edition_self_hosted from .wraps import only_edition_self_hosted




@console_ns.route("/setup")
class SetupApi(Resource): class SetupApi(Resource):
@api.doc("get_setup_status")
@api.doc(description="Get system setup status")
@api.response(
200,
"Success",
api.model(
"SetupStatusResponse",
{
"step": fields.String(description="Setup step status", enum=["not_started", "finished"]),
"setup_at": fields.String(description="Setup completion time (ISO format)", required=False),
},
),
)
def get(self): def get(self):
"""Get system setup status"""
if dify_config.EDITION == "SELF_HOSTED": if dify_config.EDITION == "SELF_HOSTED":
setup_status = get_setup_status() setup_status = get_setup_status()
if setup_status:
# Check if setup_status is a DifySetup object rather than a bool
if setup_status and not isinstance(setup_status, bool):
return {"step": "finished", "setup_at": setup_status.setup_at.isoformat()} return {"step": "finished", "setup_at": setup_status.setup_at.isoformat()}
elif setup_status:
return {"step": "finished"}
return {"step": "not_started"} return {"step": "not_started"}
return {"step": "finished"} return {"step": "finished"}


@api.doc("setup_system")
@api.doc(description="Initialize system setup with admin account")
@api.expect(
api.model(
"SetupRequest",
{
"email": fields.String(required=True, description="Admin email address"),
"name": fields.String(required=True, description="Admin name (max 30 characters)"),
"password": fields.String(required=True, description="Admin password"),
},
)
)
@api.response(201, "Success", api.model("SetupResponse", {"result": fields.String(description="Setup result")}))
@api.response(400, "Already setup or validation failed")
@only_edition_self_hosted @only_edition_self_hosted
def post(self): def post(self):
"""Initialize system setup with admin account"""
# is set up # is set up
if get_setup_status(): if get_setup_status():
raise AlreadySetupError() raise AlreadySetupError()
return db.session.query(DifySetup).first() return db.session.query(DifySetup).first()
else: else:
return True return True


api.add_resource(SetupApi, "/setup")

+ 25
- 5
api/controllers/console/version.py 查看文件

import logging import logging


import requests import requests
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse
from packaging import version from packaging import version


from configs import dify_config from configs import dify_config


from . import api
from . import api, console_ns


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




@console_ns.route("/version")
class VersionApi(Resource): class VersionApi(Resource):
@api.doc("check_version_update")
@api.doc(description="Check for application version updates")
@api.expect(
api.parser().add_argument(
"current_version", type=str, required=True, location="args", help="Current application version"
)
)
@api.response(
200,
"Success",
api.model(
"VersionResponse",
{
"version": fields.String(description="Latest version number"),
"release_date": fields.String(description="Release date of latest version"),
"release_notes": fields.String(description="Release notes for latest version"),
"can_auto_update": fields.Boolean(description="Whether auto-update is supported"),
"features": fields.Raw(description="Feature flags and capabilities"),
},
),
)
def get(self): def get(self):
"""Check for application version updates"""
parser = reqparse.RequestParser() parser = reqparse.RequestParser()
parser.add_argument("current_version", type=str, required=True, location="args") parser.add_argument("current_version", type=str, required=True, location="args")
args = parser.parse_args() args = parser.parse_args()
except version.InvalidVersion: except version.InvalidVersion:
logger.warning("Invalid version format: latest=%s, current=%s", latest_version, current_version) logger.warning("Invalid version format: latest=%s, current=%s", latest_version, current_version)
return False return False


api.add_resource(VersionApi, "/version")

+ 19
- 6
api/controllers/console/workspace/agent_providers.py 查看文件

from flask_login import current_user from flask_login import current_user
from flask_restx import Resource
from flask_restx import Resource, fields


from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from libs.login import login_required from libs.login import login_required
from services.agent_service import AgentService from services.agent_service import AgentService




@console_ns.route("/workspaces/current/agent-providers")
class AgentProviderListApi(Resource): class AgentProviderListApi(Resource):
@api.doc("list_agent_providers")
@api.doc(description="Get list of available agent providers")
@api.response(
200,
"Success",
fields.List(fields.Raw(description="Agent provider information")),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
return jsonable_encoder(AgentService.list_agent_providers(user_id, tenant_id)) return jsonable_encoder(AgentService.list_agent_providers(user_id, tenant_id))




@console_ns.route("/workspaces/current/agent-provider/<path:provider_name>")
class AgentProviderApi(Resource): class AgentProviderApi(Resource):
@api.doc("get_agent_provider")
@api.doc(description="Get specific agent provider details")
@api.doc(params={"provider_name": "Agent provider name"})
@api.response(
200,
"Success",
fields.Raw(description="Agent provider details"),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
user_id = user.id user_id = user.id
tenant_id = user.current_tenant_id tenant_id = user.current_tenant_id
return jsonable_encoder(AgentService.get_agent_provider(user_id, tenant_id, provider_name)) return jsonable_encoder(AgentService.get_agent_provider(user_id, tenant_id, provider_name))


api.add_resource(AgentProviderListApi, "/workspaces/current/agent-providers")
api.add_resource(AgentProviderApi, "/workspaces/current/agent-provider/<path:provider_name>")

+ 105
- 11
api/controllers/console/workspace/endpoint.py 查看文件

from flask_login import current_user from flask_login import current_user
from flask_restx import Resource, reqparse
from flask_restx import Resource, fields, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden


from controllers.console import api
from controllers.console import api, console_ns
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.exc import PluginPermissionDeniedError from core.plugin.impl.exc import PluginPermissionDeniedError
from services.plugin.endpoint_service import EndpointService from services.plugin.endpoint_service import EndpointService




@console_ns.route("/workspaces/current/endpoints/create")
class EndpointCreateApi(Resource): class EndpointCreateApi(Resource):
@api.doc("create_endpoint")
@api.doc(description="Create a new plugin endpoint")
@api.expect(
api.model(
"EndpointCreateRequest",
{
"plugin_unique_identifier": fields.String(required=True, description="Plugin unique identifier"),
"settings": fields.Raw(required=True, description="Endpoint settings"),
"name": fields.String(required=True, description="Endpoint name"),
},
)
)
@api.response(
200,
"Endpoint created successfully",
api.model("EndpointCreateResponse", {"success": fields.Boolean(description="Operation success")}),
)
@api.response(403, "Admin privileges required")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
raise ValueError(e.description) from e raise ValueError(e.description) from e




@console_ns.route("/workspaces/current/endpoints/list")
class EndpointListApi(Resource): class EndpointListApi(Resource):
@api.doc("list_endpoints")
@api.doc(description="List plugin endpoints with pagination")
@api.expect(
api.parser()
.add_argument("page", type=int, required=True, location="args", help="Page number")
.add_argument("page_size", type=int, required=True, location="args", help="Page size")
)
@api.response(
200,
"Success",
api.model("EndpointListResponse", {"endpoints": fields.List(fields.Raw(description="Endpoint information"))}),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
) )




@console_ns.route("/workspaces/current/endpoints/list/plugin")
class EndpointListForSinglePluginApi(Resource): class EndpointListForSinglePluginApi(Resource):
@api.doc("list_plugin_endpoints")
@api.doc(description="List endpoints for a specific plugin")
@api.expect(
api.parser()
.add_argument("page", type=int, required=True, location="args", help="Page number")
.add_argument("page_size", type=int, required=True, location="args", help="Page size")
.add_argument("plugin_id", type=str, required=True, location="args", help="Plugin ID")
)
@api.response(
200,
"Success",
api.model(
"PluginEndpointListResponse", {"endpoints": fields.List(fields.Raw(description="Endpoint information"))}
),
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
) )




@console_ns.route("/workspaces/current/endpoints/delete")
class EndpointDeleteApi(Resource): class EndpointDeleteApi(Resource):
@api.doc("delete_endpoint")
@api.doc(description="Delete a plugin endpoint")
@api.expect(
api.model("EndpointDeleteRequest", {"endpoint_id": fields.String(required=True, description="Endpoint ID")})
)
@api.response(
200,
"Endpoint deleted successfully",
api.model("EndpointDeleteResponse", {"success": fields.Boolean(description="Operation success")}),
)
@api.response(403, "Admin privileges required")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
} }




@console_ns.route("/workspaces/current/endpoints/update")
class EndpointUpdateApi(Resource): class EndpointUpdateApi(Resource):
@api.doc("update_endpoint")
@api.doc(description="Update a plugin endpoint")
@api.expect(
api.model(
"EndpointUpdateRequest",
{
"endpoint_id": fields.String(required=True, description="Endpoint ID"),
"settings": fields.Raw(required=True, description="Updated settings"),
"name": fields.String(required=True, description="Updated name"),
},
)
)
@api.response(
200,
"Endpoint updated successfully",
api.model("EndpointUpdateResponse", {"success": fields.Boolean(description="Operation success")}),
)
@api.response(403, "Admin privileges required")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
} }




@console_ns.route("/workspaces/current/endpoints/enable")
class EndpointEnableApi(Resource): class EndpointEnableApi(Resource):
@api.doc("enable_endpoint")
@api.doc(description="Enable a plugin endpoint")
@api.expect(
api.model("EndpointEnableRequest", {"endpoint_id": fields.String(required=True, description="Endpoint ID")})
)
@api.response(
200,
"Endpoint enabled successfully",
api.model("EndpointEnableResponse", {"success": fields.Boolean(description="Operation success")}),
)
@api.response(403, "Admin privileges required")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
} }




@console_ns.route("/workspaces/current/endpoints/disable")
class EndpointDisableApi(Resource): class EndpointDisableApi(Resource):
@api.doc("disable_endpoint")
@api.doc(description="Disable a plugin endpoint")
@api.expect(
api.model("EndpointDisableRequest", {"endpoint_id": fields.String(required=True, description="Endpoint ID")})
)
@api.response(
200,
"Endpoint disabled successfully",
api.model("EndpointDisableResponse", {"success": fields.Boolean(description="Operation success")}),
)
@api.response(403, "Admin privileges required")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
tenant_id=user.current_tenant_id, user_id=user.id, endpoint_id=endpoint_id tenant_id=user.current_tenant_id, user_id=user.id, endpoint_id=endpoint_id
) )
} }


api.add_resource(EndpointCreateApi, "/workspaces/current/endpoints/create")
api.add_resource(EndpointListApi, "/workspaces/current/endpoints/list")
api.add_resource(EndpointListForSinglePluginApi, "/workspaces/current/endpoints/list/plugin")
api.add_resource(EndpointDeleteApi, "/workspaces/current/endpoints/delete")
api.add_resource(EndpointUpdateApi, "/workspaces/current/endpoints/update")
api.add_resource(EndpointEnableApi, "/workspaces/current/endpoints/enable")
api.add_resource(EndpointDisableApi, "/workspaces/current/endpoints/disable")

+ 0
- 1
api/controllers/files/__init__.py 查看文件

version="1.0", version="1.0",
title="Files API", title="Files API",
description="API for file operations including upload and preview", description="API for file operations including upload and preview",
doc="/docs", # Enable Swagger UI at /files/docs
) )


files_ns = Namespace("files", description="File operations", path="/") files_ns = Namespace("files", description="File operations", path="/")

+ 0
- 1
api/controllers/inner_api/__init__.py 查看文件

version="1.0", version="1.0",
title="Inner API", title="Inner API",
description="Internal APIs for enterprise features, billing, and plugin communication", description="Internal APIs for enterprise features, billing, and plugin communication",
doc="/docs", # Enable Swagger UI at /inner/api/docs
) )


# Create namespace # Create namespace

+ 0
- 1
api/controllers/mcp/__init__.py 查看文件

version="1.0", version="1.0",
title="MCP API", title="MCP API",
description="API for Model Context Protocol operations", description="API for Model Context Protocol operations",
doc="/docs", # Enable Swagger UI at /mcp/docs
) )


mcp_ns = Namespace("mcp", description="MCP operations", path="/") mcp_ns = Namespace("mcp", description="MCP operations", path="/")

+ 0
- 1
api/controllers/service_api/__init__.py 查看文件

version="1.0", version="1.0",
title="Service API", title="Service API",
description="API for application services", description="API for application services",
doc="/docs", # Enable Swagger UI at /v1/docs
) )


service_api_ns = Namespace("service_api", description="Service operations", path="/") service_api_ns = Namespace("service_api", description="Service operations", path="/")

+ 0
- 1
api/controllers/web/__init__.py 查看文件

version="1.0", version="1.0",
title="Web API", title="Web API",
description="Public APIs for web applications including file uploads, chat interactions, and app management", description="Public APIs for web applications including file uploads, chat interactions, and app management",
doc="/docs", # Enable Swagger UI at /api/docs
) )


# Create namespace # Create namespace

+ 9
- 11
api/controllers/web/audio.py 查看文件

from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError


import services import services
from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import ( from controllers.web.error import (
AppUnavailableError, AppUnavailableError,
AudioTooLargeError, AudioTooLargeError,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




@web_ns.route("/audio-to-text")
class AudioApi(WebApiResource): class AudioApi(WebApiResource):
audio_to_text_response_fields = { audio_to_text_response_fields = {
"text": fields.String, "text": fields.String,
} }


@marshal_with(audio_to_text_response_fields) @marshal_with(audio_to_text_response_fields)
@api.doc("Audio to Text")
@api.doc(description="Convert audio file to text using speech-to-text service.")
@api.doc(
@web_ns.doc("Audio to Text")
@web_ns.doc(description="Convert audio file to text using speech-to-text service.")
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
raise InternalServerError() raise InternalServerError()




@web_ns.route("/text-to-audio")
class TextApi(WebApiResource): class TextApi(WebApiResource):
text_to_audio_response_fields = { text_to_audio_response_fields = {
"audio_url": fields.String, "audio_url": fields.String,
} }


@marshal_with(text_to_audio_response_fields) @marshal_with(text_to_audio_response_fields)
@api.doc("Text to Audio")
@api.doc(description="Convert text to audio using text-to-speech service.")
@api.doc(
@web_ns.doc("Text to Audio")
@web_ns.doc(description="Convert text to audio using text-to-speech service.")
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
except Exception as e: except Exception as e:
logger.exception("Failed to handle post request to TextApi") logger.exception("Failed to handle post request to TextApi")
raise InternalServerError() raise InternalServerError()


api.add_resource(AudioApi, "/audio-to-text")
api.add_resource(TextApi, "/text-to-audio")

+ 21
- 23
api/controllers/web/completion.py 查看文件

from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound


import services import services
from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import ( from controllers.web.error import (
AppUnavailableError, AppUnavailableError,
CompletionRequestError, CompletionRequestError,




# define completion api for user # define completion api for user
@web_ns.route("/completion-messages")
class CompletionApi(WebApiResource): class CompletionApi(WebApiResource):
@api.doc("Create Completion Message")
@api.doc(description="Create a completion message for text generation applications.")
@api.doc(
@web_ns.doc("Create Completion Message")
@web_ns.doc(description="Create a completion message for text generation applications.")
@web_ns.doc(
params={ params={
"inputs": {"description": "Input variables for the completion", "type": "object", "required": True}, "inputs": {"description": "Input variables for the completion", "type": "object", "required": True},
"query": {"description": "Query text for completion", "type": "string", "required": False}, "query": {"description": "Query text for completion", "type": "string", "required": False},
"retriever_from": {"description": "Source of retriever", "type": "string", "required": False}, "retriever_from": {"description": "Source of retriever", "type": "string", "required": False},
} }
) )
@api.doc(
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
raise InternalServerError() raise InternalServerError()




@web_ns.route("/completion-messages/<string:task_id>/stop")
class CompletionStopApi(WebApiResource): class CompletionStopApi(WebApiResource):
@api.doc("Stop Completion Message")
@api.doc(description="Stop a running completion message task.")
@api.doc(params={"task_id": {"description": "Task ID to stop", "type": "string", "required": True}})
@api.doc(
@web_ns.doc("Stop Completion Message")
@web_ns.doc(description="Stop a running completion message task.")
@web_ns.doc(params={"task_id": {"description": "Task ID to stop", "type": "string", "required": True}})
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
return {"result": "success"}, 200 return {"result": "success"}, 200




@web_ns.route("/chat-messages")
class ChatApi(WebApiResource): class ChatApi(WebApiResource):
@api.doc("Create Chat Message")
@api.doc(description="Create a chat message for conversational applications.")
@api.doc(
@web_ns.doc("Create Chat Message")
@web_ns.doc(description="Create a chat message for conversational applications.")
@web_ns.doc(
params={ params={
"inputs": {"description": "Input variables for the chat", "type": "object", "required": True}, "inputs": {"description": "Input variables for the chat", "type": "object", "required": True},
"query": {"description": "User query/message", "type": "string", "required": True}, "query": {"description": "User query/message", "type": "string", "required": True},
"retriever_from": {"description": "Source of retriever", "type": "string", "required": False}, "retriever_from": {"description": "Source of retriever", "type": "string", "required": False},
} }
) )
@api.doc(
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
raise InternalServerError() raise InternalServerError()




@web_ns.route("/chat-messages/<string:task_id>/stop")
class ChatStopApi(WebApiResource): class ChatStopApi(WebApiResource):
@api.doc("Stop Chat Message")
@api.doc(description="Stop a running chat message task.")
@api.doc(params={"task_id": {"description": "Task ID to stop", "type": "string", "required": True}})
@api.doc(
@web_ns.doc("Stop Chat Message")
@web_ns.doc(description="Stop a running chat message task.")
@web_ns.doc(params={"task_id": {"description": "Task ID to stop", "type": "string", "required": True}})
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id) AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)


return {"result": "success"}, 200 return {"result": "success"}, 200


api.add_resource(CompletionApi, "/completion-messages")
api.add_resource(CompletionStopApi, "/completion-messages/<string:task_id>/stop")
api.add_resource(ChatApi, "/chat-messages")
api.add_resource(ChatStopApi, "/chat-messages/<string:task_id>/stop")

+ 105
- 8
api/controllers/web/conversation.py 查看文件

from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound


from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import NotChatAppError from controllers.web.error import NotChatAppError
from controllers.web.wraps import WebApiResource from controllers.web.wraps import WebApiResource
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from services.web_conversation_service import WebConversationService from services.web_conversation_service import WebConversationService




@web_ns.route("/conversations")
class ConversationListApi(WebApiResource): class ConversationListApi(WebApiResource):
@web_ns.doc("Get Conversation List")
@web_ns.doc(description="Retrieve paginated list of conversations for a chat application.")
@web_ns.doc(
params={
"last_id": {"description": "Last conversation ID for pagination", "type": "string", "required": False},
"limit": {
"description": "Number of conversations to return (1-100)",
"type": "integer",
"required": False,
"default": 20,
},
"pinned": {
"description": "Filter by pinned status",
"type": "string",
"enum": ["true", "false"],
"required": False,
},
"sort_by": {
"description": "Sort order",
"type": "string",
"enum": ["created_at", "-created_at", "updated_at", "-updated_at"],
"required": False,
"default": "-updated_at",
},
}
)
@web_ns.doc(
responses={
200: "Success",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "App Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(conversation_infinite_scroll_pagination_fields) @marshal_with(conversation_infinite_scroll_pagination_fields)
def get(self, app_model, end_user): def get(self, app_model, end_user):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
raise NotFound("Last Conversation Not Exists.") raise NotFound("Last Conversation Not Exists.")




@web_ns.route("/conversations/<uuid:c_id>")
class ConversationApi(WebApiResource): class ConversationApi(WebApiResource):
delete_response_fields = { delete_response_fields = {
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Delete Conversation")
@web_ns.doc(description="Delete a specific conversation.")
@web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}})
@web_ns.doc(
responses={
204: "Conversation deleted successfully",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Conversation Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(delete_response_fields) @marshal_with(delete_response_fields)
def delete(self, app_model, end_user, c_id): def delete(self, app_model, end_user, c_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
return {"result": "success"}, 204 return {"result": "success"}, 204




@web_ns.route("/conversations/<uuid:c_id>/name")
class ConversationRenameApi(WebApiResource): class ConversationRenameApi(WebApiResource):
@web_ns.doc("Rename Conversation")
@web_ns.doc(description="Rename a specific conversation with a custom name or auto-generate one.")
@web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}})
@web_ns.doc(
params={
"name": {"description": "New conversation name", "type": "string", "required": False},
"auto_generate": {
"description": "Auto-generate conversation name",
"type": "boolean",
"required": False,
"default": False,
},
}
)
@web_ns.doc(
responses={
200: "Conversation renamed successfully",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Conversation Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(simple_conversation_fields) @marshal_with(simple_conversation_fields)
def post(self, app_model, end_user, c_id): def post(self, app_model, end_user, c_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
raise NotFound("Conversation Not Exists.") raise NotFound("Conversation Not Exists.")




@web_ns.route("/conversations/<uuid:c_id>/pin")
class ConversationPinApi(WebApiResource): class ConversationPinApi(WebApiResource):
pin_response_fields = { pin_response_fields = {
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Pin Conversation")
@web_ns.doc(description="Pin a specific conversation to keep it at the top of the list.")
@web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}})
@web_ns.doc(
responses={
200: "Conversation pinned successfully",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Conversation Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(pin_response_fields) @marshal_with(pin_response_fields)
def patch(self, app_model, end_user, c_id): def patch(self, app_model, end_user, c_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
return {"result": "success"} return {"result": "success"}




@web_ns.route("/conversations/<uuid:c_id>/unpin")
class ConversationUnPinApi(WebApiResource): class ConversationUnPinApi(WebApiResource):
unpin_response_fields = { unpin_response_fields = {
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Unpin Conversation")
@web_ns.doc(description="Unpin a specific conversation to remove it from the top of the list.")
@web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}})
@web_ns.doc(
responses={
200: "Conversation unpinned successfully",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Conversation Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(unpin_response_fields) @marshal_with(unpin_response_fields)
def patch(self, app_model, end_user, c_id): def patch(self, app_model, end_user, c_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
WebConversationService.unpin(app_model, conversation_id, end_user) WebConversationService.unpin(app_model, conversation_id, end_user)


return {"result": "success"} return {"result": "success"}


api.add_resource(ConversationRenameApi, "/conversations/<uuid:c_id>/name", endpoint="web_conversation_name")
api.add_resource(ConversationListApi, "/conversations")
api.add_resource(ConversationApi, "/conversations/<uuid:c_id>")
api.add_resource(ConversationPinApi, "/conversations/<uuid:c_id>/pin")
api.add_resource(ConversationUnPinApi, "/conversations/<uuid:c_id>/unpin")

+ 89
- 7
api/controllers/web/message.py 查看文件

from flask_restx.inputs import int_range from flask_restx.inputs import int_range
from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound


from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import ( from controllers.web.error import (
AppMoreLikeThisDisabledError, AppMoreLikeThisDisabledError,
AppSuggestedQuestionsAfterAnswerDisabledError, AppSuggestedQuestionsAfterAnswerDisabledError,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




@web_ns.route("/messages")
class MessageListApi(WebApiResource): class MessageListApi(WebApiResource):
message_fields = { message_fields = {
"id": fields.String, "id": fields.String,
"data": fields.List(fields.Nested(message_fields)), "data": fields.List(fields.Nested(message_fields)),
} }


@web_ns.doc("Get Message List")
@web_ns.doc(description="Retrieve paginated list of messages from a conversation in a chat application.")
@web_ns.doc(
params={
"conversation_id": {"description": "Conversation UUID", "type": "string", "required": True},
"first_id": {"description": "First message ID for pagination", "type": "string", "required": False},
"limit": {
"description": "Number of messages to return (1-100)",
"type": "integer",
"required": False,
"default": 20,
},
}
)
@web_ns.doc(
responses={
200: "Success",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Conversation Not Found or Not a Chat App",
500: "Internal Server Error",
}
)
@marshal_with(message_infinite_scroll_pagination_fields) @marshal_with(message_infinite_scroll_pagination_fields)
def get(self, app_model, end_user): def get(self, app_model, end_user):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
raise NotFound("First Message Not Exists.") raise NotFound("First Message Not Exists.")




@web_ns.route("/messages/<uuid:message_id>/feedbacks")
class MessageFeedbackApi(WebApiResource): class MessageFeedbackApi(WebApiResource):
feedback_response_fields = { feedback_response_fields = {
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Create Message Feedback")
@web_ns.doc(description="Submit feedback (like/dislike) for a specific message.")
@web_ns.doc(params={"message_id": {"description": "Message UUID", "type": "string", "required": True}})
@web_ns.doc(
params={
"rating": {
"description": "Feedback rating",
"type": "string",
"enum": ["like", "dislike"],
"required": False,
},
"content": {"description": "Feedback content/comment", "type": "string", "required": False},
}
)
@web_ns.doc(
responses={
200: "Feedback submitted successfully",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Message Not Found",
500: "Internal Server Error",
}
)
@marshal_with(feedback_response_fields) @marshal_with(feedback_response_fields)
def post(self, app_model, end_user, message_id): def post(self, app_model, end_user, message_id):
message_id = str(message_id) message_id = str(message_id)
return {"result": "success"} return {"result": "success"}




@web_ns.route("/messages/<uuid:message_id>/more-like-this")
class MessageMoreLikeThisApi(WebApiResource): class MessageMoreLikeThisApi(WebApiResource):
@web_ns.doc("Generate More Like This")
@web_ns.doc(description="Generate a new completion similar to an existing message (completion apps only).")
@web_ns.doc(
params={
"message_id": {"description": "Message UUID", "type": "string", "required": True},
"response_mode": {
"description": "Response mode",
"type": "string",
"enum": ["blocking", "streaming"],
"required": True,
},
}
)
@web_ns.doc(
responses={
200: "Success",
400: "Bad Request - Not a completion app or feature disabled",
401: "Unauthorized",
403: "Forbidden",
404: "Message Not Found",
500: "Internal Server Error",
}
)
def get(self, app_model, end_user, message_id): def get(self, app_model, end_user, message_id):
if app_model.mode != "completion": if app_model.mode != "completion":
raise NotCompletionAppError() raise NotCompletionAppError()
raise InternalServerError() raise InternalServerError()




@web_ns.route("/messages/<uuid:message_id>/suggested-questions")
class MessageSuggestedQuestionApi(WebApiResource): class MessageSuggestedQuestionApi(WebApiResource):
suggested_questions_response_fields = { suggested_questions_response_fields = {
"data": fields.List(fields.String), "data": fields.List(fields.String),
} }


@web_ns.doc("Get Suggested Questions")
@web_ns.doc(description="Get suggested follow-up questions after a message (chat apps only).")
@web_ns.doc(params={"message_id": {"description": "Message UUID", "type": "string", "required": True}})
@web_ns.doc(
responses={
200: "Success",
400: "Bad Request - Not a chat app or feature disabled",
401: "Unauthorized",
403: "Forbidden",
404: "Message Not Found or Conversation Not Found",
500: "Internal Server Error",
}
)
@marshal_with(suggested_questions_response_fields) @marshal_with(suggested_questions_response_fields)
def get(self, app_model, end_user, message_id): def get(self, app_model, end_user, message_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
raise InternalServerError() raise InternalServerError()


return {"data": questions} return {"data": questions}


api.add_resource(MessageListApi, "/messages")
api.add_resource(MessageFeedbackApi, "/messages/<uuid:message_id>/feedbacks")
api.add_resource(MessageMoreLikeThisApi, "/messages/<uuid:message_id>/more-like-this")
api.add_resource(MessageSuggestedQuestionApi, "/messages/<uuid:message_id>/suggested-questions")

+ 56
- 5
api/controllers/web/saved_message.py 查看文件

from flask_restx.inputs import int_range from flask_restx.inputs import int_range
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound


from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import NotCompletionAppError from controllers.web.error import NotCompletionAppError
from controllers.web.wraps import WebApiResource from controllers.web.wraps import WebApiResource
from fields.conversation_fields import message_file_fields from fields.conversation_fields import message_file_fields
} }




@web_ns.route("/saved-messages")
class SavedMessageListApi(WebApiResource): class SavedMessageListApi(WebApiResource):
saved_message_infinite_scroll_pagination_fields = { saved_message_infinite_scroll_pagination_fields = {
"limit": fields.Integer, "limit": fields.Integer,
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Get Saved Messages")
@web_ns.doc(description="Retrieve paginated list of saved messages for a completion application.")
@web_ns.doc(
params={
"last_id": {"description": "Last message ID for pagination", "type": "string", "required": False},
"limit": {
"description": "Number of messages to return (1-100)",
"type": "integer",
"required": False,
"default": 20,
},
}
)
@web_ns.doc(
responses={
200: "Success",
400: "Bad Request - Not a completion app",
401: "Unauthorized",
403: "Forbidden",
404: "App Not Found",
500: "Internal Server Error",
}
)
@marshal_with(saved_message_infinite_scroll_pagination_fields) @marshal_with(saved_message_infinite_scroll_pagination_fields)
def get(self, app_model, end_user): def get(self, app_model, end_user):
if app_model.mode != "completion": if app_model.mode != "completion":


return SavedMessageService.pagination_by_last_id(app_model, end_user, args["last_id"], args["limit"]) return SavedMessageService.pagination_by_last_id(app_model, end_user, args["last_id"], args["limit"])


@web_ns.doc("Save Message")
@web_ns.doc(description="Save a specific message for later reference.")
@web_ns.doc(
params={
"message_id": {"description": "Message UUID to save", "type": "string", "required": True},
}
)
@web_ns.doc(
responses={
200: "Message saved successfully",
400: "Bad Request - Not a completion app",
401: "Unauthorized",
403: "Forbidden",
404: "Message Not Found",
500: "Internal Server Error",
}
)
@marshal_with(post_response_fields) @marshal_with(post_response_fields)
def post(self, app_model, end_user): def post(self, app_model, end_user):
if app_model.mode != "completion": if app_model.mode != "completion":
return {"result": "success"} return {"result": "success"}




@web_ns.route("/saved-messages/<uuid:message_id>")
class SavedMessageApi(WebApiResource): class SavedMessageApi(WebApiResource):
delete_response_fields = { delete_response_fields = {
"result": fields.String, "result": fields.String,
} }


@web_ns.doc("Delete Saved Message")
@web_ns.doc(description="Remove a message from saved messages.")
@web_ns.doc(params={"message_id": {"description": "Message UUID to delete", "type": "string", "required": True}})
@web_ns.doc(
responses={
204: "Message removed successfully",
400: "Bad Request - Not a completion app",
401: "Unauthorized",
403: "Forbidden",
404: "Message Not Found",
500: "Internal Server Error",
}
)
@marshal_with(delete_response_fields) @marshal_with(delete_response_fields)
def delete(self, app_model, end_user, message_id): def delete(self, app_model, end_user, message_id):
message_id = str(message_id) message_id = str(message_id)
SavedMessageService.delete(app_model, end_user, message_id) SavedMessageService.delete(app_model, end_user, message_id)


return {"result": "success"}, 204 return {"result": "success"}, 204


api.add_resource(SavedMessageListApi, "/saved-messages")
api.add_resource(SavedMessageApi, "/saved-messages/<uuid:message_id>")

+ 5
- 7
api/controllers/web/site.py 查看文件

from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden


from configs import dify_config from configs import dify_config
from controllers.web import api
from controllers.web import web_ns
from controllers.web.wraps import WebApiResource from controllers.web.wraps import WebApiResource
from extensions.ext_database import db from extensions.ext_database import db
from libs.helper import AppIconUrlField from libs.helper import AppIconUrlField
from services.feature_service import FeatureService from services.feature_service import FeatureService




@web_ns.route("/site")
class AppSiteApi(WebApiResource): class AppSiteApi(WebApiResource):
"""Resource for app sites.""" """Resource for app sites."""


"custom_config": fields.Raw(attribute="custom_config"), "custom_config": fields.Raw(attribute="custom_config"),
} }


@api.doc("Get App Site Info")
@api.doc(description="Retrieve app site information and configuration.")
@api.doc(
@web_ns.doc("Get App Site Info")
@web_ns.doc(description="Retrieve app site information and configuration.")
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
return AppSiteInfo(app_model.tenant, app_model, site, end_user.id, can_replace_logo) return AppSiteInfo(app_model.tenant, app_model, site, end_user.id, can_replace_logo)




api.add_resource(AppSiteApi, "/site")


class AppSiteInfo: class AppSiteInfo:
"""Class to store site information.""" """Class to store site information."""



+ 11
- 13
api/controllers/web/workflow.py 查看文件

from flask_restx import reqparse from flask_restx import reqparse
from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError


from controllers.web import api
from controllers.web import web_ns
from controllers.web.error import ( from controllers.web.error import (
CompletionRequestError, CompletionRequestError,
NotWorkflowAppError, NotWorkflowAppError,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




@web_ns.route("/workflows/run")
class WorkflowRunApi(WebApiResource): class WorkflowRunApi(WebApiResource):
@api.doc("Run Workflow")
@api.doc(description="Execute a workflow with provided inputs and files.")
@api.doc(
@web_ns.doc("Run Workflow")
@web_ns.doc(description="Execute a workflow with provided inputs and files.")
@web_ns.doc(
params={ params={
"inputs": {"description": "Input variables for the workflow", "type": "object", "required": True}, "inputs": {"description": "Input variables for the workflow", "type": "object", "required": True},
"files": {"description": "Files to be processed by the workflow", "type": "array", "required": False}, "files": {"description": "Files to be processed by the workflow", "type": "array", "required": False},
} }
) )
@api.doc(
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
raise InternalServerError() raise InternalServerError()




@web_ns.route("/workflows/tasks/<string:task_id>/stop")
class WorkflowTaskStopApi(WebApiResource): class WorkflowTaskStopApi(WebApiResource):
@api.doc("Stop Workflow Task")
@api.doc(description="Stop a running workflow task.")
@api.doc(
@web_ns.doc("Stop Workflow Task")
@web_ns.doc(description="Stop a running workflow task.")
@web_ns.doc(
params={ params={
"task_id": {"description": "Task ID to stop", "type": "string", "required": True}, "task_id": {"description": "Task ID to stop", "type": "string", "required": True},
} }
) )
@api.doc(
@web_ns.doc(
responses={ responses={
200: "Success", 200: "Success",
400: "Bad Request", 400: "Bad Request",
AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id) AppQueueManager.set_stop_flag(task_id, InvokeFrom.WEB_APP, end_user.id)


return {"result": "success"} return {"result": "success"}


api.add_resource(WorkflowRunApi, "/workflows/run")
api.add_resource(WorkflowTaskStopApi, "/workflows/tasks/<string:task_id>/stop")

正在加载...
取消
保存