mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 05:49:25 +08:00
Merge remote-tracking branch 'origin/main' into codex/dify-ui-package-migration
This commit is contained in:
@@ -89,6 +89,12 @@ if $web_modified; then
|
||||
echo "No staged TypeScript changes detected, skipping type-check:tsgo"
|
||||
fi
|
||||
|
||||
echo "Running knip"
|
||||
if ! pnpm run knip; then
|
||||
echo "Knip check failed. Please run 'pnpm run knip' to fix the errors."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running unit tests check"
|
||||
modified_files=$(git diff --cached --name-only -- utils | grep -v '\.spec\.ts$' || true)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from flask_restx import Resource, fields, marshal_with
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import (
|
||||
@@ -71,7 +71,7 @@ class AppImportApi(Resource):
|
||||
args = AppImportPayload.model_validate(console_ns.payload)
|
||||
|
||||
# Create service with session
|
||||
with sessionmaker(db.engine).begin() as session:
|
||||
with Session(db.engine) as session:
|
||||
import_service = AppDslService(session)
|
||||
# Import app
|
||||
account = current_user
|
||||
|
||||
@@ -158,10 +158,11 @@ class DataSourceApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def patch(self, binding_id, action: Literal["enable", "disable"]):
|
||||
_, current_tenant_id = current_account_with_tenant()
|
||||
binding_id = str(binding_id)
|
||||
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
|
||||
data_source_binding = session.execute(
|
||||
select(DataSourceOauthBinding).filter_by(id=binding_id)
|
||||
select(DataSourceOauthBinding).filter_by(id=binding_id, tenant_id=current_tenant_id)
|
||||
).scalar_one_or_none()
|
||||
if data_source_binding is None:
|
||||
raise NotFound("Data source binding not found.")
|
||||
|
||||
@@ -7,7 +7,7 @@ from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
||||
from controllers.trigger import bp
|
||||
from core.trigger.debug.event_bus import TriggerDebugEventBus
|
||||
from core.trigger.debug.events import WebhookDebugEvent, build_webhook_pool_key
|
||||
from services.trigger.webhook_service import WebhookService
|
||||
from services.trigger.webhook_service import RawWebhookDataDict, WebhookService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,6 +23,7 @@ def _prepare_webhook_execution(webhook_id: str, is_debug: bool = False):
|
||||
webhook_id, is_debug=is_debug
|
||||
)
|
||||
|
||||
webhook_data: RawWebhookDataDict
|
||||
try:
|
||||
# Use new unified extraction and validation
|
||||
webhook_data = WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config)
|
||||
|
||||
@@ -3,13 +3,19 @@
|
||||
import logging
|
||||
import traceback
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import orjson
|
||||
|
||||
from configs import dify_config
|
||||
|
||||
|
||||
class IdentityDict(TypedDict, total=False):
|
||||
tenant_id: str
|
||||
user_id: str
|
||||
user_type: str
|
||||
|
||||
|
||||
class StructuredJSONFormatter(logging.Formatter):
|
||||
"""
|
||||
JSON log formatter following the specified schema:
|
||||
@@ -84,7 +90,7 @@ class StructuredJSONFormatter(logging.Formatter):
|
||||
|
||||
return log_dict
|
||||
|
||||
def _extract_identity(self, record: logging.LogRecord) -> dict[str, str] | None:
|
||||
def _extract_identity(self, record: logging.LogRecord) -> IdentityDict | None:
|
||||
tenant_id = getattr(record, "tenant_id", None)
|
||||
user_id = getattr(record, "user_id", None)
|
||||
user_type = getattr(record, "user_type", None)
|
||||
@@ -92,7 +98,7 @@ class StructuredJSONFormatter(logging.Formatter):
|
||||
if not any([tenant_id, user_id, user_type]):
|
||||
return None
|
||||
|
||||
identity: dict[str, str] = {}
|
||||
identity: IdentityDict = {}
|
||||
if tenant_id:
|
||||
identity["tenant_id"] = tenant_id
|
||||
if user_id:
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Callable
|
||||
from concurrent.futures import Future, ThreadPoolExecutor, TimeoutError
|
||||
from datetime import timedelta
|
||||
from types import TracebackType
|
||||
from typing import Any, Self, cast
|
||||
from typing import Any, Self
|
||||
|
||||
from httpx import HTTPStatusError
|
||||
from pydantic import BaseModel
|
||||
@@ -338,12 +338,11 @@ class BaseSession[
|
||||
validated_request = self._receive_request_type.model_validate(
|
||||
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||
)
|
||||
validated_request = cast(ReceiveRequestT, validated_request)
|
||||
|
||||
responder = RequestResponder[ReceiveRequestT, SendResultT](
|
||||
request_id=message.message.root.id,
|
||||
request_meta=validated_request.root.params.meta if validated_request.root.params else None,
|
||||
request=validated_request,
|
||||
request=validated_request, # type: ignore[arg-type] # mypy can't narrow constrained TypeVar from model_validate
|
||||
session=self,
|
||||
on_complete=lambda r: self._in_flight.pop(r.request_id, None),
|
||||
)
|
||||
@@ -359,15 +358,14 @@ class BaseSession[
|
||||
notification = self._receive_notification_type.model_validate(
|
||||
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||
)
|
||||
notification = cast(ReceiveNotificationT, notification)
|
||||
# Handle cancellation notifications
|
||||
if isinstance(notification.root, CancelledNotification):
|
||||
cancelled_id = notification.root.params.requestId
|
||||
if cancelled_id in self._in_flight:
|
||||
self._in_flight[cancelled_id].cancel()
|
||||
else:
|
||||
self._received_notification(notification)
|
||||
self._handle_incoming(notification)
|
||||
self._received_notification(notification) # type: ignore[arg-type]
|
||||
self._handle_incoming(notification) # type: ignore[arg-type]
|
||||
except Exception as e:
|
||||
# For other validation errors, log and continue
|
||||
logger.warning("Failed to validate notification: %s. Message was: %s", e, message.message.root)
|
||||
|
||||
@@ -144,22 +144,26 @@ class AccountService:
|
||||
|
||||
@staticmethod
|
||||
def load_user(user_id: str) -> None | Account:
|
||||
account = db.session.query(Account).filter_by(id=user_id).first()
|
||||
account = db.session.get(Account, user_id)
|
||||
if not account:
|
||||
return None
|
||||
|
||||
if account.status == AccountStatus.BANNED:
|
||||
raise Unauthorized("Account is banned.")
|
||||
|
||||
current_tenant = db.session.query(TenantAccountJoin).filter_by(account_id=account.id, current=True).first()
|
||||
current_tenant = db.session.scalar(
|
||||
select(TenantAccountJoin)
|
||||
.where(TenantAccountJoin.account_id == account.id, TenantAccountJoin.current == True)
|
||||
.limit(1)
|
||||
)
|
||||
if current_tenant:
|
||||
account.set_tenant_id(current_tenant.tenant_id)
|
||||
else:
|
||||
available_ta = (
|
||||
db.session.query(TenantAccountJoin)
|
||||
.filter_by(account_id=account.id)
|
||||
available_ta = db.session.scalar(
|
||||
select(TenantAccountJoin)
|
||||
.where(TenantAccountJoin.account_id == account.id)
|
||||
.order_by(TenantAccountJoin.id.asc())
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not available_ta:
|
||||
return None
|
||||
@@ -195,7 +199,7 @@ class AccountService:
|
||||
def authenticate(email: str, password: str, invite_token: str | None = None) -> Account:
|
||||
"""authenticate account with email and password"""
|
||||
|
||||
account = db.session.query(Account).filter_by(email=email).first()
|
||||
account = db.session.scalar(select(Account).where(Account.email == email).limit(1))
|
||||
if not account:
|
||||
raise AccountPasswordError("Invalid email or password.")
|
||||
|
||||
@@ -371,8 +375,10 @@ class AccountService:
|
||||
"""Link account integrate"""
|
||||
try:
|
||||
# Query whether there is an existing binding record for the same provider
|
||||
account_integrate: AccountIntegrate | None = (
|
||||
db.session.query(AccountIntegrate).filter_by(account_id=account.id, provider=provider).first()
|
||||
account_integrate: AccountIntegrate | None = db.session.scalar(
|
||||
select(AccountIntegrate)
|
||||
.where(AccountIntegrate.account_id == account.id, AccountIntegrate.provider == provider)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if account_integrate:
|
||||
@@ -416,7 +422,9 @@ class AccountService:
|
||||
def update_account_email(account: Account, email: str) -> Account:
|
||||
"""Update account email"""
|
||||
account.email = email
|
||||
account_integrate = db.session.query(AccountIntegrate).filter_by(account_id=account.id).first()
|
||||
account_integrate = db.session.scalar(
|
||||
select(AccountIntegrate).where(AccountIntegrate.account_id == account.id).limit(1)
|
||||
)
|
||||
if account_integrate:
|
||||
db.session.delete(account_integrate)
|
||||
db.session.add(account)
|
||||
@@ -818,7 +826,7 @@ class AccountService:
|
||||
)
|
||||
)
|
||||
|
||||
account = db.session.query(Account).where(Account.email == email).first()
|
||||
account = db.session.scalar(select(Account).where(Account.email == email).limit(1))
|
||||
if not account:
|
||||
return None
|
||||
|
||||
@@ -1018,7 +1026,7 @@ class AccountService:
|
||||
|
||||
@staticmethod
|
||||
def check_email_unique(email: str) -> bool:
|
||||
return db.session.query(Account).filter_by(email=email).first() is None
|
||||
return db.session.scalar(select(Account).where(Account.email == email).limit(1)) is None
|
||||
|
||||
|
||||
class TenantService:
|
||||
|
||||
@@ -32,6 +32,11 @@ class SubscriptionPlan(TypedDict):
|
||||
expiration_date: int
|
||||
|
||||
|
||||
class KnowledgeRateLimitDict(TypedDict):
|
||||
limit: int
|
||||
subscription_plan: str
|
||||
|
||||
|
||||
class BillingService:
|
||||
base_url = os.environ.get("BILLING_API_URL", "BILLING_API_URL")
|
||||
secret_key = os.environ.get("BILLING_API_SECRET_KEY", "BILLING_API_SECRET_KEY")
|
||||
@@ -58,7 +63,7 @@ class BillingService:
|
||||
return usage_info
|
||||
|
||||
@classmethod
|
||||
def get_knowledge_rate_limit(cls, tenant_id: str):
|
||||
def get_knowledge_rate_limit(cls, tenant_id: str) -> KnowledgeRateLimitDict:
|
||||
params = {"tenant_id": tenant_id}
|
||||
|
||||
knowledge_rate_limit = cls._send_request("GET", "/subscription/knowledge-rate-limit", params=params)
|
||||
|
||||
@@ -5,7 +5,7 @@ from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from graphon.nodes.http_request.exc import InvalidHttpMethodError
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from constants import HIDDEN_VALUE
|
||||
from core.helper import ssrf_proxy
|
||||
@@ -103,8 +103,10 @@ class ExternalDatasetService:
|
||||
|
||||
@staticmethod
|
||||
def get_external_knowledge_api(external_knowledge_api_id: str, tenant_id: str) -> ExternalKnowledgeApis:
|
||||
external_knowledge_api: ExternalKnowledgeApis | None = (
|
||||
db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_api: ExternalKnowledgeApis | None = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(ExternalKnowledgeApis.id == external_knowledge_api_id, ExternalKnowledgeApis.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if external_knowledge_api is None:
|
||||
raise ValueError("api template not found")
|
||||
@@ -112,8 +114,10 @@ class ExternalDatasetService:
|
||||
|
||||
@staticmethod
|
||||
def update_external_knowledge_api(tenant_id, user_id, external_knowledge_api_id, args) -> ExternalKnowledgeApis:
|
||||
external_knowledge_api: ExternalKnowledgeApis | None = (
|
||||
db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_api: ExternalKnowledgeApis | None = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(ExternalKnowledgeApis.id == external_knowledge_api_id, ExternalKnowledgeApis.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if external_knowledge_api is None:
|
||||
raise ValueError("api template not found")
|
||||
@@ -132,8 +136,10 @@ class ExternalDatasetService:
|
||||
|
||||
@staticmethod
|
||||
def delete_external_knowledge_api(tenant_id: str, external_knowledge_api_id: str):
|
||||
external_knowledge_api = (
|
||||
db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_api = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(ExternalKnowledgeApis.id == external_knowledge_api_id, ExternalKnowledgeApis.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if external_knowledge_api is None:
|
||||
raise ValueError("api template not found")
|
||||
@@ -144,9 +150,12 @@ class ExternalDatasetService:
|
||||
@staticmethod
|
||||
def external_knowledge_api_use_check(external_knowledge_api_id: str) -> tuple[bool, int]:
|
||||
count = (
|
||||
db.session.query(ExternalKnowledgeBindings)
|
||||
.filter_by(external_knowledge_api_id=external_knowledge_api_id)
|
||||
.count()
|
||||
db.session.scalar(
|
||||
select(func.count(ExternalKnowledgeBindings.id)).where(
|
||||
ExternalKnowledgeBindings.external_knowledge_api_id == external_knowledge_api_id
|
||||
)
|
||||
)
|
||||
or 0
|
||||
)
|
||||
if count > 0:
|
||||
return True, count
|
||||
@@ -154,8 +163,10 @@ class ExternalDatasetService:
|
||||
|
||||
@staticmethod
|
||||
def get_external_knowledge_binding_with_dataset_id(tenant_id: str, dataset_id: str) -> ExternalKnowledgeBindings:
|
||||
external_knowledge_binding: ExternalKnowledgeBindings | None = (
|
||||
db.session.query(ExternalKnowledgeBindings).filter_by(dataset_id=dataset_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_binding: ExternalKnowledgeBindings | None = db.session.scalar(
|
||||
select(ExternalKnowledgeBindings)
|
||||
.where(ExternalKnowledgeBindings.dataset_id == dataset_id, ExternalKnowledgeBindings.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if not external_knowledge_binding:
|
||||
raise ValueError("external knowledge binding not found")
|
||||
@@ -163,8 +174,10 @@ class ExternalDatasetService:
|
||||
|
||||
@staticmethod
|
||||
def document_create_args_validate(tenant_id: str, external_knowledge_api_id: str, process_parameter: dict):
|
||||
external_knowledge_api = (
|
||||
db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_api = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(ExternalKnowledgeApis.id == external_knowledge_api_id, ExternalKnowledgeApis.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if external_knowledge_api is None or external_knowledge_api.settings is None:
|
||||
raise ValueError("api template not found")
|
||||
@@ -238,12 +251,17 @@ class ExternalDatasetService:
|
||||
@staticmethod
|
||||
def create_external_dataset(tenant_id: str, user_id: str, args: dict) -> Dataset:
|
||||
# check if dataset name already exists
|
||||
if db.session.query(Dataset).filter_by(name=args.get("name"), tenant_id=tenant_id).first():
|
||||
if db.session.scalar(
|
||||
select(Dataset).where(Dataset.name == args.get("name"), Dataset.tenant_id == tenant_id).limit(1)
|
||||
):
|
||||
raise DatasetNameDuplicateError(f"Dataset with name {args.get('name')} already exists.")
|
||||
external_knowledge_api = (
|
||||
db.session.query(ExternalKnowledgeApis)
|
||||
.filter_by(id=args.get("external_knowledge_api_id"), tenant_id=tenant_id)
|
||||
.first()
|
||||
external_knowledge_api = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(
|
||||
ExternalKnowledgeApis.id == args.get("external_knowledge_api_id"),
|
||||
ExternalKnowledgeApis.tenant_id == tenant_id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if external_knowledge_api is None:
|
||||
@@ -286,16 +304,18 @@ class ExternalDatasetService:
|
||||
external_retrieval_parameters: dict,
|
||||
metadata_condition: MetadataCondition | None = None,
|
||||
):
|
||||
external_knowledge_binding = (
|
||||
db.session.query(ExternalKnowledgeBindings).filter_by(dataset_id=dataset_id, tenant_id=tenant_id).first()
|
||||
external_knowledge_binding = db.session.scalar(
|
||||
select(ExternalKnowledgeBindings)
|
||||
.where(ExternalKnowledgeBindings.dataset_id == dataset_id, ExternalKnowledgeBindings.tenant_id == tenant_id)
|
||||
.limit(1)
|
||||
)
|
||||
if not external_knowledge_binding:
|
||||
raise ValueError("external knowledge binding not found")
|
||||
|
||||
external_knowledge_api = (
|
||||
db.session.query(ExternalKnowledgeApis)
|
||||
.filter_by(id=external_knowledge_binding.external_knowledge_api_id)
|
||||
.first()
|
||||
external_knowledge_api = db.session.scalar(
|
||||
select(ExternalKnowledgeApis)
|
||||
.where(ExternalKnowledgeApis.id == external_knowledge_binding.external_knowledge_api_id)
|
||||
.limit(1)
|
||||
)
|
||||
if external_knowledge_api is None or external_knowledge_api.settings is None:
|
||||
raise ValueError("external api template not found")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from graphon.model_runtime.entities import LLMMode
|
||||
|
||||
@@ -18,6 +18,16 @@ from models.enums import CreatorUserRole, DatasetQuerySource
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QueryDict(TypedDict):
|
||||
content: str
|
||||
|
||||
|
||||
class RetrieveResponseDict(TypedDict):
|
||||
query: QueryDict
|
||||
records: list[dict[str, Any]]
|
||||
|
||||
|
||||
default_retrieval_model = {
|
||||
"search_method": RetrievalMethod.SEMANTIC_SEARCH,
|
||||
"reranking_enable": False,
|
||||
@@ -150,7 +160,7 @@ class HitTestingService:
|
||||
return dict(cls.compact_external_retrieve_response(dataset, query, all_documents))
|
||||
|
||||
@classmethod
|
||||
def compact_retrieve_response(cls, query: str, documents: list[Document]) -> dict[Any, Any]:
|
||||
def compact_retrieve_response(cls, query: str, documents: list[Document]) -> RetrieveResponseDict:
|
||||
records = RetrievalService.format_retrieval_documents(documents)
|
||||
|
||||
return {
|
||||
@@ -161,7 +171,7 @@ class HitTestingService:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def compact_external_retrieve_response(cls, dataset: Dataset, query: str, documents: list) -> dict[Any, Any]:
|
||||
def compact_external_retrieve_response(cls, dataset: Dataset, query: str, documents: list) -> RetrieveResponseDict:
|
||||
records = []
|
||||
if dataset.provider == "external":
|
||||
for document in documents:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from sqlalchemy import delete, func, select
|
||||
|
||||
from core.rag.index_processor.constant.built_in_field import BuiltInField, MetadataDataSource
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
@@ -25,10 +27,14 @@ class MetadataService:
|
||||
raise ValueError("Metadata name cannot exceed 255 characters.")
|
||||
current_user, current_tenant_id = current_account_with_tenant()
|
||||
# check if metadata name already exists
|
||||
if (
|
||||
db.session.query(DatasetMetadata)
|
||||
.filter_by(tenant_id=current_tenant_id, dataset_id=dataset_id, name=metadata_args.name)
|
||||
.first()
|
||||
if db.session.scalar(
|
||||
select(DatasetMetadata)
|
||||
.where(
|
||||
DatasetMetadata.tenant_id == current_tenant_id,
|
||||
DatasetMetadata.dataset_id == dataset_id,
|
||||
DatasetMetadata.name == metadata_args.name,
|
||||
)
|
||||
.limit(1)
|
||||
):
|
||||
raise ValueError("Metadata name already exists.")
|
||||
for field in BuiltInField:
|
||||
@@ -54,10 +60,14 @@ class MetadataService:
|
||||
lock_key = f"dataset_metadata_lock_{dataset_id}"
|
||||
# check if metadata name already exists
|
||||
current_user, current_tenant_id = current_account_with_tenant()
|
||||
if (
|
||||
db.session.query(DatasetMetadata)
|
||||
.filter_by(tenant_id=current_tenant_id, dataset_id=dataset_id, name=name)
|
||||
.first()
|
||||
if db.session.scalar(
|
||||
select(DatasetMetadata)
|
||||
.where(
|
||||
DatasetMetadata.tenant_id == current_tenant_id,
|
||||
DatasetMetadata.dataset_id == dataset_id,
|
||||
DatasetMetadata.name == name,
|
||||
)
|
||||
.limit(1)
|
||||
):
|
||||
raise ValueError("Metadata name already exists.")
|
||||
for field in BuiltInField:
|
||||
@@ -65,7 +75,11 @@ class MetadataService:
|
||||
raise ValueError("Metadata name already exists in Built-in fields.")
|
||||
try:
|
||||
MetadataService.knowledge_base_metadata_lock_check(dataset_id, None)
|
||||
metadata = db.session.query(DatasetMetadata).filter_by(id=metadata_id, dataset_id=dataset_id).first()
|
||||
metadata = db.session.scalar(
|
||||
select(DatasetMetadata)
|
||||
.where(DatasetMetadata.id == metadata_id, DatasetMetadata.dataset_id == dataset_id)
|
||||
.limit(1)
|
||||
)
|
||||
if metadata is None:
|
||||
raise ValueError("Metadata not found.")
|
||||
old_name = metadata.name
|
||||
@@ -74,9 +88,9 @@ class MetadataService:
|
||||
metadata.updated_at = naive_utc_now()
|
||||
|
||||
# update related documents
|
||||
dataset_metadata_bindings = (
|
||||
db.session.query(DatasetMetadataBinding).filter_by(metadata_id=metadata_id).all()
|
||||
)
|
||||
dataset_metadata_bindings = db.session.scalars(
|
||||
select(DatasetMetadataBinding).where(DatasetMetadataBinding.metadata_id == metadata_id)
|
||||
).all()
|
||||
if dataset_metadata_bindings:
|
||||
document_ids = [binding.document_id for binding in dataset_metadata_bindings]
|
||||
documents = DocumentService.get_document_by_ids(document_ids)
|
||||
@@ -101,15 +115,19 @@ class MetadataService:
|
||||
lock_key = f"dataset_metadata_lock_{dataset_id}"
|
||||
try:
|
||||
MetadataService.knowledge_base_metadata_lock_check(dataset_id, None)
|
||||
metadata = db.session.query(DatasetMetadata).filter_by(id=metadata_id, dataset_id=dataset_id).first()
|
||||
metadata = db.session.scalar(
|
||||
select(DatasetMetadata)
|
||||
.where(DatasetMetadata.id == metadata_id, DatasetMetadata.dataset_id == dataset_id)
|
||||
.limit(1)
|
||||
)
|
||||
if metadata is None:
|
||||
raise ValueError("Metadata not found.")
|
||||
db.session.delete(metadata)
|
||||
|
||||
# deal related documents
|
||||
dataset_metadata_bindings = (
|
||||
db.session.query(DatasetMetadataBinding).filter_by(metadata_id=metadata_id).all()
|
||||
)
|
||||
dataset_metadata_bindings = db.session.scalars(
|
||||
select(DatasetMetadataBinding).where(DatasetMetadataBinding.metadata_id == metadata_id)
|
||||
).all()
|
||||
if dataset_metadata_bindings:
|
||||
document_ids = [binding.document_id for binding in dataset_metadata_bindings]
|
||||
documents = DocumentService.get_document_by_ids(document_ids)
|
||||
@@ -224,16 +242,23 @@ class MetadataService:
|
||||
|
||||
# deal metadata binding (in the same transaction as the doc_metadata update)
|
||||
if not operation.partial_update:
|
||||
db.session.query(DatasetMetadataBinding).filter_by(document_id=operation.document_id).delete()
|
||||
db.session.execute(
|
||||
delete(DatasetMetadataBinding).where(
|
||||
DatasetMetadataBinding.document_id == operation.document_id
|
||||
)
|
||||
)
|
||||
|
||||
current_user, current_tenant_id = current_account_with_tenant()
|
||||
for metadata_value in operation.metadata_list:
|
||||
# check if binding already exists
|
||||
if operation.partial_update:
|
||||
existing_binding = (
|
||||
db.session.query(DatasetMetadataBinding)
|
||||
.filter_by(document_id=operation.document_id, metadata_id=metadata_value.id)
|
||||
.first()
|
||||
existing_binding = db.session.scalar(
|
||||
select(DatasetMetadataBinding)
|
||||
.where(
|
||||
DatasetMetadataBinding.document_id == operation.document_id,
|
||||
DatasetMetadataBinding.metadata_id == metadata_value.id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if existing_binding:
|
||||
continue
|
||||
@@ -275,9 +300,13 @@ class MetadataService:
|
||||
"id": item.get("id"),
|
||||
"name": item.get("name"),
|
||||
"type": item.get("type"),
|
||||
"count": db.session.query(DatasetMetadataBinding)
|
||||
.filter_by(metadata_id=item.get("id"), dataset_id=dataset.id)
|
||||
.count(),
|
||||
"count": db.session.scalar(
|
||||
select(func.count(DatasetMetadataBinding.id)).where(
|
||||
DatasetMetadataBinding.metadata_id == item.get("id"),
|
||||
DatasetMetadataBinding.dataset_id == dataset.id,
|
||||
)
|
||||
)
|
||||
or 0,
|
||||
}
|
||||
for item in dataset.doc_metadata or []
|
||||
if item.get("id") != "built-in"
|
||||
|
||||
@@ -156,27 +156,27 @@ class RagPipelineService:
|
||||
:param template_id: template id
|
||||
:param template_info: template info
|
||||
"""
|
||||
customized_template: PipelineCustomizedTemplate | None = (
|
||||
db.session.query(PipelineCustomizedTemplate)
|
||||
customized_template: PipelineCustomizedTemplate | None = db.session.scalar(
|
||||
select(PipelineCustomizedTemplate)
|
||||
.where(
|
||||
PipelineCustomizedTemplate.id == template_id,
|
||||
PipelineCustomizedTemplate.tenant_id == current_user.current_tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not customized_template:
|
||||
raise ValueError("Customized pipeline template not found.")
|
||||
# check template name is exist
|
||||
template_name = template_info.name
|
||||
if template_name:
|
||||
template = (
|
||||
db.session.query(PipelineCustomizedTemplate)
|
||||
template = db.session.scalar(
|
||||
select(PipelineCustomizedTemplate)
|
||||
.where(
|
||||
PipelineCustomizedTemplate.name == template_name,
|
||||
PipelineCustomizedTemplate.tenant_id == current_user.current_tenant_id,
|
||||
PipelineCustomizedTemplate.id != template_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if template:
|
||||
raise ValueError("Template name is already exists")
|
||||
@@ -192,13 +192,13 @@ class RagPipelineService:
|
||||
"""
|
||||
Delete customized pipeline template.
|
||||
"""
|
||||
customized_template: PipelineCustomizedTemplate | None = (
|
||||
db.session.query(PipelineCustomizedTemplate)
|
||||
customized_template: PipelineCustomizedTemplate | None = db.session.scalar(
|
||||
select(PipelineCustomizedTemplate)
|
||||
.where(
|
||||
PipelineCustomizedTemplate.id == template_id,
|
||||
PipelineCustomizedTemplate.tenant_id == current_user.current_tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not customized_template:
|
||||
raise ValueError("Customized pipeline template not found.")
|
||||
@@ -210,14 +210,14 @@ class RagPipelineService:
|
||||
Get draft workflow
|
||||
"""
|
||||
# fetch draft workflow by rag pipeline
|
||||
workflow = (
|
||||
db.session.query(Workflow)
|
||||
workflow = db.session.scalar(
|
||||
select(Workflow)
|
||||
.where(
|
||||
Workflow.tenant_id == pipeline.tenant_id,
|
||||
Workflow.app_id == pipeline.id,
|
||||
Workflow.version == "draft",
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
# return draft workflow
|
||||
@@ -232,28 +232,28 @@ class RagPipelineService:
|
||||
return None
|
||||
|
||||
# fetch published workflow by workflow_id
|
||||
workflow = (
|
||||
db.session.query(Workflow)
|
||||
workflow = db.session.scalar(
|
||||
select(Workflow)
|
||||
.where(
|
||||
Workflow.tenant_id == pipeline.tenant_id,
|
||||
Workflow.app_id == pipeline.id,
|
||||
Workflow.id == pipeline.workflow_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
return workflow
|
||||
|
||||
def get_published_workflow_by_id(self, pipeline: Pipeline, workflow_id: str) -> Workflow | None:
|
||||
"""Fetch a published workflow snapshot by ID for restore operations."""
|
||||
workflow = (
|
||||
db.session.query(Workflow)
|
||||
workflow = db.session.scalar(
|
||||
select(Workflow)
|
||||
.where(
|
||||
Workflow.tenant_id == pipeline.tenant_id,
|
||||
Workflow.app_id == pipeline.id,
|
||||
Workflow.id == workflow_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if workflow and workflow.version == Workflow.VERSION_DRAFT:
|
||||
raise IsDraftWorkflowError("source workflow must be published")
|
||||
@@ -974,7 +974,7 @@ class RagPipelineService:
|
||||
if invoke_from.value == InvokeFrom.PUBLISHED_PIPELINE:
|
||||
document_id = get_system_segment(variable_pool, SystemVariableKey.DOCUMENT_ID)
|
||||
if document_id:
|
||||
document = db.session.query(Document).where(Document.id == document_id.value).first()
|
||||
document = db.session.get(Document, document_id.value)
|
||||
if document:
|
||||
document.indexing_status = IndexingStatus.ERROR
|
||||
document.error = error
|
||||
@@ -1178,12 +1178,12 @@ class RagPipelineService:
|
||||
"""
|
||||
Publish customized pipeline template
|
||||
"""
|
||||
pipeline = db.session.query(Pipeline).where(Pipeline.id == pipeline_id).first()
|
||||
pipeline = db.session.get(Pipeline, pipeline_id)
|
||||
if not pipeline:
|
||||
raise ValueError("Pipeline not found")
|
||||
if not pipeline.workflow_id:
|
||||
raise ValueError("Pipeline workflow not found")
|
||||
workflow = db.session.query(Workflow).where(Workflow.id == pipeline.workflow_id).first()
|
||||
workflow = db.session.get(Workflow, pipeline.workflow_id)
|
||||
if not workflow:
|
||||
raise ValueError("Workflow not found")
|
||||
with Session(db.engine) as session:
|
||||
@@ -1194,21 +1194,21 @@ class RagPipelineService:
|
||||
# check template name is exist
|
||||
template_name = args.get("name")
|
||||
if template_name:
|
||||
template = (
|
||||
db.session.query(PipelineCustomizedTemplate)
|
||||
template = db.session.scalar(
|
||||
select(PipelineCustomizedTemplate)
|
||||
.where(
|
||||
PipelineCustomizedTemplate.name == template_name,
|
||||
PipelineCustomizedTemplate.tenant_id == pipeline.tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if template:
|
||||
raise ValueError("Template name is already exists")
|
||||
|
||||
max_position = (
|
||||
db.session.query(func.max(PipelineCustomizedTemplate.position))
|
||||
.where(PipelineCustomizedTemplate.tenant_id == pipeline.tenant_id)
|
||||
.scalar()
|
||||
max_position = db.session.scalar(
|
||||
select(func.max(PipelineCustomizedTemplate.position)).where(
|
||||
PipelineCustomizedTemplate.tenant_id == pipeline.tenant_id
|
||||
)
|
||||
)
|
||||
|
||||
from services.rag_pipeline.rag_pipeline_dsl_service import RagPipelineDslService
|
||||
@@ -1239,13 +1239,14 @@ class RagPipelineService:
|
||||
|
||||
def is_workflow_exist(self, pipeline: Pipeline) -> bool:
|
||||
return (
|
||||
db.session.query(Workflow)
|
||||
.where(
|
||||
db.session.scalar(
|
||||
select(func.count(Workflow.id)).where(
|
||||
Workflow.tenant_id == pipeline.tenant_id,
|
||||
Workflow.app_id == pipeline.id,
|
||||
Workflow.version == Workflow.VERSION_DRAFT,
|
||||
)
|
||||
.count()
|
||||
)
|
||||
or 0
|
||||
) > 0
|
||||
|
||||
def get_node_last_run(
|
||||
@@ -1353,11 +1354,11 @@ class RagPipelineService:
|
||||
|
||||
def get_recommended_plugins(self, type: str) -> dict:
|
||||
# Query active recommended plugins
|
||||
query = db.session.query(PipelineRecommendedPlugin).where(PipelineRecommendedPlugin.active == True)
|
||||
stmt = select(PipelineRecommendedPlugin).where(PipelineRecommendedPlugin.active == True)
|
||||
if type and type != "all":
|
||||
query = query.where(PipelineRecommendedPlugin.type == type)
|
||||
stmt = stmt.where(PipelineRecommendedPlugin.type == type)
|
||||
|
||||
pipeline_recommended_plugins = query.order_by(PipelineRecommendedPlugin.position.asc()).all()
|
||||
pipeline_recommended_plugins = db.session.scalars(stmt.order_by(PipelineRecommendedPlugin.position.asc())).all()
|
||||
|
||||
if not pipeline_recommended_plugins:
|
||||
return {
|
||||
@@ -1396,14 +1397,12 @@ class RagPipelineService:
|
||||
"""
|
||||
Retry error document
|
||||
"""
|
||||
document_pipeline_execution_log = (
|
||||
db.session.query(DocumentPipelineExecutionLog)
|
||||
.where(DocumentPipelineExecutionLog.document_id == document.id)
|
||||
.first()
|
||||
document_pipeline_execution_log = db.session.scalar(
|
||||
select(DocumentPipelineExecutionLog).where(DocumentPipelineExecutionLog.document_id == document.id).limit(1)
|
||||
)
|
||||
if not document_pipeline_execution_log:
|
||||
raise ValueError("Document pipeline execution log not found")
|
||||
pipeline = db.session.query(Pipeline).where(Pipeline.id == document_pipeline_execution_log.pipeline_id).first()
|
||||
pipeline = db.session.get(Pipeline, document_pipeline_execution_log.pipeline_id)
|
||||
if not pipeline:
|
||||
raise ValueError("Pipeline not found")
|
||||
# convert to app config
|
||||
@@ -1432,23 +1431,23 @@ class RagPipelineService:
|
||||
"""
|
||||
Get datasource plugins
|
||||
"""
|
||||
dataset: Dataset | None = (
|
||||
db.session.query(Dataset)
|
||||
dataset: Dataset | None = db.session.scalar(
|
||||
select(Dataset)
|
||||
.where(
|
||||
Dataset.id == dataset_id,
|
||||
Dataset.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not dataset:
|
||||
raise ValueError("Dataset not found")
|
||||
pipeline: Pipeline | None = (
|
||||
db.session.query(Pipeline)
|
||||
pipeline: Pipeline | None = db.session.scalar(
|
||||
select(Pipeline)
|
||||
.where(
|
||||
Pipeline.id == dataset.pipeline_id,
|
||||
Pipeline.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not pipeline:
|
||||
raise ValueError("Pipeline not found")
|
||||
@@ -1530,23 +1529,23 @@ class RagPipelineService:
|
||||
"""
|
||||
Get pipeline
|
||||
"""
|
||||
dataset: Dataset | None = (
|
||||
db.session.query(Dataset)
|
||||
dataset: Dataset | None = db.session.scalar(
|
||||
select(Dataset)
|
||||
.where(
|
||||
Dataset.id == dataset_id,
|
||||
Dataset.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not dataset:
|
||||
raise ValueError("Dataset not found")
|
||||
pipeline: Pipeline | None = (
|
||||
db.session.query(Pipeline)
|
||||
pipeline: Pipeline | None = db.session.scalar(
|
||||
select(Pipeline)
|
||||
.where(
|
||||
Pipeline.id == dataset.pipeline_id,
|
||||
Pipeline.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if not pipeline:
|
||||
raise ValueError("Pipeline not found")
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from datetime import datetime
|
||||
|
||||
from graphon.model_runtime.utils.encoders import jsonable_encoder
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy import delete, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.tools.__base.tool_provider import ToolProviderController
|
||||
@@ -42,20 +42,22 @@ class WorkflowToolManageService:
|
||||
labels: list[str] | None = None,
|
||||
):
|
||||
# check if the name is unique
|
||||
existing_workflow_tool_provider = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
existing_workflow_tool_provider = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(
|
||||
WorkflowToolProvider.tenant_id == tenant_id,
|
||||
# name or app_id
|
||||
or_(WorkflowToolProvider.name == name, WorkflowToolProvider.app_id == workflow_app_id),
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if existing_workflow_tool_provider is not None:
|
||||
raise ValueError(f"Tool with name {name} or app_id {workflow_app_id} already exists")
|
||||
|
||||
app: App | None = db.session.query(App).where(App.id == workflow_app_id, App.tenant_id == tenant_id).first()
|
||||
app: App | None = db.session.scalar(
|
||||
select(App).where(App.id == workflow_app_id, App.tenant_id == tenant_id).limit(1)
|
||||
)
|
||||
|
||||
if app is None:
|
||||
raise ValueError(f"App {workflow_app_id} not found")
|
||||
@@ -122,30 +124,30 @@ class WorkflowToolManageService:
|
||||
:return: the updated tool
|
||||
"""
|
||||
# check if the name is unique
|
||||
existing_workflow_tool_provider = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
existing_workflow_tool_provider = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(
|
||||
WorkflowToolProvider.tenant_id == tenant_id,
|
||||
WorkflowToolProvider.name == name,
|
||||
WorkflowToolProvider.id != workflow_tool_id,
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if existing_workflow_tool_provider is not None:
|
||||
raise ValueError(f"Tool with name {name} already exists")
|
||||
|
||||
workflow_tool_provider: WorkflowToolProvider | None = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
workflow_tool_provider: WorkflowToolProvider | None = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if workflow_tool_provider is None:
|
||||
raise ValueError(f"Tool {workflow_tool_id} not found")
|
||||
|
||||
app: App | None = (
|
||||
db.session.query(App).where(App.id == workflow_tool_provider.app_id, App.tenant_id == tenant_id).first()
|
||||
app: App | None = db.session.scalar(
|
||||
select(App).where(App.id == workflow_tool_provider.app_id, App.tenant_id == tenant_id).limit(1)
|
||||
)
|
||||
|
||||
if app is None:
|
||||
@@ -234,9 +236,11 @@ class WorkflowToolManageService:
|
||||
:param tenant_id: the tenant id
|
||||
:param workflow_tool_id: the workflow tool id
|
||||
"""
|
||||
db.session.query(WorkflowToolProvider).where(
|
||||
db.session.execute(
|
||||
delete(WorkflowToolProvider).where(
|
||||
WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id
|
||||
).delete()
|
||||
)
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -251,10 +255,10 @@ class WorkflowToolManageService:
|
||||
:param workflow_tool_id: the workflow tool id
|
||||
:return: the tool
|
||||
"""
|
||||
db_tool: WorkflowToolProvider | None = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
db_tool: WorkflowToolProvider | None = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
return cls._get_workflow_tool(tenant_id, db_tool)
|
||||
|
||||
@@ -267,10 +271,10 @@ class WorkflowToolManageService:
|
||||
:param workflow_app_id: the workflow app id
|
||||
:return: the tool
|
||||
"""
|
||||
db_tool: WorkflowToolProvider | None = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
db_tool: WorkflowToolProvider | None = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.app_id == workflow_app_id)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
return cls._get_workflow_tool(tenant_id, db_tool)
|
||||
|
||||
@@ -284,8 +288,8 @@ class WorkflowToolManageService:
|
||||
if db_tool is None:
|
||||
raise ValueError("Tool not found")
|
||||
|
||||
workflow_app: App | None = (
|
||||
db.session.query(App).where(App.id == db_tool.app_id, App.tenant_id == db_tool.tenant_id).first()
|
||||
workflow_app: App | None = db.session.scalar(
|
||||
select(App).where(App.id == db_tool.app_id, App.tenant_id == db_tool.tenant_id).limit(1)
|
||||
)
|
||||
|
||||
if workflow_app is None:
|
||||
@@ -331,10 +335,10 @@ class WorkflowToolManageService:
|
||||
:param workflow_tool_id: the workflow tool id
|
||||
:return: the list of tools
|
||||
"""
|
||||
db_tool: WorkflowToolProvider | None = (
|
||||
db.session.query(WorkflowToolProvider)
|
||||
db_tool: WorkflowToolProvider | None = db.session.scalar(
|
||||
select(WorkflowToolProvider)
|
||||
.where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if db_tool is None:
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
import mimetypes
|
||||
import secrets
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from typing import Any
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import orjson
|
||||
from flask import request
|
||||
@@ -50,6 +50,14 @@ logger = logging.getLogger(__name__)
|
||||
_file_access_controller = DatabaseFileAccessController()
|
||||
|
||||
|
||||
class RawWebhookDataDict(TypedDict):
|
||||
method: str
|
||||
headers: dict[str, str]
|
||||
query_params: dict[str, str]
|
||||
body: dict[str, Any]
|
||||
files: dict[str, Any]
|
||||
|
||||
|
||||
class WebhookService:
|
||||
"""Service for handling webhook operations."""
|
||||
|
||||
@@ -145,7 +153,7 @@ class WebhookService:
|
||||
@classmethod
|
||||
def extract_and_validate_webhook_data(
|
||||
cls, webhook_trigger: WorkflowWebhookTrigger, node_config: NodeConfigDict
|
||||
) -> dict[str, Any]:
|
||||
) -> RawWebhookDataDict:
|
||||
"""Extract and validate webhook data in a single unified process.
|
||||
|
||||
Args:
|
||||
@@ -173,7 +181,7 @@ class WebhookService:
|
||||
return processed_data
|
||||
|
||||
@classmethod
|
||||
def extract_webhook_data(cls, webhook_trigger: WorkflowWebhookTrigger) -> dict[str, Any]:
|
||||
def extract_webhook_data(cls, webhook_trigger: WorkflowWebhookTrigger) -> RawWebhookDataDict:
|
||||
"""Extract raw data from incoming webhook request without type conversion.
|
||||
|
||||
Args:
|
||||
@@ -189,7 +197,7 @@ class WebhookService:
|
||||
"""
|
||||
cls._validate_content_length()
|
||||
|
||||
data = {
|
||||
data: RawWebhookDataDict = {
|
||||
"method": request.method,
|
||||
"headers": dict(request.headers),
|
||||
"query_params": dict(request.args),
|
||||
@@ -223,7 +231,7 @@ class WebhookService:
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def _process_and_validate_data(cls, raw_data: dict[str, Any], node_data: WebhookData) -> dict[str, Any]:
|
||||
def _process_and_validate_data(cls, raw_data: RawWebhookDataDict, node_data: WebhookData) -> RawWebhookDataDict:
|
||||
"""Process and validate webhook data according to node configuration.
|
||||
|
||||
Args:
|
||||
@@ -664,7 +672,7 @@ class WebhookService:
|
||||
raise ValueError(f"Required header missing: {header_name}")
|
||||
|
||||
@classmethod
|
||||
def _validate_http_metadata(cls, webhook_data: dict[str, Any], node_data: WebhookData) -> dict[str, Any]:
|
||||
def _validate_http_metadata(cls, webhook_data: RawWebhookDataDict, node_data: WebhookData) -> dict[str, Any]:
|
||||
"""Validate HTTP method and content-type.
|
||||
|
||||
Args:
|
||||
@@ -729,7 +737,7 @@ class WebhookService:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def build_workflow_inputs(cls, webhook_data: dict[str, Any]) -> dict[str, Any]:
|
||||
def build_workflow_inputs(cls, webhook_data: RawWebhookDataDict) -> dict[str, Any]:
|
||||
"""Construct workflow inputs payload from webhook data.
|
||||
|
||||
Args:
|
||||
@@ -747,7 +755,7 @@ class WebhookService:
|
||||
|
||||
@classmethod
|
||||
def trigger_workflow_execution(
|
||||
cls, webhook_trigger: WorkflowWebhookTrigger, webhook_data: dict[str, Any], workflow: Workflow
|
||||
cls, webhook_trigger: WorkflowWebhookTrigger, webhook_data: RawWebhookDataDict, workflow: Workflow
|
||||
) -> None:
|
||||
"""Trigger workflow execution via AsyncWorkflowService.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -401,10 +401,7 @@ class TestMetadataServiceCreateMetadata:
|
||||
metadata_args = MetadataTestDataFactory.create_metadata_args_mock(name="category", metadata_type="string")
|
||||
|
||||
# Mock query to return None (no existing metadata with same name)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
# Mock BuiltInField enum iteration
|
||||
with patch("services.metadata_service.BuiltInField") as mock_builtin:
|
||||
@@ -417,10 +414,6 @@ class TestMetadataServiceCreateMetadata:
|
||||
assert result is not None
|
||||
assert isinstance(result, DatasetMetadata)
|
||||
|
||||
# Verify query was made to check for duplicates
|
||||
mock_db_session.query.assert_called()
|
||||
mock_query.filter_by.assert_called()
|
||||
|
||||
# Verify metadata was added and committed
|
||||
mock_db_session.add.assert_called_once()
|
||||
mock_db_session.commit.assert_called_once()
|
||||
@@ -468,10 +461,7 @@ class TestMetadataServiceCreateMetadata:
|
||||
|
||||
# Mock existing metadata with same name
|
||||
existing_metadata = MetadataTestDataFactory.create_metadata_mock(name="category")
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_metadata
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = existing_metadata
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="Metadata name already exists"):
|
||||
@@ -500,10 +490,7 @@ class TestMetadataServiceCreateMetadata:
|
||||
)
|
||||
|
||||
# Mock query to return None (no duplicate in database)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
# Mock BuiltInField to include the conflicting name
|
||||
with patch("services.metadata_service.BuiltInField") as mock_builtin:
|
||||
@@ -597,27 +584,11 @@ class TestMetadataServiceUpdateMetadataName:
|
||||
|
||||
existing_metadata = MetadataTestDataFactory.create_metadata_mock(metadata_id=metadata_id, name="category")
|
||||
|
||||
# Mock query for duplicate check (no duplicate)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Mock metadata retrieval
|
||||
def query_side_effect(model):
|
||||
if model == DatasetMetadata:
|
||||
mock_meta_query = Mock()
|
||||
mock_meta_query.filter_by.return_value = mock_meta_query
|
||||
mock_meta_query.first.return_value = existing_metadata
|
||||
return mock_meta_query
|
||||
return mock_query
|
||||
|
||||
mock_db_session.query.side_effect = query_side_effect
|
||||
# Mock scalar calls: first for duplicate check (None), second for metadata retrieval
|
||||
mock_db_session.scalar.side_effect = [None, existing_metadata]
|
||||
|
||||
# Mock no metadata bindings (no documents to update)
|
||||
mock_binding_query = Mock()
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.all.return_value = []
|
||||
mock_db_session.scalars.return_value.all.return_value = []
|
||||
|
||||
# Mock BuiltInField enum
|
||||
with patch("services.metadata_service.BuiltInField") as mock_builtin:
|
||||
@@ -655,22 +626,8 @@ class TestMetadataServiceUpdateMetadataName:
|
||||
metadata_id = "non-existent-metadata"
|
||||
new_name = "updated_category"
|
||||
|
||||
# Mock query for duplicate check (no duplicate)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Mock metadata retrieval to return None
|
||||
def query_side_effect(model):
|
||||
if model == DatasetMetadata:
|
||||
mock_meta_query = Mock()
|
||||
mock_meta_query.filter_by.return_value = mock_meta_query
|
||||
mock_meta_query.first.return_value = None # Not found
|
||||
return mock_meta_query
|
||||
return mock_query
|
||||
|
||||
mock_db_session.query.side_effect = query_side_effect
|
||||
# Mock scalar calls: first for duplicate check (None), second for metadata retrieval (None = not found)
|
||||
mock_db_session.scalar.side_effect = [None, None]
|
||||
|
||||
# Mock BuiltInField enum
|
||||
with patch("services.metadata_service.BuiltInField") as mock_builtin:
|
||||
@@ -746,15 +703,10 @@ class TestMetadataServiceDeleteMetadata:
|
||||
existing_metadata = MetadataTestDataFactory.create_metadata_mock(metadata_id=metadata_id, name="category")
|
||||
|
||||
# Mock metadata retrieval
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_metadata
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = existing_metadata
|
||||
|
||||
# Mock no metadata bindings (no documents to update)
|
||||
mock_binding_query = Mock()
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.all.return_value = []
|
||||
mock_db_session.scalars.return_value.all.return_value = []
|
||||
|
||||
# Act
|
||||
result = MetadataService.delete_metadata(dataset_id, metadata_id)
|
||||
@@ -788,10 +740,7 @@ class TestMetadataServiceDeleteMetadata:
|
||||
metadata_id = "non-existent-metadata"
|
||||
|
||||
# Mock metadata retrieval to return None
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="Metadata not found"):
|
||||
@@ -1013,10 +962,7 @@ class TestMetadataServiceGetDatasetMetadatas:
|
||||
)
|
||||
|
||||
# Mock usage count queries
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.count.return_value = 5 # 5 documents use this metadata
|
||||
mock_db_session.query.return_value = mock_query
|
||||
mock_db_session.scalar.return_value = 5 # 5 documents use this metadata
|
||||
|
||||
# Act
|
||||
result = MetadataService.get_dataset_metadatas(dataset)
|
||||
|
||||
@@ -292,7 +292,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
"""
|
||||
|
||||
api = Mock(spec=ExternalKnowledgeApis)
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = api
|
||||
mock_db_session.scalar.return_value = api
|
||||
|
||||
result = ExternalDatasetService.get_external_knowledge_api("api-id", "tenant-id")
|
||||
assert result is api
|
||||
@@ -302,7 +302,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
When the record is absent, a ``ValueError`` is raised.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
ExternalDatasetService.get_external_knowledge_api("missing-id", "tenant-id")
|
||||
@@ -320,7 +320,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
existing_api = Mock(spec=ExternalKnowledgeApis)
|
||||
existing_api.settings_dict = {"api_key": "stored-key"}
|
||||
existing_api.settings = '{"api_key":"stored-key"}'
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = existing_api
|
||||
mock_db_session.scalar.return_value = existing_api
|
||||
|
||||
args = {
|
||||
"name": "New Name",
|
||||
@@ -340,7 +340,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
Updating a non‑existent API template should raise ``ValueError``.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
ExternalDatasetService.update_external_knowledge_api(
|
||||
@@ -356,7 +356,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
"""
|
||||
|
||||
api = Mock(spec=ExternalKnowledgeApis)
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = api
|
||||
mock_db_session.scalar.return_value = api
|
||||
|
||||
ExternalDatasetService.delete_external_knowledge_api("tenant-1", "api-1")
|
||||
|
||||
@@ -368,7 +368,7 @@ class TestExternalDatasetServiceCrudExternalKnowledgeApi:
|
||||
Deletion of a missing template should raise ``ValueError``.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
ExternalDatasetService.delete_external_knowledge_api("tenant-1", "missing")
|
||||
@@ -394,7 +394,7 @@ class TestExternalDatasetServiceUsageAndBindings:
|
||||
When there are bindings, ``external_knowledge_api_use_check`` returns True and count.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.count.return_value = 3
|
||||
mock_db_session.scalar.return_value = 3
|
||||
|
||||
in_use, count = ExternalDatasetService.external_knowledge_api_use_check("api-1")
|
||||
|
||||
@@ -406,7 +406,7 @@ class TestExternalDatasetServiceUsageAndBindings:
|
||||
Zero bindings should return ``(False, 0)``.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.count.return_value = 0
|
||||
mock_db_session.scalar.return_value = 0
|
||||
|
||||
in_use, count = ExternalDatasetService.external_knowledge_api_use_check("api-1")
|
||||
|
||||
@@ -419,7 +419,7 @@ class TestExternalDatasetServiceUsageAndBindings:
|
||||
"""
|
||||
|
||||
binding = Mock(spec=ExternalKnowledgeBindings)
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = binding
|
||||
mock_db_session.scalar.return_value = binding
|
||||
|
||||
result = ExternalDatasetService.get_external_knowledge_binding_with_dataset_id("tenant-1", "ds-1")
|
||||
assert result is binding
|
||||
@@ -429,7 +429,7 @@ class TestExternalDatasetServiceUsageAndBindings:
|
||||
Missing binding should result in a ``ValueError``.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="external knowledge binding not found"):
|
||||
ExternalDatasetService.get_external_knowledge_binding_with_dataset_id("tenant-1", "ds-1")
|
||||
@@ -460,7 +460,7 @@ class TestExternalDatasetServiceDocumentCreateArgsValidate:
|
||||
'[{"document_process_setting":[{"name":"foo","required":true},{"name":"bar","required":false}]}]'
|
||||
)
|
||||
# Raw string; the service itself calls json.loads on it
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = external_api
|
||||
mock_db_session.scalar.return_value = external_api
|
||||
|
||||
process_parameter = {"foo": "value", "bar": "optional"}
|
||||
|
||||
@@ -474,7 +474,7 @@ class TestExternalDatasetServiceDocumentCreateArgsValidate:
|
||||
When the referenced API template is missing, a ``ValueError`` is raised.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
ExternalDatasetService.document_create_args_validate("tenant-1", "missing", {})
|
||||
@@ -488,7 +488,7 @@ class TestExternalDatasetServiceDocumentCreateArgsValidate:
|
||||
external_api.settings = (
|
||||
'[{"document_process_setting":[{"name":"foo","required":true},{"name":"bar","required":false}]}]'
|
||||
)
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = external_api
|
||||
mock_db_session.scalar.return_value = external_api
|
||||
|
||||
process_parameter = {"bar": "present"} # missing "foo"
|
||||
|
||||
@@ -702,7 +702,7 @@ class TestExternalDatasetServiceCreateExternalDataset:
|
||||
}
|
||||
|
||||
# No existing dataset with same name.
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
mock_db_session.scalar.side_effect = [
|
||||
None, # duplicate‑name check
|
||||
Mock(spec=ExternalKnowledgeApis), # external knowledge api
|
||||
]
|
||||
@@ -724,7 +724,7 @@ class TestExternalDatasetServiceCreateExternalDataset:
|
||||
"""
|
||||
|
||||
existing_dataset = Mock(spec=Dataset)
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = existing_dataset
|
||||
mock_db_session.scalar.return_value = existing_dataset
|
||||
|
||||
args = {
|
||||
"name": "Existing",
|
||||
@@ -744,7 +744,7 @@ class TestExternalDatasetServiceCreateExternalDataset:
|
||||
"""
|
||||
|
||||
# First call: duplicate name check – not found.
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
mock_db_session.scalar.side_effect = [
|
||||
None,
|
||||
None, # external knowledge api lookup
|
||||
]
|
||||
@@ -763,8 +763,10 @@ class TestExternalDatasetServiceCreateExternalDataset:
|
||||
``external_knowledge_id`` and ``external_knowledge_api_id`` are mandatory.
|
||||
"""
|
||||
|
||||
# duplicate name check
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
# duplicate name check — two calls to create_external_dataset, each does 2 scalar calls
|
||||
mock_db_session.scalar.side_effect = [
|
||||
None,
|
||||
Mock(spec=ExternalKnowledgeApis),
|
||||
None,
|
||||
Mock(spec=ExternalKnowledgeApis),
|
||||
]
|
||||
@@ -826,7 +828,7 @@ class TestExternalDatasetServiceFetchExternalKnowledgeRetrieval:
|
||||
api.settings = '{"endpoint":"https://example.com","api_key":"secret"}'
|
||||
|
||||
# First query: binding; second query: api.
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
mock_db_session.scalar.side_effect = [
|
||||
binding,
|
||||
api,
|
||||
]
|
||||
@@ -861,7 +863,7 @@ class TestExternalDatasetServiceFetchExternalKnowledgeRetrieval:
|
||||
Missing binding should raise ``ValueError``.
|
||||
"""
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="external knowledge binding not found"):
|
||||
ExternalDatasetService.fetch_external_knowledge_retrieval(
|
||||
@@ -878,7 +880,7 @@ class TestExternalDatasetServiceFetchExternalKnowledgeRetrieval:
|
||||
"""
|
||||
|
||||
binding = ExternalDatasetTestDataFactory.create_external_binding()
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
mock_db_session.scalar.side_effect = [
|
||||
binding,
|
||||
None,
|
||||
]
|
||||
@@ -901,7 +903,7 @@ class TestExternalDatasetServiceFetchExternalKnowledgeRetrieval:
|
||||
api = Mock(spec=ExternalKnowledgeApis)
|
||||
api.settings = '{"endpoint":"https://example.com","api_key":"secret"}'
|
||||
|
||||
mock_db_session.query.return_value.filter_by.return_value.first.side_effect = [
|
||||
mock_db_session.scalar.side_effect = [
|
||||
binding,
|
||||
api,
|
||||
]
|
||||
|
||||
@@ -117,9 +117,7 @@ def test_get_all_published_workflow_applies_limit_and_has_more(rag_pipeline_serv
|
||||
|
||||
|
||||
def test_get_pipeline_raises_when_dataset_not_found(mocker, rag_pipeline_service) -> None:
|
||||
first_query = mocker.Mock()
|
||||
first_query.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=first_query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Dataset not found"):
|
||||
rag_pipeline_service.get_pipeline("tenant-1", "dataset-1")
|
||||
@@ -131,12 +129,8 @@ def test_get_pipeline_raises_when_dataset_not_found(mocker, rag_pipeline_service
|
||||
def test_update_customized_pipeline_template_success(mocker) -> None:
|
||||
template = SimpleNamespace(name="old", description="old", icon={}, updated_by=None)
|
||||
|
||||
# First query finds the template, second query (duplicate check) returns None
|
||||
query_mock_1 = mocker.Mock()
|
||||
query_mock_1.where.return_value.first.return_value = template
|
||||
query_mock_2 = mocker.Mock()
|
||||
query_mock_2.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", side_effect=[query_mock_1, query_mock_2])
|
||||
# First scalar finds the template, second scalar (duplicate check) returns None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[template, None])
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.commit")
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
|
||||
@@ -152,9 +146,7 @@ def test_update_customized_pipeline_template_success(mocker) -> None:
|
||||
|
||||
|
||||
def test_update_customized_pipeline_template_not_found(mocker) -> None:
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=None)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
|
||||
info = PipelineTemplateInfoEntity(name="x", description="d", icon_info=IconInfo(icon="i"))
|
||||
@@ -166,9 +158,7 @@ def test_update_customized_pipeline_template_duplicate_name(mocker) -> None:
|
||||
template = SimpleNamespace(name="old", description="old", icon={}, updated_by=None)
|
||||
duplicate = SimpleNamespace(name="dup")
|
||||
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.first.side_effect = [template, duplicate]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[template, duplicate])
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
|
||||
info = PipelineTemplateInfoEntity(name="dup", description="d", icon_info=IconInfo(icon="i"))
|
||||
@@ -181,9 +171,7 @@ def test_update_customized_pipeline_template_duplicate_name(mocker) -> None:
|
||||
|
||||
def test_delete_customized_pipeline_template_success(mocker) -> None:
|
||||
template = SimpleNamespace(id="tpl-1")
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.first.return_value = template
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=template)
|
||||
delete_mock = mocker.patch("services.rag_pipeline.rag_pipeline.db.session.delete")
|
||||
commit_mock = mocker.patch("services.rag_pipeline.rag_pipeline.db.session.commit")
|
||||
|
||||
@@ -196,9 +184,7 @@ def test_delete_customized_pipeline_template_success(mocker) -> None:
|
||||
|
||||
|
||||
def test_delete_customized_pipeline_template_not_found(mocker) -> None:
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=None)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
|
||||
with pytest.raises(ValueError, match="Customized pipeline template not found"):
|
||||
@@ -397,18 +383,14 @@ def test_get_rag_pipeline_workflow_run_delegates(mocker, rag_pipeline_service) -
|
||||
|
||||
|
||||
def test_is_workflow_exist_returns_true_when_draft_exists(mocker, rag_pipeline_service) -> None:
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.count.return_value = 1
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=1)
|
||||
|
||||
pipeline = SimpleNamespace(tenant_id="t1", id="p1")
|
||||
assert rag_pipeline_service.is_workflow_exist(pipeline) is True
|
||||
|
||||
|
||||
def test_is_workflow_exist_returns_false_when_no_draft(mocker, rag_pipeline_service) -> None:
|
||||
query_mock = mocker.Mock()
|
||||
query_mock.where.return_value.count.return_value = 0
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query_mock)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=0)
|
||||
|
||||
pipeline = SimpleNamespace(tenant_id="t1", id="p1")
|
||||
assert rag_pipeline_service.is_workflow_exist(pipeline) is False
|
||||
@@ -738,8 +720,7 @@ def test_get_second_step_parameters_success(mocker, rag_pipeline_service) -> Non
|
||||
|
||||
|
||||
def test_publish_customized_pipeline_template_success(mocker, rag_pipeline_service) -> None:
|
||||
from models.dataset import Dataset, Pipeline, PipelineCustomizedTemplate
|
||||
from models.workflow import Workflow
|
||||
from models.dataset import Pipeline
|
||||
|
||||
# 1. Setup mocks
|
||||
pipeline = mocker.Mock(spec=Pipeline)
|
||||
@@ -754,36 +735,15 @@ def test_publish_customized_pipeline_template_success(mocker, rag_pipeline_servi
|
||||
# Mock db itself to avoid app context errors
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
|
||||
# Improved mocking for session.query
|
||||
def mock_query_side_effect(model):
|
||||
m = mocker.Mock()
|
||||
if model == Pipeline:
|
||||
m.where.return_value.first.return_value = pipeline
|
||||
elif model == Workflow:
|
||||
m.where.return_value.first.return_value = workflow
|
||||
elif model == PipelineCustomizedTemplate:
|
||||
m.where.return_value.first.return_value = None
|
||||
elif model == Dataset:
|
||||
m.where.return_value.first.return_value = mocker.Mock()
|
||||
else:
|
||||
# For func.max cases
|
||||
m.where.return_value.scalar.return_value = 5
|
||||
m.where.return_value.first.return_value = mocker.Mock()
|
||||
return m
|
||||
|
||||
mock_db.session.query.side_effect = mock_query_side_effect
|
||||
# Mock get() for Pipeline and Workflow PK lookups
|
||||
mock_db.session.get.side_effect = [pipeline, workflow]
|
||||
# Mock scalar() for template name check (None) and max position (5)
|
||||
mock_db.session.scalar.side_effect = [None, 5]
|
||||
|
||||
# Mock retrieve_dataset
|
||||
dataset = mocker.Mock()
|
||||
pipeline.retrieve_dataset.return_value = dataset
|
||||
|
||||
# Mock max position
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.func.max", return_value=1)
|
||||
mocker.patch(
|
||||
"services.rag_pipeline.rag_pipeline.db.session.query.return_value.where.return_value.scalar",
|
||||
return_value=5,
|
||||
)
|
||||
|
||||
# Mock RagPipelineDslService
|
||||
mock_dsl_service = mocker.Mock()
|
||||
mock_dsl_service.export_rag_pipeline_dsl.return_value = {"dsl": "content"}
|
||||
@@ -839,9 +799,7 @@ def test_get_datasource_plugins_success(mocker, rag_pipeline_service) -> None:
|
||||
workflow.rag_pipeline_variables = []
|
||||
|
||||
# Mock queries
|
||||
mock_query = mocker.Mock()
|
||||
mock_query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=mock_query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=workflow)
|
||||
|
||||
@@ -881,11 +839,9 @@ def test_retry_error_document_success(mocker, rag_pipeline_service) -> None:
|
||||
|
||||
workflow = mocker.Mock()
|
||||
|
||||
# Mock queries
|
||||
mock_query = mocker.Mock()
|
||||
# Log lookup, then Pipeline lookup
|
||||
mock_query.where.return_value.first.side_effect = [log, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=mock_query)
|
||||
# Mock queries: Log lookup via scalar, Pipeline lookup via get
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=log)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=pipeline)
|
||||
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=workflow)
|
||||
|
||||
@@ -913,7 +869,7 @@ def test_set_datasource_variables_success(mocker, rag_pipeline_service) -> None:
|
||||
# Mock db aggressively
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.engine = mocker.Mock()
|
||||
mock_db.session.query.return_value.where.return_value.first.return_value = mocker.Mock()
|
||||
mock_db.session.scalar.return_value = mocker.Mock()
|
||||
|
||||
pipeline = mocker.Mock(spec=Pipeline)
|
||||
pipeline.id = "p-1"
|
||||
@@ -976,7 +932,7 @@ def test_get_draft_workflow_success(mocker, rag_pipeline_service) -> None:
|
||||
workflow = mocker.Mock(spec=Workflow)
|
||||
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.session.query.return_value.where.return_value.first.return_value = workflow
|
||||
mock_db.session.scalar.return_value = workflow
|
||||
|
||||
# 2. Run test
|
||||
result = rag_pipeline_service.get_draft_workflow(pipeline)
|
||||
@@ -998,7 +954,7 @@ def test_get_published_workflow_success(mocker, rag_pipeline_service) -> None:
|
||||
workflow = mocker.Mock(spec=Workflow)
|
||||
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.session.query.return_value.where.return_value.first.return_value = workflow
|
||||
mock_db.session.scalar.return_value = workflow
|
||||
|
||||
# 2. Run test
|
||||
result = rag_pipeline_service.get_published_workflow(pipeline)
|
||||
@@ -1319,11 +1275,8 @@ def test_get_rag_pipeline_workflow_run_node_executions_returns_sorted_executions
|
||||
|
||||
|
||||
def test_get_recommended_plugins_returns_empty_when_no_active_plugins(mocker, rag_pipeline_service) -> None:
|
||||
query = mocker.Mock()
|
||||
query.where.return_value = query
|
||||
query.order_by.return_value.all.return_value = []
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.session.query.return_value = query
|
||||
mock_db.session.scalars.return_value.all.return_value = []
|
||||
|
||||
result = rag_pipeline_service.get_recommended_plugins("all")
|
||||
|
||||
@@ -1336,11 +1289,8 @@ def test_get_recommended_plugins_returns_empty_when_no_active_plugins(mocker, ra
|
||||
def test_get_recommended_plugins_returns_installed_and_uninstalled(mocker, rag_pipeline_service) -> None:
|
||||
plugin_a = SimpleNamespace(plugin_id="plugin-a")
|
||||
plugin_b = SimpleNamespace(plugin_id="plugin-b")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value = query
|
||||
query.order_by.return_value.all.return_value = [plugin_a, plugin_b]
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.session.query.return_value = query
|
||||
mock_db.session.scalars.return_value.all.return_value = [plugin_a, plugin_b]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
mocker.patch(
|
||||
"services.rag_pipeline.rag_pipeline.BuiltinToolManageService.list_builtin_tools",
|
||||
@@ -1568,9 +1518,7 @@ def test_get_second_step_parameters_filters_first_step_variables(mocker, rag_pip
|
||||
|
||||
|
||||
def test_retry_error_document_raises_when_execution_log_not_found(mocker, rag_pipeline_service) -> None:
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Document pipeline execution log not found"):
|
||||
rag_pipeline_service.retry_error_document(
|
||||
@@ -1581,9 +1529,7 @@ def test_retry_error_document_raises_when_execution_log_not_found(mocker, rag_pi
|
||||
def test_get_datasource_plugins_raises_when_workflow_not_found(mocker, rag_pipeline_service) -> None:
|
||||
dataset = SimpleNamespace(pipeline_id="p1")
|
||||
pipeline = SimpleNamespace(id="p1", tenant_id="t1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Pipeline or workflow not found"):
|
||||
@@ -1656,8 +1602,7 @@ def test_handle_node_run_result_marks_document_error_for_published_invoke(mocker
|
||||
|
||||
document = SimpleNamespace(indexing_status="waiting", error=None)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = document
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=document)
|
||||
add_mock = mocker.patch("services.rag_pipeline.rag_pipeline.db.session.add")
|
||||
commit_mock = mocker.patch("services.rag_pipeline.rag_pipeline.db.session.commit")
|
||||
|
||||
@@ -1712,9 +1657,7 @@ def test_run_datasource_node_preview_raises_for_unsupported_provider(mocker, rag
|
||||
|
||||
|
||||
def test_publish_customized_pipeline_template_raises_for_missing_pipeline(mocker, rag_pipeline_service) -> None:
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Pipeline not found"):
|
||||
rag_pipeline_service.publish_customized_pipeline_template("p1", {})
|
||||
@@ -1722,9 +1665,7 @@ def test_publish_customized_pipeline_template_raises_for_missing_pipeline(mocker
|
||||
|
||||
def test_publish_customized_pipeline_template_raises_for_missing_workflow_id(mocker, rag_pipeline_service) -> None:
|
||||
pipeline = SimpleNamespace(id="p1", tenant_id="t1", workflow_id=None)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = pipeline
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=pipeline)
|
||||
|
||||
with pytest.raises(ValueError, match="Pipeline workflow not found"):
|
||||
rag_pipeline_service.publish_customized_pipeline_template("p1", {"name": "template-name"})
|
||||
@@ -1732,8 +1673,7 @@ def test_publish_customized_pipeline_template_raises_for_missing_workflow_id(moc
|
||||
|
||||
def test_get_pipeline_raises_when_dataset_missing(mocker, rag_pipeline_service) -> None:
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = None
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Dataset not found"):
|
||||
rag_pipeline_service.get_pipeline("t1", "d1")
|
||||
@@ -1742,8 +1682,7 @@ def test_get_pipeline_raises_when_dataset_missing(mocker, rag_pipeline_service)
|
||||
def test_get_pipeline_raises_when_pipeline_missing(mocker, rag_pipeline_service) -> None:
|
||||
dataset = SimpleNamespace(pipeline_id="p1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, None]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, None])
|
||||
|
||||
with pytest.raises(ValueError, match="Pipeline not found"):
|
||||
rag_pipeline_service.get_pipeline("t1", "d1")
|
||||
@@ -1783,8 +1722,7 @@ def test_get_pipeline_templates_builtin_en_us_no_fallback(mocker) -> None:
|
||||
def test_update_customized_pipeline_template_commits_when_name_empty(mocker) -> None:
|
||||
template = SimpleNamespace(name="old", description="old", icon={}, updated_by=None)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.return_value = template
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=template)
|
||||
commit = mocker.patch("services.rag_pipeline.rag_pipeline.db.session.commit")
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
|
||||
@@ -2011,8 +1949,7 @@ def test_run_free_workflow_node_delegates_to_handle_result(mocker, rag_pipeline_
|
||||
def test_publish_customized_pipeline_template_raises_when_workflow_missing(mocker, rag_pipeline_service) -> None:
|
||||
pipeline = SimpleNamespace(id="p1", tenant_id="t1", workflow_id="wf-1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [pipeline, None]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", side_effect=[pipeline, None])
|
||||
|
||||
with pytest.raises(ValueError, match="Workflow not found"):
|
||||
rag_pipeline_service.publish_customized_pipeline_template("p1", {})
|
||||
@@ -2021,11 +1958,9 @@ def test_publish_customized_pipeline_template_raises_when_workflow_missing(mocke
|
||||
def test_publish_customized_pipeline_template_raises_when_dataset_missing(mocker, rag_pipeline_service) -> None:
|
||||
pipeline = SimpleNamespace(id="p1", tenant_id="t1", workflow_id="wf-1")
|
||||
workflow = SimpleNamespace(id="wf-1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [pipeline, workflow]
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.engine = mocker.Mock()
|
||||
mock_db.session.query.return_value = query
|
||||
mock_db.session.get.side_effect = [pipeline, workflow]
|
||||
session_ctx = mocker.MagicMock()
|
||||
session_ctx.__enter__.return_value = SimpleNamespace()
|
||||
session_ctx.__exit__.return_value = False
|
||||
@@ -2038,11 +1973,8 @@ def test_publish_customized_pipeline_template_raises_when_dataset_missing(mocker
|
||||
|
||||
def test_get_recommended_plugins_skips_manifest_when_missing(mocker, rag_pipeline_service) -> None:
|
||||
plugin = SimpleNamespace(plugin_id="plugin-a")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value = query
|
||||
query.order_by.return_value.all.return_value = [plugin]
|
||||
mock_db = mocker.patch("services.rag_pipeline.rag_pipeline.db")
|
||||
mock_db.session.query.return_value = query
|
||||
mock_db.session.scalars.return_value.all.return_value = [plugin]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.current_user", SimpleNamespace(id="u1", current_tenant_id="t1"))
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.BuiltinToolManageService.list_builtin_tools", return_value=[])
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.marketplace.batch_fetch_plugin_by_ids", return_value=[])
|
||||
@@ -2056,8 +1988,8 @@ def test_get_recommended_plugins_skips_manifest_when_missing(mocker, rag_pipelin
|
||||
def test_retry_error_document_raises_when_pipeline_missing(mocker, rag_pipeline_service) -> None:
|
||||
exec_log = SimpleNamespace(pipeline_id="p1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [exec_log, None]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=exec_log)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Pipeline not found"):
|
||||
rag_pipeline_service.retry_error_document(
|
||||
@@ -2069,8 +2001,8 @@ def test_retry_error_document_raises_when_workflow_missing(mocker, rag_pipeline_
|
||||
exec_log = SimpleNamespace(pipeline_id="p1")
|
||||
pipeline = SimpleNamespace(id="p1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [exec_log, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", return_value=exec_log)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.get", return_value=pipeline)
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=None)
|
||||
|
||||
with pytest.raises(ValueError, match="Workflow not found"):
|
||||
@@ -2086,8 +2018,7 @@ def test_get_datasource_plugins_returns_empty_for_non_datasource_nodes(mocker, r
|
||||
graph_dict={"nodes": [{"id": "n1", "data": {"type": "start"}}]}, rag_pipeline_variables=[]
|
||||
)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=workflow)
|
||||
|
||||
assert rag_pipeline_service.get_datasource_plugins("t1", "d1", True) == []
|
||||
@@ -2250,8 +2181,7 @@ def test_get_datasource_plugins_handles_empty_datasource_data_and_non_published(
|
||||
rag_pipeline_variables=[{"variable": "v1", "belong_to_node_id": "shared"}],
|
||||
)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
mocker.patch.object(rag_pipeline_service, "get_draft_workflow", return_value=workflow)
|
||||
mocker.patch(
|
||||
"services.rag_pipeline.rag_pipeline.DatasourceProviderService.list_datasource_credentials", return_value=[]
|
||||
@@ -2291,8 +2221,7 @@ def test_get_datasource_plugins_extracts_user_inputs_and_credentials(mocker, rag
|
||||
],
|
||||
)
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
mocker.patch.object(rag_pipeline_service, "get_published_workflow", return_value=workflow)
|
||||
mocker.patch(
|
||||
"services.rag_pipeline.rag_pipeline.DatasourceProviderService.list_datasource_credentials",
|
||||
@@ -2310,8 +2239,7 @@ def test_get_pipeline_returns_pipeline_when_found(mocker, rag_pipeline_service)
|
||||
dataset = SimpleNamespace(pipeline_id="p1")
|
||||
pipeline = SimpleNamespace(id="p1")
|
||||
query = mocker.Mock()
|
||||
query.where.return_value.first.side_effect = [dataset, pipeline]
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.query", return_value=query)
|
||||
mocker.patch("services.rag_pipeline.rag_pipeline.db.session.scalar", side_effect=[dataset, pipeline])
|
||||
|
||||
result = rag_pipeline_service.get_pipeline("t1", "d1")
|
||||
|
||||
|
||||
@@ -173,9 +173,7 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock()
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {("Account", "email", "test@example.com"): mock_account}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.scalar.return_value = mock_account
|
||||
|
||||
mock_password_dependencies["compare_password"].return_value = True
|
||||
|
||||
@@ -188,9 +186,7 @@ class TestAccountService:
|
||||
|
||||
def test_authenticate_account_not_found(self, mock_db_dependencies):
|
||||
"""Test authentication when account does not exist."""
|
||||
# Setup smart database query mock - no matching results
|
||||
query_results = {("Account", "email", "notfound@example.com"): None}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.scalar.return_value = None
|
||||
|
||||
# Execute test and verify exception
|
||||
self._assert_exception_raised(
|
||||
@@ -202,9 +198,7 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock(status="banned")
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {("Account", "email", "banned@example.com"): mock_account}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.scalar.return_value = mock_account
|
||||
|
||||
# Execute test and verify exception
|
||||
self._assert_exception_raised(AccountLoginError, AccountService.authenticate, "banned@example.com", "password")
|
||||
@@ -214,9 +208,7 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock()
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {("Account", "email", "test@example.com"): mock_account}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.scalar.return_value = mock_account
|
||||
|
||||
mock_password_dependencies["compare_password"].return_value = False
|
||||
|
||||
@@ -230,9 +222,7 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock(status="pending")
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {("Account", "email", "pending@example.com"): mock_account}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.scalar.return_value = mock_account
|
||||
|
||||
mock_password_dependencies["compare_password"].return_value = True
|
||||
|
||||
@@ -422,12 +412,8 @@ class TestAccountService:
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock()
|
||||
mock_tenant_join = TestAccountAssociatedDataFactory.create_tenant_join_mock()
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {
|
||||
("Account", "id", "user-123"): mock_account,
|
||||
("TenantAccountJoin", "account_id", "user-123"): mock_tenant_join,
|
||||
}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.get.return_value = mock_account
|
||||
mock_db_dependencies["db"].session.scalar.return_value = mock_tenant_join
|
||||
|
||||
# Mock datetime
|
||||
with patch("services.account_service.datetime") as mock_datetime:
|
||||
@@ -444,9 +430,7 @@ class TestAccountService:
|
||||
|
||||
def test_load_user_not_found(self, mock_db_dependencies):
|
||||
"""Test user loading when user does not exist."""
|
||||
# Setup smart database query mock - no matching results
|
||||
query_results = {("Account", "id", "non-existent-user"): None}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.get.return_value = None
|
||||
|
||||
# Execute test
|
||||
result = AccountService.load_user("non-existent-user")
|
||||
@@ -459,9 +443,7 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock(status="banned")
|
||||
|
||||
# Setup smart database query mock
|
||||
query_results = {("Account", "id", "user-123"): mock_account}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.get.return_value = mock_account
|
||||
|
||||
# Execute test and verify exception
|
||||
self._assert_exception_raised(
|
||||
@@ -476,13 +458,9 @@ class TestAccountService:
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock()
|
||||
mock_available_tenant = TestAccountAssociatedDataFactory.create_tenant_join_mock(current=False)
|
||||
|
||||
# Setup smart database query mock for complex scenario
|
||||
query_results = {
|
||||
("Account", "id", "user-123"): mock_account,
|
||||
("TenantAccountJoin", "account_id", "user-123"): None, # No current tenant
|
||||
("TenantAccountJoin", "order_by", "first_available"): mock_available_tenant, # First available tenant
|
||||
}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.get.return_value = mock_account
|
||||
# First scalar: current tenant (None), second scalar: available tenant
|
||||
mock_db_dependencies["db"].session.scalar.side_effect = [None, mock_available_tenant]
|
||||
|
||||
# Mock datetime
|
||||
with patch("services.account_service.datetime") as mock_datetime:
|
||||
@@ -503,13 +481,9 @@ class TestAccountService:
|
||||
# Setup test data
|
||||
mock_account = TestAccountAssociatedDataFactory.create_account_mock()
|
||||
|
||||
# Setup smart database query mock for no tenants scenario
|
||||
query_results = {
|
||||
("Account", "id", "user-123"): mock_account,
|
||||
("TenantAccountJoin", "account_id", "user-123"): None, # No current tenant
|
||||
("TenantAccountJoin", "order_by", "first_available"): None, # No available tenants
|
||||
}
|
||||
ServiceDbTestHelper.setup_db_query_filter_by_mock(mock_db_dependencies["db"], query_results)
|
||||
mock_db_dependencies["db"].session.get.return_value = mock_account
|
||||
# First scalar: current tenant (None), second scalar: available tenant (None)
|
||||
mock_db_dependencies["db"].session.scalar.side_effect = [None, None]
|
||||
|
||||
# Mock datetime
|
||||
with patch("services.account_service.datetime") as mock_datetime:
|
||||
|
||||
@@ -799,10 +799,7 @@ class TestExternalDatasetServiceGetAPI:
|
||||
api_id = "api-123"
|
||||
expected_api = factory.create_external_knowledge_api_mock(api_id=api_id)
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = expected_api
|
||||
mock_db.session.scalar.return_value = expected_api
|
||||
|
||||
# Act
|
||||
tenant_id = "tenant-123"
|
||||
@@ -810,16 +807,12 @@ class TestExternalDatasetServiceGetAPI:
|
||||
|
||||
# Assert
|
||||
assert result.id == api_id
|
||||
mock_query.filter_by.assert_called_once_with(id=api_id, tenant_id=tenant_id)
|
||||
|
||||
@patch("services.external_knowledge_service.db")
|
||||
def test_get_external_knowledge_api_not_found(self, mock_db, factory):
|
||||
"""Test error when API is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
@@ -848,10 +841,7 @@ class TestExternalDatasetServiceUpdateAPI:
|
||||
"settings": {"endpoint": "https://new.example.com", "api_key": "new-key"},
|
||||
}
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_api
|
||||
mock_db.session.scalar.return_value = existing_api
|
||||
|
||||
# Act
|
||||
result = ExternalDatasetService.update_external_knowledge_api(tenant_id, user_id, api_id, args)
|
||||
@@ -881,10 +871,7 @@ class TestExternalDatasetServiceUpdateAPI:
|
||||
"settings": {"endpoint": "https://api.example.com", "api_key": HIDDEN_VALUE},
|
||||
}
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_api
|
||||
mock_db.session.scalar.return_value = existing_api
|
||||
|
||||
# Act
|
||||
result = ExternalDatasetService.update_external_knowledge_api(tenant_id, "user-123", api_id, args)
|
||||
@@ -897,10 +884,7 @@ class TestExternalDatasetServiceUpdateAPI:
|
||||
def test_update_external_knowledge_api_not_found(self, mock_db, factory):
|
||||
"""Test error when API is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
args = {"name": "Updated API"}
|
||||
|
||||
@@ -912,10 +896,7 @@ class TestExternalDatasetServiceUpdateAPI:
|
||||
def test_update_external_knowledge_api_tenant_mismatch(self, mock_db, factory):
|
||||
"""Test error when tenant ID doesn't match."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
args = {"name": "Updated API"}
|
||||
|
||||
@@ -934,10 +915,7 @@ class TestExternalDatasetServiceUpdateAPI:
|
||||
|
||||
args = {"name": "New Name Only"}
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_api
|
||||
mock_db.session.scalar.return_value = existing_api
|
||||
|
||||
# Act
|
||||
result = ExternalDatasetService.update_external_knowledge_api("tenant-123", "user-123", "api-123", args)
|
||||
@@ -958,10 +936,7 @@ class TestExternalDatasetServiceDeleteAPI:
|
||||
|
||||
existing_api = factory.create_external_knowledge_api_mock(api_id=api_id, tenant_id=tenant_id)
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_api
|
||||
mock_db.session.scalar.return_value = existing_api
|
||||
|
||||
# Act
|
||||
ExternalDatasetService.delete_external_knowledge_api(tenant_id, api_id)
|
||||
@@ -974,10 +949,7 @@ class TestExternalDatasetServiceDeleteAPI:
|
||||
def test_delete_external_knowledge_api_not_found(self, mock_db, factory):
|
||||
"""Test error when API is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
@@ -987,10 +959,7 @@ class TestExternalDatasetServiceDeleteAPI:
|
||||
def test_delete_external_knowledge_api_tenant_mismatch(self, mock_db, factory):
|
||||
"""Test error when tenant ID doesn't match."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
@@ -1006,10 +975,7 @@ class TestExternalDatasetServiceAPIUseCheck:
|
||||
# Arrange
|
||||
api_id = "api-123"
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.count.return_value = 1
|
||||
mock_db.session.scalar.return_value = 1
|
||||
|
||||
# Act
|
||||
in_use, count = ExternalDatasetService.external_knowledge_api_use_check(api_id)
|
||||
@@ -1024,10 +990,7 @@ class TestExternalDatasetServiceAPIUseCheck:
|
||||
# Arrange
|
||||
api_id = "api-123"
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.count.return_value = 10
|
||||
mock_db.session.scalar.return_value = 10
|
||||
|
||||
# Act
|
||||
in_use, count = ExternalDatasetService.external_knowledge_api_use_check(api_id)
|
||||
@@ -1042,10 +1005,7 @@ class TestExternalDatasetServiceAPIUseCheck:
|
||||
# Arrange
|
||||
api_id = "api-123"
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.count.return_value = 0
|
||||
mock_db.session.scalar.return_value = 0
|
||||
|
||||
# Act
|
||||
in_use, count = ExternalDatasetService.external_knowledge_api_use_check(api_id)
|
||||
@@ -1067,10 +1027,7 @@ class TestExternalDatasetServiceGetBinding:
|
||||
|
||||
expected_binding = factory.create_external_knowledge_binding_mock(tenant_id=tenant_id, dataset_id=dataset_id)
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = expected_binding
|
||||
mock_db.session.scalar.return_value = expected_binding
|
||||
|
||||
# Act
|
||||
result = ExternalDatasetService.get_external_knowledge_binding_with_dataset_id(tenant_id, dataset_id)
|
||||
@@ -1083,10 +1040,7 @@ class TestExternalDatasetServiceGetBinding:
|
||||
def test_get_external_knowledge_binding_not_found(self, mock_db, factory):
|
||||
"""Test error when binding is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="external knowledge binding not found"):
|
||||
@@ -1113,10 +1067,7 @@ class TestExternalDatasetServiceDocumentValidate:
|
||||
|
||||
api = factory.create_external_knowledge_api_mock(api_id=api_id, settings=[settings])
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = api
|
||||
mock_db.session.scalar.return_value = api
|
||||
|
||||
process_parameter = {"param1": "value1", "param2": "value2"}
|
||||
|
||||
@@ -1134,10 +1085,7 @@ class TestExternalDatasetServiceDocumentValidate:
|
||||
|
||||
api = factory.create_external_knowledge_api_mock(api_id=api_id, settings=[settings])
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = api
|
||||
mock_db.session.scalar.return_value = api
|
||||
|
||||
process_parameter = {}
|
||||
|
||||
@@ -1149,10 +1097,7 @@ class TestExternalDatasetServiceDocumentValidate:
|
||||
def test_document_create_args_validate_api_not_found(self, mock_db, factory):
|
||||
"""Test validation fails when API is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="api template not found"):
|
||||
@@ -1165,10 +1110,7 @@ class TestExternalDatasetServiceDocumentValidate:
|
||||
settings = {}
|
||||
api = factory.create_external_knowledge_api_mock(settings=[settings])
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = api
|
||||
mock_db.session.scalar.return_value = api
|
||||
|
||||
# Act & Assert - should not raise
|
||||
ExternalDatasetService.document_create_args_validate("tenant-123", "api-123", {})
|
||||
@@ -1186,10 +1128,7 @@ class TestExternalDatasetServiceDocumentValidate:
|
||||
|
||||
api = factory.create_external_knowledge_api_mock(settings=[settings])
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = api
|
||||
mock_db.session.scalar.return_value = api
|
||||
|
||||
process_parameter = {"required_param": "value"}
|
||||
|
||||
@@ -1498,24 +1437,7 @@ class TestExternalDatasetServiceCreateDataset:
|
||||
|
||||
api = factory.create_external_knowledge_api_mock(api_id="api-123")
|
||||
|
||||
# Mock database queries
|
||||
mock_dataset_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == Dataset:
|
||||
return mock_dataset_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_dataset_query.filter_by.return_value = mock_dataset_query
|
||||
mock_dataset_query.first.return_value = None
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [None, api]
|
||||
|
||||
# Act
|
||||
result = ExternalDatasetService.create_external_dataset(tenant_id, user_id, args)
|
||||
@@ -1534,10 +1456,7 @@ class TestExternalDatasetServiceCreateDataset:
|
||||
# Arrange
|
||||
existing_dataset = factory.create_dataset_mock(name="Duplicate Dataset")
|
||||
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = existing_dataset
|
||||
mock_db.session.scalar.return_value = existing_dataset
|
||||
|
||||
args = {"name": "Duplicate Dataset"}
|
||||
|
||||
@@ -1549,23 +1468,7 @@ class TestExternalDatasetServiceCreateDataset:
|
||||
def test_create_external_dataset_api_not_found_error(self, mock_db, factory):
|
||||
"""Test error when external knowledge API is not found."""
|
||||
# Arrange
|
||||
mock_dataset_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == Dataset:
|
||||
return mock_dataset_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_dataset_query.filter_by.return_value = mock_dataset_query
|
||||
mock_dataset_query.first.return_value = None
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = None
|
||||
mock_db.session.scalar.side_effect = [None, None]
|
||||
|
||||
args = {"name": "Test Dataset", "external_knowledge_api_id": "nonexistent-api"}
|
||||
|
||||
@@ -1579,23 +1482,7 @@ class TestExternalDatasetServiceCreateDataset:
|
||||
# Arrange
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_dataset_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == Dataset:
|
||||
return mock_dataset_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_dataset_query.filter_by.return_value = mock_dataset_query
|
||||
mock_dataset_query.first.return_value = None
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [None, api]
|
||||
|
||||
args = {"name": "Test Dataset", "external_knowledge_api_id": "api-123"}
|
||||
|
||||
@@ -1609,23 +1496,7 @@ class TestExternalDatasetServiceCreateDataset:
|
||||
# Arrange
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_dataset_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == Dataset:
|
||||
return mock_dataset_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_dataset_query.filter_by.return_value = mock_dataset_query
|
||||
mock_dataset_query.first.return_value = None
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [None, api]
|
||||
|
||||
args = {"name": "Test Dataset", "external_knowledge_id": "knowledge-123"}
|
||||
|
||||
@@ -1651,23 +1522,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
)
|
||||
api = factory.create_external_knowledge_api_mock(api_id="api-123")
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
@@ -1695,10 +1550,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
def test_fetch_external_knowledge_retrieval_binding_not_found_error(self, mock_db, factory):
|
||||
"""Test error when external knowledge binding is not found."""
|
||||
# Arrange
|
||||
mock_query = MagicMock()
|
||||
mock_db.session.query.return_value = mock_query
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None
|
||||
mock_db.session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ValueError, match="external knowledge binding not found"):
|
||||
@@ -1712,23 +1564,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
binding = factory.create_external_knowledge_binding_mock()
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
@@ -1751,23 +1587,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
binding = factory.create_external_knowledge_binding_mock()
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
@@ -1799,23 +1619,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
binding = factory.create_external_knowledge_binding_mock()
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
@@ -1856,23 +1660,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
)
|
||||
api = factory.create_external_knowledge_api_mock(api_id="api-123")
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = status_code
|
||||
@@ -1891,23 +1679,7 @@ class TestExternalDatasetServiceFetchRetrieval:
|
||||
binding = factory.create_external_knowledge_binding_mock()
|
||||
api = factory.create_external_knowledge_api_mock()
|
||||
|
||||
mock_binding_query = MagicMock()
|
||||
mock_api_query = MagicMock()
|
||||
|
||||
def query_side_effect(model):
|
||||
if model == ExternalKnowledgeBindings:
|
||||
return mock_binding_query
|
||||
elif model == ExternalKnowledgeApis:
|
||||
return mock_api_query
|
||||
return MagicMock()
|
||||
|
||||
mock_db.session.query.side_effect = query_side_effect
|
||||
|
||||
mock_binding_query.filter_by.return_value = mock_binding_query
|
||||
mock_binding_query.first.return_value = binding
|
||||
|
||||
mock_api_query.filter_by.return_value = mock_api_query
|
||||
mock_api_query.first.return_value = api
|
||||
mock_db.session.scalar.side_effect = [binding, api]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 503
|
||||
|
||||
@@ -18,8 +18,6 @@ import { parsePlacement } from '../internal/placement.js'
|
||||
|
||||
export const ContextMenu = BaseContextMenu.Root
|
||||
export const ContextMenuTrigger = BaseContextMenu.Trigger
|
||||
export const ContextMenuPortal = BaseContextMenu.Portal
|
||||
export const ContextMenuBackdrop = BaseContextMenu.Backdrop
|
||||
export const ContextMenuSub = BaseContextMenu.SubmenuRoot
|
||||
export const ContextMenuGroup = BaseContextMenu.Group
|
||||
export const ContextMenuRadioGroup = BaseContextMenu.RadioGroup
|
||||
@@ -195,7 +193,6 @@ export function ContextMenuItemIndicator({
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function ContextMenuCheckboxItemIndicator({
|
||||
className,
|
||||
...props
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import { parsePlacement } from '../internal/placement.js'
|
||||
|
||||
export const DropdownMenu = Menu.Root
|
||||
export const DropdownMenuPortal = Menu.Portal
|
||||
export const DropdownMenuTrigger = Menu.Trigger
|
||||
export const DropdownMenuSub = Menu.SubmenuRoot
|
||||
export const DropdownMenuGroup = Menu.Group
|
||||
|
||||
2613
pnpm-lock.yaml
generated
2613
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,9 @@
|
||||
catalogMode: prefer
|
||||
trustPolicy: no-downgrade
|
||||
minimumReleaseAge: 2880
|
||||
trustPolicyExclude:
|
||||
- chokidar@4.0.3
|
||||
- reselect@5.1.1
|
||||
- semver@6.3.1
|
||||
blockExoticSubdeps: true
|
||||
strictDepBuilds: true
|
||||
allowBuilds:
|
||||
@@ -23,7 +27,7 @@ overrides:
|
||||
array.prototype.flatmap: npm:@nolyfill/array.prototype.flatmap@^1.0.44
|
||||
array.prototype.tosorted: npm:@nolyfill/array.prototype.tosorted@^1.0.44
|
||||
assert: npm:@nolyfill/assert@^1.0.26
|
||||
brace-expansion@<2.0.2: 2.0.2
|
||||
brace-expansion@>=2.0.0 <2.0.3: 2.0.3
|
||||
canvas: ^3.2.2
|
||||
devalue@<5.3.2: 5.3.2
|
||||
dompurify@>=3.1.3 <=3.3.1: 3.3.2
|
||||
@@ -37,6 +41,7 @@ overrides:
|
||||
is-generator-function: npm:@nolyfill/is-generator-function@^1.0.44
|
||||
is-typed-array: npm:@nolyfill/is-typed-array@^1.0.44
|
||||
isarray: npm:@nolyfill/isarray@^1.0.44
|
||||
lodash-es@>=4.0.0 <= 4.17.23: 4.18.0
|
||||
object.assign: npm:@nolyfill/object.assign@^1.0.44
|
||||
object.entries: npm:@nolyfill/object.entries@^1.0.44
|
||||
object.fromentries: npm:@nolyfill/object.fromentries@^1.0.44
|
||||
@@ -64,15 +69,15 @@ overrides:
|
||||
tar@<=7.5.10: 7.5.11
|
||||
typed-array-buffer: npm:@nolyfill/typed-array-buffer@^1.0.44
|
||||
undici@>=7.0.0 <7.24.0: 7.24.0
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.14
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.14
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.15
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.15
|
||||
which-typed-array: npm:@nolyfill/which-typed-array@^1.0.44
|
||||
yaml@>=2.0.0 <2.8.3: 2.8.3
|
||||
yauzl@<3.2.1: 3.2.1
|
||||
catalog:
|
||||
"@amplitude/analytics-browser": 2.38.0
|
||||
"@amplitude/plugin-session-replay-browser": 1.27.5
|
||||
"@antfu/eslint-config": 7.7.3
|
||||
"@amplitude/analytics-browser": 2.38.1
|
||||
"@amplitude/plugin-session-replay-browser": 1.27.6
|
||||
"@antfu/eslint-config": 8.0.0
|
||||
"@base-ui/react": 1.3.0
|
||||
"@chromatic-com/storybook": 5.1.1
|
||||
"@cucumber/cucumber": 12.7.0
|
||||
@@ -84,7 +89,7 @@ catalog:
|
||||
"@formatjs/intl-localematcher": 0.8.2
|
||||
"@headlessui/react": 2.2.9
|
||||
"@heroicons/react": 2.2.0
|
||||
"@hono/node-server": 1.19.11
|
||||
"@hono/node-server": 1.19.12
|
||||
"@iconify-json/heroicons": 1.2.3
|
||||
"@iconify-json/ri": 1.2.10
|
||||
"@lexical/code": 0.42.0
|
||||
@@ -98,34 +103,34 @@ catalog:
|
||||
"@mdx-js/react": 3.1.1
|
||||
"@mdx-js/rollup": 3.1.1
|
||||
"@monaco-editor/react": 4.7.0
|
||||
"@next/eslint-plugin-next": 16.2.1
|
||||
"@next/mdx": 16.2.1
|
||||
"@next/eslint-plugin-next": 16.2.2
|
||||
"@next/mdx": 16.2.2
|
||||
"@orpc/client": 1.13.13
|
||||
"@orpc/contract": 1.13.13
|
||||
"@orpc/openapi-client": 1.13.13
|
||||
"@orpc/tanstack-query": 1.13.13
|
||||
"@playwright/test": 1.58.2
|
||||
"@playwright/test": 1.59.1
|
||||
"@remixicon/react": 4.9.0
|
||||
"@rgrove/parse-xml": 4.2.0
|
||||
"@sentry/react": 10.46.0
|
||||
"@storybook/addon-docs": 10.3.3
|
||||
"@storybook/addon-links": 10.3.3
|
||||
"@storybook/addon-onboarding": 10.3.3
|
||||
"@storybook/addon-themes": 10.3.3
|
||||
"@storybook/nextjs-vite": 10.3.3
|
||||
"@storybook/react": 10.3.3
|
||||
"@sentry/react": 10.47.0
|
||||
"@storybook/addon-docs": 10.3.4
|
||||
"@storybook/addon-links": 10.3.4
|
||||
"@storybook/addon-onboarding": 10.3.4
|
||||
"@storybook/addon-themes": 10.3.4
|
||||
"@storybook/nextjs-vite": 10.3.4
|
||||
"@storybook/react": 10.3.4
|
||||
"@streamdown/math": 1.0.2
|
||||
"@svgdotjs/svg.js": 3.2.5
|
||||
"@t3-oss/env-nextjs": 0.13.11
|
||||
"@tailwindcss/postcss": 4.2.2
|
||||
"@tailwindcss/typography": 0.5.19
|
||||
"@tailwindcss/vite": 4.2.2
|
||||
"@tanstack/eslint-plugin-query": 5.95.2
|
||||
"@tanstack/react-devtools": 0.10.0
|
||||
"@tanstack/react-form": 1.28.5
|
||||
"@tanstack/react-form-devtools": 0.2.19
|
||||
"@tanstack/react-query": 5.95.2
|
||||
"@tanstack/react-query-devtools": 5.95.2
|
||||
"@tanstack/eslint-plugin-query": 5.96.1
|
||||
"@tanstack/react-devtools": 0.10.1
|
||||
"@tanstack/react-form": 1.28.6
|
||||
"@tanstack/react-form-devtools": 0.2.20
|
||||
"@tanstack/react-query": 5.96.1
|
||||
"@tanstack/react-query-devtools": 5.96.1
|
||||
"@testing-library/dom": 10.4.1
|
||||
"@testing-library/jest-dom": 6.9.1
|
||||
"@testing-library/react": 16.3.2
|
||||
@@ -144,12 +149,12 @@ catalog:
|
||||
"@types/react-syntax-highlighter": 15.5.13
|
||||
"@types/react-window": 1.8.8
|
||||
"@types/sortablejs": 1.15.9
|
||||
"@typescript-eslint/eslint-plugin": 8.57.2
|
||||
"@typescript-eslint/parser": 8.57.2
|
||||
"@typescript/native-preview": 7.0.0-dev.20260329.1
|
||||
"@typescript-eslint/eslint-plugin": 8.58.0
|
||||
"@typescript-eslint/parser": 8.58.0
|
||||
"@typescript/native-preview": 7.0.0-dev.20260401.1
|
||||
"@vitejs/plugin-react": 6.0.1
|
||||
"@vitejs/plugin-rsc": 0.5.21
|
||||
"@vitest/coverage-v8": 4.1.1
|
||||
"@vitest/coverage-v8": 4.1.2
|
||||
abcjs: 6.6.2
|
||||
agentation: 3.0.2
|
||||
ahooks: 3.9.7
|
||||
@@ -157,7 +162,7 @@ catalog:
|
||||
class-variance-authority: 0.7.1
|
||||
clsx: 2.1.1
|
||||
cmdk: 1.1.1
|
||||
code-inspector-plugin: 1.4.5
|
||||
code-inspector-plugin: 1.5.0
|
||||
copy-to-clipboard: 3.3.3
|
||||
cron-parser: 5.5.0
|
||||
dayjs: 1.11.20
|
||||
@@ -174,19 +179,18 @@ catalog:
|
||||
eslint-markdown: 0.6.0
|
||||
eslint-plugin-better-tailwindcss: 4.3.2
|
||||
eslint-plugin-hyoban: 0.14.1
|
||||
eslint-plugin-markdown-preferences: 0.40.3
|
||||
eslint-plugin-markdown-preferences: 0.41.0
|
||||
eslint-plugin-no-barrel-files: 1.2.2
|
||||
eslint-plugin-react-hooks: 7.0.1
|
||||
eslint-plugin-react-refresh: 0.5.2
|
||||
eslint-plugin-sonarjs: 4.0.2
|
||||
eslint-plugin-storybook: 10.3.3
|
||||
eslint-plugin-storybook: 10.3.4
|
||||
fast-deep-equal: 3.1.3
|
||||
foxact: 0.3.0
|
||||
happy-dom: 20.8.9
|
||||
hono: 4.12.9
|
||||
hono: 4.12.10
|
||||
html-entities: 2.6.0
|
||||
html-to-image: 1.11.13
|
||||
i18next: 25.10.10
|
||||
i18next: 26.0.3
|
||||
i18next-resources-to-backend: 1.2.1
|
||||
iconify-import-svg: 0.1.2
|
||||
immer: 11.1.4
|
||||
@@ -196,15 +200,15 @@ catalog:
|
||||
js-yaml: 4.1.1
|
||||
jsonschema: 1.5.0
|
||||
katex: 0.16.44
|
||||
knip: 6.1.0
|
||||
knip: 6.2.0
|
||||
ky: 1.14.3
|
||||
lamejs: 1.2.1
|
||||
lexical: 0.42.0
|
||||
mermaid: 11.13.0
|
||||
mermaid: 11.14.0
|
||||
mime: 4.1.0
|
||||
mitt: 3.0.1
|
||||
negotiator: 1.0.0
|
||||
next: 16.2.1
|
||||
next: 16.2.2
|
||||
next-themes: 0.4.6
|
||||
nuqs: 2.8.9
|
||||
pinyin-pro: 3.28.0
|
||||
@@ -217,7 +221,7 @@ catalog:
|
||||
react-dom: 19.2.4
|
||||
react-easy-crop: 5.5.7
|
||||
react-hotkeys-hook: 5.2.4
|
||||
react-i18next: 16.6.6
|
||||
react-i18next: 17.0.2
|
||||
react-multi-email: 1.0.25
|
||||
react-papaparse: 4.4.0
|
||||
react-pdf-highlighter: 8.0.0-rc.0
|
||||
@@ -229,30 +233,29 @@ catalog:
|
||||
reactflow: 11.11.4
|
||||
remark-breaks: 4.0.0
|
||||
remark-directive: 4.0.0
|
||||
sass: 1.98.0
|
||||
scheduler: 0.27.0
|
||||
sharp: 0.34.5
|
||||
sortablejs: 1.15.7
|
||||
std-semver: 1.0.8
|
||||
storybook: 10.3.3
|
||||
storybook: 10.3.4
|
||||
streamdown: 2.5.0
|
||||
string-ts: 2.3.1
|
||||
tailwind-merge: 3.5.0
|
||||
tailwindcss: 4.2.2
|
||||
taze: 19.10.0
|
||||
taze: 19.11.0
|
||||
tldts: 7.0.27
|
||||
tsup: ^8.5.1
|
||||
tsdown: 0.21.7
|
||||
tsx: 4.21.0
|
||||
typescript: 5.9.3
|
||||
typescript: 6.0.2
|
||||
uglify-js: 3.19.3
|
||||
unist-util-visit: 5.1.0
|
||||
use-context-selector: 2.0.0
|
||||
uuid: 13.0.0
|
||||
vinext: 0.0.38
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.14
|
||||
vinext: 0.0.39
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.15
|
||||
vite-plugin-inspect: 12.0.0-beta.1
|
||||
vite-plus: 0.1.14
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.14
|
||||
vite-plus: 0.1.15
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.15
|
||||
vitest-canvas-mock: 1.1.4
|
||||
zod: 4.3.6
|
||||
zundo: 2.3.0
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
"homepage": "https://dify.ai",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build": "vp pack",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"type-check": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test": "vp test",
|
||||
"test:coverage": "vp test --coverage",
|
||||
"publish:check": "./scripts/publish.sh --dry-run",
|
||||
"publish:npm": "./scripts/publish.sh"
|
||||
},
|
||||
@@ -61,8 +61,8 @@
|
||||
"@typescript-eslint/parser": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"tsup": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite-plus": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
splitting: false,
|
||||
treeshake: true,
|
||||
outDir: "dist",
|
||||
});
|
||||
@@ -1,6 +1,17 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { defineConfig } from "vite-plus";
|
||||
|
||||
export default defineConfig({
|
||||
pack: {
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
// splitting: false,
|
||||
treeshake: true,
|
||||
outDir: "dist",
|
||||
target: false,
|
||||
},
|
||||
test: {
|
||||
environment: "node",
|
||||
include: ["**/*.test.ts"],
|
||||
@@ -6,10 +6,5 @@ export default defineConfig({
|
||||
'react-syntax-highlighter',
|
||||
'react-window',
|
||||
'@types/react-window',
|
||||
|
||||
// We can not upgrade these yet
|
||||
'typescript',
|
||||
],
|
||||
|
||||
maturityPeriod: 2,
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ const TagManagementModal = dynamic(() => import('@/app/components/base/tag-manag
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
appId: string
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useAppWorkflow } from '@/service/use-workflow'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
|
||||
export type ICardViewProps = {
|
||||
type ICardViewProps = {
|
||||
appId: string
|
||||
isInPanel?: boolean
|
||||
className?: string
|
||||
|
||||
@@ -27,7 +27,7 @@ const TIME_PERIOD_MAPPING: { value: number, name: TimePeriodName }[] = [
|
||||
|
||||
const queryDateFormat = 'YYYY-MM-DD HH:mm'
|
||||
|
||||
export type IChartViewProps = {
|
||||
type IChartViewProps = {
|
||||
appId: string
|
||||
headerRight: React.ReactNode
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../styles/globals.css";
|
||||
|
||||
.app {
|
||||
flex-grow: 1;
|
||||
height: 0;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
@reference "../../../../../styles/globals.css";
|
||||
|
||||
.logTable td {
|
||||
padding: 7px 8px;
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pagination li {
|
||||
list-style: none;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import { usePathname } from '@/next/navigation'
|
||||
import { useDatasetDetail, useDatasetRelatedApps } from '@/service/knowledge/use-dataset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
datasetId: string
|
||||
}
|
||||
|
||||
@@ -13,10 +13,6 @@ import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useLogout, useUserProfile } from '@/service/use-common'
|
||||
|
||||
export type IAppSelector = {
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
export default function AppSelector() {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -5,7 +5,7 @@ import AppInfoModals from './app-info-modals'
|
||||
import AppInfoTrigger from './app-info-trigger'
|
||||
import { useAppInfoActions } from './use-app-info-actions'
|
||||
|
||||
export type IAppInfoProps = {
|
||||
type IAppInfoProps = {
|
||||
expand: boolean
|
||||
onlyShowDetail?: boolean
|
||||
openState?: boolean
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import AppIcon from '../base/app-icon'
|
||||
|
||||
export type IAppBasicProps = {
|
||||
type IAppBasicProps = {
|
||||
iconType?: 'app' | 'api' | 'dataset' | 'webapp' | 'notion'
|
||||
icon?: string
|
||||
icon_background?: string | null
|
||||
|
||||
@@ -17,7 +17,7 @@ import DatasetSidebarDropdown from './dataset-sidebar-dropdown'
|
||||
import NavLink from './nav-link'
|
||||
import ToggleButton from './toggle-button'
|
||||
|
||||
export type IAppDetailNavProps = {
|
||||
type IAppDetailNavProps = {
|
||||
iconType?: 'app' | 'dataset'
|
||||
navigation: Array<{
|
||||
name: string
|
||||
|
||||
@@ -39,7 +39,5 @@ export enum AnnotationEnableStatus {
|
||||
}
|
||||
|
||||
export enum JobStatus {
|
||||
waiting = 'waiting',
|
||||
processing = 'processing',
|
||||
completed = 'completed',
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { HTMLProps, PropsWithChildren } from 'react'
|
||||
import { RiArrowRightUpLine } from '@remixicon/react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type SuggestedActionProps = PropsWithChildren<HTMLProps<HTMLAnchorElement> & {
|
||||
type SuggestedActionProps = PropsWithChildren<HTMLProps<HTMLAnchorElement> & {
|
||||
icon?: React.ReactNode
|
||||
link?: string
|
||||
disabled?: boolean
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FC, ReactNode } from 'react'
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type IFeaturePanelProps = {
|
||||
type IFeaturePanelProps = {
|
||||
className?: string
|
||||
headerIcon?: ReactNode
|
||||
title: ReactNode
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
|
||||
export type IGroupNameProps = {
|
||||
type IGroupNameProps = {
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type IOperationBtnProps = {
|
||||
type IOperationBtnProps = {
|
||||
className?: string
|
||||
type: 'add' | 'edit'
|
||||
actionName?: string
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
|
||||
import s from './style.module.css'
|
||||
|
||||
export type IVarHighlightProps = {
|
||||
type IVarHighlightProps = {
|
||||
name: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../../styles/globals.css";
|
||||
|
||||
.item {
|
||||
background-color: rgba(21, 94, 239, 0.05);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import WarningMask from '.'
|
||||
|
||||
export type IFormattingChangedProps = {
|
||||
type IFormattingChangedProps = {
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import WarningMask from '.'
|
||||
|
||||
export type IFormattingChangedProps = {
|
||||
type IFormattingChangedProps = {
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export type IHasNotSetAPIProps = {
|
||||
type IHasNotSetAPIProps = {
|
||||
onSetting: () => void
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
|
||||
import s from './style.module.css'
|
||||
|
||||
export type IWarningMaskProps = {
|
||||
type IWarningMaskProps = {
|
||||
title: string
|
||||
description: string
|
||||
footer: React.ReactNode
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../../styles/globals.css";
|
||||
|
||||
.mask {
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VarHighlight from '../../base/var-highlight'
|
||||
|
||||
export type IConfirmAddVarProps = {
|
||||
type IConfirmAddVarProps = {
|
||||
varNameArr: string[]
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
|
||||
@@ -33,7 +33,7 @@ import { getNewVar, getVars } from '@/utils/var'
|
||||
import ConfirmAddVar from './confirm-add-var'
|
||||
import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
|
||||
|
||||
export type ISimplePromptInput = {
|
||||
type ISimplePromptInput = {
|
||||
mode: AppModeEnum
|
||||
promptTemplate: string
|
||||
promptVariables: PromptVariable[]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../styles/globals.css";
|
||||
|
||||
.gradientBorder {
|
||||
background: radial-gradient(circle at 100% 100%, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 0% 0%/12px 12px no-repeat,
|
||||
radial-gradient(circle at 0 100%, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 100% 0%/12px 12px no-repeat,
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
export const jsonObjectWrap = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
additionalProperties: true,
|
||||
}
|
||||
|
||||
export const jsonConfigPlaceHolder = JSON.stringify(
|
||||
{
|
||||
type: 'object',
|
||||
|
||||
@@ -52,7 +52,7 @@ const normalizeSelectDefaultValue = (inputVar: InputVar) => {
|
||||
return inputVar
|
||||
}
|
||||
|
||||
export type IConfigModalProps = {
|
||||
type IConfigModalProps = {
|
||||
isCreate?: boolean
|
||||
payload?: InputVar
|
||||
isShow: boolean
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ReactSortable } from 'react-sortablejs'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type Options = string[]
|
||||
export type IConfigSelectProps = {
|
||||
type IConfigSelectProps = {
|
||||
options: Options
|
||||
onChange: (options: Options) => void
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type IModalFootProps = {
|
||||
type IModalFootProps = {
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type ISelectTypeItemProps = {
|
||||
type ISelectTypeItemProps = {
|
||||
type: InputVarType
|
||||
selected: boolean
|
||||
onClick: () => void
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import s from '@/app/components/app/configuration/config-prompt/style.module.css'
|
||||
import {
|
||||
Copy,
|
||||
CopyCheck,
|
||||
} from '@/app/components/base/icons/src/vender/line/files'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
type: 'first-prompt' | 'next-iteration'
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
className,
|
||||
type,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isCopied, setIsCopied] = React.useState(false)
|
||||
const {
|
||||
modelConfig,
|
||||
hasSetBlockStatus,
|
||||
dataSets,
|
||||
showSelectDataSet,
|
||||
externalDataToolsConfig,
|
||||
setExternalDataToolsConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const promptVariables = modelConfig.configs.prompt_variables
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
const isFirstPrompt = type === 'first-prompt'
|
||||
const editorHeight = isFirstPrompt ? 'h-[336px]' : 'h-[52px]'
|
||||
|
||||
const handleOpenExternalDataToolModal = () => {
|
||||
setShowExternalDataToolModal({
|
||||
payload: {},
|
||||
onSaveCallback: (newExternalDataTool?: ExternalDataTool) => {
|
||||
if (!newExternalDataTool)
|
||||
return
|
||||
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool])
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
if (promptVariables[i].key === newExternalDataTool.variable) {
|
||||
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: promptVariables[i].key }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < externalDataToolsConfig.length; i++) {
|
||||
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
|
||||
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: externalDataToolsConfig[i].variable }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className={cn(className, s.gradientBorder, 'relative')}>
|
||||
<div className="rounded-xl bg-white">
|
||||
<div className={cn(s.boxHeader, 'flex h-11 items-center justify-between rounded-tl-xl rounded-tr-xl bg-white pb-1 pl-4 pr-3 pt-2 hover:shadow-xs')}>
|
||||
<div className="text-sm font-semibold uppercase text-indigo-800">{t(`agent.${isFirstPrompt ? 'firstPrompt' : 'nextIteration'}`, { ns: 'appDebug' })}</div>
|
||||
<div className={cn(s.optionWrap, 'items-center space-x-1')}>
|
||||
{!isCopied
|
||||
? (
|
||||
<Copy
|
||||
className="h-6 w-6 cursor-pointer p-1 text-gray-500"
|
||||
onClick={() => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<CopyCheck className="h-6 w-6 p-1 text-gray-500" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(editorHeight, 'min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}>
|
||||
<PromptEditor
|
||||
className={editorHeight}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: true,
|
||||
selectable: !hasSetBlockStatus.context,
|
||||
datasets: dataSets.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: item.data_source_type,
|
||||
})),
|
||||
onAddContext: showSelectDataSet,
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables: modelConfig.configs.prompt_variables.filter(item => item.key && item.key.trim() && item.name && item.name.trim()).map(item => ({
|
||||
name: item.name,
|
||||
value: item.key,
|
||||
})),
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: true,
|
||||
externalTools: externalDataToolsConfig.map(item => ({
|
||||
name: item.label!,
|
||||
variableName: item.variable!,
|
||||
icon: item.icon,
|
||||
icon_background: item.icon_background,
|
||||
})),
|
||||
onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: noop,
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={noop}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex pb-2 pl-4">
|
||||
<div className="h-[18px] rounded-md bg-gray-100 px-1 text-xs leading-[18px] text-gray-500">{value.length}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Editor)
|
||||
@@ -7,7 +7,7 @@ import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type IAutomaticBtnProps = {
|
||||
type IAutomaticBtnProps = {
|
||||
onClick: () => void
|
||||
}
|
||||
const AutomaticBtn: FC<IAutomaticBtnProps> = ({
|
||||
|
||||
@@ -43,7 +43,7 @@ import useGenData from './use-gen-data'
|
||||
|
||||
const i18nPrefix = 'generate'
|
||||
|
||||
export type IGetAutomaticResProps = {
|
||||
type IGetAutomaticResProps = {
|
||||
mode: AppModeEnum
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../../styles/globals.css";
|
||||
|
||||
.textGradient {
|
||||
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
|
||||
@@ -31,7 +31,7 @@ import { GeneratorType } from '../automatic/types'
|
||||
import useGenData from '../automatic/use-gen-data'
|
||||
|
||||
const i18nPrefix = 'generate'
|
||||
export type IGetCodeGeneratorResProps = {
|
||||
type IGetCodeGeneratorResProps = {
|
||||
flowId: string
|
||||
nodeId: string
|
||||
currentCode?: string
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import s from './style.module.css'
|
||||
|
||||
export type IContrlBtnGroupProps = {
|
||||
type IContrlBtnGroupProps = {
|
||||
onSave: () => void
|
||||
onReset: () => void
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../styles/globals.css";
|
||||
|
||||
.ctrlBtn {
|
||||
left: -16px;
|
||||
right: -16px;
|
||||
|
||||
@@ -17,7 +17,7 @@ import Link from '@/next/link'
|
||||
import { useInfiniteDatasets } from '@/service/knowledge/use-dataset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type ISelectDataSetProps = {
|
||||
type ISelectDataSetProps = {
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
selectedIds: string[]
|
||||
|
||||
@@ -18,5 +18,3 @@ export const DebugWithMultipleModelContext = createContext<DebugWithMultipleMode
|
||||
})
|
||||
|
||||
export const useDebugWithMultipleModelContext = () => useContext(DebugWithMultipleModelContext)
|
||||
|
||||
export default DebugWithMultipleModelContext
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
@reference "../../../styles/globals.css";
|
||||
|
||||
.advancedPromptMode {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.advancedPromptMode::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -1px;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: rgba(68, 76, 231, 0.18);
|
||||
transform: skewX(-30deg);
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowDownSLine,
|
||||
RiDeleteBinLine,
|
||||
} from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import {
|
||||
Settings01,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Tool03 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/app/components/base/ui/tooltip'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
||||
const Tools = () => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
const {
|
||||
externalDataToolsConfig,
|
||||
setExternalDataToolsConfig,
|
||||
modelConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleSaveExternalDataToolModal = (externalDataTool: ExternalDataTool, index: number) => {
|
||||
if (index > -1) {
|
||||
setExternalDataToolsConfig([
|
||||
...externalDataToolsConfig.slice(0, index),
|
||||
externalDataTool,
|
||||
...externalDataToolsConfig.slice(index + 1),
|
||||
])
|
||||
}
|
||||
else {
|
||||
setExternalDataToolsConfig([...externalDataToolsConfig, externalDataTool])
|
||||
}
|
||||
}
|
||||
const handleValidateBeforeSaveExternalDataToolModal = (newExternalDataTool: ExternalDataTool, index: number) => {
|
||||
const promptVariables = modelConfig?.configs?.prompt_variables || []
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
if (promptVariables[i].key === newExternalDataTool.variable) {
|
||||
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: promptVariables[i].key }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let existedExternalDataTools = []
|
||||
if (index > -1) {
|
||||
existedExternalDataTools = [
|
||||
...externalDataToolsConfig.slice(0, index),
|
||||
...externalDataToolsConfig.slice(index + 1),
|
||||
]
|
||||
}
|
||||
else {
|
||||
existedExternalDataTools = [...externalDataToolsConfig]
|
||||
}
|
||||
|
||||
for (let i = 0; i < existedExternalDataTools.length; i++) {
|
||||
if (existedExternalDataTools[i].variable === newExternalDataTool.variable) {
|
||||
toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: existedExternalDataTools[i].variable }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
const handleOpenExternalDataToolModal = (payload: ExternalDataTool, index: number) => {
|
||||
setShowExternalDataToolModal({
|
||||
payload,
|
||||
onSaveCallback: (externalDataTool?: ExternalDataTool) => {
|
||||
if (!externalDataTool)
|
||||
return
|
||||
handleSaveExternalDataToolModal(externalDataTool, index)
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => handleValidateBeforeSaveExternalDataToolModal(newExternalDataTool, index),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-3 rounded-xl bg-gray-50 px-3">
|
||||
<div className="flex h-12 items-center">
|
||||
<div className="flex grow items-center">
|
||||
<div
|
||||
className={`
|
||||
group mr-1 flex h-6 w-6 items-center justify-center rounded-md
|
||||
${externalDataToolsConfig.length && 'hover:bg-white hover:shadow-xs'}
|
||||
`}
|
||||
onClick={() => setExpanded(v => !v)}
|
||||
>
|
||||
{
|
||||
externalDataToolsConfig.length
|
||||
? <Tool03 className="h-4 w-4 text-[#444CE7] group-hover:hidden" />
|
||||
: <Tool03 className="h-4 w-4 text-[#444CE7]" />
|
||||
}
|
||||
{
|
||||
!!externalDataToolsConfig.length && (
|
||||
<RiArrowDownSLine className={`hidden h-4 w-4 cursor-pointer text-primary-600 group-hover:block ${expanded ? 'rotate-180' : 'rotate-0'}`} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="mr-1 text-sm font-semibold text-gray-800">
|
||||
{t('feature.tools.title', { ns: 'appDebug' })}
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={<span className="i-ri-question-line ml-1 h-4 w-4 shrink-0 text-text-quaternary" />} />
|
||||
<TooltipContent>
|
||||
<div className="max-w-[160px]">
|
||||
{t('feature.tools.tips', { ns: 'appDebug' })}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{
|
||||
!expanded && !!externalDataToolsConfig.length && (
|
||||
<>
|
||||
<div className="mr-3 text-xs text-gray-500">{t('feature.tools.toolsInUse', { ns: 'appDebug', count: externalDataToolsConfig.length })}</div>
|
||||
<div className="mr-1 h-3.5 w-px bg-gray-200" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div
|
||||
className="flex h-7 cursor-pointer items-center px-3 text-xs font-medium text-gray-700"
|
||||
onClick={() => handleOpenExternalDataToolModal({}, -1)}
|
||||
>
|
||||
<RiAddLine className="mr-[5px] h-3.5 w-3.5" />
|
||||
{t('operation.add', { ns: 'common' })}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
expanded && !!externalDataToolsConfig.length && (
|
||||
<div className="pb-3">
|
||||
{
|
||||
externalDataToolsConfig.map((item, index: number) => (
|
||||
<div
|
||||
key={`${index}-${item.type}-${item.label}-${item.variable}`}
|
||||
className="group mb-1 flex items-center rounded-lg border-[0.5px] border-gray-200 bg-white px-2.5 py-2 shadow-xs last-of-type:mb-0"
|
||||
>
|
||||
<div className="flex grow items-center">
|
||||
<AppIcon
|
||||
size="large"
|
||||
className="mr-2 h-6! w-6! rounded-md border-[0.5px] border-black/5"
|
||||
icon={item.icon}
|
||||
background={item.icon_background}
|
||||
/>
|
||||
<div className="mr-2 text-[13px] font-medium text-gray-800">{item.label}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div
|
||||
className="text-xs text-gray-500"
|
||||
onClick={() => {
|
||||
copy(item.variable || '')
|
||||
setCopied(true)
|
||||
}}
|
||||
>
|
||||
{item.variable}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{copied ? t('copied', { ns: 'appApi' }) : `${item.variable}, ${t('copy', { ns: 'appApi' })}`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
className="mr-1 hidden h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-black/5 group-hover:flex"
|
||||
onClick={() => handleOpenExternalDataToolModal(item, index)}
|
||||
>
|
||||
<Settings01 className="h-4 w-4 text-gray-500" />
|
||||
</div>
|
||||
<div
|
||||
className="group/action hidden h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-[#FEE4E2] group-hover:flex"
|
||||
onClick={() => setExternalDataToolsConfig([...externalDataToolsConfig.slice(0, index), ...externalDataToolsConfig.slice(index + 1)])}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4 text-gray-500 group-hover/action:text-[#D92D20]" />
|
||||
</div>
|
||||
<div className="ml-2 mr-3 hidden h-3.5 w-px bg-gray-200 group-hover:block" />
|
||||
<Switch
|
||||
size="lg"
|
||||
value={item.enabled ?? false}
|
||||
onChange={(enabled: boolean) => handleSaveExternalDataToolModal({ ...item, enabled }, index)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tools
|
||||
@@ -12,7 +12,7 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { AppTypeIcon, AppTypeLabel } from '../../type-selector'
|
||||
|
||||
export type AppCardProps = {
|
||||
type AppCardProps = {
|
||||
app: App
|
||||
canCreate: boolean
|
||||
onCreate: () => void
|
||||
|
||||
@@ -13,7 +13,7 @@ import { toast } from '@/app/components/base/ui/toast'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { formatFileSize } from '@/utils/format'
|
||||
|
||||
export type Props = {
|
||||
type Props = {
|
||||
file: File | undefined
|
||||
updateFile: (file?: File) => void
|
||||
className?: string
|
||||
|
||||
@@ -17,7 +17,7 @@ import EmptyElement from './empty-element'
|
||||
import Filter, { TIME_PERIOD_MAPPING } from './filter'
|
||||
import List from './list'
|
||||
|
||||
export type ILogsProps = {
|
||||
type ILogsProps = {
|
||||
appDetail: App
|
||||
}
|
||||
|
||||
|
||||
@@ -72,17 +72,17 @@ const defaultModalContext: ModalContextState = {
|
||||
setShowTriggerEventsLimitModal: noop,
|
||||
}
|
||||
|
||||
export type MockOverrides = {
|
||||
type MockOverrides = {
|
||||
providerContext?: Partial<typeof defaultProviderContext>
|
||||
modalContext?: Partial<typeof defaultModalContext>
|
||||
}
|
||||
|
||||
export type APIKeyInfoPanelRenderOptions = {
|
||||
type APIKeyInfoPanelRenderOptions = {
|
||||
mockOverrides?: MockOverrides
|
||||
} & Omit<RenderOptions, 'wrapper'>
|
||||
|
||||
// Setup function to configure mocks
|
||||
export function setupMocks(overrides: MockOverrides = {}) {
|
||||
function setupMocks(overrides: MockOverrides = {}) {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
...defaultProviderContext,
|
||||
...overrides.providerContext,
|
||||
@@ -95,7 +95,7 @@ export function setupMocks(overrides: MockOverrides = {}) {
|
||||
}
|
||||
|
||||
// Custom render function
|
||||
export function renderAPIKeyInfoPanel(options: APIKeyInfoPanelRenderOptions = {}) {
|
||||
function renderAPIKeyInfoPanel(options: APIKeyInfoPanelRenderOptions = {}) {
|
||||
const { mockOverrides, ...renderOptions } = options
|
||||
|
||||
setupMocks(mockOverrides)
|
||||
@@ -210,4 +210,4 @@ export function clearAllMocks() {
|
||||
}
|
||||
|
||||
// Export mock functions for external access
|
||||
export { defaultModalContext, mockUseModalContext, mockUseProviderContext }
|
||||
export { defaultModalContext, mockUseModalContext }
|
||||
|
||||
@@ -102,12 +102,12 @@ export type PeriodParamsWithTimeRange = {
|
||||
query?: TimeRange
|
||||
}
|
||||
|
||||
export type IBizChartProps = {
|
||||
type IBizChartProps = {
|
||||
period: PeriodParams
|
||||
id: string
|
||||
}
|
||||
|
||||
export type IChartProps = {
|
||||
type IChartProps = {
|
||||
className?: string
|
||||
basicInfo: { title: string, explanation: string, timePeriod: string }
|
||||
valueKey?: string
|
||||
@@ -508,5 +508,3 @@ export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Chart
|
||||
|
||||
@@ -29,7 +29,7 @@ import Link from '@/next/link'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type ISettingsModalProps = {
|
||||
type ISettingsModalProps = {
|
||||
isChat: boolean
|
||||
appInfo: AppDetailResponse & Partial<AppSSO>
|
||||
isShow: boolean
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { useAllTriggerPlugins } from '@/service/use-triggers'
|
||||
import { canFindTool } from '@/utils'
|
||||
|
||||
export type ITriggerCardProps = {
|
||||
type ITriggerCardProps = {
|
||||
appInfo: AppDetailResponse & Partial<AppSSO>
|
||||
onToggleResult?: (err: Error | null, message?: I18nKeysByPrefix<'common', 'actionMsg.'>) => void
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import ResultTab from './result-tab'
|
||||
|
||||
const MAX_DEPTH = 3
|
||||
|
||||
export type IGenerationItemProps = {
|
||||
type IGenerationItemProps = {
|
||||
isWorkflow?: boolean
|
||||
workflowProcessData?: WorkflowProcess
|
||||
className?: string
|
||||
@@ -67,12 +67,6 @@ export type IGenerationItemProps = {
|
||||
inSidePanel?: boolean
|
||||
}
|
||||
|
||||
export const copyIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.3335 2.33341C9.87598 2.33341 10.1472 2.33341 10.3698 2.39304C10.9737 2.55486 11.4454 3.02657 11.6072 3.63048C11.6668 3.85302 11.6668 4.12426 11.6668 4.66675V10.0334C11.6668 11.0135 11.6668 11.5036 11.4761 11.8779C11.3083 12.2072 11.0406 12.4749 10.7113 12.6427C10.337 12.8334 9.84692 12.8334 8.86683 12.8334H5.1335C4.1534 12.8334 3.66336 12.8334 3.28901 12.6427C2.95973 12.4749 2.69201 12.2072 2.52423 11.8779C2.3335 11.5036 2.3335 11.0135 2.3335 10.0334V4.66675C2.3335 4.12426 2.3335 3.85302 2.39313 3.63048C2.55494 3.02657 3.02665 2.55486 3.63056 2.39304C3.8531 2.33341 4.12435 2.33341 4.66683 2.33341M5.60016 3.50008H8.40016C8.72686 3.50008 8.89021 3.50008 9.01499 3.4365C9.12475 3.38058 9.21399 3.29134 9.26992 3.18158C9.3335 3.05679 9.3335 2.89345 9.3335 2.56675V2.10008C9.3335 1.77338 9.3335 1.61004 9.26992 1.48525C9.21399 1.37549 9.12475 1.28625 9.01499 1.23033C8.89021 1.16675 8.72686 1.16675 8.40016 1.16675H5.60016C5.27347 1.16675 5.11012 1.16675 4.98534 1.23033C4.87557 1.28625 4.78634 1.37549 4.73041 1.48525C4.66683 1.61004 4.66683 1.77338 4.66683 2.10008V2.56675C4.66683 2.89345 4.66683 3.05679 4.73041 3.18158C4.78634 3.29134 4.87557 3.38058 4.98534 3.4365C5.11012 3.50008 5.27347 3.50008 5.60016 3.50008Z" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
isWorkflow,
|
||||
workflowProcessData,
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type INoDataProps = {
|
||||
type INoDataProps = {
|
||||
onStartCreateContent: () => void
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type AppSelectorProps = {
|
||||
type AppSelectorProps = {
|
||||
value: Array<AppModeEnum>
|
||||
onChange: (value: AppSelectorProps['value']) => void
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ const AccessControl = dynamic(() => import('@/app/components/app/app-access-cont
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export type AppCardProps = {
|
||||
type AppCardProps = {
|
||||
app: App
|
||||
onRefresh?: () => void
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-fro
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export type CreateAppCardProps = {
|
||||
type CreateAppCardProps = {
|
||||
className?: string
|
||||
isLoading?: boolean
|
||||
onSuccess?: () => void
|
||||
|
||||
@@ -15,7 +15,7 @@ import { cn } from '@/utils/classnames'
|
||||
import ResultPanel from './result'
|
||||
import TracingPanel from './tracing'
|
||||
|
||||
export type AgentLogDetailProps = {
|
||||
type AgentLogDetailProps = {
|
||||
activeTab?: 'DETAIL' | 'TRACING'
|
||||
conversationID: string
|
||||
log: IChatItem
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default } from './lazy-amplitude-provider'
|
||||
export { resetUser, setUserId, setUserProperties, trackEvent } from './utils'
|
||||
export { setUserId, setUserProperties, trackEvent } from './utils'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { cn } from '@/utils/classnames'
|
||||
|
||||
init({ data })
|
||||
|
||||
export type AnswerIconProps = {
|
||||
type AnswerIconProps = {
|
||||
iconType?: AppIconType | null
|
||||
icon?: string | null
|
||||
background?: string | null
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../styles/globals.css";
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { cn } from '@/utils/classnames'
|
||||
|
||||
init({ data })
|
||||
|
||||
export type AppIconProps = {
|
||||
type AppIconProps = {
|
||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large' | 'xl' | 'xxl'
|
||||
rounded?: boolean
|
||||
iconType?: AppIconType | null
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../styles/globals.css";
|
||||
|
||||
.playIcon {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/play.svg);
|
||||
background-position: center;
|
||||
|
||||
@@ -24,11 +24,11 @@ export type AvatarProps = {
|
||||
onLoadingStatusChange?: (status: ImageLoadingStatus) => void
|
||||
}
|
||||
|
||||
export type AvatarRootProps = React.ComponentPropsWithRef<typeof BaseAvatar.Root> & {
|
||||
type AvatarRootProps = React.ComponentPropsWithRef<typeof BaseAvatar.Root> & {
|
||||
size?: AvatarSize
|
||||
}
|
||||
|
||||
export function AvatarRoot({
|
||||
function AvatarRoot({
|
||||
size = 'md',
|
||||
className,
|
||||
...props
|
||||
@@ -45,9 +45,9 @@ export function AvatarRoot({
|
||||
)
|
||||
}
|
||||
|
||||
export type AvatarImageProps = React.ComponentPropsWithRef<typeof BaseAvatar.Image>
|
||||
type AvatarImageProps = React.ComponentPropsWithRef<typeof BaseAvatar.Image>
|
||||
|
||||
export function AvatarImage({
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: AvatarImageProps) {
|
||||
@@ -59,11 +59,11 @@ export function AvatarImage({
|
||||
)
|
||||
}
|
||||
|
||||
export type AvatarFallbackProps = React.ComponentPropsWithRef<typeof BaseAvatar.Fallback> & {
|
||||
type AvatarFallbackProps = React.ComponentPropsWithRef<typeof BaseAvatar.Fallback> & {
|
||||
size?: AvatarSize
|
||||
}
|
||||
|
||||
export function AvatarFallback({
|
||||
function AvatarFallback({
|
||||
size = 'md',
|
||||
className,
|
||||
...props
|
||||
|
||||
@@ -29,7 +29,7 @@ export const getInputKeys = (value: string) => {
|
||||
return res
|
||||
}
|
||||
|
||||
export type IBlockInputProps = {
|
||||
type IBlockInputProps = {
|
||||
value: string
|
||||
className?: string // wrapper class
|
||||
highLightClassName?: string // class for the highlighted text default is text-blue-500
|
||||
|
||||
@@ -97,7 +97,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export type ChatWithHistoryWrapProps = {
|
||||
type ChatWithHistoryWrapProps = {
|
||||
installedAppInfo?: InstalledApp
|
||||
className?: string
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
||||
export type IRenameModalProps = {
|
||||
type IRenameModalProps = {
|
||||
isShow: boolean
|
||||
saveLoading: boolean
|
||||
name: string
|
||||
|
||||
@@ -26,5 +26,3 @@ export const ChatContext = createContext<ChatContextValue>({
|
||||
})
|
||||
|
||||
export const useChatContext = () => useContext(ChatContext)
|
||||
|
||||
export default ChatContext
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import s from './style.module.css'
|
||||
|
||||
export type ILoadingAnimProps = {
|
||||
type ILoadingAnimProps = {
|
||||
type: 'text' | 'avatar'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@reference "../../../../../styles/globals.css";
|
||||
|
||||
.dot-flashing {
|
||||
position: relative;
|
||||
animation: dot-flashing 1s infinite linear alternate;
|
||||
|
||||
@@ -29,8 +29,6 @@ export type SubmitAnnotationFunc = (
|
||||
content: string,
|
||||
) => Promise<any>
|
||||
|
||||
export type DisplayScene = 'web' | 'console'
|
||||
|
||||
export type ToolInfoInThought = {
|
||||
name: string
|
||||
label: string
|
||||
@@ -151,15 +149,6 @@ export type MessageReplace = {
|
||||
conversation_id: string
|
||||
}
|
||||
|
||||
export type AnnotationReply = {
|
||||
id: string
|
||||
task_id: string
|
||||
answer: string
|
||||
conversation_id: string
|
||||
annotation_id: string
|
||||
annotation_author_name: string
|
||||
}
|
||||
|
||||
export type InputForm = {
|
||||
type: InputVarType
|
||||
label: string
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '../context'
|
||||
import { CssTransform } from '../theme/utils'
|
||||
|
||||
export type IHeaderProps = {
|
||||
type IHeaderProps = {
|
||||
isMobile?: boolean
|
||||
allowResetChat?: boolean
|
||||
customerIcon?: React.ReactNode
|
||||
|
||||
@@ -3,42 +3,16 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import type {
|
||||
ModelConfig,
|
||||
VisionSettings,
|
||||
} from '@/types/app'
|
||||
import type { HumanInputFilledFormData, HumanInputFormData, NodeTracing } from '@/types/workflow'
|
||||
|
||||
export type {
|
||||
Inputs,
|
||||
PromptVariable,
|
||||
|
||||
} from '@/models/debug'
|
||||
export type { VisionFile } from '@/types/app'
|
||||
|
||||
export { TransferMethod } from '@/types/app'
|
||||
|
||||
export type UserInputForm = {
|
||||
default: string
|
||||
label: string
|
||||
required: boolean
|
||||
variable: string
|
||||
}
|
||||
|
||||
export type UserInputFormTextInput = {
|
||||
'text-input': UserInputForm & {
|
||||
max_length: number
|
||||
}
|
||||
}
|
||||
|
||||
export type UserInputFormSelect = {
|
||||
select: UserInputForm & {
|
||||
options: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export type UserInputFormParagraph = {
|
||||
paragraph: UserInputForm
|
||||
}
|
||||
|
||||
export type VisionConfig = VisionSettings
|
||||
|
||||
export type EnableType = {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user