mirror of
https://github.com/langgenius/dify.git
synced 2026-05-27 12:26:15 +08:00
Compare commits
1 Commits
codex/fix-
...
fix/webapp
| Author | SHA1 | Date | |
|---|---|---|---|
| 20ec074924 |
@ -5,7 +5,7 @@ from controllers.console import console_ns
|
||||
from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
from services.agent.composer_service import AgentComposerService
|
||||
from services.agent.composer_validator import ComposerConfigValidator
|
||||
from services.entities.agent_entities import ComposerSavePayload
|
||||
@ -19,7 +19,7 @@ class WorkflowAgentComposerApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def get(self, app_model: App, node_id: str):
|
||||
def get(self, app_model, node_id: str):
|
||||
_, tenant_id = current_account_with_tenant()
|
||||
return AgentComposerService.load_workflow_composer(
|
||||
tenant_id=tenant_id,
|
||||
@ -33,7 +33,7 @@ class WorkflowAgentComposerApi(Resource):
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def put(self, app_model: App, node_id: str):
|
||||
def put(self, app_model, node_id: str):
|
||||
account, tenant_id = current_account_with_tenant()
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
return AgentComposerService.save_workflow_composer(
|
||||
@ -52,7 +52,7 @@ class WorkflowAgentComposerValidateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def post(self, app_model: App, node_id: str):
|
||||
def post(self, app_model, node_id: str):
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
ComposerConfigValidator.validate_save_payload(payload)
|
||||
return {"result": "success", "errors": []}
|
||||
@ -64,7 +64,7 @@ class WorkflowAgentComposerCandidatesApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def get(self, app_model: App, node_id: str):
|
||||
def get(self, app_model, node_id: str):
|
||||
return AgentComposerService.get_workflow_candidates(app_id=app_model.id)
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@ class WorkflowAgentComposerImpactApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def post(self, app_model: App, node_id: str):
|
||||
def post(self, app_model, node_id: str):
|
||||
_, tenant_id = current_account_with_tenant()
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
current_snapshot_id = payload.binding.current_snapshot_id if payload.binding else None
|
||||
@ -91,7 +91,7 @@ class WorkflowAgentComposerSaveToRosterApi(Resource):
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
|
||||
def post(self, app_model: App, node_id: str):
|
||||
def post(self, app_model, node_id: str):
|
||||
account, tenant_id = current_account_with_tenant()
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
return AgentComposerService.save_workflow_composer(
|
||||
@ -109,7 +109,7 @@ class AgentAppComposerApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model()
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
_, tenant_id = current_account_with_tenant()
|
||||
return AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_model.id)
|
||||
|
||||
@ -119,7 +119,7 @@ class AgentAppComposerApi(Resource):
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@get_app_model()
|
||||
def put(self, app_model: App):
|
||||
def put(self, app_model):
|
||||
account, tenant_id = current_account_with_tenant()
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
return AgentComposerService.save_agent_app_composer(
|
||||
@ -137,7 +137,7 @@ class AgentAppComposerValidateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model()
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||
ComposerConfigValidator.validate_save_payload(payload)
|
||||
return {"result": "success", "errors": []}
|
||||
@ -149,5 +149,5 @@ class AgentAppComposerCandidatesApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model()
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
return AgentComposerService.get_agent_app_candidates(app_id=app_model.id)
|
||||
|
||||
@ -8,7 +8,7 @@ from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import account_initialization_required, setup_required
|
||||
from libs.helper import uuid_value
|
||||
from libs.login import login_required
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
from services.agent_service import AgentService
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ class AgentLogApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.AGENT_CHAT])
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
"""Get agent logs"""
|
||||
args = AgentLogQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
|
||||
@ -573,7 +573,7 @@ class AppApi(Resource):
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@get_app_model(mode=None)
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
"""Get app detail"""
|
||||
app_service = AppService()
|
||||
|
||||
@ -581,7 +581,7 @@ class AppApi(Resource):
|
||||
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id))
|
||||
app_model.access_mode = app_setting.access_mode # type: ignore[attr-defined]
|
||||
app_model.access_mode = app_setting.access_mode
|
||||
|
||||
response_model = AppDetailWithSite.model_validate(app_model, from_attributes=True)
|
||||
return response_model.model_dump(mode="json")
|
||||
@ -598,7 +598,7 @@ class AppApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def put(self, app_model: App):
|
||||
def put(self, app_model):
|
||||
"""Update app"""
|
||||
args = UpdateAppPayload.model_validate(console_ns.payload)
|
||||
|
||||
@ -627,7 +627,7 @@ class AppApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
def delete(self, app_model: App):
|
||||
def delete(self, app_model):
|
||||
"""Delete app"""
|
||||
app_service = AppService()
|
||||
app_service.delete_app(app_model)
|
||||
@ -648,7 +648,7 @@ class AppCopyApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
"""Copy app"""
|
||||
# The role of the current user in the ta table must be admin, owner, or editor
|
||||
current_user, _ = current_account_with_tenant()
|
||||
@ -709,7 +709,7 @@ class AppExportApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
"""Export app"""
|
||||
args = AppExportQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
@ -731,7 +731,7 @@ class AppPublishToCreatorsPlatformApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
"""Publish app to Creators Platform"""
|
||||
from configs import dify_config
|
||||
from core.helper.creators import get_redirect_url, upload_dsl
|
||||
@ -762,7 +762,7 @@ class AppNameApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args = AppNamePayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
@ -784,7 +784,7 @@ class AppIconApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args = AppIconPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
app_service = AppService()
|
||||
@ -811,7 +811,7 @@ class AppSiteStatus(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args = AppSiteStatusPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
@ -833,7 +833,7 @@ class AppApiStatus(Resource):
|
||||
@is_admin_or_owner_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=None)
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args = AppApiStatusPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
@ -874,7 +874,7 @@ class AppTraceApi(Resource):
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@get_app_model
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
# add app trace
|
||||
args = AppTracePayload.model_validate(console_ns.payload)
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ class ChatMessageAudioApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
file = request.files["file"]
|
||||
|
||||
try:
|
||||
@ -171,7 +171,7 @@ class TextModesApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
try:
|
||||
args = TextToSpeechVoiceQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ from libs import helper
|
||||
from libs.helper import uuid_value
|
||||
from libs.login import current_user, login_required
|
||||
from models import Account
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
from services.app_generate_service import AppGenerateService
|
||||
from services.app_task_service import AppTaskService
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
@ -84,7 +84,7 @@ class CompletionMessageApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args_model = CompletionMessagePayload.model_validate(console_ns.payload)
|
||||
args = args_model.model_dump(exclude_none=True, by_alias=True)
|
||||
|
||||
@ -131,7 +131,7 @@ class CompletionMessageStopApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def post(self, app_model: App, task_id: str):
|
||||
def post(self, app_model, task_id: str):
|
||||
if not isinstance(current_user, Account):
|
||||
raise ValueError("current_user must be an Account instance")
|
||||
|
||||
@ -159,7 +159,7 @@ class ChatMessageApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT])
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args_model = ChatMessagePayload.model_validate(console_ns.payload)
|
||||
args = args_model.model_dump(exclude_none=True, by_alias=True)
|
||||
|
||||
@ -212,7 +212,7 @@ class ChatMessageStopApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
def post(self, app_model: App, task_id: str):
|
||||
def post(self, app_model, task_id: str):
|
||||
if not isinstance(current_user, Account):
|
||||
raise ValueError("current_user must be an Account instance")
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ from fields.conversation_fields import (
|
||||
from libs.datetime_utils import naive_utc_now, parse_time_range
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import Conversation, EndUser, Message, MessageAnnotation
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
from services.conversation_service import ConversationService
|
||||
from services.errors.conversation import ConversationNotExistsError
|
||||
|
||||
@ -93,7 +93,7 @@ class CompletionConversationApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
args = CompletionConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
@ -165,7 +165,7 @@ class CompletionConversationDetailApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App, conversation_id: UUID):
|
||||
def get(self, app_model, conversation_id: UUID):
|
||||
conversation_id_str = str(conversation_id)
|
||||
return ConversationMessageDetailResponse.model_validate(
|
||||
_get_conversation(app_model, conversation_id_str), from_attributes=True
|
||||
@ -182,7 +182,7 @@ class CompletionConversationDetailApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@edit_permission_required
|
||||
def delete(self, app_model: App, conversation_id: UUID):
|
||||
def delete(self, app_model, conversation_id: UUID):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
conversation_id_str = str(conversation_id)
|
||||
|
||||
@ -207,7 +207,7 @@ class ChatConversationApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
args = ChatConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
@ -318,7 +318,7 @@ class ChatConversationDetailApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App, conversation_id: UUID):
|
||||
def get(self, app_model, conversation_id: UUID):
|
||||
conversation_id_str = str(conversation_id)
|
||||
return ConversationDetailResponse.model_validate(
|
||||
_get_conversation(app_model, conversation_id_str), from_attributes=True
|
||||
@ -335,7 +335,7 @@ class ChatConversationDetailApi(Resource):
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
def delete(self, app_model: App, conversation_id: UUID):
|
||||
def delete(self, app_model, conversation_id: UUID):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
conversation_id_str = str(conversation_id)
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ from fields.base import ResponseModel
|
||||
from libs.helper import to_timestamp
|
||||
from libs.login import login_required
|
||||
from models import ConversationVariable
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
|
||||
|
||||
class ConversationVariablesQuery(BaseModel):
|
||||
@ -94,7 +94,7 @@ class ConversationVariablesApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
args = ConversationVariablesQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
stmt = (
|
||||
|
||||
@ -17,7 +17,7 @@ from fields.base import ResponseModel
|
||||
from libs.helper import to_timestamp
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.enums import AppMCPServerStatus
|
||||
from models.model import App, AppMCPServer
|
||||
from models.model import AppMCPServer
|
||||
|
||||
|
||||
class MCPServerCreatePayload(BaseModel):
|
||||
@ -73,7 +73,7 @@ class AppMCPServerController(Resource):
|
||||
@account_initialization_required
|
||||
@setup_required
|
||||
@get_app_model
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1))
|
||||
if server is None:
|
||||
return {}
|
||||
@ -92,7 +92,7 @@ class AppMCPServerController(Resource):
|
||||
@login_required
|
||||
@setup_required
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
_, current_tenant_id = current_account_with_tenant()
|
||||
payload = MCPServerCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
@ -127,7 +127,7 @@ class AppMCPServerController(Resource):
|
||||
@setup_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
def put(self, app_model: App):
|
||||
def put(self, app_model):
|
||||
payload = MCPServerUpdatePayload.model_validate(console_ns.payload or {})
|
||||
server = db.session.get(AppMCPServer, payload.id)
|
||||
if not server:
|
||||
|
||||
@ -45,7 +45,7 @@ from libs.helper import to_timestamp, uuid_value
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.enums import FeedbackFromSource, FeedbackRating
|
||||
from models.model import App, AppMode, Conversation, Message, MessageAnnotation, MessageFeedback
|
||||
from models.model import AppMode, Conversation, Message, MessageAnnotation, MessageFeedback
|
||||
from services.errors.conversation import ConversationNotExistsError
|
||||
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
|
||||
from services.message_service import MessageService, attach_message_extra_contents
|
||||
@ -180,7 +180,7 @@ class ChatMessageListApi(Resource):
|
||||
@setup_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
args = ChatMessagesQuery.model_validate(request.args.to_dict())
|
||||
|
||||
conversation = db.session.scalar(
|
||||
@ -257,7 +257,7 @@ class MessageFeedbackApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
|
||||
args = MessageFeedbackPayload.model_validate(console_ns.payload)
|
||||
@ -314,7 +314,7 @@ class MessageAnnotationCountApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
count = db.session.scalar(
|
||||
select(func.count(MessageAnnotation.id)).where(MessageAnnotation.app_id == app_model.id)
|
||||
)
|
||||
@ -337,7 +337,7 @@ class MessageSuggestedQuestionApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
def get(self, app_model: App, message_id: UUID):
|
||||
def get(self, app_model, message_id: UUID):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
message_id_str = str(message_id)
|
||||
|
||||
@ -379,7 +379,7 @@ class MessageFeedbackExportApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
args = FeedbackExportQuery.model_validate(request.args.to_dict())
|
||||
|
||||
# Import the service function
|
||||
@ -417,7 +417,7 @@ class MessageApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App, message_id: UUID):
|
||||
def get(self, app_model, message_id: UUID):
|
||||
message_id_str = str(message_id)
|
||||
|
||||
message = db.session.scalar(
|
||||
|
||||
@ -16,7 +16,7 @@ from events.app_event import app_model_config_was_updated
|
||||
from extensions.ext_database import db
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.model import App, AppMode, AppModelConfig
|
||||
from models.model import AppMode, AppModelConfig
|
||||
from services.app_model_config_service import AppModelConfigService
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ class ModelConfigResource(Resource):
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.AGENT_CHAT, AppMode.CHAT, AppMode.COMPLETION])
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
"""Modify app model config"""
|
||||
current_user, current_tenant_id = current_account_with_tenant()
|
||||
# validate config
|
||||
|
||||
@ -20,7 +20,6 @@ from fields.base import ResponseModel
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import Site
|
||||
from models.model import App
|
||||
|
||||
|
||||
class AppSiteUpdatePayload(BaseModel):
|
||||
@ -85,7 +84,7 @@ class AppSite(Resource):
|
||||
@edit_permission_required
|
||||
@account_initialization_required
|
||||
@get_app_model
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
args = AppSiteUpdatePayload.model_validate(console_ns.payload or {})
|
||||
current_user, _ = current_account_with_tenant()
|
||||
site = db.session.scalar(select(Site).where(Site.app_id == app_model.id).limit(1))
|
||||
@ -134,7 +133,7 @@ class AppSiteAccessTokenReset(Resource):
|
||||
@is_admin_or_owner_required
|
||||
@account_initialization_required
|
||||
@get_app_model
|
||||
def post(self, app_model: App):
|
||||
def post(self, app_model):
|
||||
current_user, _ = current_account_with_tenant()
|
||||
site = db.session.scalar(select(Site).where(Site.app_id == app_model.id).limit(1))
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ from libs.datetime_utils import parse_time_range
|
||||
from libs.helper import convert_datetime_to_date
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import AppMode
|
||||
from models.model import App
|
||||
|
||||
|
||||
class StatisticTimeRangeQuery(BaseModel):
|
||||
@ -48,7 +47,7 @@ class DailyMessageStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -62,12 +61,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -109,7 +104,7 @@ class DailyConversationStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -123,12 +118,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -169,7 +160,7 @@ class DailyTerminalsStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -183,12 +174,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -230,7 +217,7 @@ class DailyTokenCostStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -245,12 +232,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -294,7 +277,7 @@ class AverageSessionInteractionStatistic(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -316,12 +299,8 @@ FROM
|
||||
WHERE
|
||||
c.app_id = :app_id
|
||||
AND m.invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -374,7 +353,7 @@ class UserSatisfactionRateStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -392,12 +371,8 @@ LEFT JOIN
|
||||
WHERE
|
||||
m.app_id = :app_id
|
||||
AND m.invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -444,7 +419,7 @@ class AverageResponseTimeStatistic(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -458,12 +433,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
@ -505,7 +476,7 @@ class TokensPerSecondStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
@ -521,12 +492,8 @@ FROM
|
||||
WHERE
|
||||
app_id = :app_id
|
||||
AND invoke_from != :invoke_from"""
|
||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id, "invoke_from": InvokeFrom.DEBUGGER}
|
||||
assert account.timezone is not None
|
||||
arg_dict: dict[str, object] = {
|
||||
"tz": account.timezone,
|
||||
"app_id": app_model.id,
|
||||
"invoke_from": InvokeFrom.DEBUGGER,
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
|
||||
@ -11,7 +11,7 @@ from extensions.ext_database import db
|
||||
from libs.datetime_utils import parse_time_range
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models.enums import WorkflowRunTriggeredFrom
|
||||
from models.model import App, AppMode
|
||||
from models.model import AppMode
|
||||
from repositories.factory import DifyAPIRepositoryFactory
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ class WorkflowDailyRunsStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -86,7 +86,7 @@ class WorkflowDailyTerminalsStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -126,7 +126,7 @@ class WorkflowDailyTokenCostStatistic(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@ -166,7 +166,7 @@ class WorkflowAverageAppInteractionStatistic(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||
def get(self, app_model: App):
|
||||
def get(self, app_model):
|
||||
account, _ = current_account_with_tenant()
|
||||
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
@ -87,12 +87,10 @@ const createDefaultCollections = () => [
|
||||
]
|
||||
|
||||
let mockCollectionData: ReturnType<typeof createDefaultCollections> = []
|
||||
let mockIsLoadingToolProviders = false
|
||||
const mockRefetch = vi.fn()
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllToolProviders: () => ({
|
||||
data: mockCollectionData,
|
||||
isLoading: mockIsLoadingToolProviders,
|
||||
refetch: mockRefetch,
|
||||
}),
|
||||
}))
|
||||
@ -108,19 +106,7 @@ vi.mock('@/service/use-plugins', () => ({
|
||||
|
||||
vi.mock('@/app/components/plugins/card', () => ({
|
||||
default: ({ payload, className }: { payload: { name: string }, className?: string }) => (
|
||||
<div data-testid={`card-${payload.name}`} className={className}>
|
||||
{payload.name}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/tools/provider/tool-card-skeleton', () => ({
|
||||
default: () => (
|
||||
<>
|
||||
{Array.from({ length: 6 }, (_, index) => (
|
||||
<div key={index} data-testid="tool-card-skeleton">Loading tool</div>
|
||||
))}
|
||||
</>
|
||||
<div data-testid={`card-${payload.name}`} className={className}>{payload.name}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
@ -235,7 +221,6 @@ describe('ProviderList', () => {
|
||||
vi.clearAllMocks()
|
||||
mockEnableMarketplace = false
|
||||
mockCollectionData = createDefaultCollections()
|
||||
mockIsLoadingToolProviders = false
|
||||
mockCheckedInstalledData = null
|
||||
Element.prototype.scrollTo = vi.fn()
|
||||
})
|
||||
@ -346,13 +331,6 @@ describe('ProviderList', () => {
|
||||
renderProviderList({ category: 'api' })
|
||||
expect(screen.getByTestId('custom-create-card')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows card skeletons instead of custom create card while tool providers are loading', () => {
|
||||
mockIsLoadingToolProviders = true
|
||||
renderProviderList({ category: 'api' })
|
||||
expect(screen.getAllByTestId('tool-card-skeleton')).toHaveLength(6)
|
||||
expect(screen.queryByTestId('custom-create-card')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Workflow Tab', () => {
|
||||
@ -366,14 +344,6 @@ describe('ProviderList', () => {
|
||||
renderProviderList({ category: 'workflow' })
|
||||
expect(screen.getByTestId('workflow-empty')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows card skeletons instead of empty state while tool providers are loading', () => {
|
||||
mockIsLoadingToolProviders = true
|
||||
mockCollectionData = []
|
||||
renderProviderList({ category: 'workflow' })
|
||||
expect(screen.getAllByTestId('tool-card-skeleton')).toHaveLength(6)
|
||||
expect(screen.queryByTestId('workflow-empty')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Builtin Tab Empty State', () => {
|
||||
@ -383,14 +353,6 @@ describe('ProviderList', () => {
|
||||
expect(screen.getByTestId('empty')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows card skeletons instead of empty component while tool providers are loading', () => {
|
||||
mockIsLoadingToolProviders = true
|
||||
mockCollectionData = []
|
||||
renderProviderList()
|
||||
expect(screen.getAllByTestId('tool-card-skeleton')).toHaveLength(6)
|
||||
expect(screen.queryByTestId('empty')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders collection that has no labels property', () => {
|
||||
mockCollectionData = [{
|
||||
id: 'no-labels',
|
||||
|
||||
@ -13,26 +13,14 @@ type MockDetail = MockProvider | undefined
|
||||
// Mock dependencies
|
||||
const mockRefetch = vi.fn()
|
||||
let mockProviders: MockProvider[] = []
|
||||
let mockIsLoadingToolProviders = false
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllToolProviders: () => ({
|
||||
data: mockProviders,
|
||||
isLoading: mockIsLoadingToolProviders,
|
||||
refetch: mockRefetch,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/tools/provider/tool-card-skeleton', () => ({
|
||||
default: () => (
|
||||
<>
|
||||
{Array.from({ length: 6 }, (_, index) => (
|
||||
<div key={index} data-testid="mcp-card-skeleton">Loading MCP</div>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock child components
|
||||
vi.mock('../create-card', () => ({
|
||||
default: ({ handleCreate }: { handleCreate: (provider: { id: string, name: string }) => void }) => (
|
||||
@ -77,7 +65,6 @@ describe('MCPList', () => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
mockProviders = []
|
||||
mockIsLoadingToolProviders = false
|
||||
mockRefetch.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
@ -98,18 +85,15 @@ describe('MCPList', () => {
|
||||
expect(screen.getByTestId('create-card')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render card skeletons while tool providers are loading', () => {
|
||||
mockIsLoadingToolProviders = true
|
||||
it('should render default skeleton cards when list is empty', () => {
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
expect(screen.getAllByTestId('mcp-card-skeleton')).toHaveLength(6)
|
||||
expect(screen.queryByTestId('provider-card-1')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render card skeletons when the loaded list is empty', () => {
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
expect(screen.queryByTestId('mcp-card-skeleton')).not.toBeInTheDocument()
|
||||
// Should render skeleton cards when no providers
|
||||
const container = document.querySelector('.grid')
|
||||
expect(container).toBeInTheDocument()
|
||||
// Check for skeleton cards (36 of them)
|
||||
const skeletonCards = document.querySelectorAll('.h-\\[111px\\]')
|
||||
expect(skeletonCards.length).toBe(36)
|
||||
})
|
||||
|
||||
it('should not render skeleton cards when providers exist', () => {
|
||||
@ -118,7 +102,8 @@ describe('MCPList', () => {
|
||||
]
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
expect(screen.queryByTestId('mcp-card-skeleton')).not.toBeInTheDocument()
|
||||
const skeletonCards = document.querySelectorAll('.h-\\[111px\\]')
|
||||
expect(skeletonCards.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@ -340,16 +325,15 @@ describe('MCPList', () => {
|
||||
expect(grid).toHaveClass('xl:grid-cols-4')
|
||||
})
|
||||
|
||||
it('should have overflow hidden while loading', () => {
|
||||
it('should have overflow hidden when list is empty', () => {
|
||||
mockProviders = []
|
||||
mockIsLoadingToolProviders = true
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
const grid = document.querySelector('.grid')
|
||||
expect(grid).toHaveClass('overflow-hidden')
|
||||
})
|
||||
|
||||
it('should not have overflow hidden when loading is complete', () => {
|
||||
it('should not have overflow hidden when list has providers', () => {
|
||||
mockProviders = [{ id: '1', name: 'Provider 1', type: 'mcp' }]
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useMemo, useState } from 'react'
|
||||
import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton'
|
||||
import {
|
||||
useAllToolProviders,
|
||||
} from '@/service/use-tools'
|
||||
@ -14,10 +13,29 @@ type Props = {
|
||||
searchText: string
|
||||
}
|
||||
|
||||
function renderDefaultCard() {
|
||||
const defaultCards = Array.from({ length: 36 }, (_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
'inline-flex h-[111px] rounded-xl bg-background-default-lighter opacity-10',
|
||||
index < 4 && 'opacity-60',
|
||||
index >= 4 && index < 8 && 'opacity-50',
|
||||
index >= 8 && index < 12 && 'opacity-40',
|
||||
index >= 12 && index < 16 && 'opacity-30',
|
||||
index >= 16 && index < 20 && 'opacity-25',
|
||||
index >= 20 && index < 24 && 'opacity-20',
|
||||
)}
|
||||
>
|
||||
</div>
|
||||
))
|
||||
return defaultCards
|
||||
}
|
||||
|
||||
const MCPList = ({
|
||||
searchText,
|
||||
}: Props) => {
|
||||
const { data: list = [] as ToolWithProvider[], isLoading, refetch } = useAllToolProviders()
|
||||
const { data: list = [] as ToolWithProvider[], refetch } = useAllToolProviders()
|
||||
const [isTriggerAuthorize, setIsTriggerAuthorize] = useState<boolean>(false)
|
||||
|
||||
const filteredList = useMemo(() => {
|
||||
@ -50,22 +68,21 @@ const MCPList = ({
|
||||
<div
|
||||
className={cn(
|
||||
'relative grid shrink-0 grid-cols-1 content-start gap-4 px-12 pt-2 pb-4 2k:grid-cols-6 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5',
|
||||
isLoading && 'h-[calc(100vh-136px)] overflow-hidden',
|
||||
!list.length && 'h-[calc(100vh-136px)] overflow-hidden',
|
||||
)}
|
||||
>
|
||||
<NewMCPCard handleCreate={handleCreate} />
|
||||
{isLoading
|
||||
? <ToolCardSkeletonGrid />
|
||||
: filteredList.map(provider => (
|
||||
<MCPCard
|
||||
key={provider.id}
|
||||
data={provider}
|
||||
currentProvider={currentProvider as ToolWithProvider}
|
||||
handleSelect={setCurrentProviderID}
|
||||
onUpdate={handleUpdate}
|
||||
onDeleted={refetch}
|
||||
/>
|
||||
))}
|
||||
{filteredList.map(provider => (
|
||||
<MCPCard
|
||||
key={provider.id}
|
||||
data={provider}
|
||||
currentProvider={currentProvider as ToolWithProvider}
|
||||
handleSelect={setCurrentProviderID}
|
||||
onUpdate={handleUpdate}
|
||||
onDeleted={refetch}
|
||||
/>
|
||||
))}
|
||||
{!list.length && renderDefaultCard()}
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<MCPDetailPanel
|
||||
|
||||
@ -16,7 +16,6 @@ import LabelFilter from '@/app/components/tools/labels/filter'
|
||||
import CustomCreateCard from '@/app/components/tools/provider/custom-create-card'
|
||||
import ProviderDetail from '@/app/components/tools/provider/detail'
|
||||
import WorkflowToolEmpty from '@/app/components/tools/provider/empty'
|
||||
import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useAllToolProviders } from '@/service/use-tools'
|
||||
@ -62,7 +61,7 @@ const ProviderList = () => {
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
}
|
||||
const { data: collectionList = [], isLoading: isCollectionListLoading, refetch } = useAllToolProviders()
|
||||
const { data: collectionList = [], refetch } = useAllToolProviders()
|
||||
const filteredCollectionList = useMemo(() => {
|
||||
return collectionList.filter((collection) => {
|
||||
if (collection.type !== activeTab)
|
||||
@ -166,42 +165,36 @@ const ProviderList = () => {
|
||||
!filteredCollectionList.length && activeTab === 'workflow' && 'grow',
|
||||
)}
|
||||
>
|
||||
{isCollectionListLoading
|
||||
? <ToolCardSkeletonGrid />
|
||||
: (
|
||||
<>
|
||||
{activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
|
||||
{filteredCollectionList.map(collection => (
|
||||
<div
|
||||
key={collection.id}
|
||||
onClick={() => setCurrentProviderId(collection.id)}
|
||||
>
|
||||
<Card
|
||||
className={cn(
|
||||
'cursor-pointer border-[1.5px] border-transparent',
|
||||
currentProviderId === collection.id && 'border-components-option-card-option-selected-border',
|
||||
)}
|
||||
hideCornerMark
|
||||
payload={{
|
||||
...collection,
|
||||
brief: collection.description,
|
||||
org: collection.plugin_id ? collection.plugin_id.split('/')[0] : '',
|
||||
name: collection.plugin_id ? collection.plugin_id.split('/')[1] : collection.name,
|
||||
} as any}
|
||||
footer={(
|
||||
<CardMoreInfo
|
||||
tags={collection.labels?.map(label => getTagLabel(label)) || []}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{!filteredCollectionList.length && activeTab === 'workflow' && <div className="absolute top-1/2 left-1/2 -translate-1/2"><WorkflowToolEmpty type={getToolType(activeTab)} /></div>}
|
||||
</>
|
||||
)}
|
||||
{activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
|
||||
{filteredCollectionList.map(collection => (
|
||||
<div
|
||||
key={collection.id}
|
||||
onClick={() => setCurrentProviderId(collection.id)}
|
||||
>
|
||||
<Card
|
||||
className={cn(
|
||||
'cursor-pointer border-[1.5px] border-transparent',
|
||||
currentProviderId === collection.id && 'border-components-option-card-option-selected-border',
|
||||
)}
|
||||
hideCornerMark
|
||||
payload={{
|
||||
...collection,
|
||||
brief: collection.description,
|
||||
org: collection.plugin_id ? collection.plugin_id.split('/')[0] : '',
|
||||
name: collection.plugin_id ? collection.plugin_id.split('/')[1] : collection.name,
|
||||
} as any}
|
||||
footer={(
|
||||
<CardMoreInfo
|
||||
tags={collection.labels?.map(label => getTagLabel(label)) || []}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{!filteredCollectionList.length && activeTab === 'workflow' && <div className="absolute top-1/2 left-1/2 -translate-1/2"><WorkflowToolEmpty type={getToolType(activeTab)} /></div>}
|
||||
</div>
|
||||
)}
|
||||
{!isCollectionListLoading && !filteredCollectionList.length && activeTab === 'builtin' && (
|
||||
{!filteredCollectionList.length && activeTab === 'builtin' && (
|
||||
<Empty lightCard text={t('noTools', { ns: 'tools' })} className="h-[224px] shrink-0 px-12" />
|
||||
)}
|
||||
<div ref={toolListTailRef} />
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { SkeletonContainer, SkeletonPoint, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
|
||||
|
||||
type ToolCardSkeletonGridProps = {
|
||||
className?: string
|
||||
count?: number
|
||||
}
|
||||
|
||||
const ToolCardSkeleton = () => (
|
||||
<div className="relative overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs">
|
||||
<div className="p-4 pb-3">
|
||||
<div className="flex">
|
||||
<SkeletonRectangle className="my-0 size-10 shrink-0 animate-pulse rounded-md" />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex h-5 items-center">
|
||||
<SkeletonRectangle className="h-4 w-2/3 animate-pulse" />
|
||||
</div>
|
||||
<SkeletonRow className="mt-0.5 h-4">
|
||||
<SkeletonRectangle className="w-[41px] animate-pulse" />
|
||||
<SkeletonPoint />
|
||||
<SkeletonRectangle className="w-1/3 animate-pulse" />
|
||||
</SkeletonRow>
|
||||
</div>
|
||||
</div>
|
||||
<SkeletonContainer className="mt-3 h-8 gap-0">
|
||||
<SkeletonRectangle className="h-3 w-full animate-pulse" />
|
||||
<SkeletonRectangle className="h-3 w-4/5 animate-pulse" />
|
||||
</SkeletonContainer>
|
||||
<div className="flex h-5 items-center gap-2">
|
||||
<SkeletonRectangle className="h-3 w-12 animate-pulse" />
|
||||
<SkeletonRectangle className="h-3 w-20 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const ToolCardSkeletonGrid = ({
|
||||
className,
|
||||
count = 6,
|
||||
}: ToolCardSkeletonGridProps) => (
|
||||
<>
|
||||
{Array.from({ length: count }, (_, index) => (
|
||||
<div key={index} className={cn(className)}>
|
||||
<ToolCardSkeleton />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
export default ToolCardSkeletonGrid
|
||||
@ -787,7 +787,7 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
|
||||
isPublicAPI = false,
|
||||
silent,
|
||||
} = otherOptionsForBaseFetch
|
||||
if (isPublicAPI && code === 'unauthorized') {
|
||||
if (isPublicAPI && code === 'unauthorized' && IS_CE_EDITION) {
|
||||
requiredWebSSOLogin()
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user