mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 21:23:22 +08:00
Merge branch 'main' into feat/rag-2
This commit is contained in:
commit
40f3524cfe
27
.github/ISSUE_TEMPLATE/chore.yaml
vendored
Normal file
27
.github/ISSUE_TEMPLATE/chore.yaml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: "✨ Refactor"
|
||||||
|
description: Refactor existing code for improved readability and maintainability.
|
||||||
|
title: "[Chore/Refactor] "
|
||||||
|
labels:
|
||||||
|
- refactor
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
placeholder: "Describe the refactor you are proposing."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: motivation
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
placeholder: "Explain why this refactor is necessary."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
placeholder: "Add any other context or screenshots about the request here."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
1
.github/workflows/build-push.yml
vendored
1
.github/workflows/build-push.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
- "deploy/dev"
|
- "deploy/dev"
|
||||||
- "deploy/enterprise"
|
- "deploy/enterprise"
|
||||||
- "build/**"
|
- "build/**"
|
||||||
|
- "release/e-*"
|
||||||
- "deploy/rag-dev"
|
- "deploy/rag-dev"
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
|
|||||||
@ -225,14 +225,15 @@ class AnnotationBatchImportApi(Resource):
|
|||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
|
|
||||||
app_id = str(app_id)
|
app_id = str(app_id)
|
||||||
# get file from request
|
|
||||||
file = request.files["file"]
|
|
||||||
# check file
|
# check file
|
||||||
if "file" not in request.files:
|
if "file" not in request.files:
|
||||||
raise NoFileUploadedError()
|
raise NoFileUploadedError()
|
||||||
|
|
||||||
if len(request.files) > 1:
|
if len(request.files) > 1:
|
||||||
raise TooManyFilesError()
|
raise TooManyFilesError()
|
||||||
|
|
||||||
|
# get file from request
|
||||||
|
file = request.files["file"]
|
||||||
# check file type
|
# check file type
|
||||||
if not file.filename or not file.filename.lower().endswith(".csv"):
|
if not file.filename or not file.filename.lower().endswith(".csv"):
|
||||||
raise ValueError("Invalid file type. Only CSV files are allowed")
|
raise ValueError("Invalid file type. Only CSV files are allowed")
|
||||||
|
|||||||
@ -58,21 +58,38 @@ class InstalledAppsListApi(Resource):
|
|||||||
# filter out apps that user doesn't have access to
|
# filter out apps that user doesn't have access to
|
||||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
res = []
|
|
||||||
app_ids = [installed_app["app"].id for installed_app in installed_app_list]
|
app_ids = [installed_app["app"].id for installed_app in installed_app_list]
|
||||||
webapp_settings = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids)
|
webapp_settings = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids)
|
||||||
|
|
||||||
|
# Pre-filter out apps without setting or with sso_verified
|
||||||
|
filtered_installed_apps = []
|
||||||
|
app_id_to_app_code = {}
|
||||||
|
|
||||||
for installed_app in installed_app_list:
|
for installed_app in installed_app_list:
|
||||||
webapp_setting = webapp_settings.get(installed_app["app"].id)
|
app_id = installed_app["app"].id
|
||||||
if not webapp_setting:
|
webapp_setting = webapp_settings.get(app_id)
|
||||||
|
if not webapp_setting or webapp_setting.access_mode == "sso_verified":
|
||||||
continue
|
continue
|
||||||
if webapp_setting.access_mode == "sso_verified":
|
app_code = AppService.get_app_code_by_id(str(app_id))
|
||||||
continue
|
app_id_to_app_code[app_id] = app_code
|
||||||
app_code = AppService.get_app_code_by_id(str(installed_app["app"].id))
|
filtered_installed_apps.append(installed_app)
|
||||||
if EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(
|
|
||||||
user_id=user_id,
|
app_codes = list(app_id_to_app_code.values())
|
||||||
app_code=app_code,
|
|
||||||
):
|
# Batch permission check
|
||||||
|
permissions = EnterpriseService.WebAppAuth.batch_is_user_allowed_to_access_webapps(
|
||||||
|
user_id=user_id,
|
||||||
|
app_codes=app_codes,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep only allowed apps
|
||||||
|
res = []
|
||||||
|
for installed_app in filtered_installed_apps:
|
||||||
|
app_id = installed_app["app"].id
|
||||||
|
app_code = app_id_to_app_code[app_id]
|
||||||
|
if permissions.get(app_code):
|
||||||
res.append(installed_app)
|
res.append(installed_app)
|
||||||
|
|
||||||
installed_app_list = res
|
installed_app_list = res
|
||||||
logger.debug("installed_app_list: %s, user_id: %s", installed_app_list, user_id)
|
logger.debug("installed_app_list: %s, user_id: %s", installed_app_list, user_id)
|
||||||
|
|
||||||
|
|||||||
@ -208,6 +208,7 @@ class BasePluginClient:
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise PluginDaemonInnerError(code=rep.code, message=rep.message)
|
raise PluginDaemonInnerError(code=rep.code, message=rep.message)
|
||||||
|
|
||||||
|
logger.error("Error in stream reponse for plugin %s", rep.__dict__)
|
||||||
self._handle_plugin_daemon_error(error.error_type, error.message)
|
self._handle_plugin_daemon_error(error.error_type, error.message)
|
||||||
raise ValueError(f"plugin daemon: {rep.message}, code: {rep.code}")
|
raise ValueError(f"plugin daemon: {rep.message}, code: {rep.code}")
|
||||||
if rep.data is None:
|
if rep.data is None:
|
||||||
|
|||||||
@ -2,6 +2,8 @@ from collections.abc import Mapping
|
|||||||
|
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
from extensions.ext_logging import get_request_id
|
||||||
|
|
||||||
|
|
||||||
class PluginDaemonError(Exception):
|
class PluginDaemonError(Exception):
|
||||||
"""Base class for all plugin daemon errors."""
|
"""Base class for all plugin daemon errors."""
|
||||||
@ -11,7 +13,7 @@ class PluginDaemonError(Exception):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
# returns the class name and description
|
# returns the class name and description
|
||||||
return f"{self.__class__.__name__}: {self.description}"
|
return f"req_id: {get_request_id()} {self.__class__.__name__}: {self.description}"
|
||||||
|
|
||||||
|
|
||||||
class PluginDaemonInternalError(PluginDaemonError):
|
class PluginDaemonInternalError(PluginDaemonError):
|
||||||
|
|||||||
@ -5,14 +5,13 @@ from __future__ import annotations
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.model_providers.__base.tokenizers.gpt2_tokenzier import GPT2Tokenizer
|
from core.model_runtime.model_providers.__base.tokenizers.gpt2_tokenizer import GPT2Tokenizer
|
||||||
from core.rag.splitter.text_splitter import (
|
from core.rag.splitter.text_splitter import (
|
||||||
TS,
|
TS,
|
||||||
Collection,
|
Collection,
|
||||||
Literal,
|
Literal,
|
||||||
RecursiveCharacterTextSplitter,
|
RecursiveCharacterTextSplitter,
|
||||||
Set,
|
Set,
|
||||||
TokenTextSplitter,
|
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,14 +44,6 @@ class EnhanceRecursiveCharacterTextSplitter(RecursiveCharacterTextSplitter):
|
|||||||
|
|
||||||
return [len(text) for text in texts]
|
return [len(text) for text in texts]
|
||||||
|
|
||||||
if issubclass(cls, TokenTextSplitter):
|
|
||||||
extra_kwargs = {
|
|
||||||
"model_name": embedding_model_instance.model if embedding_model_instance else "gpt2",
|
|
||||||
"allowed_special": allowed_special,
|
|
||||||
"disallowed_special": disallowed_special,
|
|
||||||
}
|
|
||||||
kwargs = {**kwargs, **extra_kwargs}
|
|
||||||
|
|
||||||
return cls(length_function=_character_encoder, **kwargs)
|
return cls(length_function=_character_encoder, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,6 @@ class Tool(ABC):
|
|||||||
The base class of a tool
|
The base class of a tool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entity: ToolEntity
|
|
||||||
runtime: ToolRuntime
|
|
||||||
|
|
||||||
def __init__(self, entity: ToolEntity, runtime: ToolRuntime) -> None:
|
def __init__(self, entity: ToolEntity, runtime: ToolRuntime) -> None:
|
||||||
self.entity = entity
|
self.entity = entity
|
||||||
self.runtime = runtime
|
self.runtime = runtime
|
||||||
|
|||||||
@ -20,8 +20,6 @@ class BuiltinTool(Tool):
|
|||||||
:param meta: the meta data of a tool call processing
|
:param meta: the meta data of a tool call processing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
provider: str
|
|
||||||
|
|
||||||
def __init__(self, provider: str, **kwargs):
|
def __init__(self, provider: str, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
|
|||||||
@ -21,9 +21,6 @@ API_TOOL_DEFAULT_TIMEOUT = (
|
|||||||
|
|
||||||
|
|
||||||
class ApiTool(Tool):
|
class ApiTool(Tool):
|
||||||
api_bundle: ApiToolBundle
|
|
||||||
provider_id: str
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Api tool
|
Api tool
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -8,23 +8,16 @@ from core.mcp.mcp_client import MCPClient
|
|||||||
from core.mcp.types import ImageContent, TextContent
|
from core.mcp.types import ImageContent, TextContent
|
||||||
from core.tools.__base.tool import Tool
|
from core.tools.__base.tool import Tool
|
||||||
from core.tools.__base.tool_runtime import ToolRuntime
|
from core.tools.__base.tool_runtime import ToolRuntime
|
||||||
from core.tools.entities.tool_entities import ToolEntity, ToolInvokeMessage, ToolParameter, ToolProviderType
|
from core.tools.entities.tool_entities import ToolEntity, ToolInvokeMessage, ToolProviderType
|
||||||
|
|
||||||
|
|
||||||
class MCPTool(Tool):
|
class MCPTool(Tool):
|
||||||
tenant_id: str
|
|
||||||
icon: str
|
|
||||||
runtime_parameters: Optional[list[ToolParameter]]
|
|
||||||
server_url: str
|
|
||||||
provider_id: str
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, entity: ToolEntity, runtime: ToolRuntime, tenant_id: str, icon: str, server_url: str, provider_id: str
|
self, entity: ToolEntity, runtime: ToolRuntime, tenant_id: str, icon: str, server_url: str, provider_id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(entity, runtime)
|
super().__init__(entity, runtime)
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.runtime_parameters = None
|
|
||||||
self.server_url = server_url
|
self.server_url = server_url
|
||||||
self.provider_id = provider_id
|
self.provider_id = provider_id
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,6 @@ from core.tools.entities.tool_entities import ToolEntity, ToolInvokeMessage, Too
|
|||||||
|
|
||||||
|
|
||||||
class PluginTool(Tool):
|
class PluginTool(Tool):
|
||||||
tenant_id: str
|
|
||||||
icon: str
|
|
||||||
plugin_unique_identifier: str
|
|
||||||
runtime_parameters: Optional[list[ToolParameter]]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, entity: ToolEntity, runtime: ToolRuntime, tenant_id: str, icon: str, plugin_unique_identifier: str
|
self, entity: ToolEntity, runtime: ToolRuntime, tenant_id: str, icon: str, plugin_unique_identifier: str
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -21,7 +16,7 @@ class PluginTool(Tool):
|
|||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.plugin_unique_identifier = plugin_unique_identifier
|
self.plugin_unique_identifier = plugin_unique_identifier
|
||||||
self.runtime_parameters = None
|
self.runtime_parameters: Optional[list[ToolParameter]] = None
|
||||||
|
|
||||||
def tool_provider_type(self) -> ToolProviderType:
|
def tool_provider_type(self) -> ToolProviderType:
|
||||||
return ToolProviderType.PLUGIN
|
return ToolProviderType.PLUGIN
|
||||||
|
|||||||
@ -20,8 +20,6 @@ from core.tools.utils.dataset_retriever.dataset_retriever_base_tool import Datas
|
|||||||
|
|
||||||
|
|
||||||
class DatasetRetrieverTool(Tool):
|
class DatasetRetrieverTool(Tool):
|
||||||
retrieval_tool: DatasetRetrieverBaseTool
|
|
||||||
|
|
||||||
def __init__(self, entity: ToolEntity, runtime: ToolRuntime, retrieval_tool: DatasetRetrieverBaseTool) -> None:
|
def __init__(self, entity: ToolEntity, runtime: ToolRuntime, retrieval_tool: DatasetRetrieverBaseTool) -> None:
|
||||||
super().__init__(entity, runtime)
|
super().__init__(entity, runtime)
|
||||||
self.retrieval_tool = retrieval_tool
|
self.retrieval_tool = retrieval_tool
|
||||||
|
|||||||
@ -25,15 +25,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowTool(Tool):
|
class WorkflowTool(Tool):
|
||||||
workflow_app_id: str
|
|
||||||
version: str
|
|
||||||
workflow_entities: dict[str, Any]
|
|
||||||
workflow_call_depth: int
|
|
||||||
thread_pool_id: Optional[str] = None
|
|
||||||
workflow_as_tool_id: str
|
|
||||||
|
|
||||||
label: str
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Workflow tool.
|
Workflow tool.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -136,6 +136,8 @@ def init_app(app: DifyApp):
|
|||||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPSpanExporter
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPSpanExporter
|
||||||
from opentelemetry.instrumentation.celery import CeleryInstrumentor
|
from opentelemetry.instrumentation.celery import CeleryInstrumentor
|
||||||
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
||||||
|
from opentelemetry.instrumentation.redis import RedisInstrumentor
|
||||||
|
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||||
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||||
from opentelemetry.metrics import get_meter, get_meter_provider, set_meter_provider
|
from opentelemetry.metrics import get_meter, get_meter_provider, set_meter_provider
|
||||||
from opentelemetry.propagate import set_global_textmap
|
from opentelemetry.propagate import set_global_textmap
|
||||||
@ -234,6 +236,8 @@ def init_app(app: DifyApp):
|
|||||||
CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument()
|
CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument()
|
||||||
instrument_exception_logging()
|
instrument_exception_logging()
|
||||||
init_sqlalchemy_instrumentor(app)
|
init_sqlalchemy_instrumentor(app)
|
||||||
|
RedisInstrumentor().instrument()
|
||||||
|
RequestsInstrumentor().instrument()
|
||||||
atexit.register(shutdown_tracer)
|
atexit.register(shutdown_tracer)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -895,6 +895,19 @@ class WorkflowAppLog(Base):
|
|||||||
created_by_role = CreatorUserRole(self.created_by_role)
|
created_by_role = CreatorUserRole(self.created_by_role)
|
||||||
return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None
|
return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"tenant_id": self.tenant_id,
|
||||||
|
"app_id": self.app_id,
|
||||||
|
"workflow_id": self.workflow_id,
|
||||||
|
"workflow_run_id": self.workflow_run_id,
|
||||||
|
"created_from": self.created_from,
|
||||||
|
"created_by_role": self.created_by_role,
|
||||||
|
"created_by": self.created_by,
|
||||||
|
"created_at": self.created_at,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConversationVariable(Base):
|
class ConversationVariable(Base):
|
||||||
__tablename__ = "workflow_conversation_variables"
|
__tablename__ = "workflow_conversation_variables"
|
||||||
|
|||||||
@ -49,6 +49,8 @@ dependencies = [
|
|||||||
"opentelemetry-instrumentation==0.48b0",
|
"opentelemetry-instrumentation==0.48b0",
|
||||||
"opentelemetry-instrumentation-celery==0.48b0",
|
"opentelemetry-instrumentation-celery==0.48b0",
|
||||||
"opentelemetry-instrumentation-flask==0.48b0",
|
"opentelemetry-instrumentation-flask==0.48b0",
|
||||||
|
"opentelemetry-instrumentation-redis==0.48b0",
|
||||||
|
"opentelemetry-instrumentation-requests==0.48b0",
|
||||||
"opentelemetry-instrumentation-sqlalchemy==0.48b0",
|
"opentelemetry-instrumentation-sqlalchemy==0.48b0",
|
||||||
"opentelemetry-propagator-b3==1.27.0",
|
"opentelemetry-propagator-b3==1.27.0",
|
||||||
# opentelemetry-proto1.28.0 depends on protobuf (>=5.0,<6.0),
|
# opentelemetry-proto1.28.0 depends on protobuf (>=5.0,<6.0),
|
||||||
|
|||||||
@ -13,7 +13,19 @@ from core.model_runtime.utils.encoders import jsonable_encoder
|
|||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_storage import storage
|
from extensions.ext_storage import storage
|
||||||
from models.account import Tenant
|
from models.account import Tenant
|
||||||
from models.model import App, Conversation, Message
|
from models.model import (
|
||||||
|
App,
|
||||||
|
AppAnnotationHitHistory,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
MessageAgentThought,
|
||||||
|
MessageAnnotation,
|
||||||
|
MessageChain,
|
||||||
|
MessageFeedback,
|
||||||
|
MessageFile,
|
||||||
|
)
|
||||||
|
from models.web import SavedMessage
|
||||||
|
from models.workflow import WorkflowAppLog
|
||||||
from repositories.factory import DifyAPIRepositoryFactory
|
from repositories.factory import DifyAPIRepositoryFactory
|
||||||
from services.billing_service import BillingService
|
from services.billing_service import BillingService
|
||||||
|
|
||||||
@ -21,6 +33,85 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class ClearFreePlanTenantExpiredLogs:
|
class ClearFreePlanTenantExpiredLogs:
|
||||||
|
@classmethod
|
||||||
|
def _clear_message_related_tables(cls, session: Session, tenant_id: str, batch_message_ids: list[str]) -> None:
|
||||||
|
"""
|
||||||
|
Clean up message-related tables to avoid data redundancy.
|
||||||
|
This method cleans up tables that have foreign key relationships with Message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session: Database session, the same with the one in process_tenant method
|
||||||
|
tenant_id: Tenant ID for logging purposes
|
||||||
|
batch_message_ids: List of message IDs to clean up
|
||||||
|
"""
|
||||||
|
if not batch_message_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clean up each related table
|
||||||
|
related_tables = [
|
||||||
|
(MessageFeedback, "message_feedbacks"),
|
||||||
|
(MessageFile, "message_files"),
|
||||||
|
(MessageAnnotation, "message_annotations"),
|
||||||
|
(MessageChain, "message_chains"),
|
||||||
|
(MessageAgentThought, "message_agent_thoughts"),
|
||||||
|
(AppAnnotationHitHistory, "app_annotation_hit_histories"),
|
||||||
|
(SavedMessage, "saved_messages"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for model, table_name in related_tables:
|
||||||
|
# Query records related to expired messages
|
||||||
|
records = (
|
||||||
|
session.query(model)
|
||||||
|
.filter(
|
||||||
|
model.message_id.in_(batch_message_ids), # type: ignore
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(records) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Save records before deletion
|
||||||
|
record_ids = [record.id for record in records]
|
||||||
|
try:
|
||||||
|
record_data = []
|
||||||
|
for record in records:
|
||||||
|
try:
|
||||||
|
if hasattr(record, "to_dict"):
|
||||||
|
record_data.append(record.to_dict())
|
||||||
|
else:
|
||||||
|
# if record doesn't have to_dict method, we need to transform it to dict manually
|
||||||
|
record_dict = {}
|
||||||
|
for column in record.__table__.columns:
|
||||||
|
record_dict[column.name] = getattr(record, column.name)
|
||||||
|
record_data.append(record_dict)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to transform %s record: %s", table_name, record.id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if record_data:
|
||||||
|
storage.save(
|
||||||
|
f"free_plan_tenant_expired_logs/"
|
||||||
|
f"{tenant_id}/{table_name}/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||||
|
f"-{time.time()}.json",
|
||||||
|
json.dumps(
|
||||||
|
jsonable_encoder(record_data),
|
||||||
|
).encode("utf-8"),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to save %s records", table_name)
|
||||||
|
|
||||||
|
session.query(model).filter(
|
||||||
|
model.id.in_(record_ids), # type: ignore
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
f"[{datetime.datetime.now()}] Processed {len(record_ids)} "
|
||||||
|
f"{table_name} records for tenant {tenant_id}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process_tenant(cls, flask_app: Flask, tenant_id: str, days: int, batch: int):
|
def process_tenant(cls, flask_app: Flask, tenant_id: str, days: int, batch: int):
|
||||||
with flask_app.app_context():
|
with flask_app.app_context():
|
||||||
@ -58,6 +149,7 @@ class ClearFreePlanTenantExpiredLogs:
|
|||||||
Message.id.in_(message_ids),
|
Message.id.in_(message_ids),
|
||||||
).delete(synchronize_session=False)
|
).delete(synchronize_session=False)
|
||||||
|
|
||||||
|
cls._clear_message_related_tables(session, tenant_id, message_ids)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
click.echo(
|
click.echo(
|
||||||
@ -199,6 +291,48 @@ class ClearFreePlanTenantExpiredLogs:
|
|||||||
if len(workflow_runs) < batch:
|
if len(workflow_runs) < batch:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
while True:
|
||||||
|
with Session(db.engine).no_autoflush as session:
|
||||||
|
workflow_app_logs = (
|
||||||
|
session.query(WorkflowAppLog)
|
||||||
|
.filter(
|
||||||
|
WorkflowAppLog.tenant_id == tenant_id,
|
||||||
|
WorkflowAppLog.created_at < datetime.datetime.now() - datetime.timedelta(days=days),
|
||||||
|
)
|
||||||
|
.limit(batch)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(workflow_app_logs) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# save workflow app logs
|
||||||
|
storage.save(
|
||||||
|
f"free_plan_tenant_expired_logs/"
|
||||||
|
f"{tenant_id}/workflow_app_logs/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||||
|
f"-{time.time()}.json",
|
||||||
|
json.dumps(
|
||||||
|
jsonable_encoder(
|
||||||
|
[workflow_app_log.to_dict() for workflow_app_log in workflow_app_logs],
|
||||||
|
),
|
||||||
|
).encode("utf-8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
workflow_app_log_ids = [workflow_app_log.id for workflow_app_log in workflow_app_logs]
|
||||||
|
|
||||||
|
# delete workflow app logs
|
||||||
|
session.query(WorkflowAppLog).filter(
|
||||||
|
WorkflowAppLog.id.in_(workflow_app_log_ids),
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
f"[{datetime.datetime.now()}] Processed {len(workflow_app_log_ids)}"
|
||||||
|
f" workflow app logs for tenant {tenant_id}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process(cls, days: int, batch: int, tenant_ids: list[str]):
|
def process(cls, days: int, batch: int, tenant_ids: list[str]):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -52,6 +52,16 @@ class EnterpriseService:
|
|||||||
|
|
||||||
return data.get("result", False)
|
return data.get("result", False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def batch_is_user_allowed_to_access_webapps(cls, user_id: str, app_codes: list[str]):
|
||||||
|
if not app_codes:
|
||||||
|
return {}
|
||||||
|
body = {"userId": user_id, "appCodes": app_codes}
|
||||||
|
data = EnterpriseRequest.send_request("POST", "/webapp/permission/batch", json=body)
|
||||||
|
if not data:
|
||||||
|
raise ValueError("No data found.")
|
||||||
|
return data.get("permissions", {})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_app_access_mode_by_id(cls, app_id: str) -> WebAppSettings:
|
def get_app_access_mode_by_id(cls, app_id: str) -> WebAppSettings:
|
||||||
if not app_id:
|
if not app_id:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,739 @@
|
|||||||
|
import pytest
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
from core.variables.segments import StringSegment
|
||||||
|
from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
|
||||||
|
from models import App, Workflow
|
||||||
|
from models.enums import DraftVariableType
|
||||||
|
from models.workflow import WorkflowDraftVariable
|
||||||
|
from services.workflow_draft_variable_service import (
|
||||||
|
UpdateNotSupportedError,
|
||||||
|
WorkflowDraftVariableService,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestWorkflowDraftVariableService:
|
||||||
|
"""
|
||||||
|
Comprehensive integration tests for WorkflowDraftVariableService using testcontainers.
|
||||||
|
|
||||||
|
This test class covers all major functionality of the WorkflowDraftVariableService:
|
||||||
|
- CRUD operations for workflow draft variables (Create, Read, Update, Delete)
|
||||||
|
- Variable listing and filtering by type (conversation, system, node)
|
||||||
|
- Variable updates and resets with proper validation
|
||||||
|
- Variable deletion operations at different scopes
|
||||||
|
- Special functionality like prefill and conversation ID retrieval
|
||||||
|
- Error handling for various edge cases and invalid operations
|
||||||
|
|
||||||
|
All tests use the testcontainers infrastructure to ensure proper database isolation
|
||||||
|
and realistic testing environment with actual database interactions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_external_service_dependencies(self):
|
||||||
|
"""
|
||||||
|
Mock setup for external service dependencies.
|
||||||
|
|
||||||
|
WorkflowDraftVariableService doesn't have external dependencies that need mocking,
|
||||||
|
so this fixture returns an empty dictionary to maintain consistency with other test classes.
|
||||||
|
This ensures the test structure remains consistent across different service test files.
|
||||||
|
"""
|
||||||
|
# WorkflowDraftVariableService doesn't have external dependencies that need mocking
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _create_test_app(self, db_session_with_containers, mock_external_service_dependencies, fake=None):
|
||||||
|
"""
|
||||||
|
Helper method to create a test app with realistic data for testing.
|
||||||
|
|
||||||
|
This method creates a complete App instance with all required fields populated
|
||||||
|
using Faker for generating realistic test data. The app is configured for
|
||||||
|
workflow mode to support workflow draft variable testing.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_session_with_containers: Database session from testcontainers infrastructure
|
||||||
|
mock_external_service_dependencies: Mock dependencies (unused in this service)
|
||||||
|
fake: Faker instance for generating test data, creates new instance if not provided
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
App: Created test app instance with all required fields populated
|
||||||
|
"""
|
||||||
|
fake = fake or Faker()
|
||||||
|
app = App()
|
||||||
|
app.id = fake.uuid4()
|
||||||
|
app.tenant_id = fake.uuid4()
|
||||||
|
app.name = fake.company()
|
||||||
|
app.description = fake.text()
|
||||||
|
app.mode = "workflow"
|
||||||
|
app.icon_type = "emoji"
|
||||||
|
app.icon = "🤖"
|
||||||
|
app.icon_background = "#FFEAD5"
|
||||||
|
app.enable_site = True
|
||||||
|
app.enable_api = True
|
||||||
|
app.created_by = fake.uuid4()
|
||||||
|
app.updated_by = app.created_by
|
||||||
|
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.add(app)
|
||||||
|
db.session.commit()
|
||||||
|
return app
|
||||||
|
|
||||||
|
def _create_test_workflow(self, db_session_with_containers, app, fake=None):
|
||||||
|
"""
|
||||||
|
Helper method to create a test workflow associated with an app.
|
||||||
|
|
||||||
|
This method creates a Workflow instance using the proper factory method
|
||||||
|
to ensure all required fields are set correctly. The workflow is configured
|
||||||
|
as a draft version with basic graph structure for testing workflow variables.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_session_with_containers: Database session from testcontainers infrastructure
|
||||||
|
app: The app to associate the workflow with
|
||||||
|
fake: Faker instance for generating test data, creates new instance if not provided
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Workflow: Created test workflow instance with proper configuration
|
||||||
|
"""
|
||||||
|
fake = fake or Faker()
|
||||||
|
workflow = Workflow.new(
|
||||||
|
tenant_id=app.tenant_id,
|
||||||
|
app_id=app.id,
|
||||||
|
type="workflow",
|
||||||
|
version="draft",
|
||||||
|
graph='{"nodes": [], "edges": []}',
|
||||||
|
features="{}",
|
||||||
|
created_by=app.created_by,
|
||||||
|
environment_variables=[],
|
||||||
|
conversation_variables=[],
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.add(workflow)
|
||||||
|
db.session.commit()
|
||||||
|
return workflow
|
||||||
|
|
||||||
|
def _create_test_variable(
|
||||||
|
self, db_session_with_containers, app_id, node_id, name, value, variable_type="conversation", fake=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper method to create a test workflow draft variable with proper configuration.
|
||||||
|
|
||||||
|
This method creates different types of variables (conversation, system, node) using
|
||||||
|
the appropriate factory methods to ensure proper initialization. Each variable type
|
||||||
|
has specific requirements and this method handles the creation logic for all types.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_session_with_containers: Database session from testcontainers infrastructure
|
||||||
|
app_id: ID of the app to associate the variable with
|
||||||
|
node_id: ID of the node (or special constants like CONVERSATION_VARIABLE_NODE_ID)
|
||||||
|
name: Name of the variable for identification
|
||||||
|
value: StringSegment value for the variable content
|
||||||
|
variable_type: Type of variable ("conversation", "system", "node") determining creation method
|
||||||
|
fake: Faker instance for generating test data, creates new instance if not provided
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
WorkflowDraftVariable: Created test variable instance with proper type configuration
|
||||||
|
"""
|
||||||
|
fake = fake or Faker()
|
||||||
|
if variable_type == "conversation":
|
||||||
|
# Create conversation variable using the appropriate factory method
|
||||||
|
variable = WorkflowDraftVariable.new_conversation_variable(
|
||||||
|
app_id=app_id,
|
||||||
|
name=name,
|
||||||
|
value=value,
|
||||||
|
description=fake.text(max_nb_chars=20),
|
||||||
|
)
|
||||||
|
elif variable_type == "system":
|
||||||
|
# Create system variable with editable flag and execution context
|
||||||
|
variable = WorkflowDraftVariable.new_sys_variable(
|
||||||
|
app_id=app_id,
|
||||||
|
name=name,
|
||||||
|
value=value,
|
||||||
|
node_execution_id=fake.uuid4(),
|
||||||
|
editable=True,
|
||||||
|
)
|
||||||
|
else: # node variable
|
||||||
|
# Create node variable with visibility and editability settings
|
||||||
|
variable = WorkflowDraftVariable.new_node_variable(
|
||||||
|
app_id=app_id,
|
||||||
|
node_id=node_id,
|
||||||
|
name=name,
|
||||||
|
value=value,
|
||||||
|
node_execution_id=fake.uuid4(),
|
||||||
|
visible=True,
|
||||||
|
editable=True,
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.add(variable)
|
||||||
|
db.session.commit()
|
||||||
|
return variable
|
||||||
|
|
||||||
|
def test_get_variable_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test getting a single variable by ID successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can retrieve a specific variable
|
||||||
|
by its ID and that the returned variable contains the correct data.
|
||||||
|
It ensures the basic CRUD read operation works correctly for workflow draft variables.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
test_value = StringSegment(value=fake.word())
|
||||||
|
variable = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "test_var", test_value, fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_variable = service.get_variable(variable.id)
|
||||||
|
assert retrieved_variable is not None
|
||||||
|
assert retrieved_variable.id == variable.id
|
||||||
|
assert retrieved_variable.name == "test_var"
|
||||||
|
assert retrieved_variable.app_id == app.id
|
||||||
|
assert retrieved_variable.get_value().value == test_value.value
|
||||||
|
|
||||||
|
def test_get_variable_not_found(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test getting a variable that doesn't exist.
|
||||||
|
|
||||||
|
This test verifies that the service returns None when trying to
|
||||||
|
retrieve a variable with a non-existent ID. This ensures proper
|
||||||
|
handling of missing data scenarios.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
non_existent_id = fake.uuid4()
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_variable = service.get_variable(non_existent_id)
|
||||||
|
assert retrieved_variable is None
|
||||||
|
|
||||||
|
def test_get_draft_variables_by_selectors_success(
|
||||||
|
self, db_session_with_containers, mock_external_service_dependencies
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test getting variables by selectors successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can retrieve multiple variables
|
||||||
|
using selector pairs (node_id, variable_name) and returns the correct
|
||||||
|
variables for each selector. This is useful for bulk variable retrieval
|
||||||
|
operations in workflow execution contexts.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
var1_value = StringSegment(value=fake.word())
|
||||||
|
var2_value = StringSegment(value=fake.word())
|
||||||
|
var3_value = StringSegment(value=fake.word())
|
||||||
|
var1 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "var1", var1_value, fake=fake
|
||||||
|
)
|
||||||
|
var2 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "var2", var2_value, fake=fake
|
||||||
|
)
|
||||||
|
var3 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, "test_node_1", "var3", var3_value, "node", fake=fake
|
||||||
|
)
|
||||||
|
selectors = [
|
||||||
|
[CONVERSATION_VARIABLE_NODE_ID, "var1"],
|
||||||
|
[CONVERSATION_VARIABLE_NODE_ID, "var2"],
|
||||||
|
["test_node_1", "var3"],
|
||||||
|
]
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_variables = service.get_draft_variables_by_selectors(app.id, selectors)
|
||||||
|
assert len(retrieved_variables) == 3
|
||||||
|
var_names = [var.name for var in retrieved_variables]
|
||||||
|
assert "var1" in var_names
|
||||||
|
assert "var2" in var_names
|
||||||
|
assert "var3" in var_names
|
||||||
|
for var in retrieved_variables:
|
||||||
|
if var.name == "var1":
|
||||||
|
assert var.get_value().value == var1_value.value
|
||||||
|
elif var.name == "var2":
|
||||||
|
assert var.get_value().value == var2_value.value
|
||||||
|
elif var.name == "var3":
|
||||||
|
assert var.get_value().value == var3_value.value
|
||||||
|
|
||||||
|
def test_list_variables_without_values_success(
|
||||||
|
self, db_session_with_containers, mock_external_service_dependencies
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test listing variables without values successfully with pagination.
|
||||||
|
|
||||||
|
This test verifies that the service can list variables with pagination
|
||||||
|
and that the returned variables don't include their values (for performance).
|
||||||
|
This is important for scenarios where only variable metadata is needed
|
||||||
|
without loading the actual content.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
for i in range(5):
|
||||||
|
test_value = StringSegment(value=fake.numerify("value##"))
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, fake.word(), test_value, fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
result = service.list_variables_without_values(app.id, page=1, limit=3)
|
||||||
|
assert result.total == 5
|
||||||
|
assert len(result.variables) == 3
|
||||||
|
assert result.variables[0].created_at >= result.variables[1].created_at
|
||||||
|
assert result.variables[1].created_at >= result.variables[2].created_at
|
||||||
|
for var in result.variables:
|
||||||
|
assert var.name is not None
|
||||||
|
assert var.app_id == app.id
|
||||||
|
|
||||||
|
def test_list_node_variables_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test listing variables for a specific node successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can filter and return only
|
||||||
|
variables associated with a specific node ID. This is crucial for
|
||||||
|
workflow execution where variables need to be scoped to specific nodes.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
node_id = fake.word()
|
||||||
|
var1_value = StringSegment(value=fake.word())
|
||||||
|
var2_value = StringSegment(value=fake.word())
|
||||||
|
var3_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(db_session_with_containers, app.id, node_id, "var1", var1_value, "node", fake=fake)
|
||||||
|
self._create_test_variable(db_session_with_containers, app.id, node_id, "var2", var3_value, "node", fake=fake)
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, "other_node", "var3", var2_value, "node", fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
result = service.list_node_variables(app.id, node_id)
|
||||||
|
assert len(result.variables) == 2
|
||||||
|
for var in result.variables:
|
||||||
|
assert var.node_id == node_id
|
||||||
|
assert var.app_id == app.id
|
||||||
|
var_names = [var.name for var in result.variables]
|
||||||
|
assert "var1" in var_names
|
||||||
|
assert "var2" in var_names
|
||||||
|
assert "var3" not in var_names
|
||||||
|
|
||||||
|
def test_list_conversation_variables_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test listing conversation variables successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can filter and return only
|
||||||
|
conversation variables, excluding system and node variables.
|
||||||
|
Conversation variables are user-facing variables that can be
|
||||||
|
modified during conversation flows.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
conv_var1_value = StringSegment(value=fake.word())
|
||||||
|
conv_var2_value = StringSegment(value=fake.word())
|
||||||
|
conv_var1 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "conv_var1", conv_var1_value, fake=fake
|
||||||
|
)
|
||||||
|
conv_var2 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "conv_var2", conv_var2_value, fake=fake
|
||||||
|
)
|
||||||
|
sys_var_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, SYSTEM_VARIABLE_NODE_ID, "sys_var", sys_var_value, "system", fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
result = service.list_conversation_variables(app.id)
|
||||||
|
assert len(result.variables) == 2
|
||||||
|
for var in result.variables:
|
||||||
|
assert var.node_id == CONVERSATION_VARIABLE_NODE_ID
|
||||||
|
assert var.app_id == app.id
|
||||||
|
assert var.get_variable_type() == DraftVariableType.CONVERSATION
|
||||||
|
var_names = [var.name for var in result.variables]
|
||||||
|
assert "conv_var1" in var_names
|
||||||
|
assert "conv_var2" in var_names
|
||||||
|
assert "sys_var" not in var_names
|
||||||
|
|
||||||
|
def test_update_variable_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test updating a variable's name and value successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can update both the name and value
|
||||||
|
of an editable variable and that the changes are persisted correctly.
|
||||||
|
It also checks that the last_edited_at timestamp is updated appropriately.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
original_value = StringSegment(value=fake.word())
|
||||||
|
new_value = StringSegment(value=fake.word())
|
||||||
|
variable = self._create_test_variable(
|
||||||
|
db_session_with_containers,
|
||||||
|
app.id,
|
||||||
|
CONVERSATION_VARIABLE_NODE_ID,
|
||||||
|
"original_name",
|
||||||
|
original_value,
|
||||||
|
fake=fake,
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
updated_variable = service.update_variable(variable, name="new_name", value=new_value)
|
||||||
|
assert updated_variable.name == "new_name"
|
||||||
|
assert updated_variable.get_value().value == new_value.value
|
||||||
|
assert updated_variable.last_edited_at is not None
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.refresh(variable)
|
||||||
|
assert variable.name == "new_name"
|
||||||
|
assert variable.get_value().value == new_value.value
|
||||||
|
assert variable.last_edited_at is not None
|
||||||
|
|
||||||
|
def test_update_variable_not_editable(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test that updating a non-editable variable raises an exception.
|
||||||
|
|
||||||
|
This test verifies that the service properly prevents updates to
|
||||||
|
variables that are not marked as editable. This is important for
|
||||||
|
maintaining data integrity and preventing unauthorized modifications
|
||||||
|
to system-controlled variables.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
original_value = StringSegment(value=fake.word())
|
||||||
|
new_value = StringSegment(value=fake.word())
|
||||||
|
variable = WorkflowDraftVariable.new_sys_variable(
|
||||||
|
app_id=app.id,
|
||||||
|
name=fake.word(), # This is typically not editable
|
||||||
|
value=original_value,
|
||||||
|
node_execution_id=fake.uuid4(),
|
||||||
|
editable=False, # Set as non-editable
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.add(variable)
|
||||||
|
db.session.commit()
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
with pytest.raises(UpdateNotSupportedError) as exc_info:
|
||||||
|
service.update_variable(variable, name="new_name", value=new_value)
|
||||||
|
assert "variable not support updating" in str(exc_info.value)
|
||||||
|
assert variable.id in str(exc_info.value)
|
||||||
|
|
||||||
|
def test_reset_conversation_variable_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test resetting conversation variable successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can reset a conversation variable
|
||||||
|
to its default value and clear the last_edited_at timestamp.
|
||||||
|
This functionality is useful for reverting user modifications
|
||||||
|
back to the original workflow configuration.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
workflow = self._create_test_workflow(db_session_with_containers, app, fake=fake)
|
||||||
|
from core.variables.variables import StringVariable
|
||||||
|
|
||||||
|
conv_var = StringVariable(
|
||||||
|
id=fake.uuid4(),
|
||||||
|
name="test_conv_var",
|
||||||
|
value="default_value",
|
||||||
|
selector=[CONVERSATION_VARIABLE_NODE_ID, "test_conv_var"],
|
||||||
|
)
|
||||||
|
workflow.conversation_variables = [conv_var]
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
modified_value = StringSegment(value=fake.word())
|
||||||
|
variable = self._create_test_variable(
|
||||||
|
db_session_with_containers,
|
||||||
|
app.id,
|
||||||
|
CONVERSATION_VARIABLE_NODE_ID,
|
||||||
|
"test_conv_var",
|
||||||
|
modified_value,
|
||||||
|
fake=fake,
|
||||||
|
)
|
||||||
|
variable.last_edited_at = fake.date_time()
|
||||||
|
db.session.commit()
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
reset_variable = service.reset_variable(workflow, variable)
|
||||||
|
assert reset_variable is not None
|
||||||
|
assert reset_variable.get_value().value == "default_value"
|
||||||
|
assert reset_variable.last_edited_at is None
|
||||||
|
db.session.refresh(variable)
|
||||||
|
assert variable.get_value().value == "default_value"
|
||||||
|
assert variable.last_edited_at is None
|
||||||
|
|
||||||
|
def test_delete_variable_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test deleting a single variable successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can delete a specific variable
|
||||||
|
and that it's properly removed from the database. It ensures that
|
||||||
|
the deletion operation is atomic and complete.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
test_value = StringSegment(value=fake.word())
|
||||||
|
variable = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "test_var", test_value, fake=fake
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
assert db.session.query(WorkflowDraftVariable).filter_by(id=variable.id).first() is not None
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
service.delete_variable(variable)
|
||||||
|
assert db.session.query(WorkflowDraftVariable).filter_by(id=variable.id).first() is None
|
||||||
|
|
||||||
|
def test_delete_workflow_variables_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test deleting all variables for a workflow successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can delete all variables
|
||||||
|
associated with a specific app/workflow. This is useful for
|
||||||
|
cleanup operations when workflows are deleted or reset.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
for i in range(3):
|
||||||
|
test_value = StringSegment(value=fake.numerify("value##"))
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, fake.word(), test_value, fake=fake
|
||||||
|
)
|
||||||
|
other_app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
other_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, other_app.id, CONVERSATION_VARIABLE_NODE_ID, fake.word(), other_value, fake=fake
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
app_variables = db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id).all()
|
||||||
|
other_app_variables = db.session.query(WorkflowDraftVariable).filter_by(app_id=other_app.id).all()
|
||||||
|
assert len(app_variables) == 3
|
||||||
|
assert len(other_app_variables) == 1
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
service.delete_workflow_variables(app.id)
|
||||||
|
app_variables_after = db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id).all()
|
||||||
|
other_app_variables_after = db.session.query(WorkflowDraftVariable).filter_by(app_id=other_app.id).all()
|
||||||
|
assert len(app_variables_after) == 0
|
||||||
|
assert len(other_app_variables_after) == 1
|
||||||
|
|
||||||
|
def test_delete_node_variables_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test deleting all variables for a specific node successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can delete all variables
|
||||||
|
associated with a specific node while preserving variables
|
||||||
|
for other nodes and conversation variables. This is important
|
||||||
|
for node-specific cleanup operations in workflow management.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
node_id = fake.word()
|
||||||
|
for i in range(2):
|
||||||
|
test_value = StringSegment(value=fake.numerify("node_value##"))
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, node_id, fake.word(), test_value, "node", fake=fake
|
||||||
|
)
|
||||||
|
other_node_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, "other_node", fake.word(), other_node_value, "node", fake=fake
|
||||||
|
)
|
||||||
|
conv_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, fake.word(), conv_value, fake=fake
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
target_node_variables = db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id, node_id=node_id).all()
|
||||||
|
other_node_variables = (
|
||||||
|
db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id, node_id="other_node").all()
|
||||||
|
)
|
||||||
|
conv_variables = (
|
||||||
|
db.session.query(WorkflowDraftVariable)
|
||||||
|
.filter_by(app_id=app.id, node_id=CONVERSATION_VARIABLE_NODE_ID)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
assert len(target_node_variables) == 2
|
||||||
|
assert len(other_node_variables) == 1
|
||||||
|
assert len(conv_variables) == 1
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
service.delete_node_variables(app.id, node_id)
|
||||||
|
target_node_variables_after = (
|
||||||
|
db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id, node_id=node_id).all()
|
||||||
|
)
|
||||||
|
other_node_variables_after = (
|
||||||
|
db.session.query(WorkflowDraftVariable).filter_by(app_id=app.id, node_id="other_node").all()
|
||||||
|
)
|
||||||
|
conv_variables_after = (
|
||||||
|
db.session.query(WorkflowDraftVariable)
|
||||||
|
.filter_by(app_id=app.id, node_id=CONVERSATION_VARIABLE_NODE_ID)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
assert len(target_node_variables_after) == 0
|
||||||
|
assert len(other_node_variables_after) == 1
|
||||||
|
assert len(conv_variables_after) == 1
|
||||||
|
|
||||||
|
def test_prefill_conversation_variable_default_values_success(
|
||||||
|
self, db_session_with_containers, mock_external_service_dependencies
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test prefill conversation variable default values successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can automatically create
|
||||||
|
conversation variables with default values based on the workflow
|
||||||
|
configuration when none exist. This is important for initializing
|
||||||
|
workflow variables with proper defaults from the workflow definition.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
workflow = self._create_test_workflow(db_session_with_containers, app, fake=fake)
|
||||||
|
from core.variables.variables import StringVariable
|
||||||
|
|
||||||
|
conv_var1 = StringVariable(
|
||||||
|
id=fake.uuid4(),
|
||||||
|
name="conv_var1",
|
||||||
|
value="default_value1",
|
||||||
|
selector=[CONVERSATION_VARIABLE_NODE_ID, "conv_var1"],
|
||||||
|
)
|
||||||
|
conv_var2 = StringVariable(
|
||||||
|
id=fake.uuid4(),
|
||||||
|
name="conv_var2",
|
||||||
|
value="default_value2",
|
||||||
|
selector=[CONVERSATION_VARIABLE_NODE_ID, "conv_var2"],
|
||||||
|
)
|
||||||
|
workflow.conversation_variables = [conv_var1, conv_var2]
|
||||||
|
from extensions.ext_database import db
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
service.prefill_conversation_variable_default_values(workflow)
|
||||||
|
draft_variables = (
|
||||||
|
db.session.query(WorkflowDraftVariable)
|
||||||
|
.filter_by(app_id=app.id, node_id=CONVERSATION_VARIABLE_NODE_ID)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
assert len(draft_variables) == 2
|
||||||
|
var_names = [var.name for var in draft_variables]
|
||||||
|
assert "conv_var1" in var_names
|
||||||
|
assert "conv_var2" in var_names
|
||||||
|
for var in draft_variables:
|
||||||
|
assert var.app_id == app.id
|
||||||
|
assert var.node_id == CONVERSATION_VARIABLE_NODE_ID
|
||||||
|
assert var.editable is True
|
||||||
|
assert var.get_variable_type() == DraftVariableType.CONVERSATION
|
||||||
|
|
||||||
|
def test_get_conversation_id_from_draft_variable_success(
|
||||||
|
self, db_session_with_containers, mock_external_service_dependencies
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test getting conversation ID from draft variable successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can extract the conversation ID
|
||||||
|
from a system variable named "conversation_id". This is important
|
||||||
|
for maintaining conversation context across workflow executions.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
conversation_id = fake.uuid4()
|
||||||
|
conv_id_value = StringSegment(value=conversation_id)
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers,
|
||||||
|
app.id,
|
||||||
|
SYSTEM_VARIABLE_NODE_ID,
|
||||||
|
"conversation_id",
|
||||||
|
conv_id_value,
|
||||||
|
"system",
|
||||||
|
fake=fake,
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_conv_id = service._get_conversation_id_from_draft_variable(app.id)
|
||||||
|
assert retrieved_conv_id == conversation_id
|
||||||
|
|
||||||
|
def test_get_conversation_id_from_draft_variable_not_found(
|
||||||
|
self, db_session_with_containers, mock_external_service_dependencies
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test getting conversation ID when it doesn't exist.
|
||||||
|
|
||||||
|
This test verifies that the service returns None when no
|
||||||
|
conversation_id variable exists for the app. This ensures
|
||||||
|
proper handling of missing conversation context scenarios.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_conv_id = service._get_conversation_id_from_draft_variable(app.id)
|
||||||
|
assert retrieved_conv_id is None
|
||||||
|
|
||||||
|
def test_list_system_variables_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test listing system variables successfully.
|
||||||
|
|
||||||
|
This test verifies that the service can filter and return only
|
||||||
|
system variables, excluding conversation and node variables.
|
||||||
|
System variables are internal variables used by the workflow
|
||||||
|
engine for maintaining state and context.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
sys_var1_value = StringSegment(value=fake.word())
|
||||||
|
sys_var2_value = StringSegment(value=fake.word())
|
||||||
|
sys_var1 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, SYSTEM_VARIABLE_NODE_ID, "sys_var1", sys_var1_value, "system", fake=fake
|
||||||
|
)
|
||||||
|
sys_var2 = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, SYSTEM_VARIABLE_NODE_ID, "sys_var2", sys_var2_value, "system", fake=fake
|
||||||
|
)
|
||||||
|
conv_var_value = StringSegment(value=fake.word())
|
||||||
|
self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "conv_var", conv_var_value, fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
result = service.list_system_variables(app.id)
|
||||||
|
assert len(result.variables) == 2
|
||||||
|
for var in result.variables:
|
||||||
|
assert var.node_id == SYSTEM_VARIABLE_NODE_ID
|
||||||
|
assert var.app_id == app.id
|
||||||
|
assert var.get_variable_type() == DraftVariableType.SYS
|
||||||
|
var_names = [var.name for var in result.variables]
|
||||||
|
assert "sys_var1" in var_names
|
||||||
|
assert "sys_var2" in var_names
|
||||||
|
assert "conv_var" not in var_names
|
||||||
|
|
||||||
|
def test_get_variable_by_name_success(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test getting variables by name successfully for different types.
|
||||||
|
|
||||||
|
This test verifies that the service can retrieve variables by name
|
||||||
|
for different variable types (conversation, system, node). This
|
||||||
|
functionality is important for variable lookup operations during
|
||||||
|
workflow execution and user interactions.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
test_value = StringSegment(value=fake.word())
|
||||||
|
conv_var = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, CONVERSATION_VARIABLE_NODE_ID, "test_conv_var", test_value, fake=fake
|
||||||
|
)
|
||||||
|
sys_var = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, SYSTEM_VARIABLE_NODE_ID, "test_sys_var", test_value, "system", fake=fake
|
||||||
|
)
|
||||||
|
node_var = self._create_test_variable(
|
||||||
|
db_session_with_containers, app.id, "test_node", "test_node_var", test_value, "node", fake=fake
|
||||||
|
)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_conv_var = service.get_conversation_variable(app.id, "test_conv_var")
|
||||||
|
assert retrieved_conv_var is not None
|
||||||
|
assert retrieved_conv_var.name == "test_conv_var"
|
||||||
|
assert retrieved_conv_var.node_id == CONVERSATION_VARIABLE_NODE_ID
|
||||||
|
retrieved_sys_var = service.get_system_variable(app.id, "test_sys_var")
|
||||||
|
assert retrieved_sys_var is not None
|
||||||
|
assert retrieved_sys_var.name == "test_sys_var"
|
||||||
|
assert retrieved_sys_var.node_id == SYSTEM_VARIABLE_NODE_ID
|
||||||
|
retrieved_node_var = service.get_node_variable(app.id, "test_node", "test_node_var")
|
||||||
|
assert retrieved_node_var is not None
|
||||||
|
assert retrieved_node_var.name == "test_node_var"
|
||||||
|
assert retrieved_node_var.node_id == "test_node"
|
||||||
|
|
||||||
|
def test_get_variable_by_name_not_found(self, db_session_with_containers, mock_external_service_dependencies):
|
||||||
|
"""
|
||||||
|
Test getting variables by name when they don't exist.
|
||||||
|
|
||||||
|
This test verifies that the service returns None when trying to
|
||||||
|
retrieve variables by name that don't exist. This ensures proper
|
||||||
|
handling of missing variable scenarios for all variable types.
|
||||||
|
"""
|
||||||
|
fake = Faker()
|
||||||
|
app = self._create_test_app(db_session_with_containers, mock_external_service_dependencies, fake=fake)
|
||||||
|
service = WorkflowDraftVariableService(db_session_with_containers)
|
||||||
|
retrieved_conv_var = service.get_conversation_variable(app.id, "non_existent_conv_var")
|
||||||
|
assert retrieved_conv_var is None
|
||||||
|
retrieved_sys_var = service.get_system_variable(app.id, "non_existent_sys_var")
|
||||||
|
assert retrieved_sys_var is None
|
||||||
|
retrieved_node_var = service.get_node_variable(app.id, "test_node", "non_existent_node_var")
|
||||||
|
assert retrieved_node_var is None
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
import datetime
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs
|
||||||
|
|
||||||
|
|
||||||
|
class TestClearFreePlanTenantExpiredLogs:
|
||||||
|
"""Unit tests for ClearFreePlanTenantExpiredLogs._clear_message_related_tables method."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_session(self):
|
||||||
|
"""Create a mock database session."""
|
||||||
|
session = Mock(spec=Session)
|
||||||
|
session.query.return_value.filter.return_value.all.return_value = []
|
||||||
|
session.query.return_value.filter.return_value.delete.return_value = 0
|
||||||
|
return session
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_storage(self):
|
||||||
|
"""Create a mock storage object."""
|
||||||
|
storage = Mock()
|
||||||
|
storage.save.return_value = None
|
||||||
|
return storage
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_message_ids(self):
|
||||||
|
"""Sample message IDs for testing."""
|
||||||
|
return ["msg-1", "msg-2", "msg-3"]
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_records(self):
|
||||||
|
"""Sample records for testing."""
|
||||||
|
records = []
|
||||||
|
for i in range(3):
|
||||||
|
record = Mock()
|
||||||
|
record.id = f"record-{i}"
|
||||||
|
record.to_dict.return_value = {
|
||||||
|
"id": f"record-{i}",
|
||||||
|
"message_id": f"msg-{i}",
|
||||||
|
"created_at": datetime.datetime.now().isoformat(),
|
||||||
|
}
|
||||||
|
records.append(record)
|
||||||
|
return records
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_empty_message_ids(self, mock_session):
|
||||||
|
"""Test that method returns early when message_ids is empty."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", [])
|
||||||
|
|
||||||
|
# Should not call any database operations
|
||||||
|
mock_session.query.assert_not_called()
|
||||||
|
mock_storage.save.assert_not_called()
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_no_records_found(self, mock_session, sample_message_ids):
|
||||||
|
"""Test when no related records are found."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = []
|
||||||
|
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should call query for each related table but find no records
|
||||||
|
assert mock_session.query.call_count > 0
|
||||||
|
mock_storage.save.assert_not_called()
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_with_records_and_to_dict(
|
||||||
|
self, mock_session, sample_message_ids, sample_records
|
||||||
|
):
|
||||||
|
"""Test when records are found and have to_dict method."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = sample_records
|
||||||
|
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should call to_dict on each record (called once per table, so 7 times total)
|
||||||
|
for record in sample_records:
|
||||||
|
assert record.to_dict.call_count == 7
|
||||||
|
|
||||||
|
# Should save backup data
|
||||||
|
assert mock_storage.save.call_count > 0
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_with_records_no_to_dict(self, mock_session, sample_message_ids):
|
||||||
|
"""Test when records are found but don't have to_dict method."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
# Create records without to_dict method
|
||||||
|
records = []
|
||||||
|
for i in range(2):
|
||||||
|
record = Mock()
|
||||||
|
mock_table = Mock()
|
||||||
|
mock_id_column = Mock()
|
||||||
|
mock_id_column.name = "id"
|
||||||
|
mock_message_id_column = Mock()
|
||||||
|
mock_message_id_column.name = "message_id"
|
||||||
|
mock_table.columns = [mock_id_column, mock_message_id_column]
|
||||||
|
record.__table__ = mock_table
|
||||||
|
record.id = f"record-{i}"
|
||||||
|
record.message_id = f"msg-{i}"
|
||||||
|
del record.to_dict
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
|
# Mock records for first table only, empty for others
|
||||||
|
mock_session.query.return_value.filter.return_value.all.side_effect = [
|
||||||
|
records,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
]
|
||||||
|
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should save backup data even without to_dict
|
||||||
|
assert mock_storage.save.call_count > 0
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_storage_error_continues(
|
||||||
|
self, mock_session, sample_message_ids, sample_records
|
||||||
|
):
|
||||||
|
"""Test that method continues even when storage.save fails."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
mock_storage.save.side_effect = Exception("Storage error")
|
||||||
|
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = sample_records
|
||||||
|
|
||||||
|
# Should not raise exception
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should still delete records even if backup fails
|
||||||
|
assert mock_session.query.return_value.filter.return_value.delete.called
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_serialization_error_continues(self, mock_session, sample_message_ids):
|
||||||
|
"""Test that method continues even when record serialization fails."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
record = Mock()
|
||||||
|
record.id = "record-1"
|
||||||
|
record.to_dict.side_effect = Exception("Serialization error")
|
||||||
|
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = [record]
|
||||||
|
|
||||||
|
# Should not raise exception
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should still delete records even if serialization fails
|
||||||
|
assert mock_session.query.return_value.filter.return_value.delete.called
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_deletion_called(self, mock_session, sample_message_ids, sample_records):
|
||||||
|
"""Test that deletion is called for found records."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = sample_records
|
||||||
|
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
# Should call delete for each table that has records
|
||||||
|
assert mock_session.query.return_value.filter.return_value.delete.called
|
||||||
|
|
||||||
|
def test_clear_message_related_tables_logging_output(
|
||||||
|
self, mock_session, sample_message_ids, sample_records, capsys
|
||||||
|
):
|
||||||
|
"""Test that logging output is generated."""
|
||||||
|
with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage:
|
||||||
|
mock_session.query.return_value.filter.return_value.all.return_value = sample_records
|
||||||
|
|
||||||
|
ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids)
|
||||||
|
|
||||||
|
pass
|
||||||
36
api/uv.lock
generated
36
api/uv.lock
generated
@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.11, <3.13"
|
requires-python = ">=3.11, <3.13"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'",
|
"python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'",
|
||||||
@ -1265,6 +1265,8 @@ dependencies = [
|
|||||||
{ name = "opentelemetry-instrumentation" },
|
{ name = "opentelemetry-instrumentation" },
|
||||||
{ name = "opentelemetry-instrumentation-celery" },
|
{ name = "opentelemetry-instrumentation-celery" },
|
||||||
{ name = "opentelemetry-instrumentation-flask" },
|
{ name = "opentelemetry-instrumentation-flask" },
|
||||||
|
{ name = "opentelemetry-instrumentation-redis" },
|
||||||
|
{ name = "opentelemetry-instrumentation-requests" },
|
||||||
{ name = "opentelemetry-instrumentation-sqlalchemy" },
|
{ name = "opentelemetry-instrumentation-sqlalchemy" },
|
||||||
{ name = "opentelemetry-propagator-b3" },
|
{ name = "opentelemetry-propagator-b3" },
|
||||||
{ name = "opentelemetry-proto" },
|
{ name = "opentelemetry-proto" },
|
||||||
@ -1448,6 +1450,8 @@ requires-dist = [
|
|||||||
{ name = "opentelemetry-instrumentation", specifier = "==0.48b0" },
|
{ name = "opentelemetry-instrumentation", specifier = "==0.48b0" },
|
||||||
{ name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" },
|
{ name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" },
|
||||||
{ name = "opentelemetry-instrumentation-flask", specifier = "==0.48b0" },
|
{ name = "opentelemetry-instrumentation-flask", specifier = "==0.48b0" },
|
||||||
|
{ name = "opentelemetry-instrumentation-redis", specifier = "==0.48b0" },
|
||||||
|
{ name = "opentelemetry-instrumentation-requests", specifier = "==0.48b0" },
|
||||||
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = "==0.48b0" },
|
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = "==0.48b0" },
|
||||||
{ name = "opentelemetry-propagator-b3", specifier = "==1.27.0" },
|
{ name = "opentelemetry-propagator-b3", specifier = "==1.27.0" },
|
||||||
{ name = "opentelemetry-proto", specifier = "==1.27.0" },
|
{ name = "opentelemetry-proto", specifier = "==1.27.0" },
|
||||||
@ -3670,6 +3674,36 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588, upload-time = "2024-08-28T21:26:58.504Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588, upload-time = "2024-08-28T21:26:58.504Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-instrumentation-redis"
|
||||||
|
version = "0.48b0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-instrumentation" },
|
||||||
|
{ name = "opentelemetry-semantic-conventions" },
|
||||||
|
{ name = "wrapt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/70/be/92e98e4c7f275be3d373899a41b0a7d4df64266657d985dbbdb9a54de0d5/opentelemetry_instrumentation_redis-0.48b0.tar.gz", hash = "sha256:61e33e984b4120e1b980d9fba6e9f7ca0c8d972f9970654d8f6e9f27fa115a8c", size = 10511, upload-time = "2024-08-28T21:28:15.061Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/40/892f30d400091106309cc047fd3f6d76a828fedd984a953fd5386b78a2fb/opentelemetry_instrumentation_redis-0.48b0-py3-none-any.whl", hash = "sha256:48c7f2e25cbb30bde749dc0d8b9c74c404c851f554af832956b9630b27f5bcb7", size = 11610, upload-time = "2024-08-28T21:27:18.759Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-instrumentation-requests"
|
||||||
|
version = "0.48b0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-instrumentation" },
|
||||||
|
{ name = "opentelemetry-semantic-conventions" },
|
||||||
|
{ name = "opentelemetry-util-http" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/52/ac/5eb78efde21ff21d0ad5dc8c6cc6a0f8ae482ce8a46293c2f45a628b6166/opentelemetry_instrumentation_requests-0.48b0.tar.gz", hash = "sha256:67ab9bd877a0352ee0db4616c8b4ae59736ddd700c598ed907482d44f4c9a2b3", size = 14120, upload-time = "2024-08-28T21:28:16.933Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/df/0df9226d1b14f29d23c07e6194b9fd5ad50e7d987b7fd13df7dcf718aeb1/opentelemetry_instrumentation_requests-0.48b0-py3-none-any.whl", hash = "sha256:d4f01852121d0bd4c22f14f429654a735611d4f7bf3cf93f244bdf1489b2233d", size = 12366, upload-time = "2024-08-28T21:27:20.771Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opentelemetry-instrumentation-sqlalchemy"
|
name = "opentelemetry-instrumentation-sqlalchemy"
|
||||||
version = "0.48b0"
|
version = "0.48b0"
|
||||||
|
|||||||
@ -538,7 +538,7 @@ services:
|
|||||||
|
|
||||||
milvus-standalone:
|
milvus-standalone:
|
||||||
container_name: milvus-standalone
|
container_name: milvus-standalone
|
||||||
image: milvusdb/milvus:v2.5.0-beta
|
image: milvusdb/milvus:v2.5.15
|
||||||
profiles:
|
profiles:
|
||||||
- milvus
|
- milvus
|
||||||
command: [ 'milvus', 'run', 'standalone' ]
|
command: [ 'milvus', 'run', 'standalone' ]
|
||||||
|
|||||||
@ -1087,7 +1087,7 @@ services:
|
|||||||
|
|
||||||
milvus-standalone:
|
milvus-standalone:
|
||||||
container_name: milvus-standalone
|
container_name: milvus-standalone
|
||||||
image: milvusdb/milvus:v2.5.0-beta
|
image: milvusdb/milvus:v2.5.15
|
||||||
profiles:
|
profiles:
|
||||||
- milvus
|
- milvus
|
||||||
command: [ 'milvus', 'run', 'standalone' ]
|
command: [ 'milvus', 'run', 'standalone' ]
|
||||||
|
|||||||
@ -0,0 +1,156 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { OpikIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||||
|
|
||||||
|
// Mock dependencies to isolate the SVG rendering issue
|
||||||
|
jest.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string) => key,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('SVG Attribute Error Reproduction', () => {
|
||||||
|
// Capture console errors
|
||||||
|
const originalError = console.error
|
||||||
|
let errorMessages: string[] = []
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
errorMessages = []
|
||||||
|
console.error = jest.fn((message) => {
|
||||||
|
errorMessages.push(message)
|
||||||
|
originalError(message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
console.error = originalError
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reproduce inkscape attribute errors when rendering OpikIconBig', () => {
|
||||||
|
console.log('\n=== TESTING OpikIconBig SVG ATTRIBUTE ERRORS ===')
|
||||||
|
|
||||||
|
// Test multiple renders to check for inconsistency
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
console.log(`\nRender attempt ${i + 1}:`)
|
||||||
|
|
||||||
|
const { unmount } = render(<OpikIconBig />)
|
||||||
|
|
||||||
|
// Check for specific inkscape attribute errors
|
||||||
|
const inkscapeErrors = errorMessages.filter(msg =>
|
||||||
|
typeof msg === 'string' && msg.includes('inkscape'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inkscapeErrors.length > 0) {
|
||||||
|
console.log(`Found ${inkscapeErrors.length} inkscape errors:`)
|
||||||
|
inkscapeErrors.forEach((error, index) => {
|
||||||
|
console.log(` ${index + 1}. ${error.substring(0, 100)}...`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('No inkscape errors found in this render')
|
||||||
|
}
|
||||||
|
|
||||||
|
unmount()
|
||||||
|
|
||||||
|
// Clear errors for next iteration
|
||||||
|
errorMessages = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should analyze the SVG structure causing the errors', () => {
|
||||||
|
console.log('\n=== ANALYZING SVG STRUCTURE ===')
|
||||||
|
|
||||||
|
// Import the JSON data directly
|
||||||
|
const iconData = require('@/app/components/base/icons/src/public/tracing/OpikIconBig.json')
|
||||||
|
|
||||||
|
console.log('Icon structure analysis:')
|
||||||
|
console.log('- Root element:', iconData.icon.name)
|
||||||
|
console.log('- Children count:', iconData.icon.children?.length || 0)
|
||||||
|
|
||||||
|
// Find problematic elements
|
||||||
|
const findProblematicElements = (node: any, path = '') => {
|
||||||
|
const problematicElements: any[] = []
|
||||||
|
|
||||||
|
if (node.name && (node.name.includes(':') || node.name.startsWith('sodipodi'))) {
|
||||||
|
problematicElements.push({
|
||||||
|
path,
|
||||||
|
name: node.name,
|
||||||
|
attributes: Object.keys(node.attributes || {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check attributes for inkscape/sodipodi properties
|
||||||
|
if (node.attributes) {
|
||||||
|
const problematicAttrs = Object.keys(node.attributes).filter(attr =>
|
||||||
|
attr.startsWith('inkscape:') || attr.startsWith('sodipodi:'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (problematicAttrs.length > 0) {
|
||||||
|
problematicElements.push({
|
||||||
|
path,
|
||||||
|
name: node.name,
|
||||||
|
problematicAttributes: problematicAttrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach((child: any, index: number) => {
|
||||||
|
problematicElements.push(
|
||||||
|
...findProblematicElements(child, `${path}/${node.name}[${index}]`),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return problematicElements
|
||||||
|
}
|
||||||
|
|
||||||
|
const problematicElements = findProblematicElements(iconData.icon, 'root')
|
||||||
|
|
||||||
|
console.log(`\n🚨 Found ${problematicElements.length} problematic elements:`)
|
||||||
|
problematicElements.forEach((element, index) => {
|
||||||
|
console.log(`\n${index + 1}. Element: ${element.name}`)
|
||||||
|
console.log(` Path: ${element.path}`)
|
||||||
|
if (element.problematicAttributes)
|
||||||
|
console.log(` Problematic attributes: ${element.problematicAttributes.join(', ')}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test the normalizeAttrs function behavior', () => {
|
||||||
|
console.log('\n=== TESTING normalizeAttrs FUNCTION ===')
|
||||||
|
|
||||||
|
const { normalizeAttrs } = require('@/app/components/base/icons/utils')
|
||||||
|
|
||||||
|
const testAttributes = {
|
||||||
|
'inkscape:showpageshadow': '2',
|
||||||
|
'inkscape:pageopacity': '0.0',
|
||||||
|
'inkscape:pagecheckerboard': '0',
|
||||||
|
'inkscape:deskcolor': '#d1d1d1',
|
||||||
|
'sodipodi:docname': 'opik-icon-big.svg',
|
||||||
|
'xmlns:inkscape': 'https://www.inkscape.org/namespaces/inkscape',
|
||||||
|
'xmlns:sodipodi': 'https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
|
||||||
|
'xmlns:svg': 'https://www.w3.org/2000/svg',
|
||||||
|
'data-name': 'Layer 1',
|
||||||
|
'normal-attr': 'value',
|
||||||
|
'class': 'test-class',
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Input attributes:', Object.keys(testAttributes))
|
||||||
|
|
||||||
|
const normalized = normalizeAttrs(testAttributes)
|
||||||
|
|
||||||
|
console.log('Normalized attributes:', Object.keys(normalized))
|
||||||
|
console.log('Normalized values:', normalized)
|
||||||
|
|
||||||
|
// Check if problematic attributes are still present
|
||||||
|
const problematicKeys = Object.keys(normalized).filter(key =>
|
||||||
|
key.toLowerCase().includes('inkscape') || key.toLowerCase().includes('sodipodi'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (problematicKeys.length > 0)
|
||||||
|
console.log(`🚨 PROBLEM: Still found problematic attributes: ${problematicKeys.join(', ')}`)
|
||||||
|
else
|
||||||
|
console.log('✅ No problematic attributes found after normalization')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,12 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
import {
|
|
||||||
RiEqualizer2Line,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import type { PopupProps } from './config-popup'
|
import type { PopupProps } from './config-popup'
|
||||||
import ConfigPopup from './config-popup'
|
import ConfigPopup from './config-popup'
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
@ -17,13 +14,13 @@ type Props = {
|
|||||||
readOnly: boolean
|
readOnly: boolean
|
||||||
className?: string
|
className?: string
|
||||||
hasConfigured: boolean
|
hasConfigured: boolean
|
||||||
controlShowPopup?: number
|
children?: React.ReactNode
|
||||||
} & PopupProps
|
} & PopupProps
|
||||||
|
|
||||||
const ConfigBtn: FC<Props> = ({
|
const ConfigBtn: FC<Props> = ({
|
||||||
className,
|
className,
|
||||||
hasConfigured,
|
hasConfigured,
|
||||||
controlShowPopup,
|
children,
|
||||||
...popupProps
|
...popupProps
|
||||||
}) => {
|
}) => {
|
||||||
const [open, doSetOpen] = useState(false)
|
const [open, doSetOpen] = useState(false)
|
||||||
@ -37,13 +34,6 @@ const ConfigBtn: FC<Props> = ({
|
|||||||
setOpen(!openRef.current)
|
setOpen(!openRef.current)
|
||||||
}, [setOpen])
|
}, [setOpen])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (controlShowPopup)
|
|
||||||
// setOpen(!openRef.current)
|
|
||||||
setOpen(true)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [controlShowPopup])
|
|
||||||
|
|
||||||
if (popupProps.readOnly && !hasConfigured)
|
if (popupProps.readOnly && !hasConfigured)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
@ -52,14 +42,11 @@ const ConfigBtn: FC<Props> = ({
|
|||||||
open={open}
|
open={open}
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
placement='bottom-end'
|
placement='bottom-end'
|
||||||
offset={{
|
offset={12}
|
||||||
mainAxis: 12,
|
|
||||||
crossAxis: hasConfigured ? 8 : 49,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||||
<div className={cn(className, 'rounded-md p-1')}>
|
<div className="select-none">
|
||||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-[11]'>
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
RiArrowDownDoubleLine,
|
RiArrowDownDoubleLine,
|
||||||
|
RiEqualizer2Line,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
@ -180,10 +181,6 @@ const Panel: FC = () => {
|
|||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [controlShowPopup, setControlShowPopup] = useState<number>(0)
|
|
||||||
const showPopup = useCallback(() => {
|
|
||||||
setControlShowPopup(Date.now())
|
|
||||||
}, [setControlShowPopup])
|
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
return (
|
return (
|
||||||
<div className='mb-3 flex items-center justify-between'>
|
<div className='mb-3 flex items-center justify-between'>
|
||||||
@ -196,46 +193,66 @@ const Panel: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center justify-between')}>
|
<div className={cn('flex items-center justify-between')}>
|
||||||
<div
|
{!inUseTracingProvider && (
|
||||||
className={cn(
|
<ConfigButton
|
||||||
'flex cursor-pointer items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
|
appId={appId}
|
||||||
controlShowPopup && 'border-effects-highlight-lightmode-off bg-background-default-lighter',
|
readOnly={readOnly}
|
||||||
)}
|
hasConfigured={false}
|
||||||
onClick={showPopup}
|
enabled={enabled}
|
||||||
>
|
onStatusChange={handleTracingEnabledChange}
|
||||||
{!inUseTracingProvider && (
|
chosenProvider={inUseTracingProvider}
|
||||||
<>
|
onChooseProvider={handleChooseProvider}
|
||||||
|
arizeConfig={arizeConfig}
|
||||||
|
phoenixConfig={phoenixConfig}
|
||||||
|
langSmithConfig={langSmithConfig}
|
||||||
|
langFuseConfig={langFuseConfig}
|
||||||
|
opikConfig={opikConfig}
|
||||||
|
weaveConfig={weaveConfig}
|
||||||
|
aliyunConfig={aliyunConfig}
|
||||||
|
onConfigUpdated={handleTracingConfigUpdated}
|
||||||
|
onConfigRemoved={handleTracingConfigRemoved}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-pointer select-none items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<TracingIcon size='md' />
|
<TracingIcon size='md' />
|
||||||
<div className='system-sm-semibold mx-2 text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
|
<div className='system-sm-semibold mx-2 text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
|
||||||
<div className='flex items-center' onClick={e => e.stopPropagation()}>
|
<div className='rounded-md p-1'>
|
||||||
<ConfigButton
|
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||||
appId={appId}
|
|
||||||
readOnly={readOnly}
|
|
||||||
hasConfigured={false}
|
|
||||||
enabled={enabled}
|
|
||||||
onStatusChange={handleTracingEnabledChange}
|
|
||||||
chosenProvider={inUseTracingProvider}
|
|
||||||
onChooseProvider={handleChooseProvider}
|
|
||||||
arizeConfig={arizeConfig}
|
|
||||||
phoenixConfig={phoenixConfig}
|
|
||||||
langSmithConfig={langSmithConfig}
|
|
||||||
langFuseConfig={langFuseConfig}
|
|
||||||
opikConfig={opikConfig}
|
|
||||||
weaveConfig={weaveConfig}
|
|
||||||
aliyunConfig={aliyunConfig}
|
|
||||||
onConfigUpdated={handleTracingConfigUpdated}
|
|
||||||
onConfigRemoved={handleTracingConfigRemoved}
|
|
||||||
controlShowPopup={controlShowPopup}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Divider type='vertical' className='h-3.5' />
|
<Divider type='vertical' className='h-3.5' />
|
||||||
<div className='rounded-md p-1'>
|
<div className='rounded-md p-1'>
|
||||||
<RiArrowDownDoubleLine className='h-4 w-4 text-text-tertiary' />
|
<RiArrowDownDoubleLine className='h-4 w-4 text-text-tertiary' />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
</ConfigButton>
|
||||||
{hasConfiguredTracing && (
|
)}
|
||||||
<>
|
{hasConfiguredTracing && (
|
||||||
|
<ConfigButton
|
||||||
|
appId={appId}
|
||||||
|
readOnly={readOnly}
|
||||||
|
hasConfigured
|
||||||
|
enabled={enabled}
|
||||||
|
onStatusChange={handleTracingEnabledChange}
|
||||||
|
chosenProvider={inUseTracingProvider}
|
||||||
|
onChooseProvider={handleChooseProvider}
|
||||||
|
arizeConfig={arizeConfig}
|
||||||
|
phoenixConfig={phoenixConfig}
|
||||||
|
langSmithConfig={langSmithConfig}
|
||||||
|
langFuseConfig={langFuseConfig}
|
||||||
|
opikConfig={opikConfig}
|
||||||
|
weaveConfig={weaveConfig}
|
||||||
|
aliyunConfig={aliyunConfig}
|
||||||
|
onConfigUpdated={handleTracingConfigUpdated}
|
||||||
|
onConfigRemoved={handleTracingConfigRemoved}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-pointer select-none items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className='ml-4 mr-1 flex items-center'>
|
<div className='ml-4 mr-1 flex items-center'>
|
||||||
<Indicator color={enabled ? 'green' : 'gray'} />
|
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||||
<div className='system-xs-semibold-uppercase ml-1.5 text-text-tertiary'>
|
<div className='system-xs-semibold-uppercase ml-1.5 text-text-tertiary'>
|
||||||
@ -243,33 +260,14 @@ const Panel: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{InUseProviderIcon && <InUseProviderIcon className='ml-1 h-4' />}
|
{InUseProviderIcon && <InUseProviderIcon className='ml-1 h-4' />}
|
||||||
<Divider type='vertical' className='h-3.5' />
|
<div className='ml-2 rounded-md p-1'>
|
||||||
<div className='flex items-center' onClick={e => e.stopPropagation()}>
|
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||||
<ConfigButton
|
|
||||||
appId={appId}
|
|
||||||
readOnly={readOnly}
|
|
||||||
hasConfigured
|
|
||||||
className='ml-2'
|
|
||||||
enabled={enabled}
|
|
||||||
onStatusChange={handleTracingEnabledChange}
|
|
||||||
chosenProvider={inUseTracingProvider}
|
|
||||||
onChooseProvider={handleChooseProvider}
|
|
||||||
arizeConfig={arizeConfig}
|
|
||||||
phoenixConfig={phoenixConfig}
|
|
||||||
langSmithConfig={langSmithConfig}
|
|
||||||
langFuseConfig={langFuseConfig}
|
|
||||||
opikConfig={opikConfig}
|
|
||||||
weaveConfig={weaveConfig}
|
|
||||||
aliyunConfig={aliyunConfig}
|
|
||||||
onConfigUpdated={handleTracingConfigUpdated}
|
|
||||||
onConfigRemoved={handleTracingConfigRemoved}
|
|
||||||
controlShowPopup={controlShowPopup}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<Divider type='vertical' className='h-3.5' />
|
||||||
)}
|
</div>
|
||||||
</div >
|
</ConfigButton>
|
||||||
</div >
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(Panel)
|
export default React.memo(Panel)
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import DatasetUpdateForm from '@/app/components/datasets/create'
|
import DatasetUpdateForm from '@/app/components/datasets/create'
|
||||||
|
|
||||||
type Props = {}
|
const DatasetCreation = async () => {
|
||||||
|
|
||||||
const DatasetCreation = async (props: Props) => {
|
|
||||||
return (
|
return (
|
||||||
<DatasetUpdateForm />
|
<DatasetUpdateForm />
|
||||||
)
|
)
|
||||||
|
|||||||
@ -88,7 +88,8 @@ const HeaderOptions: FC<Props> = ({
|
|||||||
await clearAllAnnotations(appId)
|
await clearAllAnnotations(appId)
|
||||||
onAdded()
|
onAdded()
|
||||||
}
|
}
|
||||||
catch (_) {
|
catch (e) {
|
||||||
|
console.error(`failed to clear all annotations, ${e}`)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setShowClearConfirm(false)
|
setShowClearConfirm(false)
|
||||||
|
|||||||
@ -39,10 +39,10 @@ const Footer = () => {
|
|||||||
<footer className='relative shrink-0 grow-0 px-12 py-2'>
|
<footer className='relative shrink-0 grow-0 px-12 py-2'>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className='absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full transition-colors duration-200 ease-in-out hover:bg-gray-100 dark:hover:bg-gray-800'
|
className='absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full transition-colors duration-200 ease-in-out hover:bg-components-main-nav-nav-button-bg-active'
|
||||||
aria-label="Close footer"
|
aria-label="Close footer"
|
||||||
>
|
>
|
||||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
<RiCloseLine className='h-4 w-4 text-text-tertiary hover:text-text-secondary' />
|
||||||
</button>
|
</button>
|
||||||
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3>
|
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3>
|
||||||
<p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p>
|
<p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p>
|
||||||
|
|||||||
@ -115,8 +115,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appData?.site.default_language)
|
const setLocaleFromProps = async () => {
|
||||||
changeLanguage(appData.site.default_language)
|
if (appData?.site.default_language)
|
||||||
|
await changeLanguage(appData.site.default_language)
|
||||||
|
}
|
||||||
|
setLocaleFromProps()
|
||||||
}, [appData])
|
}, [appData])
|
||||||
|
|
||||||
const [sidebarCollapseState, setSidebarCollapseState] = useState<boolean>(false)
|
const [sidebarCollapseState, setSidebarCollapseState] = useState<boolean>(false)
|
||||||
|
|||||||
@ -101,15 +101,15 @@ export const useEmbeddedChatbot = () => {
|
|||||||
|
|
||||||
if (localeParam) {
|
if (localeParam) {
|
||||||
// If locale parameter exists in URL, use it instead of default
|
// If locale parameter exists in URL, use it instead of default
|
||||||
changeLanguage(localeParam)
|
await changeLanguage(localeParam)
|
||||||
}
|
}
|
||||||
else if (localeFromSysVar) {
|
else if (localeFromSysVar) {
|
||||||
// If locale is set as a system variable, use that
|
// If locale is set as a system variable, use that
|
||||||
changeLanguage(localeFromSysVar)
|
await changeLanguage(localeFromSysVar)
|
||||||
}
|
}
|
||||||
else if (appInfo?.site.default_language) {
|
else if (appInfo?.site.default_language) {
|
||||||
// Otherwise use the default from app config
|
// Otherwise use the default from app config
|
||||||
changeLanguage(appInfo.site.default_language)
|
await changeLanguage(appInfo.site.default_language)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case SupportUploadFileTypes.custom:
|
||||||
case SupportUploadFileTypes.document: {
|
case SupportUploadFileTypes.document: {
|
||||||
if (fileSize > docSizeLimit) {
|
if (fileSize > docSizeLimit) {
|
||||||
notify({
|
notify({
|
||||||
@ -107,19 +108,6 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case SupportUploadFileTypes.custom: {
|
|
||||||
if (fileSize > docSizeLimit) {
|
|
||||||
notify({
|
|
||||||
type: 'error',
|
|
||||||
message: t('common.fileUploader.uploadFromComputerLimit', {
|
|
||||||
type: SupportUploadFileTypes.document,
|
|
||||||
size: formatFileSize(docSizeLimit),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -231,7 +219,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
url: res.url,
|
url: res.url,
|
||||||
}
|
}
|
||||||
if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
|
if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
|
||||||
notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${file.type}` })
|
notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${newFile.type}` })
|
||||||
handleRemoveFile(uploadingFile.id)
|
handleRemoveFile(uploadingFile.id)
|
||||||
}
|
}
|
||||||
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
|
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
|
||||||
|
|||||||
@ -14,9 +14,26 @@ export type Attrs = {
|
|||||||
|
|
||||||
export function normalizeAttrs(attrs: Attrs = {}): Attrs {
|
export function normalizeAttrs(attrs: Attrs = {}): Attrs {
|
||||||
return Object.keys(attrs).reduce((acc: Attrs, key) => {
|
return Object.keys(attrs).reduce((acc: Attrs, key) => {
|
||||||
|
// Filter out editor metadata attributes before processing
|
||||||
|
if (key.startsWith('inkscape:')
|
||||||
|
|| key.startsWith('sodipodi:')
|
||||||
|
|| key.startsWith('xmlns:inkscape')
|
||||||
|
|| key.startsWith('xmlns:sodipodi')
|
||||||
|
|| key.startsWith('xmlns:svg')
|
||||||
|
|| key === 'data-name')
|
||||||
|
return acc
|
||||||
|
|
||||||
const val = attrs[key]
|
const val = attrs[key]
|
||||||
key = key.replace(/([-]\w)/g, (g: string) => g[1].toUpperCase())
|
key = key.replace(/([-]\w)/g, (g: string) => g[1].toUpperCase())
|
||||||
key = key.replace(/([:]\w)/g, (g: string) => g[1].toUpperCase())
|
key = key.replace(/([:]\w)/g, (g: string) => g[1].toUpperCase())
|
||||||
|
|
||||||
|
// Additional filter after camelCase conversion
|
||||||
|
if (key === 'xmlnsInkscape'
|
||||||
|
|| key === 'xmlnsSodipodi'
|
||||||
|
|| key === 'xmlnsSvg'
|
||||||
|
|| key === 'dataName')
|
||||||
|
return acc
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'class':
|
case 'class':
|
||||||
acc.className = val
|
acc.className = val
|
||||||
|
|||||||
@ -139,7 +139,10 @@ const TagFilter: FC<TagFilterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='border-t-[0.5px] border-divider-regular' />
|
<div className='border-t-[0.5px] border-divider-regular' />
|
||||||
<div className='p-1'>
|
<div className='p-1'>
|
||||||
<div className='flex cursor-pointer items-center gap-2 rounded-lg py-[6px] pl-3 pr-2 hover:bg-state-base-hover' onClick={() => setShowTagManagementModal(true)}>
|
<div className='flex cursor-pointer items-center gap-2 rounded-lg py-[6px] pl-3 pr-2 hover:bg-state-base-hover' onClick={() => {
|
||||||
|
setShowTagManagementModal(true)
|
||||||
|
setOpen(false)
|
||||||
|
}}>
|
||||||
<Tag03 className='h-4 w-4 text-text-tertiary' />
|
<Tag03 className='h-4 w-4 text-text-tertiary' />
|
||||||
<div className='grow truncate text-sm leading-5 text-text-secondary'>
|
<div className='grow truncate text-sm leading-5 text-text-secondary'>
|
||||||
{t('common.tag.manageTags')}
|
{t('common.tag.manageTags')}
|
||||||
|
|||||||
@ -87,7 +87,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
|
|||||||
<div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
<div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
||||||
{isTocExpanded
|
{isTocExpanded
|
||||||
? (
|
? (
|
||||||
<nav className='toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-components-panel-bg p-4 shadow-md'>
|
<nav className='toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg border border-components-panel-border bg-components-panel-bg p-4 shadow-md'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h3 className='text-lg font-semibold text-text-primary'>{t('appApi.develop.toc')}</h3>
|
<h3 className='text-lg font-semibold text-text-primary'>{t('appApi.develop.toc')}</h3>
|
||||||
<button
|
<button
|
||||||
@ -115,7 +115,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
|
|||||||
: (
|
: (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsTocExpanded(true)}
|
onClick={() => setIsTocExpanded(true)}
|
||||||
className='flex h-10 w-10 items-center justify-center rounded-full bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover'
|
className='flex h-10 w-10 items-center justify-center rounded-full border border-components-panel-border bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover'
|
||||||
>
|
>
|
||||||
<RiListUnordered className='h-6 w-6 text-components-button-secondary-text' />
|
<RiListUnordered className='h-6 w-6 text-components-button-secondary-text' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-text'
|
url='/datasets/{dataset_id}/document/create-by-text'
|
||||||
@ -163,7 +163,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-file'
|
url='/datasets/{dataset_id}/document/create-by-file'
|
||||||
@ -294,7 +294,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -400,7 +400,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -472,7 +472,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -553,7 +553,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -714,7 +714,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -751,7 +751,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
||||||
@ -853,7 +853,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
||||||
@ -952,7 +952,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
||||||
@ -1007,7 +1007,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -1047,7 +1047,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents'
|
url='/datasets/{dataset_id}/documents'
|
||||||
@ -1122,7 +1122,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -1245,7 +1245,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
___
|
___
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/status/{action}'
|
url='/datasets/{dataset_id}/documents/status/{action}'
|
||||||
@ -1302,7 +1302,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1388,7 +1388,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1476,7 +1476,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1546,7 +1546,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1590,7 +1590,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1679,7 +1679,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1750,7 +1750,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1827,7 +1827,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1873,7 +1873,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1947,7 +1947,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||||
@ -1998,7 +1998,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/retrieve'
|
url='/datasets/{dataset_id}/retrieve'
|
||||||
@ -2177,7 +2177,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -2224,7 +2224,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2273,7 +2273,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2306,7 +2306,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
||||||
@ -2339,7 +2339,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/metadata'
|
url='/datasets/{dataset_id}/documents/metadata'
|
||||||
@ -2378,7 +2378,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -2424,7 +2424,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/workspaces/current/models/model-types/text-embedding'
|
url='/workspaces/current/models/model-types/text-embedding'
|
||||||
@ -2528,7 +2528,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
Okay, I will translate the Chinese text in your document while keeping all formatting and code content unchanged.
|
Okay, I will translate the Chinese text in your document while keeping all formatting and code content unchanged.
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -2574,7 +2574,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2615,7 +2615,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2662,7 +2662,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -2704,7 +2704,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/binding'
|
url='/datasets/tags/binding'
|
||||||
@ -2746,7 +2746,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/unbinding'
|
url='/datasets/tags/unbinding'
|
||||||
@ -2789,7 +2789,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/<uuid:dataset_id>/tags'
|
url='/datasets/<uuid:dataset_id>/tags'
|
||||||
@ -2837,7 +2837,7 @@ Okay, I will translate the Chinese text in your document while keeping all forma
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-text'
|
url='/datasets/{dataset_id}/document/create-by-text'
|
||||||
@ -163,7 +163,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-file'
|
url='/datasets/{dataset_id}/document/create-by-file'
|
||||||
@ -294,7 +294,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -399,7 +399,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -471,7 +471,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -508,7 +508,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
||||||
@ -610,7 +610,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
||||||
@ -709,7 +709,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
||||||
@ -764,7 +764,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -804,7 +804,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents'
|
url='/datasets/{dataset_id}/documents'
|
||||||
@ -879,7 +879,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -1002,7 +1002,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
___
|
___
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -1060,7 +1060,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1146,7 +1146,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1234,7 +1234,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1304,7 +1304,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
method='DELETE'
|
method='DELETE'
|
||||||
@ -1347,7 +1347,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
method='POST'
|
method='POST'
|
||||||
@ -1435,7 +1435,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1506,7 +1506,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1583,7 +1583,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1629,7 +1629,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1703,7 +1703,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||||
@ -1754,7 +1754,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/retrieve'
|
url='/datasets/{dataset_id}/retrieve'
|
||||||
@ -1933,7 +1933,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -1980,7 +1980,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2029,7 +2029,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2062,7 +2062,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
||||||
@ -2095,7 +2095,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/metadata'
|
url='/datasets/{dataset_id}/documents/metadata'
|
||||||
@ -2136,7 +2136,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -2182,7 +2182,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
method='POST'
|
method='POST'
|
||||||
@ -2226,7 +2226,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2267,7 +2267,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2314,7 +2314,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -2356,7 +2356,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/binding'
|
url='/datasets/tags/binding'
|
||||||
@ -2398,7 +2398,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/unbinding'
|
url='/datasets/tags/unbinding'
|
||||||
@ -2441,7 +2441,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/<uuid:dataset_id>/tags'
|
url='/datasets/<uuid:dataset_id>/tags'
|
||||||
@ -2489,7 +2489,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-text'
|
url='/datasets/{dataset_id}/document/create-by-text'
|
||||||
@ -167,7 +167,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/document/create-by-file'
|
url='/datasets/{dataset_id}/document/create-by-file'
|
||||||
@ -298,7 +298,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -403,7 +403,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets'
|
url='/datasets'
|
||||||
@ -475,7 +475,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -556,7 +556,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -721,7 +721,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}'
|
url='/datasets/{dataset_id}'
|
||||||
@ -758,7 +758,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-text'
|
||||||
@ -860,7 +860,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/update-by-file'
|
||||||
@ -959,7 +959,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
url='/datasets/{dataset_id}/documents/{batch}/indexing-status'
|
||||||
@ -1014,7 +1014,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -1054,7 +1054,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents'
|
url='/datasets/{dataset_id}/documents'
|
||||||
@ -1129,7 +1129,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}'
|
||||||
@ -1252,7 +1252,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
___
|
___
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -1310,7 +1310,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1396,7 +1396,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments'
|
||||||
@ -1484,7 +1484,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1528,7 +1528,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}'
|
||||||
@ -1598,7 +1598,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
method='POST'
|
method='POST'
|
||||||
@ -1687,7 +1687,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1758,7 +1758,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||||
@ -1835,7 +1835,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1881,7 +1881,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
@ -1915,7 +1915,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||||
@ -1989,7 +1989,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||||
@ -2040,7 +2040,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/retrieve'
|
url='/datasets/{dataset_id}/retrieve'
|
||||||
@ -2219,7 +2219,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -2266,7 +2266,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2315,7 +2315,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
url='/datasets/{dataset_id}/metadata/{metadata_id}'
|
||||||
@ -2348,7 +2348,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
url='/datasets/{dataset_id}/metadata/built-in/{action}'
|
||||||
@ -2381,7 +2381,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/documents/metadata'
|
url='/datasets/{dataset_id}/documents/metadata'
|
||||||
@ -2422,7 +2422,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/{dataset_id}/metadata'
|
url='/datasets/{dataset_id}/metadata'
|
||||||
@ -2468,7 +2468,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/workspaces/current/models/model-types/text-embedding'
|
url='/workspaces/current/models/model-types/text-embedding'
|
||||||
@ -2572,7 +2572,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2617,7 +2617,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2658,7 +2658,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags'
|
url='/datasets/tags'
|
||||||
@ -2705,7 +2705,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
@ -2747,7 +2747,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/binding'
|
url='/datasets/tags/binding'
|
||||||
@ -2789,7 +2789,7 @@ ___
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/tags/unbinding'
|
url='/datasets/tags/unbinding'
|
||||||
@ -2832,7 +2832,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
url='/datasets/<uuid:dataset_id>/tags'
|
url='/datasets/<uuid:dataset_id>/tags'
|
||||||
@ -2880,7 +2880,7 @@ ___
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
<hr className='ml-0 mr-0' />
|
<hr style={{ marginLeft: 0, marginRight: 0, width: '100%', maxWidth: '100%' }} />
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
|
|||||||
@ -87,7 +87,7 @@ const Doc = ({ appDetail }: IDocProps) => {
|
|||||||
<div className={`fixed right-8 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
<div className={`fixed right-8 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
||||||
{isTocExpanded
|
{isTocExpanded
|
||||||
? (
|
? (
|
||||||
<nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-components-panel-bg p-4 shadow-md">
|
<nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg border border-components-panel-border bg-components-panel-bg p-4 shadow-md">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-text-primary">{t('appApi.develop.toc')}</h3>
|
<h3 className="text-lg font-semibold text-text-primary">{t('appApi.develop.toc')}</h3>
|
||||||
<button
|
<button
|
||||||
@ -115,7 +115,7 @@ const Doc = ({ appDetail }: IDocProps) => {
|
|||||||
: (
|
: (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsTocExpanded(true)}
|
onClick={() => setIsTocExpanded(true)}
|
||||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover"
|
className="flex h-10 w-10 items-center justify-center rounded-full border border-components-panel-border bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover"
|
||||||
>
|
>
|
||||||
<RiListUnordered className="h-6 w-6 text-components-button-secondary-text" />
|
<RiListUnordered className="h-6 w-6 text-components-button-secondary-text" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -371,7 +371,7 @@ const TextGeneration: FC<IMainProps> = ({
|
|||||||
setAppId(appId)
|
setAppId(appId)
|
||||||
setSiteInfo(siteInfo as SiteInfo)
|
setSiteInfo(siteInfo as SiteInfo)
|
||||||
setCustomConfig(custom_config)
|
setCustomConfig(custom_config)
|
||||||
changeLanguage(siteInfo.default_language)
|
await changeLanguage(siteInfo.default_language)
|
||||||
|
|
||||||
const { user_input_form, more_like_this, file_upload, text_to_speech }: any = appParams
|
const { user_input_form, more_like_this, file_upload, text_to_speech }: any = appParams
|
||||||
setVisionConfig({
|
setVisionConfig({
|
||||||
|
|||||||
@ -134,7 +134,8 @@ const CustomEdge = ({
|
|||||||
style={{
|
style={{
|
||||||
stroke,
|
stroke,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
opacity: data._waitingRun ? 0.7 : 1,
|
opacity: data._dimmed ? 0.3 : (data._waitingRun ? 0.7 : 1),
|
||||||
|
strokeDasharray: data._isTemp ? '8 8' : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { MouseEvent } from 'react'
|
import type { MouseEvent } from 'react'
|
||||||
import { useCallback, useRef } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import type {
|
import type {
|
||||||
@ -62,6 +62,7 @@ import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history
|
|||||||
import { useNodesMetaData } from './use-nodes-meta-data'
|
import { useNodesMetaData } from './use-nodes-meta-data'
|
||||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||||
import useInspectVarsCrud from './use-inspect-vars-crud'
|
import useInspectVarsCrud from './use-inspect-vars-crud'
|
||||||
|
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
|
||||||
|
|
||||||
export const useNodesInteractions = () => {
|
export const useNodesInteractions = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -1564,6 +1565,135 @@ export const useNodesInteractions = () => {
|
|||||||
setNodes(nodes)
|
setNodes(nodes)
|
||||||
}, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
}, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
|
||||||
|
|
||||||
|
const [isDimming, setIsDimming] = useState(false)
|
||||||
|
/** Add opacity-30 to all nodes except the nodeId */
|
||||||
|
const dimOtherNodes = useCallback(() => {
|
||||||
|
if (isDimming)
|
||||||
|
return
|
||||||
|
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
||||||
|
const nodes = getNodes()
|
||||||
|
|
||||||
|
const selectedNode = nodes.find(n => n.data.selected)
|
||||||
|
if (!selectedNode)
|
||||||
|
return
|
||||||
|
|
||||||
|
setIsDimming(true)
|
||||||
|
|
||||||
|
// const workflowNodes = useStore(s => s.getNodes())
|
||||||
|
const workflowNodes = nodes
|
||||||
|
|
||||||
|
const usedVars = getNodeUsedVars(selectedNode)
|
||||||
|
const dependencyNodes: Node[] = []
|
||||||
|
usedVars.forEach((valueSelector) => {
|
||||||
|
const node = workflowNodes.find(node => node.id === valueSelector?.[0])
|
||||||
|
if (node) {
|
||||||
|
if (!dependencyNodes.includes(node))
|
||||||
|
dependencyNodes.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges)
|
||||||
|
for (let currIdx = 0; currIdx < outgoers.length; currIdx++) {
|
||||||
|
const node = outgoers[currIdx]
|
||||||
|
const outgoersForNode = getOutgoers(node, nodes as Node[], edges)
|
||||||
|
outgoersForNode.forEach((item) => {
|
||||||
|
const existed = outgoers.some(v => v.id === item.id)
|
||||||
|
if (!existed)
|
||||||
|
outgoers.push(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependentNodes: Node[] = []
|
||||||
|
outgoers.forEach((node) => {
|
||||||
|
const usedVars = getNodeUsedVars(node)
|
||||||
|
const used = usedVars.some(v => v?.[0] === selectedNode.id)
|
||||||
|
if (used) {
|
||||||
|
const existed = dependentNodes.some(v => v.id === node.id)
|
||||||
|
if (!existed)
|
||||||
|
dependentNodes.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dimNodes = [...dependencyNodes, ...dependentNodes, selectedNode]
|
||||||
|
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((n) => {
|
||||||
|
const dimNode = dimNodes.find(v => v.id === n.id)
|
||||||
|
if (!dimNode)
|
||||||
|
n.data._dimmed = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setNodes(newNodes)
|
||||||
|
|
||||||
|
const tempEdges: Edge[] = []
|
||||||
|
|
||||||
|
dependencyNodes.forEach((n) => {
|
||||||
|
tempEdges.push({
|
||||||
|
id: `tmp_${n.id}-source-${selectedNode.id}-target`,
|
||||||
|
type: CUSTOM_EDGE,
|
||||||
|
source: n.id,
|
||||||
|
sourceHandle: 'source_tmp',
|
||||||
|
target: selectedNode.id,
|
||||||
|
targetHandle: 'target_tmp',
|
||||||
|
animated: true,
|
||||||
|
data: {
|
||||||
|
sourceType: n.data.type,
|
||||||
|
targetType: selectedNode.data.type,
|
||||||
|
_isTemp: true,
|
||||||
|
_connectedNodeIsHovering: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
dependentNodes.forEach((n) => {
|
||||||
|
tempEdges.push({
|
||||||
|
id: `tmp_${selectedNode.id}-source-${n.id}-target`,
|
||||||
|
type: CUSTOM_EDGE,
|
||||||
|
source: selectedNode.id,
|
||||||
|
sourceHandle: 'source_tmp',
|
||||||
|
target: n.id,
|
||||||
|
targetHandle: 'target_tmp',
|
||||||
|
animated: true,
|
||||||
|
data: {
|
||||||
|
sourceType: selectedNode.data.type,
|
||||||
|
targetType: n.data.type,
|
||||||
|
_isTemp: true,
|
||||||
|
_connectedNodeIsHovering: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const newEdges = produce(edges, (draft) => {
|
||||||
|
draft.forEach((e) => {
|
||||||
|
e.data._dimmed = true
|
||||||
|
})
|
||||||
|
draft.push(...tempEdges)
|
||||||
|
})
|
||||||
|
setEdges(newEdges)
|
||||||
|
}, [isDimming, store])
|
||||||
|
|
||||||
|
/** Restore all nodes to full opacity */
|
||||||
|
const undimAllNodes = useCallback(() => {
|
||||||
|
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
||||||
|
const nodes = getNodes()
|
||||||
|
setIsDimming(false)
|
||||||
|
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((n) => {
|
||||||
|
n.data._dimmed = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setNodes(newNodes)
|
||||||
|
|
||||||
|
const newEdges = produce(edges.filter(e => !e.data._isTemp), (draft) => {
|
||||||
|
draft.forEach((e) => {
|
||||||
|
e.data._dimmed = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setEdges(newEdges)
|
||||||
|
}, [store])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleNodeDragStart,
|
handleNodeDragStart,
|
||||||
handleNodeDrag,
|
handleNodeDrag,
|
||||||
@ -1588,5 +1718,7 @@ export const useNodesInteractions = () => {
|
|||||||
handleNodeDisconnect,
|
handleNodeDisconnect,
|
||||||
handleHistoryBack,
|
handleHistoryBack,
|
||||||
handleHistoryForward,
|
handleHistoryForward,
|
||||||
|
dimOtherNodes,
|
||||||
|
undimAllNodes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,10 +131,34 @@ export const useSelectionInteractions = () => {
|
|||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
|
const handleSelectionContextMenu = useCallback((e: MouseEvent) => {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
if (!target.classList.contains('react-flow__nodesselection-rect'))
|
||||||
|
return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
const container = document.querySelector('#workflow-container')
|
||||||
|
const { x, y } = container!.getBoundingClientRect()
|
||||||
|
workflowStore.setState({
|
||||||
|
selectionMenu: {
|
||||||
|
top: e.clientY - y,
|
||||||
|
left: e.clientX - x,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [workflowStore])
|
||||||
|
|
||||||
|
const handleSelectionContextmenuCancel = useCallback(() => {
|
||||||
|
workflowStore.setState({
|
||||||
|
selectionMenu: undefined,
|
||||||
|
})
|
||||||
|
}, [workflowStore])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleSelectionStart,
|
handleSelectionStart,
|
||||||
handleSelectionChange,
|
handleSelectionChange,
|
||||||
handleSelectionDrag,
|
handleSelectionDrag,
|
||||||
handleSelectionCancel,
|
handleSelectionCancel,
|
||||||
|
handleSelectionContextMenu,
|
||||||
|
handleSelectionContextmenuCancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export const useShortcuts = (): void => {
|
|||||||
handleNodesDelete,
|
handleNodesDelete,
|
||||||
handleHistoryBack,
|
handleHistoryBack,
|
||||||
handleHistoryForward,
|
handleHistoryForward,
|
||||||
|
dimOtherNodes,
|
||||||
|
undimAllNodes,
|
||||||
} = useNodesInteractions()
|
} = useNodesInteractions()
|
||||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||||
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
||||||
@ -211,4 +213,35 @@ export const useShortcuts = (): void => {
|
|||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
useCapture: true,
|
useCapture: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Shift ↓
|
||||||
|
useKeyPress(
|
||||||
|
'shift',
|
||||||
|
(e) => {
|
||||||
|
console.log('Shift down', e)
|
||||||
|
if (shouldHandleShortcut(e))
|
||||||
|
dimOtherNodes()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
events: ['keydown'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shift ↑
|
||||||
|
useKeyPress(
|
||||||
|
(e) => {
|
||||||
|
return e.key === 'Shift'
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
if (shouldHandleShortcut(e))
|
||||||
|
undimAllNodes()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
events: ['keyup'],
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,7 @@ import HelpLine from './help-line'
|
|||||||
import CandidateNode from './candidate-node'
|
import CandidateNode from './candidate-node'
|
||||||
import PanelContextmenu from './panel-contextmenu'
|
import PanelContextmenu from './panel-contextmenu'
|
||||||
import NodeContextmenu from './node-contextmenu'
|
import NodeContextmenu from './node-contextmenu'
|
||||||
|
import SelectionContextmenu from './selection-contextmenu'
|
||||||
import SyncingDataModal from './syncing-data-modal'
|
import SyncingDataModal from './syncing-data-modal'
|
||||||
import LimitTips from './limit-tips'
|
import LimitTips from './limit-tips'
|
||||||
import {
|
import {
|
||||||
@ -266,6 +267,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
handleSelectionStart,
|
handleSelectionStart,
|
||||||
handleSelectionChange,
|
handleSelectionChange,
|
||||||
handleSelectionDrag,
|
handleSelectionDrag,
|
||||||
|
handleSelectionContextMenu,
|
||||||
} = useSelectionInteractions()
|
} = useSelectionInteractions()
|
||||||
const {
|
const {
|
||||||
handlePaneContextMenu,
|
handlePaneContextMenu,
|
||||||
@ -316,6 +318,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
<Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
|
<Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
|
||||||
<PanelContextmenu />
|
<PanelContextmenu />
|
||||||
<NodeContextmenu />
|
<NodeContextmenu />
|
||||||
|
<SelectionContextmenu />
|
||||||
<HelpLine />
|
<HelpLine />
|
||||||
{
|
{
|
||||||
!!showConfirm && (
|
!!showConfirm && (
|
||||||
@ -352,6 +355,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
onSelectionDrag={handleSelectionDrag}
|
onSelectionDrag={handleSelectionDrag}
|
||||||
onPaneContextMenu={handlePaneContextMenu}
|
onPaneContextMenu={handlePaneContextMenu}
|
||||||
|
onSelectionContextMenu={handleSelectionContextMenu}
|
||||||
connectionLineComponent={CustomConnectionLine}
|
connectionLineComponent={CustomConnectionLine}
|
||||||
// TODO: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
|
// TODO: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
|
||||||
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
|
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
export enum TabType {
|
export enum TabType {
|
||||||
settings = 'settings',
|
settings = 'settings',
|
||||||
lastRun = 'lastRun',
|
lastRun = 'lastRun',
|
||||||
|
relations = 'relations',
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@ -143,6 +143,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
||||||
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
||||||
data._waitingRun && 'opacity-70',
|
data._waitingRun && 'opacity-70',
|
||||||
|
data._dimmed && 'opacity-30',
|
||||||
)}
|
)}
|
||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { AnswerNodeType } from './types'
|
import type { AnswerNodeType } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: AnswerNodeType) => {
|
export const checkNodeValid = (_payload: AnswerNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { AssignerNodeType } from './types'
|
import type { AssignerNodeType } from './types'
|
||||||
import { AssignerNodeInputType, WriteMode } from './types'
|
import { AssignerNodeInputType, WriteMode } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: AssignerNodeType) => {
|
export const checkNodeValid = (_payload: AssignerNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { EndNodeType } from './types'
|
import type { EndNodeType } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: EndNodeType) => {
|
export const checkNodeValid = (_payload: EndNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,7 +202,7 @@ const ConditionItem = ({
|
|||||||
onRemoveCondition?.(caseId, condition.id)
|
onRemoveCondition?.(caseId, condition.id)
|
||||||
}, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
|
}, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
|
||||||
|
|
||||||
const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
|
const handleVarChange = useCallback((valueSelector: ValueSelector, _varItem: Var) => {
|
||||||
const resolvedVarType = getVarType({
|
const resolvedVarType = getVarType({
|
||||||
valueSelector,
|
valueSelector,
|
||||||
availableNodes,
|
availableNodes,
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` })
|
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` })
|
||||||
handleCompletionParamsChange(filtered)
|
handleCompletionParamsChange(filtered)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch {
|
||||||
Toast.notify({ type: 'error', message: t('common.error') })
|
Toast.notify({ type: 'error', message: t('common.error') })
|
||||||
handleCompletionParamsChange({})
|
handleCompletionParamsChange({})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Validator } from 'jsonschema'
|
|||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: LLMNodeType) => {
|
export const checkNodeValid = (_payload: LLMNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +280,7 @@ const validator = new Validator()
|
|||||||
|
|
||||||
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
|
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
|
||||||
const schema = produce(schemaToValidate, (draft: any) => {
|
const schema = produce(schemaToValidate, (draft: any) => {
|
||||||
// Make sure the schema has the $schema property for draft-07
|
// Make sure the schema has the $schema property for draft-07
|
||||||
if (!draft.$schema)
|
if (!draft.$schema)
|
||||||
draft.$schema = 'http://json-schema.org/draft-07/schema#'
|
draft.$schema = 'http://json-schema.org/draft-07/schema#'
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import produce from 'immer'
|
|||||||
import { v4 as uuid4 } from 'uuid'
|
import { v4 as uuid4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
useIsNodeInLoop,
|
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
@ -20,10 +19,8 @@ import type { HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCon
|
|||||||
import useIsVarFileAttribute from './use-is-var-file-attribute'
|
import useIsVarFileAttribute from './use-is-var-file-attribute'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
|
|
||||||
const DELIMITER = '@@@@@'
|
|
||||||
const useConfig = (id: string, payload: LoopNodeType) => {
|
const useConfig = (id: string, payload: LoopNodeType) => {
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
const { isNodeInLoop } = useIsNodeInLoop(id)
|
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const conversationVariables = useStore(s => s.conversationVariables)
|
const conversationVariables = useStore(s => s.conversationVariables)
|
||||||
|
|
||||||
@ -39,10 +36,8 @@ const useConfig = (id: string, payload: LoopNodeType) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// output
|
// output
|
||||||
const { getLoopNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
const { getLoopNodeChildren } = useWorkflow()
|
||||||
const beforeNodes = getBeforeNodesInSameBranch(id)
|
|
||||||
const loopChildrenNodes = [{ id, data: payload } as any, ...getLoopNodeChildren(id)]
|
const loopChildrenNodes = [{ id, data: payload } as any, ...getLoopNodeChildren(id)]
|
||||||
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
|
|
||||||
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables)
|
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { StartNodeType } from './types'
|
import type { StartNodeType } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: StartNodeType) => {
|
export const checkNodeValid = (_payload: StartNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { TemplateTransformNodeType } from './types'
|
import type { TemplateTransformNodeType } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: TemplateTransformNodeType) => {
|
export const checkNodeValid = (_payload: TemplateTransformNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ToolNodeType } from './types'
|
import type { ToolNodeType } from './types'
|
||||||
|
|
||||||
export const checkNodeValid = (payload: ToolNodeType) => {
|
export const checkNodeValid = (_payload: ToolNodeType) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
433
web/app/components/workflow/selection-contextmenu.tsx
Normal file
433
web/app/components/workflow/selection-contextmenu.tsx
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useClickAway } from 'ahooks'
|
||||||
|
import { useStore as useReactFlowStore, useStoreApi } from 'reactflow'
|
||||||
|
import {
|
||||||
|
RiAlignBottom,
|
||||||
|
RiAlignCenter,
|
||||||
|
RiAlignJustify,
|
||||||
|
RiAlignLeft,
|
||||||
|
RiAlignRight,
|
||||||
|
RiAlignTop,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import { useNodesReadOnly, useNodesSyncDraft } from './hooks'
|
||||||
|
import produce from 'immer'
|
||||||
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './hooks/use-workflow-history'
|
||||||
|
import { useStore } from './store'
|
||||||
|
import { useSelectionInteractions } from './hooks/use-selection-interactions'
|
||||||
|
import { useWorkflowStore } from './store'
|
||||||
|
|
||||||
|
enum AlignType {
|
||||||
|
Left = 'left',
|
||||||
|
Center = 'center',
|
||||||
|
Right = 'right',
|
||||||
|
Top = 'top',
|
||||||
|
Middle = 'middle',
|
||||||
|
Bottom = 'bottom',
|
||||||
|
DistributeHorizontal = 'distributeHorizontal',
|
||||||
|
DistributeVertical = 'distributeVertical',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectionContextmenu = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const ref = useRef(null)
|
||||||
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { handleSelectionContextmenuCancel } = useSelectionInteractions()
|
||||||
|
const selectionMenu = useStore(s => s.selectionMenu)
|
||||||
|
|
||||||
|
// Access React Flow methods
|
||||||
|
const store = useStoreApi()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
|
||||||
|
// Get selected nodes for alignment logic
|
||||||
|
const selectedNodes = useReactFlowStore(state =>
|
||||||
|
state.getNodes().filter(node => node.selected),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const { saveStateToHistory } = useWorkflowHistory()
|
||||||
|
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const menuPosition = useMemo(() => {
|
||||||
|
if (!selectionMenu) return { left: 0, top: 0 }
|
||||||
|
|
||||||
|
let left = selectionMenu.left
|
||||||
|
let top = selectionMenu.top
|
||||||
|
|
||||||
|
const container = document.querySelector('#workflow-container')
|
||||||
|
if (container) {
|
||||||
|
const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect()
|
||||||
|
|
||||||
|
const menuWidth = 240
|
||||||
|
|
||||||
|
const estimatedMenuHeight = 380
|
||||||
|
|
||||||
|
if (left + menuWidth > containerWidth)
|
||||||
|
left = left - menuWidth
|
||||||
|
|
||||||
|
if (top + estimatedMenuHeight > containerHeight)
|
||||||
|
top = top - estimatedMenuHeight
|
||||||
|
|
||||||
|
left = Math.max(0, left)
|
||||||
|
top = Math.max(0, top)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { left, top }
|
||||||
|
}, [selectionMenu])
|
||||||
|
|
||||||
|
useClickAway(() => {
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
}, ref)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectionMenu && selectedNodes.length <= 1)
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
}, [selectionMenu, selectedNodes.length, handleSelectionContextmenuCancel])
|
||||||
|
|
||||||
|
// Handle align nodes logic
|
||||||
|
const handleAlignNode = useCallback((currentNode: any, nodeToAlign: any, alignType: AlignType, minX: number, maxX: number, minY: number, maxY: number) => {
|
||||||
|
const width = nodeToAlign.width
|
||||||
|
const height = nodeToAlign.height
|
||||||
|
|
||||||
|
// Calculate new positions based on alignment type
|
||||||
|
switch (alignType) {
|
||||||
|
case AlignType.Left:
|
||||||
|
// For left alignment, align left edge of each node to minX
|
||||||
|
currentNode.position.x = minX
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.x = minX
|
||||||
|
break
|
||||||
|
|
||||||
|
case AlignType.Center: {
|
||||||
|
// For center alignment, center each node horizontally in the selection bounds
|
||||||
|
const centerX = minX + (maxX - minX) / 2 - width / 2
|
||||||
|
currentNode.position.x = centerX
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.x = centerX
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlignType.Right: {
|
||||||
|
// For right alignment, align right edge of each node to maxX
|
||||||
|
const rightX = maxX - width
|
||||||
|
currentNode.position.x = rightX
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.x = rightX
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlignType.Top: {
|
||||||
|
// For top alignment, align top edge of each node to minY
|
||||||
|
currentNode.position.y = minY
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.y = minY
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlignType.Middle: {
|
||||||
|
// For middle alignment, center each node vertically in the selection bounds
|
||||||
|
const middleY = minY + (maxY - minY) / 2 - height / 2
|
||||||
|
currentNode.position.y = middleY
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.y = middleY
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlignType.Bottom: {
|
||||||
|
// For bottom alignment, align bottom edge of each node to maxY
|
||||||
|
const newY = Math.round(maxY - height)
|
||||||
|
currentNode.position.y = newY
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.y = newY
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle distribute nodes logic
|
||||||
|
const handleDistributeNodes = useCallback((nodesToAlign: any[], nodes: any[], alignType: AlignType) => {
|
||||||
|
// Sort nodes appropriately
|
||||||
|
const sortedNodes = [...nodesToAlign].sort((a, b) => {
|
||||||
|
if (alignType === AlignType.DistributeHorizontal) {
|
||||||
|
// Sort by left position for horizontal distribution
|
||||||
|
return a.position.x - b.position.x
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Sort by top position for vertical distribution
|
||||||
|
return a.position.y - b.position.y
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (sortedNodes.length < 3)
|
||||||
|
return null // Need at least 3 nodes for distribution
|
||||||
|
|
||||||
|
let totalGap = 0
|
||||||
|
let fixedSpace = 0
|
||||||
|
|
||||||
|
if (alignType === AlignType.DistributeHorizontal) {
|
||||||
|
// Fixed positions - first node's left edge and last node's right edge
|
||||||
|
const firstNodeLeft = sortedNodes[0].position.x
|
||||||
|
const lastNodeRight = sortedNodes[sortedNodes.length - 1].position.x + (sortedNodes[sortedNodes.length - 1].width || 0)
|
||||||
|
|
||||||
|
// Total available space
|
||||||
|
totalGap = lastNodeRight - firstNodeLeft
|
||||||
|
|
||||||
|
// Space occupied by nodes themselves
|
||||||
|
fixedSpace = sortedNodes.reduce((sum, node) => sum + (node.width || 0), 0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Fixed positions - first node's top edge and last node's bottom edge
|
||||||
|
const firstNodeTop = sortedNodes[0].position.y
|
||||||
|
const lastNodeBottom = sortedNodes[sortedNodes.length - 1].position.y + (sortedNodes[sortedNodes.length - 1].height || 0)
|
||||||
|
|
||||||
|
// Total available space
|
||||||
|
totalGap = lastNodeBottom - firstNodeTop
|
||||||
|
|
||||||
|
// Space occupied by nodes themselves
|
||||||
|
fixedSpace = sortedNodes.reduce((sum, node) => sum + (node.height || 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available space for gaps
|
||||||
|
const availableSpace = totalGap - fixedSpace
|
||||||
|
|
||||||
|
// Calculate even spacing between node edges
|
||||||
|
const spacing = availableSpace / (sortedNodes.length - 1)
|
||||||
|
|
||||||
|
if (spacing <= 0)
|
||||||
|
return null // Nodes are overlapping, can't distribute evenly
|
||||||
|
|
||||||
|
return produce(nodes, (draft) => {
|
||||||
|
// Keep first node fixed, position others with even gaps
|
||||||
|
let currentPosition
|
||||||
|
|
||||||
|
if (alignType === AlignType.DistributeHorizontal) {
|
||||||
|
// Start from first node's right edge
|
||||||
|
currentPosition = sortedNodes[0].position.x + (sortedNodes[0].width || 0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Start from first node's bottom edge
|
||||||
|
currentPosition = sortedNodes[0].position.y + (sortedNodes[0].height || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip first node (index 0), it stays in place
|
||||||
|
for (let i = 1; i < sortedNodes.length - 1; i++) {
|
||||||
|
const nodeToAlign = sortedNodes[i]
|
||||||
|
const currentNode = draft.find(n => n.id === nodeToAlign.id)
|
||||||
|
if (!currentNode) continue
|
||||||
|
|
||||||
|
if (alignType === AlignType.DistributeHorizontal) {
|
||||||
|
// Position = previous right edge + spacing
|
||||||
|
const newX: number = currentPosition + spacing
|
||||||
|
currentNode.position.x = newX
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.x = newX
|
||||||
|
|
||||||
|
// Update for next iteration - current node's right edge
|
||||||
|
currentPosition = newX + (nodeToAlign.width || 0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Position = previous bottom edge + spacing
|
||||||
|
const newY: number = currentPosition + spacing
|
||||||
|
currentNode.position.y = newY
|
||||||
|
if (currentNode.positionAbsolute)
|
||||||
|
currentNode.positionAbsolute.y = newY
|
||||||
|
|
||||||
|
// Update for next iteration - current node's bottom edge
|
||||||
|
currentPosition = newY + (nodeToAlign.height || 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleAlignNodes = useCallback((alignType: AlignType) => {
|
||||||
|
if (getNodesReadOnly() || selectedNodes.length <= 1) {
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable node animation state - same as handleNodeDragStart
|
||||||
|
workflowStore.setState({ nodeAnimation: false })
|
||||||
|
|
||||||
|
// Get all current nodes
|
||||||
|
const nodes = store.getState().getNodes()
|
||||||
|
|
||||||
|
// Get all selected nodes
|
||||||
|
const selectedNodeIds = selectedNodes.map(node => node.id)
|
||||||
|
const nodesToAlign = nodes.filter(node => selectedNodeIds.includes(node.id))
|
||||||
|
|
||||||
|
if (nodesToAlign.length <= 1) {
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate node boundaries for alignment
|
||||||
|
let minX = Number.MAX_SAFE_INTEGER
|
||||||
|
let maxX = Number.MIN_SAFE_INTEGER
|
||||||
|
let minY = Number.MAX_SAFE_INTEGER
|
||||||
|
let maxY = Number.MIN_SAFE_INTEGER
|
||||||
|
|
||||||
|
// Calculate boundaries of selected nodes
|
||||||
|
const validNodes = nodesToAlign.filter(node => node.width && node.height)
|
||||||
|
validNodes.forEach((node) => {
|
||||||
|
const width = node.width!
|
||||||
|
const height = node.height!
|
||||||
|
minX = Math.min(minX, node.position.x)
|
||||||
|
maxX = Math.max(maxX, node.position.x + width)
|
||||||
|
minY = Math.min(minY, node.position.y)
|
||||||
|
maxY = Math.max(maxY, node.position.y + height)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle distribute nodes logic
|
||||||
|
if (alignType === AlignType.DistributeHorizontal || alignType === AlignType.DistributeVertical) {
|
||||||
|
const distributeNodes = handleDistributeNodes(nodesToAlign, nodes, alignType)
|
||||||
|
if (distributeNodes) {
|
||||||
|
// Apply node distribution updates
|
||||||
|
store.getState().setNodes(distributeNodes)
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
|
||||||
|
// Clear guide lines
|
||||||
|
const { setHelpLineHorizontal, setHelpLineVertical } = workflowStore.getState()
|
||||||
|
setHelpLineHorizontal()
|
||||||
|
setHelpLineVertical()
|
||||||
|
|
||||||
|
// Sync workflow draft
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
|
||||||
|
// Save to history
|
||||||
|
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop)
|
||||||
|
|
||||||
|
return // End function execution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
// Iterate through all selected nodes
|
||||||
|
const validNodesToAlign = nodesToAlign.filter(node => node.width && node.height)
|
||||||
|
validNodesToAlign.forEach((nodeToAlign) => {
|
||||||
|
// Find the corresponding node in draft - consistent with handleNodeDrag
|
||||||
|
const currentNode = draft.find(n => n.id === nodeToAlign.id)
|
||||||
|
if (!currentNode)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Use the extracted alignment function
|
||||||
|
handleAlignNode(currentNode, nodeToAlign, alignType, minX, maxX, minY, maxY)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply node position updates - consistent with handleNodeDrag and handleNodeDragStop
|
||||||
|
try {
|
||||||
|
// Directly use setNodes to update nodes - consistent with handleNodeDrag
|
||||||
|
store.getState().setNodes(newNodes)
|
||||||
|
|
||||||
|
// Close popup
|
||||||
|
handleSelectionContextmenuCancel()
|
||||||
|
|
||||||
|
// Clear guide lines - consistent with handleNodeDragStop
|
||||||
|
const { setHelpLineHorizontal, setHelpLineVertical } = workflowStore.getState()
|
||||||
|
setHelpLineHorizontal()
|
||||||
|
setHelpLineVertical()
|
||||||
|
|
||||||
|
// Sync workflow draft - consistent with handleNodeDragStop
|
||||||
|
handleSyncWorkflowDraft()
|
||||||
|
|
||||||
|
// Save to history - consistent with handleNodeDragStop
|
||||||
|
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error('Failed to update nodes:', err)
|
||||||
|
}
|
||||||
|
}, [store, workflowStore, selectedNodes, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory, handleSelectionContextmenuCancel, handleAlignNode, handleDistributeNodes])
|
||||||
|
|
||||||
|
if (!selectionMenu)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='absolute z-[9]'
|
||||||
|
style={{
|
||||||
|
left: menuPosition.left,
|
||||||
|
top: menuPosition.top,
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<div ref={menuRef} className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
||||||
|
<div className='p-1'>
|
||||||
|
<div className='system-xs-medium px-2 py-2 text-text-tertiary'>
|
||||||
|
{t('workflow.operator.vertical')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Top)}
|
||||||
|
>
|
||||||
|
<RiAlignTop className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.alignTop')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Middle)}
|
||||||
|
>
|
||||||
|
<RiAlignCenter className='h-4 w-4 rotate-90' />
|
||||||
|
{t('workflow.operator.alignMiddle')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Bottom)}
|
||||||
|
>
|
||||||
|
<RiAlignBottom className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.alignBottom')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.DistributeVertical)}
|
||||||
|
>
|
||||||
|
<RiAlignJustify className='h-4 w-4 rotate-90' />
|
||||||
|
{t('workflow.operator.distributeVertical')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-[1px] bg-divider-regular'></div>
|
||||||
|
<div className='p-1'>
|
||||||
|
<div className='system-xs-medium px-2 py-2 text-text-tertiary'>
|
||||||
|
{t('workflow.operator.horizontal')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Left)}
|
||||||
|
>
|
||||||
|
<RiAlignLeft className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.alignLeft')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Center)}
|
||||||
|
>
|
||||||
|
<RiAlignCenter className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.alignCenter')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.Right)}
|
||||||
|
>
|
||||||
|
<RiAlignRight className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.alignRight')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleAlignNodes(AlignType.DistributeHorizontal)}
|
||||||
|
>
|
||||||
|
<RiAlignJustify className='h-4 w-4' />
|
||||||
|
{t('workflow.operator.distributeHorizontal')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(SelectionContextmenu)
|
||||||
@ -23,7 +23,7 @@ type InspectVarsActions = {
|
|||||||
|
|
||||||
export type InspectVarsSliceShape = InspectVarsState & InspectVarsActions
|
export type InspectVarsSliceShape = InspectVarsState & InspectVarsActions
|
||||||
|
|
||||||
export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set, get) => {
|
export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set) => {
|
||||||
return ({
|
return ({
|
||||||
currentFocusNodeId: null,
|
currentFocusNodeId: null,
|
||||||
nodesWithInspectVars: [],
|
nodesWithInspectVars: [],
|
||||||
@ -75,11 +75,11 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set,
|
|||||||
if (!targetNode)
|
if (!targetNode)
|
||||||
return
|
return
|
||||||
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
if(!targetVar)
|
if (!targetVar)
|
||||||
return
|
return
|
||||||
targetVar.value = value
|
targetVar.value = value
|
||||||
targetVar.edited = true
|
targetVar.edited = true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
nodesWithInspectVars: nodes,
|
nodesWithInspectVars: nodes,
|
||||||
@ -93,11 +93,11 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set,
|
|||||||
if (!targetNode)
|
if (!targetNode)
|
||||||
return
|
return
|
||||||
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
if(!targetVar)
|
if (!targetVar)
|
||||||
return
|
return
|
||||||
targetVar.value = value
|
targetVar.value = value
|
||||||
targetVar.edited = false
|
targetVar.edited = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
nodesWithInspectVars: nodes,
|
nodesWithInspectVars: nodes,
|
||||||
@ -111,11 +111,11 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set,
|
|||||||
if (!targetNode)
|
if (!targetNode)
|
||||||
return
|
return
|
||||||
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
if(!targetVar)
|
if (!targetVar)
|
||||||
return
|
return
|
||||||
targetVar.name = selector[1]
|
targetVar.name = selector[1]
|
||||||
targetVar.selector = selector
|
targetVar.selector = selector
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
nodesWithInspectVars: nodes,
|
nodesWithInspectVars: nodes,
|
||||||
@ -131,7 +131,7 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set,
|
|||||||
const needChangeVarIndex = targetNode.vars.findIndex(varItem => varItem.id === varId)
|
const needChangeVarIndex = targetNode.vars.findIndex(varItem => varItem.id === varId)
|
||||||
if (needChangeVarIndex !== -1)
|
if (needChangeVarIndex !== -1)
|
||||||
targetNode.vars.splice(needChangeVarIndex, 1)
|
targetNode.vars.splice(needChangeVarIndex, 1)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
nodesWithInspectVars: nodes,
|
nodesWithInspectVars: nodes,
|
||||||
|
|||||||
@ -15,6 +15,11 @@ export type PanelSliceShape = {
|
|||||||
left: number
|
left: number
|
||||||
}
|
}
|
||||||
setPanelMenu: (panelMenu: PanelSliceShape['panelMenu']) => void
|
setPanelMenu: (panelMenu: PanelSliceShape['panelMenu']) => void
|
||||||
|
selectionMenu?: {
|
||||||
|
top: number
|
||||||
|
left: number
|
||||||
|
}
|
||||||
|
setSelectionMenu: (selectionMenu: PanelSliceShape['selectionMenu']) => void
|
||||||
showVariableInspectPanel: boolean
|
showVariableInspectPanel: boolean
|
||||||
setShowVariableInspectPanel: (showVariableInspectPanel: boolean) => void
|
setShowVariableInspectPanel: (showVariableInspectPanel: boolean) => void
|
||||||
initShowLastRunTab: boolean
|
initShowLastRunTab: boolean
|
||||||
@ -33,6 +38,8 @@ export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({
|
|||||||
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
||||||
panelMenu: undefined,
|
panelMenu: undefined,
|
||||||
setPanelMenu: panelMenu => set(() => ({ panelMenu })),
|
setPanelMenu: panelMenu => set(() => ({ panelMenu })),
|
||||||
|
selectionMenu: undefined,
|
||||||
|
setSelectionMenu: selectionMenu => set(() => ({ selectionMenu })),
|
||||||
showVariableInspectPanel: false,
|
showVariableInspectPanel: false,
|
||||||
setShowVariableInspectPanel: showVariableInspectPanel => set(() => ({ showVariableInspectPanel })),
|
setShowVariableInspectPanel: showVariableInspectPanel => set(() => ({ showVariableInspectPanel })),
|
||||||
initShowLastRunTab: false,
|
initShowLastRunTab: false,
|
||||||
|
|||||||
@ -103,6 +103,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
retry_config?: WorkflowRetryConfig
|
retry_config?: WorkflowRetryConfig
|
||||||
default_value?: DefaultValueForm[]
|
default_value?: DefaultValueForm[]
|
||||||
credential_id?: string
|
credential_id?: string
|
||||||
|
_dimmed?: boolean
|
||||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||||
& Partial<Pick<DataSourceDefaultValue, 'plugin_id' | 'provider_type' | 'provider_name' | 'datasource_name'>>
|
& Partial<Pick<DataSourceDefaultValue, 'plugin_id' | 'provider_type' | 'provider_name' | 'datasource_name'>>
|
||||||
|
|
||||||
@ -119,7 +120,8 @@ export type CommonEdgeType = {
|
|||||||
isInLoop?: boolean
|
isInLoop?: boolean
|
||||||
loop_id?: string
|
loop_id?: string
|
||||||
sourceType: BlockEnum
|
sourceType: BlockEnum
|
||||||
targetType: BlockEnum
|
targetType: BlockEnum,
|
||||||
|
_isTemp?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
|
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export default combine(
|
|||||||
'**/.next/',
|
'**/.next/',
|
||||||
'**/public/*',
|
'**/public/*',
|
||||||
'**/*.json',
|
'**/*.json',
|
||||||
|
'**/*.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
5
web/global.d.ts
vendored
5
web/global.d.ts
vendored
@ -3,3 +3,8 @@ declare module 'lamejs/src/js/MPEGMode';
|
|||||||
declare module 'lamejs/src/js/Lame';
|
declare module 'lamejs/src/js/Lame';
|
||||||
declare module 'lamejs/src/js/BitStream';
|
declare module 'lamejs/src/js/BitStream';
|
||||||
declare module 'react-18-input-autosize';
|
declare module 'react-18-input-autosize';
|
||||||
|
|
||||||
|
declare module '*.mdx' {
|
||||||
|
let MDXComponent: (props: any) => JSX.Element
|
||||||
|
export default MDXComponent
|
||||||
|
}
|
||||||
|
|||||||
@ -89,11 +89,11 @@ if (!i18n.isInitialized) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const changeLanguage = async (lng?: string) => {
|
export const changeLanguage = async (lng?: string) => {
|
||||||
const resolvedLng = lng ?? 'en-US'
|
if (!lng) return
|
||||||
const resource = await loadLangResources(resolvedLng)
|
const resource = await loadLangResources(lng)
|
||||||
if (!i18n.hasResourceBundle(resolvedLng, 'translation'))
|
if (!i18n.hasResourceBundle(lng, 'translation'))
|
||||||
i18n.addResourceBundle(resolvedLng, 'translation', resource, true, true)
|
i18n.addResourceBundle(lng, 'translation', resource, true, true)
|
||||||
await i18n.changeLanguage(resolvedLng)
|
await i18n.changeLanguage(lng)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
|
|||||||
@ -968,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Einstellungen',
|
settingsTab: 'Einstellungen',
|
||||||
lastRunTab: 'Letzte Ausführung',
|
lastRunTab: 'Letzte Ausführung',
|
||||||
|
relations: {
|
||||||
|
dependents: 'Angehörige',
|
||||||
|
dependenciesDescription: 'Knoten, auf die sich dieser Knoten stützt',
|
||||||
|
dependencies: 'Abhängigkeiten',
|
||||||
|
noDependencies: 'Keine Abhängigkeiten',
|
||||||
|
dependentsDescription: 'Knoten, die auf diesem Knoten basieren',
|
||||||
|
noDependents: 'Keine Angehörigen',
|
||||||
|
},
|
||||||
|
relationsTab: 'Beziehungen',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -294,6 +294,18 @@ const translation = {
|
|||||||
zoomTo50: 'Zoom to 50%',
|
zoomTo50: 'Zoom to 50%',
|
||||||
zoomTo100: 'Zoom to 100%',
|
zoomTo100: 'Zoom to 100%',
|
||||||
zoomToFit: 'Zoom to Fit',
|
zoomToFit: 'Zoom to Fit',
|
||||||
|
alignNodes: 'Align Nodes',
|
||||||
|
alignLeft: 'Left',
|
||||||
|
alignCenter: 'Center',
|
||||||
|
alignRight: 'Right',
|
||||||
|
alignTop: 'Top',
|
||||||
|
alignMiddle: 'Middle',
|
||||||
|
alignBottom: 'Bottom',
|
||||||
|
vertical: 'Vertical',
|
||||||
|
horizontal: 'Horizontal',
|
||||||
|
distributeHorizontal: 'Space Horizontally',
|
||||||
|
distributeVertical: 'Space Vertically',
|
||||||
|
selectionAlignment: 'Selection Alignment',
|
||||||
},
|
},
|
||||||
variableReference: {
|
variableReference: {
|
||||||
noAvailableVars: 'No available variables',
|
noAvailableVars: 'No available variables',
|
||||||
@ -965,6 +977,7 @@ const translation = {
|
|||||||
debug: {
|
debug: {
|
||||||
settingsTab: 'Settings',
|
settingsTab: 'Settings',
|
||||||
lastRunTab: 'Last Run',
|
lastRunTab: 'Last Run',
|
||||||
|
relationsTab: 'Relations',
|
||||||
noData: {
|
noData: {
|
||||||
description: 'The results of the last run will be displayed here',
|
description: 'The results of the last run will be displayed here',
|
||||||
runThisNode: 'Run this node',
|
runThisNode: 'Run this node',
|
||||||
@ -990,6 +1003,14 @@ const translation = {
|
|||||||
chatNode: 'Conversation',
|
chatNode: 'Conversation',
|
||||||
systemNode: 'System',
|
systemNode: 'System',
|
||||||
},
|
},
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Dependencies',
|
||||||
|
dependents: 'Dependents',
|
||||||
|
dependenciesDescription: 'Nodes that this node relies on',
|
||||||
|
dependentsDescription: 'Nodes that rely on this node',
|
||||||
|
noDependencies: 'No dependencies',
|
||||||
|
noDependents: 'No dependents',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -968,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
lastRunTab: 'Última ejecución',
|
lastRunTab: 'Última ejecución',
|
||||||
settingsTab: 'Ajustes',
|
settingsTab: 'Ajustes',
|
||||||
|
relations: {
|
||||||
|
dependents: 'Dependientes',
|
||||||
|
dependenciesDescription: 'Nodos en los que se basa este nodo',
|
||||||
|
dependentsDescription: 'Nodos que dependen de este nodo',
|
||||||
|
noDependencies: 'Sin dependencias',
|
||||||
|
noDependents: 'Sin dependientes',
|
||||||
|
dependencies: 'Dependencias',
|
||||||
|
},
|
||||||
|
relationsTab: 'Relaciones',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -104,9 +104,7 @@ const translation = {
|
|||||||
noHistory: 'بدون تاریخچه',
|
noHistory: 'بدون تاریخچه',
|
||||||
loadMore: 'بارگذاری گردش کار بیشتر',
|
loadMore: 'بارگذاری گردش کار بیشتر',
|
||||||
exportPNG: 'صادرات به فرمت PNG',
|
exportPNG: 'صادرات به فرمت PNG',
|
||||||
noExist: 'هیچگونه متغیری وجود ندارد',
|
|
||||||
exitVersions: 'نسخههای خروجی',
|
exitVersions: 'نسخههای خروجی',
|
||||||
referenceVar: 'متغیر مرجع',
|
|
||||||
exportSVG: 'صادرات به فرمت SVG',
|
exportSVG: 'صادرات به فرمت SVG',
|
||||||
exportJPEG: 'صادرات به فرمت JPEG',
|
exportJPEG: 'صادرات به فرمت JPEG',
|
||||||
exportImage: 'تصویر را صادر کنید',
|
exportImage: 'تصویر را صادر کنید',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'انتخاب',
|
select: 'انتخاب',
|
||||||
addSubVariable: 'متغیر فرعی',
|
addSubVariable: 'متغیر فرعی',
|
||||||
condition: 'شرط',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'تخصیص متغیرها',
|
title: 'تخصیص متغیرها',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'تنظیمات',
|
settingsTab: 'تنظیمات',
|
||||||
lastRunTab: 'آخرین اجرا',
|
lastRunTab: 'آخرین اجرا',
|
||||||
|
relations: {
|
||||||
|
dependents: 'وابسته',
|
||||||
|
dependencies: 'وابسته',
|
||||||
|
noDependents: 'بدون وابستگان',
|
||||||
|
noDependencies: 'بدون وابستگی',
|
||||||
|
dependenciesDescription: 'گره هایی که این گره به آنها متکی است',
|
||||||
|
dependentsDescription: 'گره هایی که به این گره متکی هستند',
|
||||||
|
},
|
||||||
|
relationsTab: 'روابط',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,9 +107,7 @@ const translation = {
|
|||||||
exitVersions: 'Versions de sortie',
|
exitVersions: 'Versions de sortie',
|
||||||
exportSVG: 'Exporter en SVG',
|
exportSVG: 'Exporter en SVG',
|
||||||
publishUpdate: 'Publier une mise à jour',
|
publishUpdate: 'Publier une mise à jour',
|
||||||
noExist: 'Aucune variable de ce type',
|
|
||||||
versionHistory: 'Historique des versions',
|
versionHistory: 'Historique des versions',
|
||||||
referenceVar: 'Variable de référence',
|
|
||||||
exportImage: 'Exporter l\'image',
|
exportImage: 'Exporter l\'image',
|
||||||
exportJPEG: 'Exporter en JPEG',
|
exportJPEG: 'Exporter en JPEG',
|
||||||
needEndNode: 'Le nœud de fin doit être ajouté',
|
needEndNode: 'Le nœud de fin doit être ajouté',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'Choisir',
|
select: 'Choisir',
|
||||||
addSubVariable: 'Sous-variable',
|
addSubVariable: 'Sous-variable',
|
||||||
condition: 'Condition',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Attribuer des variables',
|
title: 'Attribuer des variables',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Paramètres',
|
settingsTab: 'Paramètres',
|
||||||
lastRunTab: 'Dernière Exécution',
|
lastRunTab: 'Dernière Exécution',
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Dépendances',
|
||||||
|
dependentsDescription: 'Nœuds qui s’appuient sur ce nœud',
|
||||||
|
noDependents: 'Pas de personnes à charge',
|
||||||
|
dependents: 'Dépendants',
|
||||||
|
noDependencies: 'Aucune dépendance',
|
||||||
|
dependenciesDescription: 'Nœuds sur lesquels repose ce nœud',
|
||||||
|
},
|
||||||
|
relationsTab: 'Relations',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,8 +109,6 @@ const translation = {
|
|||||||
exitVersions: 'निकलने के संस्करण',
|
exitVersions: 'निकलने के संस्करण',
|
||||||
exportPNG: 'PNG के रूप में निर्यात करें',
|
exportPNG: 'PNG के रूप में निर्यात करें',
|
||||||
exportJPEG: 'JPEG के रूप में निर्यात करें',
|
exportJPEG: 'JPEG के रूप में निर्यात करें',
|
||||||
referenceVar: 'संदर्भ चर',
|
|
||||||
noExist: 'कोई ऐसा चर नहीं है',
|
|
||||||
exportImage: 'छवि निर्यात करें',
|
exportImage: 'छवि निर्यात करें',
|
||||||
publishUpdate: 'अपडेट प्रकाशित करें',
|
publishUpdate: 'अपडेट प्रकाशित करें',
|
||||||
exportSVG: 'SVG के रूप में निर्यात करें',
|
exportSVG: 'SVG के रूप में निर्यात करें',
|
||||||
@ -623,7 +621,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'चुनना',
|
select: 'चुनना',
|
||||||
addSubVariable: 'उप चर',
|
addSubVariable: 'उप चर',
|
||||||
condition: 'स्थिति',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'वेरिएबल्स असाइन करें',
|
title: 'वेरिएबल्स असाइन करें',
|
||||||
@ -991,6 +988,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'सेटिंग्स',
|
settingsTab: 'सेटिंग्स',
|
||||||
lastRunTab: 'अंतिम रन',
|
lastRunTab: 'अंतिम रन',
|
||||||
|
relations: {
|
||||||
|
dependents: 'निष्पाभ लोग',
|
||||||
|
dependentsDescription: 'इस नोड पर निर्भर नोड्स',
|
||||||
|
dependencies: 'निर्भरता',
|
||||||
|
noDependents: 'कोई आश्रित नहीं',
|
||||||
|
dependenciesDescription: 'यह नोड जिस नोड पर निर्भर करता है',
|
||||||
|
noDependencies: 'कोई निर्भरताएँ नहीं',
|
||||||
|
},
|
||||||
|
relationsTab: 'रिश्ते',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,11 +110,9 @@ const translation = {
|
|||||||
publishUpdate: 'Pubblica aggiornamento',
|
publishUpdate: 'Pubblica aggiornamento',
|
||||||
versionHistory: 'Cronologia delle versioni',
|
versionHistory: 'Cronologia delle versioni',
|
||||||
exitVersions: 'Uscita Versioni',
|
exitVersions: 'Uscita Versioni',
|
||||||
referenceVar: 'Variabile di riferimento',
|
|
||||||
exportSVG: 'Esporta come SVG',
|
exportSVG: 'Esporta come SVG',
|
||||||
exportImage: 'Esporta immagine',
|
exportImage: 'Esporta immagine',
|
||||||
exportJPEG: 'Esporta come JPEG',
|
exportJPEG: 'Esporta come JPEG',
|
||||||
noExist: 'Nessuna variabile del genere',
|
|
||||||
exportPNG: 'Esporta come PNG',
|
exportPNG: 'Esporta come PNG',
|
||||||
needEndNode: 'Deve essere aggiunto il nodo finale',
|
needEndNode: 'Deve essere aggiunto il nodo finale',
|
||||||
addBlock: 'Aggiungi nodo',
|
addBlock: 'Aggiungi nodo',
|
||||||
@ -627,7 +625,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
addSubVariable: 'Variabile secondaria',
|
addSubVariable: 'Variabile secondaria',
|
||||||
select: 'Selezionare',
|
select: 'Selezionare',
|
||||||
condition: 'Condizione',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Assegna variabili',
|
title: 'Assegna variabili',
|
||||||
@ -997,6 +994,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Impostazioni',
|
settingsTab: 'Impostazioni',
|
||||||
lastRunTab: 'Ultima corsa',
|
lastRunTab: 'Ultima corsa',
|
||||||
|
relations: {
|
||||||
|
dependents: 'Dipendenti',
|
||||||
|
noDependencies: 'Nessuna dipendenza',
|
||||||
|
dependencies: 'Dipendenze',
|
||||||
|
noDependents: 'Nessuna persona a carico',
|
||||||
|
dependentsDescription: 'Nodi che si basano su questo nodo',
|
||||||
|
dependenciesDescription: 'Nodi su cui si basa questo nodo',
|
||||||
|
},
|
||||||
|
relationsTab: 'Relazioni',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -968,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: '設定',
|
settingsTab: '設定',
|
||||||
lastRunTab: '最後の実行',
|
lastRunTab: '最後の実行',
|
||||||
|
relationsTab: '関係',
|
||||||
|
relations: {
|
||||||
|
dependencies: '依存元',
|
||||||
|
dependents: '依存先',
|
||||||
|
dependenciesDescription: 'このノードが依存している他のノード',
|
||||||
|
dependentsDescription: 'このノードに依存している他のノード',
|
||||||
|
noDependencies: '依存元なし',
|
||||||
|
noDependents: '依存先なし',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1019,6 +1019,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: '설정',
|
settingsTab: '설정',
|
||||||
lastRunTab: '마지막 실행',
|
lastRunTab: '마지막 실행',
|
||||||
|
relations: {
|
||||||
|
dependencies: '종속성',
|
||||||
|
dependentsDescription: '이 노드에 의존하는 노드',
|
||||||
|
noDependents: '부양가족 없음',
|
||||||
|
noDependencies: '종속성 없음',
|
||||||
|
dependents: '부양 가족',
|
||||||
|
dependenciesDescription: '이 노드가 의존하는 노드',
|
||||||
|
},
|
||||||
|
relationsTab: '관계',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,10 +108,8 @@ const translation = {
|
|||||||
versionHistory: 'Historia wersji',
|
versionHistory: 'Historia wersji',
|
||||||
exportSVG: 'Eksportuj jako SVG',
|
exportSVG: 'Eksportuj jako SVG',
|
||||||
exportJPEG: 'Eksportuj jako JPEG',
|
exportJPEG: 'Eksportuj jako JPEG',
|
||||||
noExist: 'Nie ma takiej zmiennej',
|
|
||||||
exportPNG: 'Eksportuj jako PNG',
|
exportPNG: 'Eksportuj jako PNG',
|
||||||
publishUpdate: 'Opublikuj aktualizację',
|
publishUpdate: 'Opublikuj aktualizację',
|
||||||
referenceVar: 'Zmienna odniesienia',
|
|
||||||
addBlock: 'Dodaj węzeł',
|
addBlock: 'Dodaj węzeł',
|
||||||
needEndNode: 'Należy dodać węzeł końcowy',
|
needEndNode: 'Należy dodać węzeł końcowy',
|
||||||
needAnswerNode: 'Węzeł odpowiedzi musi zostać dodany',
|
needAnswerNode: 'Węzeł odpowiedzi musi zostać dodany',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
addSubVariable: 'Zmienna podrzędna',
|
addSubVariable: 'Zmienna podrzędna',
|
||||||
select: 'Wybrać',
|
select: 'Wybrać',
|
||||||
condition: 'Stan',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Przypisz zmienne',
|
title: 'Przypisz zmienne',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Ustawienia',
|
settingsTab: 'Ustawienia',
|
||||||
lastRunTab: 'Ostatnie uruchomienie',
|
lastRunTab: 'Ostatnie uruchomienie',
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Zależności',
|
||||||
|
dependenciesDescription: 'Węzły, na których opiera się ten węzeł',
|
||||||
|
noDependents: 'Brak osób na utrzymaniu',
|
||||||
|
dependents: 'Zależności',
|
||||||
|
dependentsDescription: 'Węzły, które opierają się na tym węźle',
|
||||||
|
noDependencies: 'Brak zależności',
|
||||||
|
},
|
||||||
|
relationsTab: 'Stosunków',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,8 +107,6 @@ const translation = {
|
|||||||
publishUpdate: 'Publicar Atualização',
|
publishUpdate: 'Publicar Atualização',
|
||||||
versionHistory: 'Histórico de Versão',
|
versionHistory: 'Histórico de Versão',
|
||||||
exportImage: 'Exportar Imagem',
|
exportImage: 'Exportar Imagem',
|
||||||
referenceVar: 'Variável de Referência',
|
|
||||||
noExist: 'Nenhuma variável desse tipo',
|
|
||||||
exitVersions: 'Versões de Sair',
|
exitVersions: 'Versões de Sair',
|
||||||
exportSVG: 'Exportar como SVG',
|
exportSVG: 'Exportar como SVG',
|
||||||
exportJPEG: 'Exportar como JPEG',
|
exportJPEG: 'Exportar como JPEG',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
addSubVariable: 'Subvariável',
|
addSubVariable: 'Subvariável',
|
||||||
select: 'Selecionar',
|
select: 'Selecionar',
|
||||||
condition: 'Condição',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Atribuir variáveis',
|
title: 'Atribuir variáveis',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Configurações',
|
settingsTab: 'Configurações',
|
||||||
lastRunTab: 'Última execução',
|
lastRunTab: 'Última execução',
|
||||||
|
relations: {
|
||||||
|
noDependents: 'Sem dependentes',
|
||||||
|
dependenciesDescription: 'Nós dos quais esse nó depende',
|
||||||
|
dependents: 'Dependentes',
|
||||||
|
dependencies: 'Dependências',
|
||||||
|
dependentsDescription: 'Nós que dependem desse nó',
|
||||||
|
noDependencies: 'Sem dependências',
|
||||||
|
},
|
||||||
|
relationsTab: 'Relações',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -106,11 +106,9 @@ const translation = {
|
|||||||
exportImage: 'Exportă imaginea',
|
exportImage: 'Exportă imaginea',
|
||||||
exportSVG: 'Exportă ca SVG',
|
exportSVG: 'Exportă ca SVG',
|
||||||
exportPNG: 'Exportă ca PNG',
|
exportPNG: 'Exportă ca PNG',
|
||||||
noExist: 'Nu există o astfel de variabilă',
|
|
||||||
exitVersions: 'Ieșire Versiuni',
|
exitVersions: 'Ieșire Versiuni',
|
||||||
versionHistory: 'Istoricul versiunilor',
|
versionHistory: 'Istoricul versiunilor',
|
||||||
publishUpdate: 'Publicați actualizarea',
|
publishUpdate: 'Publicați actualizarea',
|
||||||
referenceVar: 'Variabilă de referință',
|
|
||||||
exportJPEG: 'Exportă ca JPEG',
|
exportJPEG: 'Exportă ca JPEG',
|
||||||
addBlock: 'Adaugă nod',
|
addBlock: 'Adaugă nod',
|
||||||
needAnswerNode: 'Nodul de răspuns trebuie adăugat',
|
needAnswerNode: 'Nodul de răspuns trebuie adăugat',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'Alege',
|
select: 'Alege',
|
||||||
addSubVariable: 'Subvariabilă',
|
addSubVariable: 'Subvariabilă',
|
||||||
condition: 'Condiție',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Atribuie variabile',
|
title: 'Atribuie variabile',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Setări',
|
settingsTab: 'Setări',
|
||||||
lastRunTab: 'Ultima execuție',
|
lastRunTab: 'Ultima execuție',
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Dependenţele',
|
||||||
|
noDependencies: 'Fără dependențe',
|
||||||
|
dependents: 'Dependenţe',
|
||||||
|
noDependents: 'Fără persoane aflate în întreținere',
|
||||||
|
dependentsDescription: 'Noduri care se bazează pe acest nod',
|
||||||
|
dependenciesDescription: 'Noduri pe care se bazează acest nod',
|
||||||
|
},
|
||||||
|
relationsTab: 'Relații',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,12 +103,10 @@ const translation = {
|
|||||||
addFailureBranch: 'Добавить ветвь Fail',
|
addFailureBranch: 'Добавить ветвь Fail',
|
||||||
noHistory: 'Без истории',
|
noHistory: 'Без истории',
|
||||||
loadMore: 'Загрузите больше рабочих процессов',
|
loadMore: 'Загрузите больше рабочих процессов',
|
||||||
noExist: 'Такой переменной не существует',
|
|
||||||
versionHistory: 'История версий',
|
versionHistory: 'История версий',
|
||||||
exportPNG: 'Экспортировать как PNG',
|
exportPNG: 'Экспортировать как PNG',
|
||||||
exportImage: 'Экспортировать изображение',
|
exportImage: 'Экспортировать изображение',
|
||||||
exportJPEG: 'Экспортировать как JPEG',
|
exportJPEG: 'Экспортировать как JPEG',
|
||||||
referenceVar: 'Ссылочная переменная',
|
|
||||||
exitVersions: 'Выходные версии',
|
exitVersions: 'Выходные версии',
|
||||||
exportSVG: 'Экспортировать как SVG',
|
exportSVG: 'Экспортировать как SVG',
|
||||||
publishUpdate: 'Опубликовать обновление',
|
publishUpdate: 'Опубликовать обновление',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'Выбирать',
|
select: 'Выбирать',
|
||||||
addSubVariable: 'Подпеременная',
|
addSubVariable: 'Подпеременная',
|
||||||
condition: 'Условие',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Назначить переменные',
|
title: 'Назначить переменные',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
lastRunTab: 'Последний запуск',
|
lastRunTab: 'Последний запуск',
|
||||||
settingsTab: 'Настройки',
|
settingsTab: 'Настройки',
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Зависимости',
|
||||||
|
dependents: 'Иждивенцев',
|
||||||
|
noDependencies: 'Нет зависимостей',
|
||||||
|
dependentsDescription: 'Узлы, которые полагаются на этот узел',
|
||||||
|
noDependents: 'Отсутствие иждивенцев',
|
||||||
|
dependenciesDescription: 'Узлы, на которые опирается этот узел',
|
||||||
|
},
|
||||||
|
relationsTab: 'Отношения',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -968,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Nastavitve',
|
settingsTab: 'Nastavitve',
|
||||||
lastRunTab: 'Zadnji zagon',
|
lastRunTab: 'Zadnji zagon',
|
||||||
|
relations: {
|
||||||
|
dependencies: 'Odvisnosti',
|
||||||
|
dependents: 'Odvisnim',
|
||||||
|
noDependents: 'Brez vzdrževanih oseb',
|
||||||
|
dependentsDescription: 'Vozlišča, ki se zanašajo na to vozlišče',
|
||||||
|
dependenciesDescription: 'Vozlišča, na katera se zanaša to vozlišče',
|
||||||
|
noDependencies: 'Brez odvisnosti',
|
||||||
|
},
|
||||||
|
relationsTab: 'Odnose',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -105,9 +105,7 @@ const translation = {
|
|||||||
noHistory: 'ไม่มีประวัติ',
|
noHistory: 'ไม่มีประวัติ',
|
||||||
versionHistory: 'ประวัติรุ่น',
|
versionHistory: 'ประวัติรุ่น',
|
||||||
exportPNG: 'ส่งออกเป็น PNG',
|
exportPNG: 'ส่งออกเป็น PNG',
|
||||||
noExist: 'ไม่มีตัวแปรดังกล่าว',
|
|
||||||
exportJPEG: 'ส่งออกเป็น JPEG',
|
exportJPEG: 'ส่งออกเป็น JPEG',
|
||||||
referenceVar: 'ตัวแปรอ้างอิง',
|
|
||||||
publishUpdate: 'เผยแพร่การอัปเดต',
|
publishUpdate: 'เผยแพร่การอัปเดต',
|
||||||
exitVersions: 'ออกเวอร์ชัน',
|
exitVersions: 'ออกเวอร์ชัน',
|
||||||
exportImage: 'ส่งออกภาพ',
|
exportImage: 'ส่งออกภาพ',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
selectVariable: 'เลือกตัวแปร...',
|
selectVariable: 'เลือกตัวแปร...',
|
||||||
addSubVariable: 'ตัวแปรย่อย',
|
addSubVariable: 'ตัวแปรย่อย',
|
||||||
select: 'เลือก',
|
select: 'เลือก',
|
||||||
condition: 'เงื่อนไข',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'กําหนดตัวแปร',
|
title: 'กําหนดตัวแปร',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'การตั้งค่า',
|
settingsTab: 'การตั้งค่า',
|
||||||
lastRunTab: 'รอบสุดท้าย',
|
lastRunTab: 'รอบสุดท้าย',
|
||||||
|
relations: {
|
||||||
|
dependents: 'ผู้อยู่ในอุปการะ',
|
||||||
|
dependencies: 'อ้าง อิง',
|
||||||
|
dependenciesDescription: 'โหนดที่โหนดนี้อาศัย',
|
||||||
|
noDependencies: 'ไม่มีการพึ่งพา',
|
||||||
|
noDependents: 'ไม่มีผู้อยู่ในอุปการะ',
|
||||||
|
dependentsDescription: 'โหนดที่อาศัยโหนดนี้',
|
||||||
|
},
|
||||||
|
relationsTab: 'สัมพันธ์',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,9 +109,7 @@ const translation = {
|
|||||||
exitVersions: 'Çıkış Sürümleri',
|
exitVersions: 'Çıkış Sürümleri',
|
||||||
versionHistory: 'Sürüm Geçmişi',
|
versionHistory: 'Sürüm Geçmişi',
|
||||||
exportJPEG: 'JPEG olarak dışa aktar',
|
exportJPEG: 'JPEG olarak dışa aktar',
|
||||||
noExist: 'Böyle bir değişken yok',
|
|
||||||
exportSVG: 'SVG olarak dışa aktar',
|
exportSVG: 'SVG olarak dışa aktar',
|
||||||
referenceVar: 'Referans Değişken',
|
|
||||||
addBlock: 'Düğüm Ekle',
|
addBlock: 'Düğüm Ekle',
|
||||||
needAnswerNode: 'Cevap düğümü eklenmelidir.',
|
needAnswerNode: 'Cevap düğümü eklenmelidir.',
|
||||||
needEndNode: 'Son düğüm eklenmelidir',
|
needEndNode: 'Son düğüm eklenmelidir',
|
||||||
@ -609,7 +607,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
addSubVariable: 'Alt Değişken',
|
addSubVariable: 'Alt Değişken',
|
||||||
select: 'Seçmek',
|
select: 'Seçmek',
|
||||||
condition: 'Koşul',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Değişken ata',
|
title: 'Değişken ata',
|
||||||
@ -972,6 +969,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
lastRunTab: 'Son Koşu',
|
lastRunTab: 'Son Koşu',
|
||||||
settingsTab: 'Ayarlar',
|
settingsTab: 'Ayarlar',
|
||||||
|
relations: {
|
||||||
|
noDependents: 'Bakmakla yükümlü olunan kişi yok',
|
||||||
|
dependentsDescription: 'Bu düğüme dayanan düğümler',
|
||||||
|
dependenciesDescription: 'Bu düğümün dayandığı düğümler',
|
||||||
|
dependencies: 'Bağımlılık',
|
||||||
|
dependents: 'Bağımlı',
|
||||||
|
noDependencies: 'Bağımlılık yok',
|
||||||
|
},
|
||||||
|
relationsTab: 'Ilişkiler',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,9 +103,7 @@ const translation = {
|
|||||||
addFailureBranch: 'Додано гілку помилки',
|
addFailureBranch: 'Додано гілку помилки',
|
||||||
noHistory: 'Без історії',
|
noHistory: 'Без історії',
|
||||||
loadMore: 'Завантажте більше робочих процесів',
|
loadMore: 'Завантажте більше робочих процесів',
|
||||||
referenceVar: 'Посилальна змінна',
|
|
||||||
exportPNG: 'Експортувати як PNG',
|
exportPNG: 'Експортувати як PNG',
|
||||||
noExist: 'Такої змінної не існує',
|
|
||||||
exitVersions: 'Вихідні версії',
|
exitVersions: 'Вихідні версії',
|
||||||
versionHistory: 'Історія версій',
|
versionHistory: 'Історія версій',
|
||||||
publishUpdate: 'Опублікувати оновлення',
|
publishUpdate: 'Опублікувати оновлення',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
select: 'Виберіть',
|
select: 'Виберіть',
|
||||||
addSubVariable: 'Підзмінна',
|
addSubVariable: 'Підзмінна',
|
||||||
condition: 'Умова',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Присвоєння змінних',
|
title: 'Присвоєння змінних',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
lastRunTab: 'Останній запуск',
|
lastRunTab: 'Останній запуск',
|
||||||
settingsTab: 'Налаштування',
|
settingsTab: 'Налаштування',
|
||||||
|
relations: {
|
||||||
|
noDependents: 'Без утриманців',
|
||||||
|
dependents: 'Утриманців',
|
||||||
|
dependencies: 'Залежностей',
|
||||||
|
noDependencies: 'Відсутність залежностей',
|
||||||
|
dependenciesDescription: 'Вузли, на які спирається цей вузол',
|
||||||
|
dependentsDescription: 'Вузли, які спираються на цей вузол',
|
||||||
|
},
|
||||||
|
relationsTab: 'Відносин',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,9 +109,7 @@ const translation = {
|
|||||||
exitVersions: 'Phiên bản thoát',
|
exitVersions: 'Phiên bản thoát',
|
||||||
exportImage: 'Xuất hình ảnh',
|
exportImage: 'Xuất hình ảnh',
|
||||||
exportPNG: 'Xuất dưới dạng PNG',
|
exportPNG: 'Xuất dưới dạng PNG',
|
||||||
noExist: 'Không có biến như vậy',
|
|
||||||
exportJPEG: 'Xuất dưới dạng JPEG',
|
exportJPEG: 'Xuất dưới dạng JPEG',
|
||||||
referenceVar: 'Biến tham chiếu',
|
|
||||||
needAnswerNode: 'Nút Trả lời phải được thêm vào',
|
needAnswerNode: 'Nút Trả lời phải được thêm vào',
|
||||||
addBlock: 'Thêm Node',
|
addBlock: 'Thêm Node',
|
||||||
needEndNode: 'Nút Kết thúc phải được thêm vào',
|
needEndNode: 'Nút Kết thúc phải được thêm vào',
|
||||||
@ -608,7 +606,6 @@ const translation = {
|
|||||||
},
|
},
|
||||||
addSubVariable: 'Biến phụ',
|
addSubVariable: 'Biến phụ',
|
||||||
select: 'Lựa',
|
select: 'Lựa',
|
||||||
condition: 'Điều kiện',
|
|
||||||
},
|
},
|
||||||
variableAssigner: {
|
variableAssigner: {
|
||||||
title: 'Gán biến',
|
title: 'Gán biến',
|
||||||
@ -971,6 +968,15 @@ const translation = {
|
|||||||
},
|
},
|
||||||
settingsTab: 'Cài đặt',
|
settingsTab: 'Cài đặt',
|
||||||
lastRunTab: 'Chạy Lần Cuối',
|
lastRunTab: 'Chạy Lần Cuối',
|
||||||
|
relations: {
|
||||||
|
noDependencies: 'Không phụ thuộc',
|
||||||
|
dependenciesDescription: 'Các nút mà nút này dựa vào',
|
||||||
|
dependents: 'Người phụ thuộc',
|
||||||
|
dependencies: 'Phụ thuộc',
|
||||||
|
noDependents: 'Không có người phụ thuộc',
|
||||||
|
dependentsDescription: 'Các nút dựa vào nút này',
|
||||||
|
},
|
||||||
|
relationsTab: 'Mối quan hệ',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -294,6 +294,18 @@ const translation = {
|
|||||||
zoomTo50: '缩放到 50%',
|
zoomTo50: '缩放到 50%',
|
||||||
zoomTo100: '放大到 100%',
|
zoomTo100: '放大到 100%',
|
||||||
zoomToFit: '自适应视图',
|
zoomToFit: '自适应视图',
|
||||||
|
alignNodes: '对齐节点',
|
||||||
|
alignLeft: '左对齐',
|
||||||
|
alignCenter: '居中对齐',
|
||||||
|
alignRight: '右对齐',
|
||||||
|
alignTop: '顶部对齐',
|
||||||
|
alignMiddle: '中部对齐',
|
||||||
|
alignBottom: '底部对齐',
|
||||||
|
vertical: '垂直方向',
|
||||||
|
horizontal: '水平方向',
|
||||||
|
distributeHorizontal: '水平等间距',
|
||||||
|
distributeVertical: '垂直等间距',
|
||||||
|
selectionAlignment: '选择对齐',
|
||||||
},
|
},
|
||||||
variableReference: {
|
variableReference: {
|
||||||
noAvailableVars: '没有可用变量',
|
noAvailableVars: '没有可用变量',
|
||||||
@ -978,6 +990,7 @@ const translation = {
|
|||||||
debug: {
|
debug: {
|
||||||
settingsTab: '设置',
|
settingsTab: '设置',
|
||||||
lastRunTab: '上次运行',
|
lastRunTab: '上次运行',
|
||||||
|
relationsTab: '关系',
|
||||||
noData: {
|
noData: {
|
||||||
description: '上次运行的结果将显示在这里',
|
description: '上次运行的结果将显示在这里',
|
||||||
runThisNode: '运行此节点',
|
runThisNode: '运行此节点',
|
||||||
@ -1003,6 +1016,14 @@ const translation = {
|
|||||||
chatNode: '会话变量',
|
chatNode: '会话变量',
|
||||||
systemNode: '系统变量',
|
systemNode: '系统变量',
|
||||||
},
|
},
|
||||||
|
relations: {
|
||||||
|
dependencies: '依赖',
|
||||||
|
dependents: '被依赖',
|
||||||
|
dependenciesDescription: '本节点依赖的其他节点',
|
||||||
|
dependentsDescription: '依赖于本节点的其他节点',
|
||||||
|
noDependencies: '无依赖',
|
||||||
|
noDependents: '无被依赖',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -941,6 +941,9 @@ const translation = {
|
|||||||
copyId: '複製ID',
|
copyId: '複製ID',
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
|
settingsTab: '設定',
|
||||||
|
lastRunTab: '最後一次運行',
|
||||||
|
relationsTab: '關係',
|
||||||
noData: {
|
noData: {
|
||||||
runThisNode: '運行此節點',
|
runThisNode: '運行此節點',
|
||||||
description: '上次運行的結果將顯示在這裡',
|
description: '上次運行的結果將顯示在這裡',
|
||||||
@ -966,8 +969,14 @@ const translation = {
|
|||||||
emptyTip: '在畫布上逐步執行節點或逐步運行節點後,您可以在變數檢視中查看節點變數的當前值。',
|
emptyTip: '在畫布上逐步執行節點或逐步運行節點後,您可以在變數檢視中查看節點變數的當前值。',
|
||||||
resetConversationVar: '將對話變數重置為默認值',
|
resetConversationVar: '將對話變數重置為默認值',
|
||||||
},
|
},
|
||||||
settingsTab: '設定',
|
relations: {
|
||||||
lastRunTab: '最後一次運行',
|
dependencies: '依賴',
|
||||||
|
dependents: '被依賴',
|
||||||
|
dependenciesDescription: '此節點所依賴的其他節點',
|
||||||
|
dependentsDescription: '依賴此節點的其他節點',
|
||||||
|
noDependencies: '無依賴',
|
||||||
|
noDependents: '無被依賴',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ function unicodeToChar(text: string) {
|
|||||||
if (!text)
|
if (!text)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {
|
return text.replace(/\\u([0-9a-f]{4})/g, (_match, p1) => {
|
||||||
return String.fromCharCode(Number.parseInt(p1, 16))
|
return String.fromCharCode(Number.parseInt(p1, 16))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user