diff --git a/api/README.md b/api/README.md index c542f11b16..9308d5dc44 100644 --- a/api/README.md +++ b/api/README.md @@ -90,3 +90,4 @@ ```bash uv run -P api bash dev/pytest/pytest_all_tests.sh ``` + diff --git a/api/app.py b/api/app.py index 9830a80904..4f393f6c20 100644 --- a/api/app.py +++ b/api/app.py @@ -18,7 +18,7 @@ else: # so we need to disable gevent in debug mode. # If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent. if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}: - from gevent import monkey # type: ignore + from gevent import monkey # gevent monkey.patch_all() diff --git a/api/app_factory.py b/api/app_factory.py index d496cc1b4a..71c7b2da17 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -55,7 +55,6 @@ def initialize_extensions(app: DifyApp): ext_phone_sms, ext_proxy_fix, ext_redis, - ext_repositories, ext_sentry, ext_set_secretkey, ext_storage, @@ -77,7 +76,6 @@ def initialize_extensions(app: DifyApp): ext_migrate, ext_redis, ext_storage, - ext_repositories, ext_celery, ext_login, ext_mail, diff --git a/api/commands.py b/api/commands.py index 79c5a9a145..ce1f59799e 100644 --- a/api/commands.py +++ b/api/commands.py @@ -742,7 +742,7 @@ def upgrade_db(): click.echo(click.style("Starting database migration.", fg="green")) # run db migration - import flask_migrate # type: ignore + import flask_migrate flask_migrate.upgrade() diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 77cfda8e9e..db70abe67c 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -405,6 +405,11 @@ class InnerAPIConfig(BaseSettings): default=False, ) + INNER_API_KEY: Optional[str] = Field( + description="API key for accessing the internal API", + default=None, + ) + class LoggingConfig(BaseSettings): """ diff --git a/api/controllers/common/fields.py b/api/controllers/common/fields.py index b1ebc444a5..79869916ed 100644 --- a/api/controllers/common/fields.py +++ b/api/controllers/common/fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields parameters__system_parameters = { "image_file_size_limit": fields.Integer, diff --git a/api/controllers/console/admin.py b/api/controllers/console/admin.py index 6e3273f5d4..8cb7ad9f5b 100644 --- a/api/controllers/console/admin.py +++ b/api/controllers/console/admin.py @@ -1,7 +1,7 @@ from functools import wraps from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound, Unauthorized diff --git a/api/controllers/console/apikey.py b/api/controllers/console/apikey.py index eb42507c63..47c93a15c6 100644 --- a/api/controllers/console/apikey.py +++ b/api/controllers/console/apikey.py @@ -1,7 +1,7 @@ from typing import Any -import flask_restful # type: ignore -from flask_login import current_user # type: ignore +import flask_restful +from flask_login import current_user from flask_restful import Resource, fields, marshal_with from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/app/advanced_prompt_template.py b/api/controllers/console/app/advanced_prompt_template.py index 8d0c5b84af..c228743fa5 100644 --- a/api/controllers/console/app/advanced_prompt_template.py +++ b/api/controllers/console/app/advanced_prompt_template.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.wraps import account_initialization_required, setup_required diff --git a/api/controllers/console/app/agent.py b/api/controllers/console/app/agent.py index 920cae0d85..d433415894 100644 --- a/api/controllers/console/app/agent.py +++ b/api/controllers/console/app/agent.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 48353a63af..91058767eb 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 3e908b76a7..f97209c369 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -1,8 +1,8 @@ import uuid from typing import cast -from flask_login import current_user # type: ignore -from flask_restful import Resource, inputs, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, inputs, marshal, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import BadRequest, Forbidden, abort diff --git a/api/controllers/console/app/app_import.py b/api/controllers/console/app/app_import.py index a159d4c5c4..5dc6515ce0 100644 --- a/api/controllers/console/app/app_import.py +++ b/api/controllers/console/app/app_import.py @@ -1,7 +1,7 @@ from typing import cast -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index 7519ae96c0..5f2def8d8e 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -1,7 +1,7 @@ import logging from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError import services diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index c9820f70f7..732f5b799a 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -1,7 +1,7 @@ import logging -import flask_login # type: ignore -from flask_restful import Resource, reqparse # type: ignore +import flask_login +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/app/conversation.py b/api/controllers/console/app/conversation.py index 8827f129d9..70d6216497 100644 --- a/api/controllers/console/app/conversation.py +++ b/api/controllers/console/app/conversation.py @@ -1,9 +1,9 @@ from datetime import UTC, datetime import pytz # pip install pytz -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy import func, or_ from sqlalchemy.orm import joinedload from werkzeug.exceptions import Forbidden, NotFound diff --git a/api/controllers/console/app/conversation_variables.py b/api/controllers/console/app/conversation_variables.py index c0a20b7160..d49f433ba1 100644 --- a/api/controllers/console/app/conversation_variables.py +++ b/api/controllers/console/app/conversation_variables.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/app/generator.py b/api/controllers/console/app/generator.py index 4046417076..790369c052 100644 --- a/api/controllers/console/app/generator.py +++ b/api/controllers/console/app/generator.py @@ -1,7 +1,7 @@ import os -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.error import ( diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index b5828b6b4b..b7a4c31a15 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -1,8 +1,8 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from controllers.console import api diff --git a/api/controllers/console/app/model_config.py b/api/controllers/console/app/model_config.py index 8ecc8a9db5..f30e3e893c 100644 --- a/api/controllers/console/app/model_config.py +++ b/api/controllers/console/app/model_config.py @@ -2,8 +2,8 @@ import json from typing import cast from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/ops_trace.py b/api/controllers/console/app/ops_trace.py index 7176440e16..978c02412c 100644 --- a/api/controllers/console/app/ops_trace.py +++ b/api/controllers/console/app/ops_trace.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import BadRequest from controllers.console import api diff --git a/api/controllers/console/app/site.py b/api/controllers/console/app/site.py index f15f9d4dae..3c3a359eeb 100644 --- a/api/controllers/console/app/site.py +++ b/api/controllers/console/app/site.py @@ -1,7 +1,7 @@ from datetime import UTC, datetime -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound from constants.languages import supported_language diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index a37d26b989..86aed77412 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -3,8 +3,8 @@ from decimal import Decimal import pytz from flask import jsonify -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 2e077d2095..0c13adce9b 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -3,7 +3,7 @@ import logging from typing import cast from flask import abort, request -from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore +from flask_restful import Resource, inputs, marshal_with, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden, InternalServerError, NotFound diff --git a/api/controllers/console/app/workflow_app_log.py b/api/controllers/console/app/workflow_app_log.py index d863747995..c475aea9fc 100644 --- a/api/controllers/console/app/workflow_app_log.py +++ b/api/controllers/console/app/workflow_app_log.py @@ -1,6 +1,6 @@ from dateutil.parser import isoparse -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from controllers.console import api diff --git a/api/controllers/console/app/workflow_run.py b/api/controllers/console/app/workflow_run.py index 25a99c1e15..08ab61bbb9 100644 --- a/api/controllers/console/app/workflow_run.py +++ b/api/controllers/console/app/workflow_run.py @@ -1,5 +1,5 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/workflow_statistic.py b/api/controllers/console/app/workflow_statistic.py index 097bf7d188..6c7c73707b 100644 --- a/api/controllers/console/app/workflow_statistic.py +++ b/api/controllers/console/app/workflow_statistic.py @@ -3,8 +3,8 @@ from decimal import Decimal import pytz from flask import jsonify -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/auth/activate.py b/api/controllers/console/auth/activate.py index c56f551d49..1795563ff7 100644 --- a/api/controllers/console/auth/activate.py +++ b/api/controllers/console/auth/activate.py @@ -1,7 +1,7 @@ import datetime from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from constants.languages import supported_language from controllers.console import api diff --git a/api/controllers/console/auth/data_source_bearer_auth.py b/api/controllers/console/auth/data_source_bearer_auth.py index 5f0762e4a5..b8c3c8f012 100644 --- a/api/controllers/console/auth/data_source_bearer_auth.py +++ b/api/controllers/console/auth/data_source_bearer_auth.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/auth/data_source_oauth.py b/api/controllers/console/auth/data_source_oauth.py index b4bd80fe2f..1049f864c3 100644 --- a/api/controllers/console/auth/data_source_oauth.py +++ b/api/controllers/console/auth/data_source_oauth.py @@ -2,8 +2,8 @@ import logging import requests from flask import current_app, redirect, request -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from werkzeug.exceptions import Forbidden from configs import dify_config diff --git a/api/controllers/console/auth/forgot_password.py b/api/controllers/console/auth/forgot_password.py index d4a33645ab..d73d8ce701 100644 --- a/api/controllers/console/auth/forgot_password.py +++ b/api/controllers/console/auth/forgot_password.py @@ -2,7 +2,7 @@ import base64 import secrets from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/auth/login.py b/api/controllers/console/auth/login.py index 16c1dcc441..27864bab3d 100644 --- a/api/controllers/console/auth/login.py +++ b/api/controllers/console/auth/login.py @@ -1,8 +1,8 @@ from typing import cast -import flask_login # type: ignore +import flask_login from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse import services from configs import dify_config diff --git a/api/controllers/console/auth/oauth.py b/api/controllers/console/auth/oauth.py index 33bafbf463..f5284cc43b 100644 --- a/api/controllers/console/auth/oauth.py +++ b/api/controllers/console/auth/oauth.py @@ -4,7 +4,7 @@ from typing import Optional import requests from flask import current_app, redirect, request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import Unauthorized diff --git a/api/controllers/console/billing/billing.py b/api/controllers/console/billing/billing.py index fd7b7bd8cb..4b0c82ae6c 100644 --- a/api/controllers/console/billing/billing.py +++ b/api/controllers/console/billing/billing.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required diff --git a/api/controllers/console/billing/compliance.py b/api/controllers/console/billing/compliance.py index 6d5d668709..9679632ac7 100644 --- a/api/controllers/console/billing/compliance.py +++ b/api/controllers/console/billing/compliance.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from libs.helper import extract_remote_ip from libs.login import login_required diff --git a/api/controllers/console/datasets/data_source.py b/api/controllers/console/datasets/data_source.py index 70bfb217eb..7b0d9373cf 100644 --- a/api/controllers/console/datasets/data_source.py +++ b/api/controllers/console/datasets/data_source.py @@ -2,8 +2,8 @@ import datetime import json from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index 43615af709..571a395780 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -1,7 +1,7 @@ -import flask_restful # type: ignore +import flask_restful from flask import request -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound import services diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 3588abeff5..68601adfed 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -4,8 +4,8 @@ from datetime import UTC, datetime from typing import cast from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal, marshal_with, reqparse from sqlalchemy import asc, desc from werkzeug.exceptions import Forbidden, NotFound diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py index 5c54ecbe81..a145038672 100644 --- a/api/controllers/console/datasets/datasets_segments.py +++ b/api/controllers/console/datasets/datasets_segments.py @@ -2,8 +2,8 @@ import uuid import pandas as pd from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, reqparse from werkzeug.exceptions import Forbidden, NotFound import services diff --git a/api/controllers/console/datasets/external.py b/api/controllers/console/datasets/external.py index aee8323f23..30b7f63aab 100644 --- a/api/controllers/console/datasets/external.py +++ b/api/controllers/console/datasets/external.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, reqparse from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services diff --git a/api/controllers/console/datasets/hit_testing.py b/api/controllers/console/datasets/hit_testing.py index d344e9d126..fba5d4c0f3 100644 --- a/api/controllers/console/datasets/hit_testing.py +++ b/api/controllers/console/datasets/hit_testing.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console import api from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase diff --git a/api/controllers/console/datasets/hit_testing_base.py b/api/controllers/console/datasets/hit_testing_base.py index bd944602c1..3b4c076863 100644 --- a/api/controllers/console/datasets/hit_testing_base.py +++ b/api/controllers/console/datasets/hit_testing_base.py @@ -1,7 +1,7 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import marshal, reqparse from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services.dataset_service diff --git a/api/controllers/console/datasets/metadata.py b/api/controllers/console/datasets/metadata.py index e4cac40ca1..b1a83aa371 100644 --- a/api/controllers/console/datasets/metadata.py +++ b/api/controllers/console/datasets/metadata.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import NotFound from controllers.console import api diff --git a/api/controllers/console/datasets/website.py b/api/controllers/console/datasets/website.py index 33c926b4c9..4200a51709 100644 --- a/api/controllers/console/datasets/website.py +++ b/api/controllers/console/datasets/website.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.datasets.error import WebsiteCrawlError diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index c7f9fec326..54bc590677 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -66,7 +66,7 @@ class ChatAudioApi(InstalledAppResource): class ChatTextApi(InstalledAppResource): def post(self, installed_app): - from flask_restful import reqparse # type: ignore + from flask_restful import reqparse app_model = installed_app.app try: diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index e693a5a71b..4367da1162 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -1,8 +1,8 @@ import logging from datetime import UTC, datetime -from flask_login import current_user # type: ignore -from flask_restful import reqparse # type: ignore +from flask_login import current_user +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/explore/conversation.py b/api/controllers/console/explore/conversation.py index 600e78e09e..d7c161cc6d 100644 --- a/api/controllers/console/explore/conversation.py +++ b/api/controllers/console/explore/conversation.py @@ -1,6 +1,6 @@ -from flask_login import current_user # type: ignore -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/console/explore/installed_app.py b/api/controllers/console/explore/installed_app.py index 132da11878..9336c35a0d 100644 --- a/api/controllers/console/explore/installed_app.py +++ b/api/controllers/console/explore/installed_app.py @@ -2,8 +2,8 @@ from datetime import UTC, datetime from typing import Any from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, inputs, marshal_with, reqparse from sqlalchemy import and_ from werkzeug.exceptions import BadRequest, Forbidden, NotFound diff --git a/api/controllers/console/explore/message.py b/api/controllers/console/explore/message.py index ff12959a65..822777604a 100644 --- a/api/controllers/console/explore/message.py +++ b/api/controllers/console/explore/message.py @@ -1,8 +1,8 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/explore/parameter.py b/api/controllers/console/explore/parameter.py index bf9f0d6b28..a1280d91d1 100644 --- a/api/controllers/console/explore/parameter.py +++ b/api/controllers/console/explore/parameter.py @@ -1,4 +1,4 @@ -from flask_restful import marshal_with # type: ignore +from flask_restful import marshal_with from controllers.common import fields from controllers.console import api diff --git a/api/controllers/console/explore/recommended_app.py b/api/controllers/console/explore/recommended_app.py index be6b1f5d21..ce85f495aa 100644 --- a/api/controllers/console/explore/recommended_app.py +++ b/api/controllers/console/explore/recommended_app.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse from constants.languages import languages from controllers.console import api diff --git a/api/controllers/console/explore/saved_message.py b/api/controllers/console/explore/saved_message.py index 3a1655d0ee..339e7007a0 100644 --- a/api/controllers/console/explore/saved_message.py +++ b/api/controllers/console/explore/saved_message.py @@ -1,6 +1,6 @@ -from flask_login import current_user # type: ignore -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import NotFound from controllers.console import api diff --git a/api/controllers/console/explore/workflow.py b/api/controllers/console/explore/workflow.py index a2653a94f6..3f625e6609 100644 --- a/api/controllers/console/explore/workflow.py +++ b/api/controllers/console/explore/workflow.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError from controllers.console.app.error import ( diff --git a/api/controllers/console/explore/wraps.py b/api/controllers/console/explore/wraps.py index b7ba81fba2..49ea81a8a0 100644 --- a/api/controllers/console/explore/wraps.py +++ b/api/controllers/console/explore/wraps.py @@ -1,7 +1,7 @@ from functools import wraps -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from werkzeug.exceptions import NotFound from controllers.console.wraps import account_initialization_required diff --git a/api/controllers/console/extension.py b/api/controllers/console/extension.py index 833da0d03c..07a241ef86 100644 --- a/api/controllers/console/extension.py +++ b/api/controllers/console/extension.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from constants import HIDDEN_VALUE from controllers.console import api diff --git a/api/controllers/console/feature.py b/api/controllers/console/feature.py index da1171412f..70ab4ff865 100644 --- a/api/controllers/console/feature.py +++ b/api/controllers/console/feature.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from libs.login import login_required from services.feature_service import FeatureService diff --git a/api/controllers/console/files.py b/api/controllers/console/files.py index 8cf754bbd6..66b6214f82 100644 --- a/api/controllers/console/files.py +++ b/api/controllers/console/files.py @@ -1,8 +1,8 @@ from typing import Literal from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with from werkzeug.exceptions import Forbidden import services diff --git a/api/controllers/console/init_validate.py b/api/controllers/console/init_validate.py index cfed5fe7a4..b19e331d2e 100644 --- a/api/controllers/console/init_validate.py +++ b/api/controllers/console/init_validate.py @@ -1,7 +1,7 @@ import os from flask import session -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/ping.py b/api/controllers/console/ping.py index 2a116112a3..cd28cc946e 100644 --- a/api/controllers/console/ping.py +++ b/api/controllers/console/ping.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console import api diff --git a/api/controllers/console/remote_files.py b/api/controllers/console/remote_files.py index 30afc930a8..b8cf019e4f 100644 --- a/api/controllers/console/remote_files.py +++ b/api/controllers/console/remote_files.py @@ -2,8 +2,8 @@ import urllib.parse from typing import cast import httpx -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse import services from controllers.common import helpers diff --git a/api/controllers/console/setup.py b/api/controllers/console/setup.py index 3b47f8f12f..e1f19a87a3 100644 --- a/api/controllers/console/setup.py +++ b/api/controllers/console/setup.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from configs import dify_config from libs.helper import StrLen, email, extract_remote_ip diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index 0d0d7ae95f..cb5dedca21 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/version.py b/api/controllers/console/version.py index 7773c99944..7dea8e554e 100644 --- a/api/controllers/console/version.py +++ b/api/controllers/console/version.py @@ -2,7 +2,7 @@ import json import logging import requests -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from packaging import version from configs import dify_config diff --git a/api/controllers/console/workspace/__init__.py b/api/controllers/console/workspace/__init__.py index 7af2b44a4a..072e904caf 100644 --- a/api/controllers/console/workspace/__init__.py +++ b/api/controllers/console/workspace/__init__.py @@ -1,6 +1,6 @@ from functools import wraps -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index e9c25e6c5b..a9dbf44456 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -2,8 +2,8 @@ import datetime import pytz from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse from configs import dify_config from constants.languages import supported_language diff --git a/api/controllers/console/workspace/agent_providers.py b/api/controllers/console/workspace/agent_providers.py index a41d6c501c..88c37767e3 100644 --- a/api/controllers/console/workspace/agent_providers.py +++ b/api/controllers/console/workspace/agent_providers.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.console import api from controllers.console.wraps import account_initialization_required, setup_required diff --git a/api/controllers/console/workspace/endpoint.py b/api/controllers/console/workspace/endpoint.py index aa1a78935d..eb53dcb16e 100644 --- a/api/controllers/console/workspace/endpoint.py +++ b/api/controllers/console/workspace/endpoint.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/load_balancing_config.py b/api/controllers/console/workspace/load_balancing_config.py index 6e1d87cb12..ba74e2c074 100644 --- a/api/controllers/console/workspace/load_balancing_config.py +++ b/api/controllers/console/workspace/load_balancing_config.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index a2b41c1d38..b9918b0d32 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -1,7 +1,7 @@ from urllib import parse -from flask_login import current_user # type: ignore -from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, abort, marshal_with, reqparse import services from configs import dify_config diff --git a/api/controllers/console/workspace/model_providers.py b/api/controllers/console/workspace/model_providers.py index d7d1cc8d00..ff0fcbda6e 100644 --- a/api/controllers/console/workspace/model_providers.py +++ b/api/controllers/console/workspace/model_providers.py @@ -1,8 +1,8 @@ import io from flask import send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/models.py b/api/controllers/console/workspace/models.py index 8b72a1ea3d..37d0f6c764 100644 --- a/api/controllers/console/workspace/models.py +++ b/api/controllers/console/workspace/models.py @@ -1,7 +1,7 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 6f9ae18750..fda5a7d3bb 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -1,8 +1,8 @@ import io from flask import request, send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from configs import dify_config diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 39ab454922..2b1379bfb2 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -1,8 +1,8 @@ import io from flask import send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/workspace/workspace.py b/api/controllers/console/workspace/workspace.py index 332ed00222..71e6f9178f 100644 --- a/api/controllers/console/workspace/workspace.py +++ b/api/controllers/console/workspace/workspace.py @@ -1,8 +1,8 @@ import logging from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse from werkzeug.exceptions import Unauthorized import services diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index 6911181d82..360cbd9246 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -4,7 +4,7 @@ import time from functools import wraps from flask import abort, request -from flask_login import current_user # type: ignore +from flask_login import current_user from configs import dify_config from controllers.console.workspace.error import AccountNotInitializedError diff --git a/api/controllers/files/image_preview.py b/api/controllers/files/image_preview.py index 9199069585..46c19e1fbb 100644 --- a/api/controllers/files/image_preview.py +++ b/api/controllers/files/image_preview.py @@ -1,7 +1,7 @@ from urllib.parse import quote from flask import Response, request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import NotFound import services diff --git a/api/controllers/files/tool_files.py b/api/controllers/files/tool_files.py index 7cb97806c8..1c3430ef4f 100644 --- a/api/controllers/files/tool_files.py +++ b/api/controllers/files/tool_files.py @@ -1,5 +1,7 @@ +from urllib.parse import quote + from flask import Response -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden, NotFound from controllers.files import api @@ -46,7 +48,8 @@ class ToolFilePreviewApi(Resource): if tool_file.size > 0: response.headers["Content-Length"] = str(tool_file.size) if args["as_attachment"]: - response.headers["Content-Disposition"] = f"attachment; filename={tool_file.name}" + encoded_filename = quote(tool_file.name) + response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}" return response diff --git a/api/controllers/files/upload.py b/api/controllers/files/upload.py index 178fb9477a..6641632169 100644 --- a/api/controllers/files/upload.py +++ b/api/controllers/files/upload.py @@ -1,7 +1,7 @@ from mimetypes import guess_extension from flask import request -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with from werkzeug.exceptions import Forbidden import services diff --git a/api/controllers/inner_api/plugin/plugin.py b/api/controllers/inner_api/plugin/plugin.py index 061ad62a4a..f3a1bd8fa5 100644 --- a/api/controllers/inner_api/plugin/plugin.py +++ b/api/controllers/inner_api/plugin/plugin.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console.wraps import setup_required from controllers.inner_api import api diff --git a/api/controllers/inner_api/plugin/wraps.py b/api/controllers/inner_api/plugin/wraps.py index c31f9d22ed..709bba3f30 100644 --- a/api/controllers/inner_api/plugin/wraps.py +++ b/api/controllers/inner_api/plugin/wraps.py @@ -3,7 +3,7 @@ from functools import wraps from typing import Optional from flask import request -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from pydantic import BaseModel from sqlalchemy.orm import Session diff --git a/api/controllers/inner_api/workspace/workspace.py b/api/controllers/inner_api/workspace/workspace.py index 9dfa5d23c3..a2fc2d4675 100644 --- a/api/controllers/inner_api/workspace/workspace.py +++ b/api/controllers/inner_api/workspace/workspace.py @@ -1,6 +1,6 @@ import json -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console.wraps import setup_required from controllers.inner_api import api diff --git a/api/controllers/inner_api/wraps.py b/api/controllers/inner_api/wraps.py index 86d3ad3dc5..f3a9312dd0 100644 --- a/api/controllers/inner_api/wraps.py +++ b/api/controllers/inner_api/wraps.py @@ -18,7 +18,7 @@ def enterprise_inner_api_only(view): # get header 'X-Inner-Api-Key' inner_api_key = request.headers.get("X-Inner-Api-Key") - if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY_FOR_PLUGIN: + if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY: abort(401) return view(*args, **kwargs) diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index c50f551faf..bd1a23b723 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.service_api import api diff --git a/api/controllers/service_api/app/app.py b/api/controllers/service_api/app/app.py index 7131e8a310..2c03aba33d 100644 --- a/api/controllers/service_api/app/app.py +++ b/api/controllers/service_api/app/app.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with from controllers.common import fields from controllers.service_api import api @@ -47,7 +47,7 @@ class AppInfoApi(Resource): def get(self, app_model: App): """Get app information""" tags = [tag.name for tag in app_model.tags] - return {"name": app_model.name, "description": app_model.description, "tags": tags} + return {"name": app_model.name, "description": app_model.description, "tags": tags, "mode": app_model.mode} api.add_resource(AppParameterApi, "/parameters") diff --git a/api/controllers/service_api/app/audio.py b/api/controllers/service_api/app/audio.py index e6bcc0bfd2..2682c2e7f1 100644 --- a/api/controllers/service_api/app/audio.py +++ b/api/controllers/service_api/app/audio.py @@ -1,7 +1,7 @@ import logging from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError import services diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index 38a65b7a90..1d9890199d 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -1,6 +1,6 @@ import logging -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/service_api/app/conversation.py b/api/controllers/service_api/app/conversation.py index dfc357e1ab..36a7905572 100644 --- a/api/controllers/service_api/app/conversation.py +++ b/api/controllers/service_api/app/conversation.py @@ -1,5 +1,5 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/service_api/app/file.py b/api/controllers/service_api/app/file.py index 27b21b9f50..b0fd8e65ef 100644 --- a/api/controllers/service_api/app/file.py +++ b/api/controllers/service_api/app/file.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with import services from controllers.common.errors import FilenameNotExistsError diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index 95e538f4c7..1b148a9756 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -1,8 +1,8 @@ import json import logging -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import BadRequest, InternalServerError, NotFound import services diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index ca3e35aab8..e9bb2b046a 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -1,8 +1,8 @@ import logging from dateutil.parser import isoparse -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import InternalServerError diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index e1e6f3168f..ee190245d5 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import marshal, reqparse # type: ignore +from flask_restful import marshal, reqparse from werkzeug.exceptions import Forbidden, NotFound import services.dataset_service diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index 9e943e2b2d..33eda37014 100644 --- a/api/controllers/service_api/dataset/document.py +++ b/api/controllers/service_api/dataset/document.py @@ -1,7 +1,7 @@ import json from flask import request -from flask_restful import marshal, reqparse # type: ignore +from flask_restful import marshal, reqparse from sqlalchemy import desc from werkzeug.exceptions import NotFound diff --git a/api/controllers/service_api/dataset/metadata.py b/api/controllers/service_api/dataset/metadata.py index 35578eae54..35582feea0 100644 --- a/api/controllers/service_api/dataset/metadata.py +++ b/api/controllers/service_api/dataset/metadata.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user # type: ignore +from flask_restful import marshal, reqparse from werkzeug.exceptions import NotFound from controllers.service_api import api diff --git a/api/controllers/service_api/dataset/segment.py b/api/controllers/service_api/dataset/segment.py index 95753cfd67..fb3ca1e15f 100644 --- a/api/controllers/service_api/dataset/segment.py +++ b/api/controllers/service_api/dataset/segment.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import marshal, reqparse from werkzeug.exceptions import NotFound from controllers.service_api import api diff --git a/api/controllers/service_api/index.py b/api/controllers/service_api/index.py index 75d9141a6d..d24c4597e2 100644 --- a/api/controllers/service_api/index.py +++ b/api/controllers/service_api/index.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from configs import dify_config from controllers.service_api import api diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py index 373f8019f9..3f18474674 100644 --- a/api/controllers/service_api/workspace/models.py +++ b/api/controllers/service_api/workspace/models.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.service_api import api from controllers.service_api.wraps import validate_dataset_token diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index 7facb03358..cd35ceac1d 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -7,7 +7,7 @@ from typing import Optional from flask import current_app, request from flask_login import user_logged_in # type: ignore -from flask_restful import Resource # type: ignore +from flask_restful import Resource from pydantic import BaseModel from sqlalchemy import select, update from sqlalchemy.orm import Session diff --git a/api/controllers/web/app.py b/api/controllers/web/app.py index a84b846112..c9a37af5ed 100644 --- a/api/controllers/web/app.py +++ b/api/controllers/web/app.py @@ -1,4 +1,4 @@ -from flask_restful import marshal_with # type: ignore +from flask_restful import marshal_with from controllers.common import fields from controllers.web import api diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index 97d980d07c..06d9ad7564 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -65,7 +65,7 @@ class AudioApi(WebApiResource): class TextApi(WebApiResource): def post(self, app_model: App, end_user): - from flask_restful import reqparse # type: ignore + from flask_restful import reqparse try: parser = reqparse.RequestParser() diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 9677401490..fd3b9aa804 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/web/conversation.py b/api/controllers/web/conversation.py index 419247ea14..98cea3974f 100644 --- a/api/controllers/web/conversation.py +++ b/api/controllers/web/conversation.py @@ -1,5 +1,5 @@ -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/web/feature.py b/api/controllers/web/feature.py index ce841a8814..0563ed2238 100644 --- a/api/controllers/web/feature.py +++ b/api/controllers/web/feature.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.web import api from services.feature_service import FeatureService diff --git a/api/controllers/web/files.py b/api/controllers/web/files.py index 1d4474015a..df06a73a85 100644 --- a/api/controllers/web/files.py +++ b/api/controllers/web/files.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import marshal_with # type: ignore +from flask_restful import marshal_with import services from controllers.common.errors import FilenameNotExistsError diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index 17e9a3990f..f2e1873601 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -1,7 +1,7 @@ import logging -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index e30998c803..267dac223d 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -1,7 +1,7 @@ import uuid from flask import request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from werkzeug.exceptions import NotFound, Unauthorized from controllers.web import api diff --git a/api/controllers/web/remote_files.py b/api/controllers/web/remote_files.py index d559ab8e07..ae68df6bdc 100644 --- a/api/controllers/web/remote_files.py +++ b/api/controllers/web/remote_files.py @@ -1,7 +1,7 @@ import urllib.parse import httpx -from flask_restful import marshal_with, reqparse # type: ignore +from flask_restful import marshal_with, reqparse import services from controllers.common import helpers diff --git a/api/controllers/web/saved_message.py b/api/controllers/web/saved_message.py index ab2d4abcd3..d7188ef0b3 100644 --- a/api/controllers/web/saved_message.py +++ b/api/controllers/web/saved_message.py @@ -1,5 +1,5 @@ -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import NotFound from controllers.web import api diff --git a/api/controllers/web/site.py b/api/controllers/web/site.py index e68dc7aa4a..0564b15ea3 100644 --- a/api/controllers/web/site.py +++ b/api/controllers/web/site.py @@ -1,4 +1,4 @@ -from flask_restful import fields, marshal_with # type: ignore +from flask_restful import fields, marshal_with from werkzeug.exceptions import Forbidden from configs import dify_config diff --git a/api/controllers/web/workflow.py b/api/controllers/web/workflow.py index d2e183be78..590fd3f2c7 100644 --- a/api/controllers/web/workflow.py +++ b/api/controllers/web/workflow.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError from controllers.web import api diff --git a/api/controllers/web/wraps.py b/api/controllers/web/wraps.py index 1b4d263bee..c327c3df18 100644 --- a/api/controllers/web/wraps.py +++ b/api/controllers/web/wraps.py @@ -1,7 +1,7 @@ from functools import wraps from flask import request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from werkzeug.exceptions import BadRequest, NotFound, Unauthorized from controllers.web.error import WebSSOAuthRequiredError diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index 54b6447fb8..e6771215b4 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -25,7 +25,7 @@ from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotA from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager from core.prompt.utils.get_thread_messages_length import get_thread_messages_length -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository from extensions.ext_database import db from factories import file_factory @@ -169,12 +169,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -237,12 +235,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -303,12 +299,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 0cfe24c97a..c400b99fd6 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -9,7 +9,6 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom from core.app.entities.queue_entities import ( @@ -55,7 +54,7 @@ from core.app.entities.task_entities import ( ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.message_cycle_manage import MessageCycleManage -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.utils.encoders import jsonable_encoder from core.ops.ops_trace_manager import TraceQueueManager @@ -63,6 +62,7 @@ from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes import NodeType from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager from events.message_event import message_was_created from extensions.ext_database import db from models import Conversation, EndUser, Message, MessageFile @@ -107,7 +107,7 @@ class AdvancedChatAppGenerateTaskPipeline: else: raise NotImplementedError(f"User type not supported: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.QUERY: message.query, diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 9c3d78a338..1d67671974 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -18,13 +18,13 @@ from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.app.apps.workflow.app_queue_manager import WorkflowAppQueueManager from core.app.apps.workflow.app_runner import WorkflowAppRunner from core.app.apps.workflow.generate_response_converter import WorkflowAppGenerateResponseConverter -from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_app_generate_task_pipeline import WorkflowAppGenerateTaskPipeline from extensions.ext_database import db from factories import file_factory from models import Account, App, EndUser, Workflow @@ -138,12 +138,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -264,12 +262,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -329,12 +325,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index 8c9c26d36e..a98a42f5df 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -9,7 +9,6 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AgentChatAppGenerateEntity, @@ -45,6 +44,7 @@ from core.app.entities.task_entities import ( ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.message_cycle_manage import MessageCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_manager import ModelInstance from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from core.model_runtime.entities.message_entities import ( diff --git a/api/core/base/__init__.py b/api/core/base/__init__.py new file mode 100644 index 0000000000..3f4bd3b771 --- /dev/null +++ b/api/core/base/__init__.py @@ -0,0 +1 @@ +# Core base package diff --git a/api/core/base/tts/__init__.py b/api/core/base/tts/__init__.py new file mode 100644 index 0000000000..37b6eeebb0 --- /dev/null +++ b/api/core/base/tts/__init__.py @@ -0,0 +1,6 @@ +from core.base.tts.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk + +__all__ = [ + "AppGeneratorTTSPublisher", + "AudioTrunk", +] diff --git a/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py b/api/core/base/tts/app_generator_tts_publisher.py similarity index 100% rename from api/core/app/apps/advanced_chat/app_generator_tts_publisher.py rename to api/core/base/tts/app_generator_tts_publisher.py diff --git a/api/core/indexing_runner.py b/api/core/indexing_runner.py index a75a4c22d1..81bf59b2b6 100644 --- a/api/core/indexing_runner.py +++ b/api/core/indexing_runner.py @@ -9,7 +9,7 @@ import uuid from typing import Any, Optional, cast from flask import current_app -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy.orm.exc import ObjectDeletedError from configs import dify_config diff --git a/api/core/ops/langfuse_trace/langfuse_trace.py b/api/core/ops/langfuse_trace/langfuse_trace.py index b229d244f7..c74617e558 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -29,7 +29,7 @@ from core.ops.langfuse_trace.entities.langfuse_trace_entity import ( UnitEnum, ) from core.ops.utils import filter_none_values -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser @@ -113,8 +113,8 @@ class LangFuseDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={"tenant_id": trace_info.tenant_id, "session_factory": session_factory}, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id ) # Get all executions for this workflow run diff --git a/api/core/ops/langsmith_trace/langsmith_trace.py b/api/core/ops/langsmith_trace/langsmith_trace.py index 78a51ff36e..d1e16d3152 100644 --- a/api/core/ops/langsmith_trace/langsmith_trace.py +++ b/api/core/ops/langsmith_trace/langsmith_trace.py @@ -28,7 +28,7 @@ from core.ops.langsmith_trace.entities.langsmith_trace_entity import ( LangSmithRunUpdateModel, ) from core.ops.utils import filter_none_values, generate_dotted_order -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser, MessageFile @@ -137,12 +137,8 @@ class LangSmithDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id, app_id=trace_info.metadata.get("app_id") ) # Get all executions for this workflow run diff --git a/api/core/ops/opik_trace/opik_trace.py b/api/core/ops/opik_trace/opik_trace.py index a14b5afb8e..1484041447 100644 --- a/api/core/ops/opik_trace/opik_trace.py +++ b/api/core/ops/opik_trace/opik_trace.py @@ -22,7 +22,7 @@ from core.ops.entities.trace_entity import ( TraceTaskName, WorkflowTraceInfo, ) -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser, MessageFile @@ -150,12 +150,8 @@ class OpikDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id, app_id=trace_info.metadata.get("app_id") ) # Get all executions for this workflow run diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 4869a21e80..b1565f10f2 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -7,7 +7,7 @@ from collections.abc import Generator, Mapping from typing import Any, Optional, Union, cast from flask import Flask, current_app -from sqlalchemy import Integer, and_, or_, text +from sqlalchemy import Float, and_, or_, text from sqlalchemy import cast as sqlalchemy_cast from core.app.app_config.entities import ( @@ -1005,28 +1005,24 @@ class DatasetRetrieval: if isinstance(value, str): filters.append(DatasetDocument.doc_metadata[metadata_name] == f'"{value}"') else: - filters.append( - sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) == value - ) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) == value) case "is not" | "≠": if isinstance(value, str): filters.append(DatasetDocument.doc_metadata[metadata_name] != f'"{value}"') else: - filters.append( - sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) != value - ) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != value) case "empty": filters.append(DatasetDocument.doc_metadata[metadata_name].is_(None)) case "not empty": filters.append(DatasetDocument.doc_metadata[metadata_name].isnot(None)) case "before" | "<": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) < value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) < value) case "after" | ">": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) > value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) > value) case "≤" | "<=": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) <= value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) <= value) case "≥" | ">=": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) >= value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) >= value) case _: pass return filters diff --git a/api/core/rag/retrieval/template_prompts.py b/api/core/rag/retrieval/template_prompts.py index acde842702..9c945e2f52 100644 --- a/api/core/rag/retrieval/template_prompts.py +++ b/api/core/rag/retrieval/template_prompts.py @@ -2,7 +2,7 @@ METADATA_FILTER_SYSTEM_PROMPT = """ ### Job Description', You are a text metadata extract engine that extract text's metadata based on user input and set the metadata value ### Task - Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". + Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", "=", "≠", ">", "<", "≥", "≤", "before", "after"] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. ### Constraint diff --git a/api/core/repositories/__init__.py b/api/core/repositories/__init__.py index 5c70d50cde..6452317120 100644 --- a/api/core/repositories/__init__.py +++ b/api/core/repositories/__init__.py @@ -4,3 +4,9 @@ Repository implementations for data access. This package contains concrete implementations of the repository interfaces defined in the core.workflow.repository package. """ + +from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository + +__all__ = [ + "SQLAlchemyWorkflowNodeExecutionRepository", +] diff --git a/api/core/repositories/repository_registry.py b/api/core/repositories/repository_registry.py deleted file mode 100644 index b66f3ba8e6..0000000000 --- a/api/core/repositories/repository_registry.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Registry for repository implementations. - -This module is responsible for registering factory functions with the repository factory. -""" - -import logging -from collections.abc import Mapping -from typing import Any - -from sqlalchemy.orm import sessionmaker - -from configs import dify_config -from core.repositories.workflow_node_execution import SQLAlchemyWorkflowNodeExecutionRepository -from core.workflow.repository.repository_factory import RepositoryFactory -from extensions.ext_database import db - -logger = logging.getLogger(__name__) - -# Storage type constants -STORAGE_TYPE_RDBMS = "rdbms" -STORAGE_TYPE_HYBRID = "hybrid" - - -def register_repositories() -> None: - """ - Register repository factory functions with the RepositoryFactory. - - This function reads configuration settings to determine which repository - implementations to register. - """ - # Configure WorkflowNodeExecutionRepository factory based on configuration - workflow_node_execution_storage = dify_config.WORKFLOW_NODE_EXECUTION_STORAGE - - # Check storage type and register appropriate implementation - if workflow_node_execution_storage == STORAGE_TYPE_RDBMS: - # Register SQLAlchemy implementation for RDBMS storage - logger.info("Registering WorkflowNodeExecution repository with RDBMS storage") - RepositoryFactory.register_workflow_node_execution_factory(create_workflow_node_execution_repository) - elif workflow_node_execution_storage == STORAGE_TYPE_HYBRID: - # Hybrid storage is not yet implemented - raise NotImplementedError("Hybrid storage for WorkflowNodeExecution repository is not yet implemented") - else: - # Unknown storage type - raise ValueError( - f"Unknown storage type '{workflow_node_execution_storage}' for WorkflowNodeExecution repository. " - f"Supported types: {STORAGE_TYPE_RDBMS}" - ) - - -def create_workflow_node_execution_repository(params: Mapping[str, Any]) -> SQLAlchemyWorkflowNodeExecutionRepository: - """ - Create a WorkflowNodeExecutionRepository instance using SQLAlchemy implementation. - - This factory function creates a repository for the RDBMS storage type. - - Args: - params: Parameters for creating the repository, including: - - tenant_id: Required. The tenant ID for multi-tenancy. - - app_id: Optional. The application ID for filtering. - - session_factory: Optional. A SQLAlchemy sessionmaker instance. If not provided, - a new sessionmaker will be created using the global database engine. - - Returns: - A WorkflowNodeExecutionRepository instance - - Raises: - ValueError: If required parameters are missing - """ - # Extract required parameters - tenant_id = params.get("tenant_id") - if tenant_id is None: - raise ValueError("tenant_id is required for WorkflowNodeExecution repository with RDBMS storage") - - # Extract optional parameters - app_id = params.get("app_id") - - # Use the session_factory from params if provided, otherwise create one using the global db engine - session_factory = params.get("session_factory") - if session_factory is None: - # Create a sessionmaker using the same engine as the global db session - session_factory = sessionmaker(bind=db.engine) - - # Create and return the repository - return SQLAlchemyWorkflowNodeExecutionRepository( - session_factory=session_factory, tenant_id=tenant_id, app_id=app_id - ) diff --git a/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py similarity index 98% rename from api/core/repositories/workflow_node_execution/sqlalchemy_repository.py rename to api/core/repositories/sqlalchemy_workflow_node_execution_repository.py index b1d37163a4..8bf2ab8761 100644 --- a/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py +++ b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py @@ -10,13 +10,13 @@ from sqlalchemy import UnaryExpression, asc, delete, desc, select from sqlalchemy.engine import Engine from sqlalchemy.orm import sessionmaker -from core.workflow.repository.workflow_node_execution_repository import OrderConfig +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionStatus, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) -class SQLAlchemyWorkflowNodeExecutionRepository: +class SQLAlchemyWorkflowNodeExecutionRepository(WorkflowNodeExecutionRepository): """ SQLAlchemy implementation of the WorkflowNodeExecutionRepository interface. diff --git a/api/core/repositories/workflow_node_execution/__init__.py b/api/core/repositories/workflow_node_execution/__init__.py deleted file mode 100644 index 76e8282b7d..0000000000 --- a/api/core/repositories/workflow_node_execution/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -WorkflowNodeExecution repository implementations. -""" - -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository - -__all__ = [ - "SQLAlchemyWorkflowNodeExecutionRepository", -] diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index c60e4f4e4a..b849f51064 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -4,6 +4,7 @@ import hmac import logging import os import time +from collections.abc import Generator from mimetypes import guess_extension, guess_type from typing import Optional, Union from uuid import uuid4 @@ -215,7 +216,7 @@ class ToolFileManager: return blob, tool_file.mimetype - def get_file_generator_by_tool_file_id(self, tool_file_id: str): + def get_file_generator_by_tool_file_id(self, tool_file_id: str) -> tuple[Optional[Generator], Optional[ToolFile]]: """ get file binary diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 5d466e645f..2c42f5a1be 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -262,7 +262,10 @@ class Executor: headers[authorization.config.header] = f"Bearer {authorization.config.api_key}" elif self.auth.config.type == "basic": credentials = authorization.config.api_key - encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") + if ":" in credentials: + encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") + else: + encoded_credentials = credentials headers[authorization.config.header] = f"Basic {encoded_credentials}" elif self.auth.config.type == "custom": headers[authorization.config.header] = authorization.config.api_key or "" diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 66be95cdd9..00dac1b7d7 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -6,7 +6,7 @@ from collections import defaultdict from collections.abc import Mapping, Sequence from typing import Any, Optional, cast -from sqlalchemy import Integer, and_, func, or_, text +from sqlalchemy import Float, and_, func, or_, text from sqlalchemy import cast as sqlalchemy_cast from core.app.app_config.entities import DatasetRetrieveConfigEntity @@ -32,11 +32,11 @@ from core.workflow.nodes.knowledge_retrieval.template_prompts import ( METADATA_FILTER_COMPLETION_PROMPT, METADATA_FILTER_SYSTEM_PROMPT, METADATA_FILTER_USER_PROMPT_1, + METADATA_FILTER_USER_PROMPT_2, METADATA_FILTER_USER_PROMPT_3, ) from core.workflow.nodes.llm.entities import LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate from core.workflow.nodes.llm.node import LLMNode -from core.workflow.nodes.question_classifier.template_prompts import QUESTION_CLASSIFIER_USER_PROMPT_2 from extensions.ext_database import db from extensions.ext_redis import redis_client from libs.json_in_md_parser import parse_and_check_json_markdown @@ -493,24 +493,24 @@ class KnowledgeRetrievalNode(LLMNode): if isinstance(value, str): filters.append(Document.doc_metadata[metadata_name] == f'"{value}"') else: - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) == value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == value) case "is not" | "≠": if isinstance(value, str): filters.append(Document.doc_metadata[metadata_name] != f'"{value}"') else: - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) != value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != value) case "empty": filters.append(Document.doc_metadata[metadata_name].is_(None)) case "not empty": filters.append(Document.doc_metadata[metadata_name].isnot(None)) case "before" | "<": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) < value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) < value) case "after" | ">": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) > value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) > value) case "≤" | "<=": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) <= value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) <= value) case "≥" | ">=": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) >= value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) >= value) case _: pass return filters @@ -618,7 +618,7 @@ class KnowledgeRetrievalNode(LLMNode): ) prompt_messages.append(assistant_prompt_message_1) user_prompt_message_2 = LLMNodeChatModelMessage( - role=PromptMessageRole.USER, text=QUESTION_CLASSIFIER_USER_PROMPT_2 + role=PromptMessageRole.USER, text=METADATA_FILTER_USER_PROMPT_2 ) prompt_messages.append(user_prompt_message_2) assistant_prompt_message_2 = LLMNodeChatModelMessage( diff --git a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py index acde842702..9c945e2f52 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py +++ b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py @@ -2,7 +2,7 @@ METADATA_FILTER_SYSTEM_PROMPT = """ ### Job Description', You are a text metadata extract engine that extract text's metadata based on user input and set the metadata value ### Task - Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". + Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", "=", "≠", ">", "<", "≥", "≤", "before", "after"] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. ### Constraint diff --git a/api/core/workflow/nodes/llm/node.py b/api/core/workflow/nodes/llm/node.py index 5481bd383a..f42bc6784d 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -149,7 +149,7 @@ class LLMNode(BaseNode[LLMNodeData]): self._llm_file_saver = llm_file_saver def _run(self) -> Generator[NodeEvent | InNodeEvent, None, None]: - def process_structured_output(text: str) -> Optional[dict[str, Any] | list[Any]]: + def process_structured_output(text: str) -> Optional[dict[str, Any]]: """Process structured output if enabled""" if not self.node_data.structured_output_enabled or not self.node_data.structured_output: return None @@ -797,18 +797,22 @@ class LLMNode(BaseNode[LLMNodeData]): stop = model_config.stop return filtered_prompt_messages, stop - def _parse_structured_output(self, result_text: str) -> dict[str, Any] | list[Any]: - structured_output: dict[str, Any] | list[Any] = {} + def _parse_structured_output(self, result_text: str) -> dict[str, Any]: + structured_output: dict[str, Any] = {} try: parsed = json.loads(result_text) - if not isinstance(parsed, (dict | list)): + if not isinstance(parsed, dict): raise LLMNodeError(f"Failed to parse structured output: {result_text}") structured_output = parsed except json.JSONDecodeError as e: # if the result_text is not a valid json, try to repair it parsed = json_repair.loads(result_text) - if not isinstance(parsed, (dict | list)): - raise LLMNodeError(f"Failed to parse structured output: {result_text}") + if not isinstance(parsed, dict): + # handle reasoning model like deepseek-r1 got '\n\n\n' prefix + if isinstance(parsed, list): + parsed = next((item for item in parsed if isinstance(item, dict)), {}) + else: + raise LLMNodeError(f"Failed to parse structured output: {result_text}") structured_output = parsed return structured_output diff --git a/api/core/workflow/repository/__init__.py b/api/core/workflow/repository/__init__.py index d91506e72f..672abb6583 100644 --- a/api/core/workflow/repository/__init__.py +++ b/api/core/workflow/repository/__init__.py @@ -6,10 +6,9 @@ for accessing and manipulating data, regardless of the underlying storage mechanism. """ -from core.workflow.repository.repository_factory import RepositoryFactory -from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository __all__ = [ - "RepositoryFactory", + "OrderConfig", "WorkflowNodeExecutionRepository", ] diff --git a/api/core/workflow/repository/repository_factory.py b/api/core/workflow/repository/repository_factory.py deleted file mode 100644 index 45d6f5d842..0000000000 --- a/api/core/workflow/repository/repository_factory.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Repository factory for creating repository instances. - -This module provides a simple factory interface for creating repository instances. -It does not contain any implementation details or dependencies on specific repositories. -""" - -from collections.abc import Callable, Mapping -from typing import Any, Literal, Optional, cast - -from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository - -# Type for factory functions - takes a dict of parameters and returns any repository type -RepositoryFactoryFunc = Callable[[Mapping[str, Any]], Any] - -# Type for workflow node execution factory function -WorkflowNodeExecutionFactoryFunc = Callable[[Mapping[str, Any]], WorkflowNodeExecutionRepository] - -# Repository type literals -_RepositoryType = Literal["workflow_node_execution"] - - -class RepositoryFactory: - """ - Factory class for creating repository instances. - - This factory delegates the actual repository creation to implementation-specific - factory functions that are registered with the factory at runtime. - """ - - # Dictionary to store factory functions - _factory_functions: dict[str, RepositoryFactoryFunc] = {} - - @classmethod - def _register_factory(cls, repository_type: _RepositoryType, factory_func: RepositoryFactoryFunc) -> None: - """ - Register a factory function for a specific repository type. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository (e.g., 'workflow_node_execution') - factory_func: A function that takes parameters and returns a repository instance - """ - cls._factory_functions[repository_type] = factory_func - - @classmethod - def _create_repository(cls, repository_type: _RepositoryType, params: Optional[Mapping[str, Any]] = None) -> Any: - """ - Create a new repository instance with the provided parameters. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository to create - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the requested repository - - Raises: - ValueError: If no factory function is registered for the repository type - """ - if repository_type not in cls._factory_functions: - raise ValueError(f"No factory function registered for repository type '{repository_type}'") - - # Use empty dict if params is None - params = params or {} - - return cls._factory_functions[repository_type](params) - - @classmethod - def register_workflow_node_execution_factory(cls, factory_func: WorkflowNodeExecutionFactoryFunc) -> None: - """ - Register a factory function for the workflow node execution repository. - - Args: - factory_func: A function that takes parameters and returns a WorkflowNodeExecutionRepository instance - """ - cls._register_factory("workflow_node_execution", factory_func) - - @classmethod - def create_workflow_node_execution_repository( - cls, params: Optional[Mapping[str, Any]] = None - ) -> WorkflowNodeExecutionRepository: - """ - Create a new WorkflowNodeExecutionRepository instance with the provided parameters. - - Args: - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the WorkflowNodeExecutionRepository - - Raises: - ValueError: If no factory function is registered for the workflow_node_execution repository type - """ - # We can safely cast here because we've registered a WorkflowNodeExecutionFactoryFunc - return cast(WorkflowNodeExecutionRepository, cls._create_repository("workflow_node_execution", params)) diff --git a/api/core/app/apps/workflow/generate_task_pipeline.py b/api/core/workflow/workflow_app_generate_task_pipeline.py similarity index 99% rename from api/core/app/apps/workflow/generate_task_pipeline.py rename to api/core/workflow/workflow_app_generate_task_pipeline.py index 67cad9c998..10a2d8b38b 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/workflow/workflow_app_generate_task_pipeline.py @@ -6,7 +6,6 @@ from typing import Optional, Union from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import ( InvokeFrom, @@ -52,10 +51,11 @@ from core.app.entities.task_entities import ( WorkflowTaskState, ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.ops.ops_trace_manager import TraceQueueManager from core.workflow.enums import SystemVariableKey from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager from extensions.ext_database import db from models.account import Account from models.enums import CreatedByRole @@ -102,7 +102,7 @@ class WorkflowAppGenerateTaskPipeline: else: raise ValueError(f"Invalid user type: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.FILES: application_generate_entity.files, diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/workflow/workflow_cycle_manager.py similarity index 99% rename from api/core/app/task_pipeline/workflow_cycle_manage.py rename to api/core/workflow/workflow_cycle_manager.py index 09e2ee74e6..01d5db4303 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/workflow/workflow_cycle_manager.py @@ -69,7 +69,7 @@ from models.workflow import ( ) -class WorkflowCycleManage: +class WorkflowCycleManager: def __init__( self, *, diff --git a/api/extensions/ext_otel.py b/api/extensions/ext_otel.py index 5ff0085553..5dff7758da 100644 --- a/api/extensions/ext_otel.py +++ b/api/extensions/ext_otel.py @@ -6,11 +6,11 @@ import socket import sys from typing import Union +import flask from celery.signals import worker_init # type: ignore -from flask_login import user_loaded_from_request, user_logged_in # type: ignore - from configs import dify_config from dify_app import DifyApp +from flask_login import user_loaded_from_request, user_logged_in # type: ignore @user_logged_in.connect @@ -27,6 +27,8 @@ def on_user_loaded(_sender, user): def init_app(app: DifyApp): + from opentelemetry.semconv.trace import SpanAttributes + def is_celery_worker(): return "celery" in sys.argv[0].lower() @@ -38,7 +40,7 @@ def init_app(app: DifyApp): meter = get_meter("http_metrics", version=dify_config.CURRENT_VERSION) _http_response_counter = meter.create_counter( "http.server.response.count", - description="Total number of HTTP responses by status code", + description="Total number of HTTP responses by status code, method and target", unit="{response}", ) @@ -52,9 +54,16 @@ def init_app(app: DifyApp): status = status.split(" ")[0] status_code = int(status) status_class = f"{status_code // 100}xx" - _http_response_counter.add( - 1, {"status_code": status_code, "status_class": status_class} - ) + attributes: dict[str, str | int] = { + "status_code": status_code, + "status_class": status_class, + } + request = flask.request + if request and request.url_rule: + attributes[SpanAttributes.HTTP_TARGET] = str(request.url_rule.rule) + if request and request.method: + attributes[SpanAttributes.HTTP_METHOD] = str(request.method) + _http_response_counter.add(1, attributes) instrumentor = FlaskInstrumentor() if dify_config.DEBUG: diff --git a/api/extensions/ext_repositories.py b/api/extensions/ext_repositories.py deleted file mode 100644 index b8cfea121b..0000000000 --- a/api/extensions/ext_repositories.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Extension for initializing repositories. - -This extension registers repository implementations with the RepositoryFactory. -""" - -from core.repositories.repository_registry import register_repositories -from dify_app import DifyApp - - -def init_app(_app: DifyApp) -> None: - """ - Initialize repository implementations. - - Args: - _app: The Flask application instance (unused) - """ - register_repositories() diff --git a/api/fields/annotation_fields.py b/api/fields/annotation_fields.py index 1c58b3a257..379dcc6d16 100644 --- a/api/fields/annotation_fields.py +++ b/api/fields/annotation_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/api_based_extension_fields.py b/api/fields/api_based_extension_fields.py index d40407bfcc..a85d4a34db 100644 --- a/api/fields/api_based_extension_fields.py +++ b/api/fields/api_based_extension_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index f42364f110..0b0e2a2f54 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.workflow_fields import workflow_partial_fields from libs.helper import AppIconUrlField, TimestampField diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 78e0794833..370e8a5a58 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.member_fields import simple_account_fields from libs.helper import TimestampField diff --git a/api/fields/conversation_variable_fields.py b/api/fields/conversation_variable_fields.py index 3aa3838def..71785e7d67 100644 --- a/api/fields/conversation_variable_fields.py +++ b/api/fields/conversation_variable_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/data_source_fields.py b/api/fields/data_source_fields.py index 608672121e..071071376f 100644 --- a/api/fields/data_source_fields.py +++ b/api/fields/data_source_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/dataset_fields.py b/api/fields/dataset_fields.py index 67d183c70d..32a88cc5db 100644 --- a/api/fields/dataset_fields.py +++ b/api/fields/dataset_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/document_fields.py b/api/fields/document_fields.py index 6d59ee9baa..7fd43e8dbe 100644 --- a/api/fields/document_fields.py +++ b/api/fields/document_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.dataset_fields import dataset_fields from libs.helper import TimestampField diff --git a/api/fields/end_user_fields.py b/api/fields/end_user_fields.py index 99a6017595..71593a5269 100644 --- a/api/fields/end_user_fields.py +++ b/api/fields/end_user_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/file_fields.py b/api/fields/file_fields.py index dfc1b623d5..8b4839ef97 100644 --- a/api/fields/file_fields.py +++ b/api/fields/file_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/hit_testing_fields.py b/api/fields/hit_testing_fields.py index 4514c1b8ca..9d67999ea4 100644 --- a/api/fields/hit_testing_fields.py +++ b/api/fields/hit_testing_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/installed_app_fields.py b/api/fields/installed_app_fields.py index 16f265b9bb..e0b3e340f6 100644 --- a/api/fields/installed_app_fields.py +++ b/api/fields/installed_app_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import AppIconUrlField, TimestampField diff --git a/api/fields/member_fields.py b/api/fields/member_fields.py index 0900bffb8a..8007b7e052 100644 --- a/api/fields/member_fields.py +++ b/api/fields/member_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import AvatarUrlField, TimestampField diff --git a/api/fields/message_fields.py b/api/fields/message_fields.py index 76e61f0707..e6aebd810f 100644 --- a/api/fields/message_fields.py +++ b/api/fields/message_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.conversation_fields import message_file_fields from libs.helper import TimestampField diff --git a/api/fields/raws.py b/api/fields/raws.py index 493d4b6cce..15ec16ab13 100644 --- a/api/fields/raws.py +++ b/api/fields/raws.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from core.file import File diff --git a/api/fields/segment_fields.py b/api/fields/segment_fields.py index 82311e5bb9..4126c24598 100644 --- a/api/fields/segment_fields.py +++ b/api/fields/segment_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/tag_fields.py b/api/fields/tag_fields.py index 986cd725f7..9af4fc57dd 100644 --- a/api/fields/tag_fields.py +++ b/api/fields/tag_fields.py @@ -1,3 +1,3 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields tag_fields = {"id": fields.String, "name": fields.String, "type": fields.String, "binding_count": fields.String} diff --git a/api/fields/workflow_app_log_fields.py b/api/fields/workflow_app_log_fields.py index e8f8684ae0..823c99ec6b 100644 --- a/api/fields/workflow_app_log_fields.py +++ b/api/fields/workflow_app_log_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.end_user_fields import simple_end_user_fields from fields.member_fields import simple_account_fields diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index 971e99c259..9f1bef3b36 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from core.helper import encrypter from core.variables import SecretVariable, SegmentType, Variable diff --git a/api/fields/workflow_run_fields.py b/api/fields/workflow_run_fields.py index ef59c57ec3..74fdf8bd97 100644 --- a/api/fields/workflow_run_fields.py +++ b/api/fields/workflow_run_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.end_user_fields import simple_end_user_fields from fields.member_fields import simple_account_fields diff --git a/api/libs/external_api.py b/api/libs/external_api.py index 922d2d9cd3..2070df3e55 100644 --- a/api/libs/external_api.py +++ b/api/libs/external_api.py @@ -3,7 +3,7 @@ import sys from typing import Any from flask import current_app, got_request_exception -from flask_restful import Api, http_status_message # type: ignore +from flask_restful import Api, http_status_message from werkzeug.datastructures import Headers from werkzeug.exceptions import HTTPException diff --git a/api/libs/helper.py b/api/libs/helper.py index f0325734d8..afc8f31681 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast from zoneinfo import available_timezones from flask import Response, stream_with_context -from flask_restful import fields # type: ignore +from flask_restful import fields from configs import dify_config from core.app.features.rate_limiting.rate_limit import RateLimitGenerator diff --git a/api/libs/oauth_data_source.py b/api/libs/oauth_data_source.py index a5ba08d351..1c151633f0 100644 --- a/api/libs/oauth_data_source.py +++ b/api/libs/oauth_data_source.py @@ -3,7 +3,7 @@ import urllib.parse from typing import Any import requests -from flask_login import current_user # type: ignore +from flask_login import current_user from extensions.ext_database import db from models.source import DataSourceOauthBinding diff --git a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py index 5b5656e7ed..00f2b15802 100644 --- a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py +++ b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py @@ -5,45 +5,61 @@ Revises: 33f5fac87f29 Create Date: 2024-10-10 05:16:14.764268 """ -from alembic import op -import models as models + import sqlalchemy as sa -from sqlalchemy.dialects import postgresql +from alembic import op, context # revision identifiers, used by Alembic. -revision = 'bbadea11becb' -down_revision = 'd8e744d88ed6' +revision = "bbadea11becb" +down_revision = "d8e744d88ed6" branch_labels = None depends_on = None def upgrade(): + def _has_name_or_size_column() -> bool: + # We cannot access the database in offline mode, so assume + # the "name" and "size" columns do not exist. + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + "-- Executing in offline mode, assuming the name and size columns do not exist.\n" + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + + return False + # Use SQLAlchemy inspector to get the columns of the 'tool_files' table + inspector = sa.inspect(conn) + columns = [col["name"] for col in inspector.get_columns("tool_files")] + + # If 'name' or 'size' columns already exist, exit the upgrade function + if "name" in columns or "size" in columns: + return True + return False + # ### commands auto generated by Alembic - please adjust! ### # Get the database connection conn = op.get_bind() - # Use SQLAlchemy inspector to get the columns of the 'tool_files' table - inspector = sa.inspect(conn) - columns = [col['name'] for col in inspector.get_columns('tool_files')] - - # If 'name' or 'size' columns already exist, exit the upgrade function - if 'name' in columns or 'size' in columns: + if _has_name_or_size_column(): return - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.add_column(sa.Column('name', sa.String(), nullable=True)) - batch_op.add_column(sa.Column('size', sa.Integer(), nullable=True)) + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.add_column(sa.Column("name", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("size", sa.Integer(), nullable=True)) op.execute("UPDATE tool_files SET name = '' WHERE name IS NULL") op.execute("UPDATE tool_files SET size = -1 WHERE size IS NULL") - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.alter_column('name', existing_type=sa.String(), nullable=False) - batch_op.alter_column('size', existing_type=sa.Integer(), nullable=False) + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.alter_column("name", existing_type=sa.String(), nullable=False) + batch_op.alter_column("size", existing_type=sa.Integer(), nullable=False) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.drop_column('size') - batch_op.drop_column('name') + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.drop_column("size") + batch_op.drop_column("name") # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py index 07454b0917..adf6421e57 100644 --- a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py +++ b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py @@ -5,28 +5,38 @@ Revises: e1944c35e15e Create Date: 2024-12-23 11:54:15.344543 """ -from alembic import op -import models as models -import sqlalchemy as sa + +from alembic import op, context from sqlalchemy import inspect # revision identifiers, used by Alembic. -revision = 'd7999dfa4aae' -down_revision = 'e1944c35e15e' +revision = "d7999dfa4aae" +down_revision = "e1944c35e15e" branch_labels = None depends_on = None def upgrade(): - # Check if column exists before attempting to remove it - conn = op.get_bind() - inspector = inspect(conn) - has_column = 'retry_index' in [col['name'] for col in inspector.get_columns('workflow_node_executions')] + def _has_retry_index_column() -> bool: + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + '-- Executing in offline mode: assuming the "retry_index" column does not exist.\n' + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + return False + conn = op.get_bind() + inspector = inspect(conn) + return "retry_index" in [col["name"] for col in inspector.get_columns("workflow_node_executions")] + + has_column = _has_retry_index_column() if has_column: - with op.batch_alter_table('workflow_node_executions', schema=None) as batch_op: - batch_op.drop_column('retry_index') + with op.batch_alter_table("workflow_node_executions", schema=None) as batch_op: + batch_op.drop_column("retry_index") def downgrade(): diff --git a/api/models/tools.py b/api/models/tools.py index 05604b9330..e027475e38 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Any, Optional, cast +from typing import Any, cast import sqlalchemy as sa from deprecated import deprecated @@ -304,8 +304,11 @@ class DeprecatedPublishedAppTool(Base): db.UniqueConstraint("app_id", "user_id", name="unique_published_app_tool"), ) + id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) # id of the app app_id = db.Column(StringUUID, ForeignKey("apps.id"), nullable=False) + + user_id: Mapped[str] = db.Column(StringUUID, nullable=False) # who published this tool description = db.Column(db.Text, nullable=False) # llm_description of the tool, for LLM @@ -325,34 +328,3 @@ class DeprecatedPublishedAppTool(Base): @property def description_i18n(self) -> I18nObject: return I18nObject(**json.loads(self.description)) - - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - user_id: Mapped[str] = db.Column(StringUUID, nullable=False) - tenant_id: Mapped[str] = db.Column(StringUUID, nullable=False) - conversation_id: Mapped[Optional[str]] = db.Column(StringUUID, nullable=True) - file_key: Mapped[str] = db.Column(db.String(255), nullable=False) - mimetype: Mapped[str] = db.Column(db.String(255), nullable=False) - original_url: Mapped[Optional[str]] = db.Column(db.String(2048), nullable=True) - name: Mapped[str] = mapped_column(default="") - size: Mapped[int] = mapped_column(default=-1) - - def __init__( - self, - *, - user_id: str, - tenant_id: str, - conversation_id: Optional[str] = None, - file_key: str, - mimetype: str, - original_url: Optional[str] = None, - name: str, - size: int, - ): - self.user_id = user_id - self.tenant_id = tenant_id - self.conversation_id = conversation_id - self.file_key = file_key - self.mimetype = mimetype - self.original_url = original_url - self.name = name - self.size = size diff --git a/api/mypy.ini b/api/mypy.ini index 2898b9b52d..865be3c17d 100644 --- a/api/mypy.ini +++ b/api/mypy.ini @@ -7,3 +7,13 @@ exclude = (?x)( | tests/ | migrations/ ) + +[mypy-flask_login] +ignore_missing_imports=True + +[mypy-flask_restful] +ignore_missing_imports=True + +[mypy-flask_restful.inputs] +ignore_missing_imports=True + diff --git a/api/pyproject.toml b/api/pyproject.toml index a3e9d6b9d4..16bc221ee0 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "boto3==1.35.99", "bs4~=0.0.1", "cachetools~=5.3.0", - "celery~=5.4.0", + "celery~=5.5.2", "chardet~=5.1.0", "flask~=3.1.0", "flask-compress~=1.17", @@ -209,6 +209,7 @@ dev = [ "types-defusedxml~=0.7.0", "types-deprecated~=1.2.15", "types-docutils~=0.21.0", + "types-jsonschema~=4.23.0", "types-flask-cors~=5.0.0", "types-flask-migrate~=4.1.0", "types-gevent~=24.11.0", diff --git a/api/services/agent_service.py b/api/services/agent_service.py index 4c63611bb3..503b31ede2 100644 --- a/api/services/agent_service.py +++ b/api/services/agent_service.py @@ -2,7 +2,7 @@ import threading from typing import Optional import pytz -from flask_login import current_user # type: ignore +from flask_login import current_user import contexts from core.app.app_config.easy_ui_based_app.agent.manager import AgentConfigManager diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 45ec1e9b5a..ae7b372b82 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -3,7 +3,7 @@ import uuid from typing import cast import pandas as pd -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy import or_ from werkzeug.datastructures import FileStorage from werkzeug.exceptions import NotFound diff --git a/api/services/app_service.py b/api/services/app_service.py index e87a1c7931..2fae479e05 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -3,7 +3,7 @@ import logging from datetime import UTC, datetime from typing import Optional, cast -from flask_login import current_user # type: ignore +from flask_login import current_user from flask_sqlalchemy.pagination import Pagination from configs import dify_config diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 44d2594ee8..de90355ebf 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -8,7 +8,7 @@ import uuid from collections import Counter from typing import Any, Optional -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy import func from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/services/file_service.py b/api/services/file_service.py index b4442c36c3..2ca6b4f9aa 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -4,7 +4,7 @@ import os import uuid from typing import Any, Literal, Union -from flask_login import current_user # type: ignore +from flask_login import current_user from werkzeug.exceptions import NotFound from configs import dify_config diff --git a/api/services/metadata_service.py b/api/services/metadata_service.py index 4cd2f9e8cb..c47c16f2f7 100644 --- a/api/services/metadata_service.py +++ b/api/services/metadata_service.py @@ -3,7 +3,7 @@ import datetime import logging from typing import Optional -from flask_login import current_user # type: ignore +from flask_login import current_user from core.rag.index_processor.constant.built_in_field import BuiltInField, MetadataDataSource from extensions.ext_database import db diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 1fbaee96e8..21cb861f87 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -1,7 +1,7 @@ import uuid from typing import Optional -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy import func from werkzeug.exceptions import NotFound diff --git a/api/services/website_service.py b/api/services/website_service.py index 460a637a43..3913dc2efe 100644 --- a/api/services/website_service.py +++ b/api/services/website_service.py @@ -3,7 +3,7 @@ import json from typing import Any import requests -from flask_login import current_user # type: ignore +from flask_login import current_user from core.helper import encrypter from core.rag.extractor.firecrawl.firecrawl_app import FirecrawlApp diff --git a/api/services/workflow_run_service.py b/api/services/workflow_run_service.py index f7c4f500a8..6d5b737962 100644 --- a/api/services/workflow_run_service.py +++ b/api/services/workflow_run_service.py @@ -2,7 +2,7 @@ import threading from typing import Optional import contexts -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import OrderConfig from extensions.ext_database import db from libs.infinite_scroll_pagination import InfiniteScrollPagination @@ -129,12 +129,8 @@ class WorkflowRunService: return [] # Use the repository to get the node executions - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=app_model.tenant_id, app_id=app_model.id ) # Use the repository to get the node executions with ordering diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ebe65e5d5f..331dba8bf1 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -11,6 +11,7 @@ from sqlalchemy.orm import Session from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.model_runtime.utils.encoders import jsonable_encoder +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.variables import Variable from core.workflow.entities.node_entities import NodeRunResult from core.workflow.errors import WorkflowNodeRunFailedError @@ -21,7 +22,6 @@ from core.workflow.nodes.enums import ErrorStrategy from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.event.types import NodeEvent from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING -from core.workflow.repository import RepositoryFactory from core.workflow.workflow_entry import WorkflowEntry from events.app_event import app_draft_workflow_was_synced, app_published_workflow_was_updated from extensions.ext_database import db @@ -285,12 +285,8 @@ class WorkflowService: workflow_node_execution.workflow_id = draft_workflow.id # Use the repository to save the workflow node execution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=app_model.tenant_id, app_id=app_model.id ) repository.save(workflow_node_execution) diff --git a/api/services/workspace_service.py b/api/services/workspace_service.py index e012fd4296..125e0c1b1e 100644 --- a/api/services/workspace_service.py +++ b/api/services/workspace_service.py @@ -1,4 +1,4 @@ -from flask_login import current_user # type: ignore +from flask_login import current_user from configs import dify_config from extensions.ext_database import db diff --git a/api/tasks/remove_app_and_related_data_task.py b/api/tasks/remove_app_and_related_data_task.py index dedf1c5334..d5a783396a 100644 --- a/api/tasks/remove_app_and_related_data_task.py +++ b/api/tasks/remove_app_and_related_data_task.py @@ -7,7 +7,7 @@ from celery import shared_task # type: ignore from sqlalchemy import delete from sqlalchemy.exc import SQLAlchemyError -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.dataset import AppDatasetJoin from models.model import ( @@ -189,12 +189,8 @@ def _delete_app_workflow_runs(tenant_id: str, app_id: str): def _delete_app_workflow_node_executions(tenant_id: str, app_id: str): # Create a repository instance for WorkflowNodeExecution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": tenant_id, - "app_id": app_id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=tenant_id, app_id=app_id ) # Use the clear method to delete all records for this tenant_id and app_id diff --git a/api/tests/integration_tests/tools/__mock_server/openapi_todo.py b/api/tests/integration_tests/tools/__mock_server/openapi_todo.py index 2860739f0e..83f4d70ce9 100644 --- a/api/tests/integration_tests/tools/__mock_server/openapi_todo.py +++ b/api/tests/integration_tests/tools/__mock_server/openapi_todo.py @@ -1,5 +1,5 @@ from flask import Flask, request -from flask_restful import Api, Resource # type: ignore +from flask_restful import Api, Resource app = Flask(__name__) api = Api(app) diff --git a/api/tests/integration_tests/workflow/nodes/test_llm.py b/api/tests/integration_tests/workflow/nodes/test_llm.py index 22354df196..777a04bd7f 100644 --- a/api/tests/integration_tests/workflow/nodes/test_llm.py +++ b/api/tests/integration_tests/workflow/nodes/test_llm.py @@ -185,3 +185,38 @@ def test_execute_llm_with_jinja2(setup_code_executor_mock, setup_model_mock): assert item.run_result.process_data is not None assert "sunny" in json.dumps(item.run_result.process_data) assert "what's the weather today?" in json.dumps(item.run_result.process_data) + + +def test_extract_json(): + node = init_llm_node( + config={ + "id": "llm", + "data": { + "title": "123", + "type": "llm", + "model": {"provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", "completion_params": {}}, + "prompt_config": { + "structured_output": { + "enabled": True, + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, + }, + } + }, + "prompt_template": [{"role": "user", "text": "{{#sys.query#}}"}], + "memory": None, + "context": {"enabled": False}, + "vision": {"enabled": False}, + }, + }, + ) + llm_texts = [ + '\n\n{"name": "test", "age": 123', # resoning model (deepseek-r1) + '{"name":"test","age":123}', # json schema model (gpt-4o) + '{\n "name": "test",\n "age": 123\n}', # small model (llama-3.2-1b) + '```json\n{"name": "test", "age": 123}\n```', # json markdown (deepseek-chat) + '{"name":"test",age:123}', # without quotes (qwen-2.5-0.5b) + ] + result = {"name": "test", "age": 123} + assert all(node._parse_structured_output(item) == result for item in llm_texts) diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index efa9ea8979..88938a0ee8 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -100,3 +100,9 @@ def test_flask_configs(example_env_file): assert str(config["CODE_EXECUTION_ENDPOINT"]) == "http://sandbox:8194/" assert str(URL(str(config["CODE_EXECUTION_ENDPOINT"])) / "v1") == "http://sandbox:8194/v1" + + +def test_inner_api_config_exist(): + config = DifyConfig() + assert config.INNER_API is False + assert config.INNER_API_KEY is None diff --git a/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py new file mode 100644 index 0000000000..6b00b203c4 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py @@ -0,0 +1,348 @@ +import json +import time +from datetime import UTC, datetime +from unittest.mock import MagicMock, patch + +import pytest +from sqlalchemy.orm import Session + +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom +from core.app.entities.queue_entities import ( + QueueNodeFailedEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, +) +from core.workflow.enums import SystemVariableKey +from core.workflow.nodes import NodeType +from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager +from models.enums import CreatedByRole +from models.workflow import ( + Workflow, + WorkflowNodeExecution, + WorkflowNodeExecutionStatus, + WorkflowRun, + WorkflowRunStatus, +) + + +@pytest.fixture +def mock_app_generate_entity(): + entity = MagicMock(spec=AdvancedChatAppGenerateEntity) + entity.inputs = {"query": "test query"} + entity.invoke_from = InvokeFrom.WEB_APP + # Create app_config as a separate mock + app_config = MagicMock() + app_config.tenant_id = "test-tenant-id" + app_config.app_id = "test-app-id" + entity.app_config = app_config + return entity + + +@pytest.fixture +def mock_workflow_system_variables(): + return { + SystemVariableKey.QUERY: "test query", + SystemVariableKey.CONVERSATION_ID: "test-conversation-id", + SystemVariableKey.USER_ID: "test-user-id", + SystemVariableKey.APP_ID: "test-app-id", + SystemVariableKey.WORKFLOW_ID: "test-workflow-id", + SystemVariableKey.WORKFLOW_RUN_ID: "test-workflow-run-id", + } + + +@pytest.fixture +def mock_node_execution_repository(): + repo = MagicMock(spec=WorkflowNodeExecutionRepository) + repo.get_by_node_execution_id.return_value = None + repo.get_running_executions.return_value = [] + return repo + + +@pytest.fixture +def workflow_cycle_manager(mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository): + return WorkflowCycleManager( + application_generate_entity=mock_app_generate_entity, + workflow_system_variables=mock_workflow_system_variables, + workflow_node_execution_repository=mock_node_execution_repository, + ) + + +@pytest.fixture +def mock_session(): + session = MagicMock(spec=Session) + return session + + +@pytest.fixture +def mock_workflow(): + workflow = MagicMock(spec=Workflow) + workflow.id = "test-workflow-id" + workflow.tenant_id = "test-tenant-id" + workflow.app_id = "test-app-id" + workflow.type = "chat" + workflow.version = "1.0" + workflow.graph = json.dumps({"nodes": [], "edges": []}) + return workflow + + +@pytest.fixture +def mock_workflow_run(): + workflow_run = MagicMock(spec=WorkflowRun) + workflow_run.id = "test-workflow-run-id" + workflow_run.tenant_id = "test-tenant-id" + workflow_run.app_id = "test-app-id" + workflow_run.workflow_id = "test-workflow-id" + workflow_run.status = WorkflowRunStatus.RUNNING + workflow_run.created_by_role = CreatedByRole.ACCOUNT + workflow_run.created_by = "test-user-id" + workflow_run.created_at = datetime.now(UTC).replace(tzinfo=None) + workflow_run.inputs_dict = {"query": "test query"} + workflow_run.outputs_dict = {"answer": "test answer"} + return workflow_run + + +def test_init( + workflow_cycle_manager, mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository +): + """Test initialization of WorkflowCycleManager""" + assert workflow_cycle_manager._workflow_run is None + assert workflow_cycle_manager._workflow_node_executions == {} + assert workflow_cycle_manager._application_generate_entity == mock_app_generate_entity + assert workflow_cycle_manager._workflow_system_variables == mock_workflow_system_variables + assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository + + +def test_handle_workflow_run_start(workflow_cycle_manager, mock_session, mock_workflow): + """Test _handle_workflow_run_start method""" + # Mock session.scalar to return the workflow and max sequence + mock_session.scalar.side_effect = [mock_workflow, 5] + + # Call the method + workflow_run = workflow_cycle_manager._handle_workflow_run_start( + session=mock_session, + workflow_id="test-workflow-id", + user_id="test-user-id", + created_by_role=CreatedByRole.ACCOUNT, + ) + + # Verify the result + assert workflow_run.tenant_id == mock_workflow.tenant_id + assert workflow_run.app_id == mock_workflow.app_id + assert workflow_run.workflow_id == mock_workflow.id + assert workflow_run.sequence_number == 6 # max_sequence + 1 + assert workflow_run.status == WorkflowRunStatus.RUNNING + assert workflow_run.created_by_role == CreatedByRole.ACCOUNT + assert workflow_run.created_by == "test-user-id" + + # Verify session.add was called + mock_session.add.assert_called_once_with(workflow_run) + + +def test_handle_workflow_run_success(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=100, + total_steps=5, + outputs={"answer": "test answer"}, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.SUCCEEDED + assert result.outputs == json.dumps({"answer": "test answer"}) + assert result.total_tokens == 100 + assert result.total_steps == 5 + assert result.finished_at is not None + + +def test_handle_workflow_run_failed(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_failed method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Mock get_running_executions to return an empty list + workflow_cycle_manager._workflow_node_execution_repository.get_running_executions.return_value = [] + + # Call the method + result = workflow_cycle_manager._handle_workflow_run_failed( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=50, + total_steps=3, + status=WorkflowRunStatus.FAILED, + error="Test error message", + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.FAILED.value + assert result.error == "Test error message" + assert result.total_tokens == 50 + assert result.total_steps == 3 + assert result.finished_at is not None + + +def test_handle_node_execution_start(workflow_cycle_manager, mock_workflow_run): + """Test _handle_node_execution_start method""" + # Create a mock event + event = MagicMock(spec=QueueNodeStartedEvent) + event.node_execution_id = "test-node-execution-id" + event.node_id = "test-node-id" + event.node_type = NodeType.LLM + + # Create node_data as a separate mock + node_data = MagicMock() + node_data.title = "Test Node" + event.node_data = node_data + + event.predecessor_node_id = "test-predecessor-node-id" + event.node_run_index = 1 + event.parallel_mode_run_id = "test-parallel-mode-run-id" + event.in_iteration_id = "test-iteration-id" + event.in_loop_id = "test-loop-id" + + # Call the method + result = workflow_cycle_manager._handle_node_execution_start( + workflow_run=mock_workflow_run, + event=event, + ) + + # Verify the result + assert result.tenant_id == mock_workflow_run.tenant_id + assert result.app_id == mock_workflow_run.app_id + assert result.workflow_id == mock_workflow_run.workflow_id + assert result.workflow_run_id == mock_workflow_run.id + assert result.node_execution_id == event.node_execution_id + assert result.node_id == event.node_id + assert result.node_type == event.node_type.value + assert result.title == event.node_data.title + assert result.status == WorkflowNodeExecutionStatus.RUNNING.value + assert result.created_by_role == mock_workflow_run.created_by_role + assert result.created_by == mock_workflow_run.created_by + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result) + + # Verify the node execution was added to the cache + assert workflow_cycle_manager._workflow_node_executions[event.node_execution_id] == result + + +def test_get_workflow_run(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _get_workflow_run method""" + # Mock session.scalar to return the workflow run + mock_session.scalar.return_value = mock_workflow_run + + # Call the method + result = workflow_cycle_manager._get_workflow_run( + session=mock_session, + workflow_run_id="test-workflow-run-id", + ) + + # Verify the result + assert result == mock_workflow_run + assert workflow_cycle_manager._workflow_run == mock_workflow_run + + +def test_handle_workflow_node_execution_success(workflow_cycle_manager): + """Test _handle_workflow_node_execution_success method""" + # Create a mock event + event = MagicMock(spec=QueueNodeSucceededEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + + # Create a mock workflow node execution + node_execution = MagicMock(spec=WorkflowNodeExecution) + node_execution.node_execution_id = "test-node-execution-id" + + # Mock _get_workflow_node_execution to return the mock node execution + with patch.object(workflow_cycle_manager, "_get_workflow_node_execution", return_value=node_execution): + # Call the method + result = workflow_cycle_manager._handle_workflow_node_execution_success( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED.value + assert result.inputs == json.dumps(event.inputs) + assert result.process_data == json.dumps(event.process_data) + assert result.outputs == json.dumps(event.outputs) + assert result.finished_at is not None + assert result.elapsed_time is not None + + # Verify update was called + workflow_cycle_manager._workflow_node_execution_repository.update.assert_called_once_with(node_execution) + + +def test_handle_workflow_run_partial_success(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_partial_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_partial_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=75, + total_steps=4, + outputs={"partial_answer": "test partial answer"}, + exceptions_count=2, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.PARTIAL_SUCCEEDED.value + assert result.outputs == json.dumps({"partial_answer": "test partial answer"}) + assert result.total_tokens == 75 + assert result.total_steps == 4 + assert result.exceptions_count == 2 + assert result.finished_at is not None + + +def test_handle_workflow_node_execution_failed(workflow_cycle_manager): + """Test _handle_workflow_node_execution_failed method""" + # Create a mock event + event = MagicMock(spec=QueueNodeFailedEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + event.error = "Test error message" + + # Create a mock workflow node execution + node_execution = MagicMock(spec=WorkflowNodeExecution) + node_execution.node_execution_id = "test-node-execution-id" + + # Mock _get_workflow_node_execution to return the mock node execution + with patch.object(workflow_cycle_manager, "_get_workflow_node_execution", return_value=node_execution): + # Call the method + result = workflow_cycle_manager._handle_workflow_node_execution_failed( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.FAILED.value + assert result.error == "Test error message" + assert result.inputs == json.dumps(event.inputs) + assert result.process_data == json.dumps(event.process_data) + assert result.outputs == json.dumps(event.outputs) + assert result.finished_at is not None + assert result.elapsed_time is not None + assert result.execution_metadata == json.dumps(event.execution_metadata) + + # Verify update was called + workflow_cycle_manager._workflow_node_execution_repository.update.assert_called_once_with(node_execution) diff --git a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py index c16b453cba..9cda873e90 100644 --- a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py +++ b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py @@ -8,7 +8,7 @@ import pytest from pytest_mock import MockerFixture from sqlalchemy.orm import Session, sessionmaker -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import OrderConfig from models.workflow import WorkflowNodeExecution @@ -80,7 +80,7 @@ def test_get_by_node_execution_id(repository, session, mocker: MockerFixture): """Test get_by_node_execution_id method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -99,7 +99,7 @@ def test_get_by_workflow_run(repository, session, mocker: MockerFixture): """Test get_by_workflow_run method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -120,7 +120,7 @@ def test_get_running_executions(repository, session, mocker: MockerFixture): """Test get_running_executions method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -158,7 +158,7 @@ def test_clear(repository, session, mocker: MockerFixture): """Test clear method.""" session_obj, _ = session # Set up mock - mock_delete = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.delete") + mock_delete = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.delete") mock_stmt = mocker.MagicMock() mock_delete.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt diff --git a/api/uv.lock b/api/uv.lock index 19d482d618..531cb75de4 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -669,7 +669,7 @@ wheels = [ [[package]] name = "celery" -version = "5.4.0" +version = "5.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "billiard" }, @@ -679,12 +679,11 @@ dependencies = [ { name = "click-repl" }, { name = "kombu" }, { name = "python-dateutil" }, - { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692, upload-time = "2024-04-17T20:29:43.675Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/03/5d9c6c449248958f1a5870e633a29d7419ff3724c452a98ffd22688a1a6a/celery-5.5.2.tar.gz", hash = "sha256:4d6930f354f9d29295425d7a37261245c74a32807c45d764bedc286afd0e724e", size = 1666892, upload-time = "2025-04-25T20:10:04.695Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983, upload-time = "2024-04-17T20:29:39.406Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/8e825ac1cf59d45d20c4345d4461e6b5263ae475f708d047c3dad0ac6401/celery-5.5.2-py3-none-any.whl", hash = "sha256:54425a067afdc88b57cd8d94ed4af2ffaf13ab8c7680041ac2c4ac44357bdf4c", size = 438626, upload-time = "2025-04-25T20:10:01.383Z" }, ] [[package]] @@ -1279,6 +1278,7 @@ dev = [ { name = "types-gevent" }, { name = "types-greenlet" }, { name = "types-html5lib" }, + { name = "types-jsonschema" }, { name = "types-markdown" }, { name = "types-oauthlib" }, { name = "types-objgraph" }, @@ -1351,7 +1351,7 @@ requires-dist = [ { name = "boto3", specifier = "==1.35.99" }, { name = "bs4", specifier = "~=0.0.1" }, { name = "cachetools", specifier = "~=5.3.0" }, - { name = "celery", specifier = "~=5.4.0" }, + { name = "celery", specifier = "~=5.5.2" }, { name = "chardet", specifier = "~=5.1.0" }, { name = "flasgger", specifier = ">=0.9.7.1" }, { name = "flask", specifier = "~=3.1.0" }, @@ -1451,6 +1451,7 @@ dev = [ { name = "types-gevent", specifier = "~=24.11.0" }, { name = "types-greenlet", specifier = "~=3.1.0" }, { name = "types-html5lib", specifier = "~=1.1.11" }, + { name = "types-jsonschema", specifier = "~=4.23.0" }, { name = "types-markdown", specifier = "~=3.7.0" }, { name = "types-oauthlib", specifier = "~=3.2.0" }, { name = "types-objgraph", specifier = "~=3.6.0" }, @@ -5655,6 +5656,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/7c/f862b1dc31268ef10fe95b43dcdf216ba21a592fafa2d124445cd6b92e93/types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403", size = 17292, upload-time = "2024-10-18T02:44:48.503Z" }, ] +[[package]] +name = "types-jsonschema" +version = "4.23.0.20241208" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/e6/9e5cd771687086844caa43dbb211ec0d1cfa899d17c110f3220efcd46e83/types_jsonschema-4.23.0.20241208.tar.gz", hash = "sha256:e8b15ad01f290ecf6aea53f93fbdf7d4730e4600313e89e8a7f95622f7e87b7c", size = 14770, upload-time = "2024-12-08T03:02:21.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/64/4b2fba8b7cb0104ba013f2a1bf6f39a98e927e14befe1ef947d373b25218/types_jsonschema-4.23.0.20241208-py3-none-any.whl", hash = "sha256:87934bd9231c99d8eff94cacfc06ba668f7973577a9bd9e1f9de957c5737313e", size = 15021, upload-time = "2024-12-08T03:02:20.528Z" }, +] + [[package]] name = "types-markdown" version = "3.7.0.20250322" diff --git a/dev/mypy-check b/dev/mypy-check index 23e3776b1a..c043faffe6 100755 --- a/dev/mypy-check +++ b/dev/mypy-check @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run mypy checks -uv run --directory api --dev \ - python -m mypy --install-types --non-interactive . +uv run --directory api --dev --with pip \ + python -m mypy --install-types --non-interactive --cache-fine-grained --sqlite-cache . diff --git a/dev/pytest/pytest_all_tests.sh b/dev/pytest/pytest_all_tests.sh index f0c8a78548..30898b4fcf 100755 --- a/dev/pytest/pytest_all_tests.sh +++ b/dev/pytest/pytest_all_tests.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # ModelRuntime dev/pytest/pytest_model_runtime.sh diff --git a/dev/pytest/pytest_artifacts.sh b/dev/pytest/pytest_artifacts.sh index d52acb2273..3086ef5cc4 100755 --- a/dev/pytest/pytest_artifacts.sh +++ b/dev/pytest/pytest_artifacts.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/artifact_tests/ diff --git a/dev/pytest/pytest_model_runtime.sh b/dev/pytest/pytest_model_runtime.sh index dc6c6ac627..2cbbbbfd81 100755 --- a/dev/pytest/pytest_model_runtime.sh +++ b/dev/pytest/pytest_model_runtime.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/model_runtime/anthropic \ api/tests/integration_tests/model_runtime/azure_openai \ api/tests/integration_tests/model_runtime/openai api/tests/integration_tests/model_runtime/chatglm \ diff --git a/dev/pytest/pytest_tools.sh b/dev/pytest/pytest_tools.sh index 0b1a8c9877..d10934626f 100755 --- a/dev/pytest/pytest_tools.sh +++ b/dev/pytest/pytest_tools.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/tools diff --git a/dev/pytest/pytest_unit_tests.sh b/dev/pytest/pytest_unit_tests.sh index 2075596b7f..1a1819ca28 100755 --- a/dev/pytest/pytest_unit_tests.sh +++ b/dev/pytest/pytest_unit_tests.sh @@ -1,5 +1,8 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # libs pytest api/tests/unit_tests diff --git a/dev/pytest/pytest_vdb.sh b/dev/pytest/pytest_vdb.sh index dd03ca3514..7f617a9c05 100755 --- a/dev/pytest/pytest_vdb.sh +++ b/dev/pytest/pytest_vdb.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/vdb/chroma \ api/tests/integration_tests/vdb/milvus \ api/tests/integration_tests/vdb/pgvecto_rs \ diff --git a/dev/pytest/pytest_workflow.sh b/dev/pytest/pytest_workflow.sh index db8fdb2fb9..b63d49069f 100755 --- a/dev/pytest/pytest_workflow.sh +++ b/dev/pytest/pytest_workflow.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/workflow diff --git a/dev/reformat b/dev/reformat index 53d7703fce..71cb6abb1e 100755 --- a/dev/reformat +++ b/dev/reformat @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run ruff linter uv run --directory api --dev ruff check --fix ./ diff --git a/dev/sync-uv b/dev/sync-uv index 7bc3bb22be..67a8133f5a 100755 --- a/dev/sync-uv +++ b/dev/sync-uv @@ -6,5 +6,8 @@ if ! command -v uv &> /dev/null; then pip install uv fi +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # check uv.lock in sync with pyproject.toml uv lock --project api diff --git a/docker/.env.example b/docker/.env.example index b13dca56dd..f866ae2344 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -397,7 +397,7 @@ SUPABASE_URL=your-server-url # ------------------------------ # The type of vector store to use. -# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `tidb_vector`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`. +# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`,`vastbase`,`tidb`,`tidb_on_qdrant`,`baidu`,`lindorm`,`huawei_cloud`,`upstash`. VECTOR_STORE=weaviate # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. @@ -867,7 +867,7 @@ CHROMA_IS_PERSISTENT=TRUE # ------------------------------ # Environment Variables for Oracle Service -# (only used when VECTOR_STORE is Oracle) +# (only used when VECTOR_STORE is oracle) # ------------------------------ ORACLE_PWD=Dify123456 ORACLE_CHARACTERSET=AL32UTF8 diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 97080a1e77..accea0a252 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -226,7 +226,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index 01c7573a95..498390b708 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -123,7 +123,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 3579407a1f..f0862fb34e 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -737,7 +737,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 078c7ebd8c..5619b1e445 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -98,7 +98,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent' href={ locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' + ? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' } target='_blank' rel='noopener noreferrer' diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index d4357a0955..b061cbc688 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -76,7 +76,7 @@ const AppPublisher = ({ const appDetail = useAppStore(state => state.appDetail) const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode - const appURL = `${appBaseURL}/${basePath}/${appMode}/${accessToken}` + const appURL = `${appBaseURL}${basePath}/${appMode}/${accessToken}` const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') const language = useGetLanguage() diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx index 78e8d8f506..592c95261c 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx @@ -46,8 +46,8 @@ const HistoryPanel: FC = ({
{t('appDebug.feature.conversationHistory.tip')} {t('appDebug.feature.conversationHistory.learnMore')} diff --git a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx index d1cd25e80a..cca775c86e 100644 --- a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx +++ b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx @@ -25,7 +25,7 @@ const AdvancedModeWarning: FC = ({ {t('appDebug.promptMode.advancedWarning.description')} {t('appDebug.promptMode.advancedWarning.learnMore')} diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 88bccc95af..6e05bb0209 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -310,17 +310,17 @@ function AppPreview({ mode }: { mode: AppMode }) { 'chat': { title: t('app.types.chatbot'), description: t('app.newApp.chatbotUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate#application_type', + link: 'https://docs.dify.ai/guides/application-orchestrate/readme', }, 'advanced-chat': { title: t('app.types.advanced'), description: t('app.newApp.advancedUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: 'https://docs.dify.ai/en/guides/workflow/README', }, 'agent-chat': { title: t('app.types.agent'), description: t('app.newApp.agentUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate/agent', + link: 'https://docs.dify.ai/en/guides/application-orchestrate/agent', }, 'completion': { title: t('app.newApp.completeApp'), @@ -330,7 +330,7 @@ function AppPreview({ mode }: { mode: AppMode }) { 'workflow': { title: t('app.types.workflow'), description: t('app.newApp.workflowUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: 'https://docs.dify.ai/en/guides/workflow/README', }, } const previewInfo = modeToPreviewInfoMap[mode] diff --git a/web/app/components/app/overview/customize/index.tsx b/web/app/components/app/overview/customize/index.tsx index 9e0fadd87d..4e84dd8b1f 100644 --- a/web/app/components/app/overview/customize/index.tsx +++ b/web/app/components/app/overview/customize/index.tsx @@ -103,7 +103,7 @@ const CustomizeModal: FC = ({ window.open( `https://docs.dify.ai/${locale !== LanguagesSupported[1] ? 'user-guide/launching-dify-apps/developing-with-apis' - : `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` + : `${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` }`, '_blank', ) diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 679d616e54..c0a5fdf757 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -241,7 +241,7 @@ const SettingsModal: FC = ({
{t(`${prefixSettings}.modalTip`)} - {t('common.operation.learnMore')} + {t('common.operation.learnMore')}
{/* form body */} diff --git a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx index 6a8292c54f..ec8da7b102 100644 --- a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx +++ b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx @@ -27,6 +27,7 @@ const HeaderInMobile = () => { handleDeleteConversation, handleRenameConversation, conversationRenaming, + inputsForms, } = useChatWithHistoryContext() const { t } = useTranslation() const isPin = pinnedConversationList.some(item => item.id === currentConversationId) @@ -99,6 +100,7 @@ const HeaderInMobile = () => { setShowChatSettings(true)} + hideViewChatSettings={inputsForms.length < 1} /> {showSidebar && ( diff --git a/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx b/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx index f5acf05b6b..4bb694033d 100644 --- a/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx +++ b/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx @@ -9,11 +9,13 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu type Props = { handleResetChat: () => void handleViewChatSettings: () => void + hideViewChatSettings?: boolean } const MobileOperationDropdown = ({ handleResetChat, handleViewChatSettings, + hideViewChatSettings = false, }: Props) => { const { t } = useTranslation() const [open, setOpen] = useState(false) @@ -42,9 +44,11 @@ const MobileOperationDropdown = ({
{t('share.chat.resetChat')}
-
- {t('share.chat.viewChatSettings')} -
+ {!hideViewChatSettings && ( +
+ {t('share.chat.viewChatSettings')} +
+ )} diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index 562bb8c0d9..b43d2c8117 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -120,10 +120,8 @@ const ComponentPicker = ({ }, [editor, checkForTriggerMatch, triggerString]) const handleClose = useCallback(() => { - ReactDOM.flushSync(() => { - const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) - editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent) - }) + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) + editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent) }, [editor]) const renderMenu = useCallback>(( @@ -132,7 +130,11 @@ const ComponentPicker = ({ ) => { if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null - refs.setReference(anchorElementRef.current) + + setTimeout(() => { + if (anchorElementRef.current) + refs.setReference(anchorElementRef.current) + }, 0) return ( <> @@ -149,7 +151,6 @@ const ComponentPicker = ({ visibility: isPositioned ? 'visible' : 'hidden', }} ref={refs.setFloating} - data-testid="component-picker-container" > { workflowVariableBlock?.show && ( @@ -173,7 +174,7 @@ const ComponentPicker = ({
) } -
+
{ options.map((option, index) => ( diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index 854c984559..32980eea1e 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -264,8 +264,8 @@ const Documents: FC = ({ datasetId }) => { target='_blank' href={ locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' - : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' + ? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' + : 'https://docs.dify.ai/en/guides/knowledge-base/integrate-knowledge-within-application' } > {t('datasetDocuments.list.learnMore')} diff --git a/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx b/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx index 33386940c4..5c3c1261b6 100644 --- a/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx @@ -16,12 +16,12 @@ const InfoPanel = () => { {t('dataset.connectDatasetIntro.content.front')} - + {t('dataset.connectDatasetIntro.content.link')} {t('dataset.connectDatasetIntro.content.end')} - + {t('dataset.connectDatasetIntro.learnMore')}

diff --git a/web/app/components/datasets/external-knowledge-base/create/index.tsx b/web/app/components/datasets/external-knowledge-base/create/index.tsx index 6cbfc05ca7..5fbddea06b 100644 --- a/web/app/components/datasets/external-knowledge-base/create/index.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/index.tsx @@ -59,7 +59,7 @@ const ExternalKnowledgeBaseCreate: React.FC = {t('dataset.connectHelper.helper1')} {t('dataset.connectHelper.helper2')} {t('dataset.connectHelper.helper3')} - + {t('dataset.connectHelper.helper4')} {t('dataset.connectHelper.helper5')} diff --git a/web/app/components/develop/template/template_workflow.en.mdx b/web/app/components/develop/template/template_workflow.en.mdx index 0f15c4406b..556b306a64 100644 --- a/web/app/components/develop/template/template_workflow.en.mdx +++ b/web/app/components/develop/template/template_workflow.en.mdx @@ -90,7 +90,7 @@ Workflow applications offers non-session support and is ideal for translation, a Each streaming chunk starts with `data:`, separated by two newline characters `\n\n`, as shown below: ```streaming {{ title: 'Response' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` The structure of the streaming chunks varies depending on the `event`: @@ -116,6 +116,13 @@ Workflow applications offers non-session support and is ideal for translation, a - `predecessor_node_id` (string) optional Prefix node ID, used for canvas display execution path - `inputs` (object) Contents of all preceding node variables used in the node - `created_at` (timestamp) timestamp of start, e.g., 1705395332 + - `event: text_chunk` Text fragment + - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API + - `workflow_run_id` (string) Unique ID of workflow execution + - `event` (string) fixed to `text_chunk` + - `data` (object) detail + - `text` (string) Text content + - `from_variable_selector` (array) Text source path, helping developers understand which node and variable generated the text - `event: node_finished` node execution ends, success or failure in different states in the same event - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API - `workflow_run_id` (string) Unique ID of workflow execution diff --git a/web/app/components/develop/template/template_workflow.ja.mdx b/web/app/components/develop/template/template_workflow.ja.mdx index 0239b40224..3f33be58b9 100644 --- a/web/app/components/develop/template/template_workflow.ja.mdx +++ b/web/app/components/develop/template/template_workflow.ja.mdx @@ -93,7 +93,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from 各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます。以下のように表示されます: ```streaming {{ title: '応答' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` ストリーミングチャンクの構造は`event`に応じて異なります: @@ -119,6 +119,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `predecessor_node_id` (string) オプションのプレフィックスノードID、キャンバス表示実行パスに使用 - `inputs` (object) ノードで使用されるすべての前のノード変数の内容 - `created_at` (timestamp) 開始のタイムスタンプ、例:1705395332 + - `event: text_chunk` テキストフラグメント + - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 + - `workflow_run_id` (string) ワークフロー実行の一意のID + - `event` (string) `text_chunk`に固定 + - `data` (object) 詳細 + - `text` (string) テキスト内容 + - `from_variable_selector` (array) テキスト生成元パス(開発者がどのノードのどの変数から生成されたかを理解するための情報) - `event: node_finished` ノード実行終了、同じイベントで異なる状態で成功または失敗 - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - `workflow_run_id` (string) ワークフロー実行の一意のID diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index 939df2703d..b032407ee9 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -87,7 +87,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 每个流式块均为 data: 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示: ```streaming {{ title: 'Response' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` 流式块中根据 `event` 不同,结构也不同,包含以下类型: @@ -113,6 +113,13 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `predecessor_node_id` (string) 前置节点 ID,用于画布展示执行路径 - `inputs` (object) 节点中所有使用到的前置节点变量内容 - `created_at` (timestamp) 开始时间 + - `event: text_chunk` 文本片段 + - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 + - `workflow_run_id` (string) workflow 执行 ID + - `event` (string) 固定为 `text_chunk` + - `data` (object) 详细内容 + - `text` (string) 文本内容 + - `from_variable_selector` (array) 文本来源路径,帮助开发者了解文本是由哪个节点的哪个变量生成的 - `event: node_finished` node 执行结束,成功失败同一事件中不同状态 - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - `workflow_run_id` (string) workflow 执行 ID diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index da3f8bae6d..98668c61d2 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -61,7 +61,8 @@ const WorkplaceSelector = () => { diff --git a/web/app/components/plugins/plugin-page/debug-info.tsx b/web/app/components/plugins/plugin-page/debug-info.tsx index bd59e5d737..28b8a7fc49 100644 --- a/web/app/components/plugins/plugin-page/debug-info.tsx +++ b/web/app/components/plugins/plugin-page/debug-info.tsx @@ -1,6 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' +import { useContext } from 'use-context-selector' +import I18n from '@/context/i18n' import { RiArrowRightUpLine, RiBugLine, @@ -9,12 +11,14 @@ import { useTranslation } from 'react-i18next' import KeyValueItem from '../base/key-value-item' import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import { getDocsUrl } from '@/app/components/plugins/utils' import { useDebugKey } from '@/service/use-plugins' const i18nPrefix = 'plugin.debugInfo' const DebugInfo: FC = () => { const { t } = useTranslation() + const { locale } = useContext(I18n) const { data: info, isLoading } = useDebugKey() // info.key likes 4580bdb7-b878-471c-a8a4-bfd760263a53 mask the middle part using *. @@ -30,7 +34,7 @@ const DebugInfo: FC = () => { <>
{t(`${i18nPrefix}.title`)} - + {t(`${i18nPrefix}.viewDocs`)} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index a4bc7a6e6c..cb57b7f4c6 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -34,10 +34,10 @@ import { import type { Dependency } from '../types' import type { PluginDeclaration, PluginManifestInMarket } from '../types' import { sleep } from '@/utils' +import { getDocsUrl } from '@/app/components/plugins/utils' import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' import { marketplaceApiPrefix } from '@/config' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { LanguagesSupported } from '@/i18n/language' import I18n from '@/context/i18n' import { noop } from 'lodash-es' import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/plugin-type-switch' @@ -187,7 +187,7 @@ const PluginPage = ({ isExploringMarketplace && ( <>
diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts index d4ea088571..509f44708d 100644 --- a/web/i18n/de-DE/common.ts +++ b/web/i18n/de-DE/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.', link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API-Erweiterung hinzufügen', selector: { title: 'API-Erweiterung', diff --git a/web/i18n/de-DE/dataset-creation.ts b/web/i18n/de-DE/dataset-creation.ts index 60102a2a7d..6e532794aa 100644 --- a/web/i18n/de-DE/dataset-creation.ts +++ b/web/i18n/de-DE/dataset-creation.ts @@ -69,7 +69,7 @@ const translation = { unknownError: 'Unbekannter Fehler', resetAll: 'Alles zurücksetzen', extractOnlyMainContent: 'Extrahieren Sie nur den Hauptinhalt (keine Kopf-, Navigations- und Fußzeilen usw.)', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', firecrawlTitle: 'Extrahieren von Webinhalten mit 🔥Firecrawl', maxDepthTooltip: 'Maximale Tiefe für das Crawlen relativ zur eingegebenen URL. Tiefe 0 kratzt nur die Seite der eingegebenen URL, Tiefe 1 kratzt die URL und alles nach der eingegebenen URL + ein / und so weiter.', crawlSubPage: 'Unterseiten crawlen', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index bf2bf83f68..0491ff57a8 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { title: 'API extensions provide centralized API management, simplifying configuration for easy use across Dify\'s applications.', link: 'Learn how to develop your own API Extension.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Add API Extension', selector: { title: 'API Extension', diff --git a/web/i18n/en-US/dataset-creation.ts b/web/i18n/en-US/dataset-creation.ts index fe48e076fa..cf2d454f06 100644 --- a/web/i18n/en-US/dataset-creation.ts +++ b/web/i18n/en-US/dataset-creation.ts @@ -80,10 +80,10 @@ const translation = { run: 'Run', firecrawlTitle: 'Extract web content with 🔥Firecrawl', firecrawlDoc: 'Firecrawl docs', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', watercrawlTitle: 'Extract web content with Watercrawl', watercrawlDoc: 'Watercrawl docs', - watercrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'Convert the entire site to Markdown', jinaReaderDoc: 'Learn more about Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/es-ES/common.ts b/web/i18n/es-ES/common.ts index 5933105ffd..483949553e 100644 --- a/web/i18n/es-ES/common.ts +++ b/web/i18n/es-ES/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'Las extensiones basadas en API proporcionan una gestión centralizada de API, simplificando la configuración para su fácil uso en las aplicaciones de Dify.', link: 'Aprende cómo desarrollar tu propia Extensión API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Agregar Extensión API', selector: { title: 'Extensión API', diff --git a/web/i18n/es-ES/dataset-creation.ts b/web/i18n/es-ES/dataset-creation.ts index 7047ff9e9a..4fcccb0633 100644 --- a/web/i18n/es-ES/dataset-creation.ts +++ b/web/i18n/es-ES/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Ejecutar', firecrawlTitle: 'Extraer contenido web con 🔥Firecrawl', firecrawlDoc: 'Documentación de Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Opciones', crawlSubPage: 'Rastrear subpáginas', limit: 'Límite', diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 44d6bb006b..5ca55da25b 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'افزونه‌های مبتنی بر API مدیریت متمرکز API را فراهم می‌کنند و پیکربندی را برای استفاده آسان در برنامه‌های Dify ساده می‌کنند.', link: 'نحوه توسعه افزونه API خود را بیاموزید.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'افزودن افزونه API', selector: { title: 'افزونه API', diff --git a/web/i18n/fa-IR/dataset-creation.ts b/web/i18n/fa-IR/dataset-creation.ts index 0ca51ef534..55eae67875 100644 --- a/web/i18n/fa-IR/dataset-creation.ts +++ b/web/i18n/fa-IR/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'اجرا', firecrawlTitle: 'استخراج محتوای وب با fireFirecrawl', firecrawlDoc: 'مستندات Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'گزینهها', crawlSubPage: 'خزش صفحات فرعی', limit: 'محدودیت', diff --git a/web/i18n/fr-FR/dataset-creation.ts b/web/i18n/fr-FR/dataset-creation.ts index e7357749c4..50d750aa44 100644 --- a/web/i18n/fr-FR/dataset-creation.ts +++ b/web/i18n/fr-FR/dataset-creation.ts @@ -61,7 +61,7 @@ const translation = { preview: 'Aperçu', crawlSubPage: 'Explorer les sous-pages', configure: 'Configurer', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', maxDepth: 'Profondeur maximale', fireCrawlNotConfigured: 'Firecrawl n’est pas configuré', firecrawlTitle: 'Extraire du contenu web avec 🔥Firecrawl', diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index c5cecc1052..dab3229fe2 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -476,7 +476,7 @@ const translation = { title: 'एपीआई एक्सटेंशन केंद्रीकृत एपीआई प्रबंधन प्रदान करते हैं, जो Dify के अनुप्रयोगों में आसान उपयोग के लिए कॉन्फ़िगरेशन को सरल बनाते हैं।', link: 'अपना खुद का एपीआई एक्सटेंशन कैसे विकसित करें, यह जानें।', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'एपीआई एक्सटेंशन जोड़ें', selector: { title: 'एपीआई एक्सटेंशन', diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts index cc8129625a..a413a9ef68 100644 --- a/web/i18n/it-IT/common.ts +++ b/web/i18n/it-IT/common.ts @@ -483,7 +483,7 @@ const translation = { title: 'Le estensioni API forniscono una gestione centralizzata delle API, semplificando la configurazione per un facile utilizzo nelle applicazioni di Dify.', link: 'Scopri come sviluppare la tua estensione API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Aggiungi Estensione API', selector: { title: 'Estensione API', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 9f480c5af6..c6163ec5cd 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { title: 'API拡張機能は、Difyのアプリケーション全体での簡単な使用のための設定を簡素化し、集中的なAPI管理を提供します。', link: '独自のAPI拡張機能を開発する方法について学ぶ。', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API拡張機能を追加', selector: { title: 'API拡張機能', diff --git a/web/i18n/ja-JP/dataset-creation.ts b/web/i18n/ja-JP/dataset-creation.ts index 3de5b4c615..3cd856d134 100644 --- a/web/i18n/ja-JP/dataset-creation.ts +++ b/web/i18n/ja-JP/dataset-creation.ts @@ -72,7 +72,7 @@ const translation = { run: '実行', firecrawlTitle: '🔥Firecrawlを使っでウエブコンテンツを抽出', firecrawlDoc: 'Firecrawlドキュメント', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'サイト全体をMarkdownに変換する', jinaReaderDoc: 'Jina Readerの詳細', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/ko-KR/common.ts b/web/i18n/ko-KR/common.ts index 8068a76d8e..aff00be97a 100644 --- a/web/i18n/ko-KR/common.ts +++ b/web/i18n/ko-KR/common.ts @@ -451,7 +451,7 @@ const translation = { apiBasedExtension: { title: 'API 기반 확장은 Dify 애플리케이션 전체에서 간편한 사용을 위한 설정을 단순화하고 집중적인 API 관리를 제공합니다.', link: '사용자 정의 API 기반 확장을 개발하는 방법 배우기', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API 기반 확장 추가', selector: { title: 'API 기반 확장', diff --git a/web/i18n/ko-KR/dataset-creation.ts b/web/i18n/ko-KR/dataset-creation.ts index 33ca624307..10385bbb71 100644 --- a/web/i18n/ko-KR/dataset-creation.ts +++ b/web/i18n/ko-KR/dataset-creation.ts @@ -52,7 +52,7 @@ const translation = { failed: '생성에 실패했습니다', }, website: { - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', limit: '한계', options: '옵션', firecrawlDoc: 'Firecrawl 문서', diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts index c8b0b79257..b0ef026f5e 100644 --- a/web/i18n/pl-PL/common.ts +++ b/web/i18n/pl-PL/common.ts @@ -469,7 +469,7 @@ const translation = { title: 'Rozszerzenia oparte na interfejsie API zapewniają scentralizowane zarządzanie interfejsami API, upraszczając konfigurację dla łatwego użytkowania w aplikacjach Dify.', link: 'Dowiedz się, jak opracować własne rozszerzenie interfejsu API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj rozszerzenie interfejsu API', selector: { title: 'Rozszerzenie interfejsu API', diff --git a/web/i18n/pl-PL/dataset-creation.ts b/web/i18n/pl-PL/dataset-creation.ts index 98c0613a30..4073fdd681 100644 --- a/web/i18n/pl-PL/dataset-creation.ts +++ b/web/i18n/pl-PL/dataset-creation.ts @@ -54,7 +54,7 @@ const translation = { }, website: { limit: 'Ograniczać', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', firecrawlDoc: 'Dokumentacja Firecrawl', unknownError: 'Nieznany błąd', fireCrawlNotConfiguredDescription: 'Skonfiguruj Firecrawl z kluczem API, aby z niego korzystać.', diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts index 180bcbb4da..eb92d9ab1d 100644 --- a/web/i18n/pt-BR/common.ts +++ b/web/i18n/pt-BR/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'As extensões de API fornecem gerenciamento centralizado de API, simplificando a configuração para uso fácil em todos os aplicativos da Dify.', link: 'Saiba como desenvolver sua própria Extensão de API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adicionar Extensão de API', selector: { title: 'Extensão de API', diff --git a/web/i18n/pt-BR/dataset-creation.ts b/web/i18n/pt-BR/dataset-creation.ts index a3949f484b..d4315e6866 100644 --- a/web/i18n/pt-BR/dataset-creation.ts +++ b/web/i18n/pt-BR/dataset-creation.ts @@ -58,7 +58,7 @@ const translation = { crawlSubPage: 'Rastrear subpáginas', selectAll: 'Selecionar tudo', resetAll: 'Redefinir tudo', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', includeOnlyPaths: 'Incluir apenas caminhos', configure: 'Configurar', limit: 'Limite', diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index ad000e26c4..e755d59354 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'Extensiile bazate pe API oferă o gestionare centralizată a API-urilor, simplificând configurația pentru o utilizare ușoară în aplicațiile Dify.', link: 'Aflați cum să dezvoltați propria extensie bazată pe API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adăugați extensie API', selector: { title: 'Extensie API', diff --git a/web/i18n/ro-RO/dataset-creation.ts b/web/i18n/ro-RO/dataset-creation.ts index 3587070999..2c92d8417c 100644 --- a/web/i18n/ro-RO/dataset-creation.ts +++ b/web/i18n/ro-RO/dataset-creation.ts @@ -65,7 +65,7 @@ const translation = { firecrawlTitle: 'Extrageți conținut web cu 🔥Firecrawl', unknownError: 'Eroare necunoscută', scrapTimeInfo: 'Pagini răzuite {{total}} în total în {{timp}}s', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', excludePaths: 'Excluderea căilor', resetAll: 'Resetați toate', extractOnlyMainContent: 'Extrageți doar conținutul principal (fără anteturi, navigări, subsoluri etc.)', diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts index d419bcc97e..de925d1742 100644 --- a/web/i18n/ru-RU/common.ts +++ b/web/i18n/ru-RU/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'API-расширения обеспечивают централизованное управление API, упрощая настройку для удобного использования в приложениях Dify.', link: 'Узнайте, как разработать собственное API-расширение.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Добавить API Extension', selector: { title: 'API Extension', diff --git a/web/i18n/ru-RU/dataset-creation.ts b/web/i18n/ru-RU/dataset-creation.ts index 765bb88497..0b7b203a28 100644 --- a/web/i18n/ru-RU/dataset-creation.ts +++ b/web/i18n/ru-RU/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Запустить', firecrawlTitle: 'Извлечь веб-контент с помощью 🔥Firecrawl', firecrawlDoc: 'Документация Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Опции', crawlSubPage: 'Сканировать подстраницы', limit: 'Лимит', diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts index 1167f33697..ce80c8a086 100644 --- a/web/i18n/sl-SI/common.ts +++ b/web/i18n/sl-SI/common.ts @@ -452,7 +452,7 @@ const translation = { apiBasedExtension: { title: 'Razširitve API omogočajo centralizirano upravljanje API, kar poenostavi konfiguracijo za enostavno uporabo v aplikacijah Dify.', link: 'Naučite se, kako razviti svojo API razširitev.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj API razširitev', selector: { title: 'API razširitev', @@ -681,7 +681,7 @@ const translation = { type: 'Vrsta', link: 'Preberite, kako razvijete lastno razširitev API-ja.', title: 'Razširitve API zagotavljajo centralizirano upravljanje API, kar poenostavlja konfiguracijo za enostavno uporabo v aplikacijah Dify.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodajanje razširitve API-ja', }, about: { diff --git a/web/i18n/sl-SI/dataset-creation.ts b/web/i18n/sl-SI/dataset-creation.ts index e675c61813..8f3a67d7d1 100644 --- a/web/i18n/sl-SI/dataset-creation.ts +++ b/web/i18n/sl-SI/dataset-creation.ts @@ -71,7 +71,7 @@ const translation = { run: 'Zaženi', firecrawlTitle: 'Izvleci spletno vsebino z 🔥Firecrawl', firecrawlDoc: 'Firecrawl dokumentacija', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'Pretvori celotno stran v Markdown', jinaReaderDoc: 'Več o Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index be1f62cdd7..fca1dc428c 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -454,7 +454,7 @@ const translation = { apiBasedExtension: { title: 'ส่วนขยาย API ให้การจัดการ API แบบรวมศูนย์ ทําให้การกําหนดค่าง่ายขึ้นเพื่อให้ใช้งานได้ง่ายในแอปพลิเคชันของ Dify', link: 'เรียนรู้วิธีพัฒนาส่วนขยาย API ของคุณเอง', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', add: 'เพิ่มส่วนขยาย API', selector: { title: 'ส่วนขยาย API', diff --git a/web/i18n/th-TH/dataset-creation.ts b/web/i18n/th-TH/dataset-creation.ts index 4be2250184..dd33e65e47 100644 --- a/web/i18n/th-TH/dataset-creation.ts +++ b/web/i18n/th-TH/dataset-creation.ts @@ -71,7 +71,7 @@ const translation = { run: 'วิ่ง', firecrawlTitle: 'แยกเนื้อหาเว็บด้วย 🔥Firecrawl', firecrawlDoc: 'เอกสาร Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'แปลงทั้งไซต์เป็น Markdown', jinaReaderDoc: 'เรียนรู้เพิ่มเติมเกี่ยวกับ Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts index 9dd2f2dd7e..d66c226aa6 100644 --- a/web/i18n/tr-TR/common.ts +++ b/web/i18n/tr-TR/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'API uzantıları merkezi API yönetimi sağlar, Dify\'nin uygulamaları arasında kolay kullanım için yapılandırmayı basitleştirir.', link: 'Kendi API Uzantınızı nasıl geliştireceğinizi öğrenin.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API Uzantısı Ekle', selector: { title: 'API Uzantısı', diff --git a/web/i18n/tr-TR/dataset-creation.ts b/web/i18n/tr-TR/dataset-creation.ts index 0394a4816a..e672433786 100644 --- a/web/i18n/tr-TR/dataset-creation.ts +++ b/web/i18n/tr-TR/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Çalıştır', firecrawlTitle: '🔥Firecrawl ile web içeriğini çıkarın', firecrawlDoc: 'Firecrawl dokümanları', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Seçenekler', crawlSubPage: 'Alt sayfaları tarayın', limit: 'Sınır', diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts index dd6dab61cc..7416e30594 100644 --- a/web/i18n/uk-UA/common.ts +++ b/web/i18n/uk-UA/common.ts @@ -456,7 +456,7 @@ const translation = { apiBasedExtension: { title: 'API-розширення забезпечують централізоване керування API, спрощуючи конфігурацію для зручного використання в різних програмах Dify.', link: 'Дізнайтеся, як розробити власне розширення API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Додати розширення API', selector: { title: 'Розширення API', diff --git a/web/i18n/uk-UA/dataset-creation.ts b/web/i18n/uk-UA/dataset-creation.ts index 96af3bbd1e..9d924629e6 100644 --- a/web/i18n/uk-UA/dataset-creation.ts +++ b/web/i18n/uk-UA/dataset-creation.ts @@ -60,7 +60,7 @@ const translation = { unknownError: 'Невідома помилка', maxDepth: 'Максимальна глибина', crawlSubPage: 'Сканування підсторінок', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: 'Попередній перегляд', fireCrawlNotConfigured: 'Firecrawl не налаштовано', includeOnlyPaths: 'Включати лише контури', diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts index a047fb6c9f..2ce9c3c41a 100644 --- a/web/i18n/vi-VN/common.ts +++ b/web/i18n/vi-VN/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'Các tiện ích API cung cấp quản lý API tập trung, giúp cấu hình dễ dàng sử dụng trên các ứng dụng của Dify.', link: 'Tìm hiểu cách phát triển Phần mở rộng API của riêng bạn.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Thêm Phần mở rộng API', selector: { title: 'Phần mở rộng API', diff --git a/web/i18n/vi-VN/dataset-creation.ts b/web/i18n/vi-VN/dataset-creation.ts index 8acaf329b2..071c1e3d13 100644 --- a/web/i18n/vi-VN/dataset-creation.ts +++ b/web/i18n/vi-VN/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { unknownError: 'Lỗi không xác định', extractOnlyMainContent: 'Chỉ trích xuất nội dung chính (không có đầu trang, điều hướng, chân trang, v.v.)', exceptionErrorTitle: 'Một ngoại lệ xảy ra trong khi chạy tác vụ Firecrawl:', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', selectAll: 'Chọn tất cả', firecrawlTitle: 'Trích xuất nội dung web bằng 🔥Firecrawl', totalPageScraped: 'Tổng số trang được cạo:', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 8ed1e28fd8..327f41ea05 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { title: 'API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify 上的各类应用中直接使用。', link: '了解如何开发您自己的 API 扩展。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/extension/api-based-extension', add: '新增 API 扩展', selector: { title: 'API 扩展', diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts index 6a91b1b996..e3ca478f40 100644 --- a/web/i18n/zh-Hans/dataset-creation.ts +++ b/web/i18n/zh-Hans/dataset-creation.ts @@ -79,7 +79,7 @@ const translation = { run: '运行', firecrawlTitle: '使用 🔥Firecrawl 提取网页内容', firecrawlDoc: 'Firecrawl 文档', - firecrawlDocLink: 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: '将整个站点内容转换为 Markdown 格式', jinaReaderDoc: '了解更多关于 Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 18f42a3b93..cf15a00d61 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'API 擴充套件提供了一個集中式的 API 管理,在此統一新增 API 配置後,方便在 Dify 上的各類應用中直接使用。', link: '瞭解如何開發您自己的 API 擴充套件。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/tools/extensions/api-based/api-based-extension', add: '新增 API 擴充套件', selector: { title: 'API 擴充套件', diff --git a/web/i18n/zh-Hant/dataset-creation.ts b/web/i18n/zh-Hant/dataset-creation.ts index 9f5186d024..61737775dc 100644 --- a/web/i18n/zh-Hant/dataset-creation.ts +++ b/web/i18n/zh-Hant/dataset-creation.ts @@ -61,7 +61,7 @@ const translation = { fireCrawlNotConfiguredDescription: '使用 API 金鑰配置 Firecrawl 以使用它。', limit: '限制', crawlSubPage: '抓取子頁面', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: '預覽', configure: '配置', excludePaths: '排除路徑',