diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 76e5c04deb..a5a5071fae 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -47,15 +47,17 @@ jobs: - name: Run Unit tests run: | uv run --project api bash dev/pytest/pytest_unit_tests.sh + + - name: Coverage Summary + run: | + set -x # Extract coverage percentage and create a summary TOTAL_COVERAGE=$(python -c 'import json; print(json.load(open("coverage.json"))["totals"]["percent_covered_display"])') # Create a detailed coverage summary echo "### Test Coverage Summary :test_tube:" >> $GITHUB_STEP_SUMMARY echo "Total Coverage: ${TOTAL_COVERAGE}%" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - uv run --project api coverage report >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + uv run --project api coverage report --format=markdown >> $GITHUB_STEP_SUMMARY - name: Run dify config tests run: uv run --project api dev/pytest/pytest_config_tests.py diff --git a/.gitignore b/.gitignore index 8f82bea00d..dd4673a3d2 100644 --- a/.gitignore +++ b/.gitignore @@ -214,3 +214,4 @@ mise.toml # AI Assistant .roo/ +api/.env.backup diff --git a/api/configs/app_config.py b/api/configs/app_config.py index 3a3ad35ee7..20f8c40427 100644 --- a/api/configs/app_config.py +++ b/api/configs/app_config.py @@ -1,8 +1,11 @@ import logging +from pathlib import Path from typing import Any from pydantic.fields import FieldInfo -from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict +from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, TomlConfigSettingsSource + +from libs.file_utils import search_file_upwards from .deploy import DeploymentConfig from .enterprise import EnterpriseFeatureConfig @@ -99,4 +102,12 @@ class DifyConfig( RemoteSettingsSourceFactory(settings_cls), dotenv_settings, file_secret_settings, + TomlConfigSettingsSource( + settings_cls=settings_cls, + toml_file=search_file_upwards( + base_dir_path=Path(__file__).parent, + target_file_name="pyproject.toml", + max_search_parent_depth=2, + ), + ), ) diff --git a/api/configs/middleware/__init__.py b/api/configs/middleware/__init__.py index 60ba272ec9..427602676f 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -223,6 +223,10 @@ class CeleryConfig(DatabaseConfig): default=None, ) + CELERY_SENTINEL_PASSWORD: Optional[str] = Field( + description="Password of the Redis Sentinel master.", + default=None, + ) CELERY_SENTINEL_SOCKET_TIMEOUT: Optional[PositiveFloat] = Field( description="Timeout for Redis Sentinel socket operations in seconds.", default=0.1, diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index dddf71c094..f511e20e6b 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -1,17 +1,13 @@ from pydantic import Field -from pydantic_settings import BaseSettings +from configs.packaging.pyproject import PyProjectConfig, PyProjectTomlConfig -class PackagingInfo(BaseSettings): + +class PackagingInfo(PyProjectTomlConfig): """ Packaging build information """ - CURRENT_VERSION: str = Field( - description="Dify version", - default="1.5.0", - ) - COMMIT_SHA: str = Field( description="SHA-1 checksum of the git commit used to build the app", default="", diff --git a/api/configs/packaging/pyproject.py b/api/configs/packaging/pyproject.py new file mode 100644 index 0000000000..90b1ecba06 --- /dev/null +++ b/api/configs/packaging/pyproject.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field +from pydantic_settings import BaseSettings + + +class PyProjectConfig(BaseModel): + version: str = Field(description="Dify version", default="") + + +class PyProjectTomlConfig(BaseSettings): + """ + configs in api/pyproject.toml + """ + + project: PyProjectConfig = Field( + description="configs in the project section of pyproject.toml", + default=PyProjectConfig(), + ) diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index 5f2def8d8e..665cf1aede 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -90,23 +90,11 @@ class ChatMessageTextApi(Resource): message_id = args.get("message_id", None) text = args.get("text", None) - if ( - app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value} - and app_model.workflow - and app_model.workflow.features_dict - ): - text_to_speech = app_model.workflow.features_dict.get("text_to_speech") - if text_to_speech is None: - raise ValueError("TTS is not enabled") - voice = args.get("voice") or text_to_speech.get("voice") - else: - try: - if app_model.app_model_config is None: - raise ValueError("AppModelConfig not found") - voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice") - except Exception: - voice = None - response = AudioService.transcript_tts(app_model=app_model, text=text, message_id=message_id, voice=voice) + voice = args.get("voice", None) + + response = AudioService.transcript_tts( + app_model=app_model, text=text, voice=voice, message_id=message_id, is_draft=True + ) return response except services.errors.app_model_config.AppModelConfigBrokenError: logging.exception("App model config broken.") diff --git a/api/controllers/console/auth/data_source_oauth.py b/api/controllers/console/auth/data_source_oauth.py index 1049f864c3..4c9697cc32 100644 --- a/api/controllers/console/auth/data_source_oauth.py +++ b/api/controllers/console/auth/data_source_oauth.py @@ -41,7 +41,7 @@ class OAuthDataSource(Resource): if not internal_secret: return ({"error": "Internal secret is not set"},) oauth_provider.save_internal_access_token(internal_secret) - return {"data": ""} + return {"data": "internal"} else: auth_url = oauth_provider.get_authorization_url() return {"data": auth_url}, 200 diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index 54bc590677..d564a00a76 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -18,7 +18,6 @@ from controllers.console.app.error import ( from controllers.console.explore.wraps import InstalledAppResource from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError -from models.model import AppMode from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -79,19 +78,9 @@ class ChatTextApi(InstalledAppResource): message_id = args.get("message_id", None) text = args.get("text", None) - if ( - app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value} - and app_model.workflow - and app_model.workflow.features_dict - ): - text_to_speech = app_model.workflow.features_dict.get("text_to_speech") - voice = args.get("voice") or text_to_speech.get("voice") - else: - try: - voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice") - except Exception: - voice = None - response = AudioService.transcript_tts(app_model=app_model, message_id=message_id, voice=voice, text=text) + voice = args.get("voice", None) + + response = AudioService.transcript_tts(app_model=app_model, text=text, voice=voice, message_id=message_id) return response except services.errors.app_model_config.AppModelConfigBrokenError: logging.exception("App model config broken.") diff --git a/api/controllers/console/version.py b/api/controllers/console/version.py index 7dea8e554e..447cc358f8 100644 --- a/api/controllers/console/version.py +++ b/api/controllers/console/version.py @@ -18,7 +18,7 @@ class VersionApi(Resource): check_update_url = dify_config.CHECK_UPDATE_URL result = { - "version": dify_config.CURRENT_VERSION, + "version": dify_config.project.version, "release_date": "", "release_notes": "", "can_auto_update": False, diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 9bddbb4b4b..c0a4734828 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -13,6 +13,7 @@ from core.model_runtime.utils.encoders import jsonable_encoder from core.plugin.impl.exc import PluginDaemonClientSideError from libs.login import login_required from models.account import TenantPluginPermission +from services.plugin.plugin_parameter_service import PluginParameterService from services.plugin.plugin_permission_service import PluginPermissionService from services.plugin.plugin_service import PluginService @@ -497,6 +498,42 @@ class PluginFetchPermissionApi(Resource): ) +class PluginFetchDynamicSelectOptionsApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self): + # check if the user is admin or owner + if not current_user.is_admin_or_owner: + raise Forbidden() + + tenant_id = current_user.current_tenant_id + user_id = current_user.id + + parser = reqparse.RequestParser() + parser.add_argument("plugin_id", type=str, required=True, location="args") + parser.add_argument("provider", type=str, required=True, location="args") + parser.add_argument("action", type=str, required=True, location="args") + parser.add_argument("parameter", type=str, required=True, location="args") + parser.add_argument("provider_type", type=str, required=True, location="args") + args = parser.parse_args() + + try: + options = PluginParameterService.get_dynamic_select_options( + tenant_id, + user_id, + args["plugin_id"], + args["provider"], + args["action"], + args["parameter"], + args["provider_type"], + ) + except PluginDaemonClientSideError as e: + raise ValueError(e) + + return jsonable_encoder({"options": options}) + + api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") api.add_resource(PluginListApi, "/workspaces/current/plugin/list") api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") @@ -521,3 +558,5 @@ api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marke api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change") api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch") + +api.add_resource(PluginFetchDynamicSelectOptionsApi, "/workspaces/current/plugin/parameters/dynamic-options") diff --git a/api/controllers/inner_api/plugin/plugin.py b/api/controllers/inner_api/plugin/plugin.py index 41063b35a5..327e9ce834 100644 --- a/api/controllers/inner_api/plugin/plugin.py +++ b/api/controllers/inner_api/plugin/plugin.py @@ -17,6 +17,7 @@ from core.plugin.entities.request import ( RequestInvokeApp, RequestInvokeEncrypt, RequestInvokeLLM, + RequestInvokeLLMWithStructuredOutput, RequestInvokeModeration, RequestInvokeParameterExtractorNode, RequestInvokeQuestionClassifierNode, @@ -47,6 +48,21 @@ class PluginInvokeLLMApi(Resource): return length_prefixed_response(0xF, generator()) +class PluginInvokeLLMWithStructuredOutputApi(Resource): + @setup_required + @plugin_inner_api_only + @get_user_tenant + @plugin_data(payload_type=RequestInvokeLLMWithStructuredOutput) + def post(self, user_model: Account | EndUser, tenant_model: Tenant, payload: RequestInvokeLLMWithStructuredOutput): + def generator(): + response = PluginModelBackwardsInvocation.invoke_llm_with_structured_output( + user_model.id, tenant_model, payload + ) + return PluginModelBackwardsInvocation.convert_to_event_stream(response) + + return length_prefixed_response(0xF, generator()) + + class PluginInvokeTextEmbeddingApi(Resource): @setup_required @plugin_inner_api_only @@ -291,6 +307,7 @@ class PluginFetchAppInfoApi(Resource): api.add_resource(PluginInvokeLLMApi, "/invoke/llm") +api.add_resource(PluginInvokeLLMWithStructuredOutputApi, "/invoke/llm/structured-output") api.add_resource(PluginInvokeTextEmbeddingApi, "/invoke/text-embedding") api.add_resource(PluginInvokeRerankApi, "/invoke/rerank") api.add_resource(PluginInvokeTTSApi, "/invoke/tts") diff --git a/api/controllers/inner_api/workspace/workspace.py b/api/controllers/inner_api/workspace/workspace.py index a2fc2d4675..77568b75f1 100644 --- a/api/controllers/inner_api/workspace/workspace.py +++ b/api/controllers/inner_api/workspace/workspace.py @@ -29,7 +29,19 @@ class EnterpriseWorkspace(Resource): tenant_was_created.send(tenant) - return {"message": "enterprise workspace created."} + resp = { + "id": tenant.id, + "name": tenant.name, + "plan": tenant.plan, + "status": tenant.status, + "created_at": tenant.created_at.isoformat() + "Z" if tenant.created_at else None, + "updated_at": tenant.updated_at.isoformat() + "Z" if tenant.updated_at else None, + } + + return { + "message": "enterprise workspace created.", + "tenant": resp, + } class EnterpriseWorkspaceNoOwnerEmail(Resource): diff --git a/api/controllers/service_api/app/audio.py b/api/controllers/service_api/app/audio.py index 2682c2e7f1..848863cf1b 100644 --- a/api/controllers/service_api/app/audio.py +++ b/api/controllers/service_api/app/audio.py @@ -20,7 +20,7 @@ from controllers.service_api.app.error import ( from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError -from models.model import App, AppMode, EndUser +from models.model import App, EndUser from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -78,20 +78,9 @@ class TextApi(Resource): message_id = args.get("message_id", None) text = args.get("text", None) - if ( - app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value} - and app_model.workflow - and app_model.workflow.features_dict - ): - text_to_speech = app_model.workflow.features_dict.get("text_to_speech", {}) - voice = args.get("voice") or text_to_speech.get("voice") - else: - try: - voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice") - except Exception: - voice = None + voice = args.get("voice", None) response = AudioService.transcript_tts( - app_model=app_model, message_id=message_id, end_user=end_user.external_user_id, voice=voice, text=text + app_model=app_model, text=text, voice=voice, end_user=end_user.external_user_id, message_id=message_id ) return response diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index 839afdb9fd..a499719fc3 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -133,6 +133,22 @@ class DatasetListApi(DatasetApiResource): parser.add_argument("embedding_model_provider", type=str, required=False, nullable=True, location="json") args = parser.parse_args() + + if args.get("embedding_model_provider"): + DatasetService.check_embedding_model_setting( + tenant_id, args.get("embedding_model_provider"), args.get("embedding_model") + ) + if ( + args.get("retrieval_model") + and args.get("retrieval_model").get("reranking_model") + and args.get("retrieval_model").get("reranking_model").get("reranking_provider_name") + ): + DatasetService.check_reranking_model_setting( + tenant_id, + args.get("retrieval_model").get("reranking_model").get("reranking_provider_name"), + args.get("retrieval_model").get("reranking_model").get("reranking_model_name"), + ) + try: dataset = DatasetService.create_empty_dataset( tenant_id=tenant_id, @@ -265,10 +281,20 @@ class DatasetApi(DatasetApiResource): data = request.get_json() # check embedding model setting - if data.get("indexing_technique") == "high_quality": + if data.get("indexing_technique") == "high_quality" or data.get("embedding_model_provider"): DatasetService.check_embedding_model_setting( dataset.tenant_id, data.get("embedding_model_provider"), data.get("embedding_model") ) + if ( + data.get("retrieval_model") + and data.get("retrieval_model").get("reranking_model") + and data.get("retrieval_model").get("reranking_model").get("reranking_provider_name") + ): + DatasetService.check_reranking_model_setting( + dataset.tenant_id, + data.get("retrieval_model").get("reranking_model").get("reranking_provider_name"), + data.get("retrieval_model").get("reranking_model").get("reranking_model_name"), + ) # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator DatasetPermissionService.check_permission( diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index e4779f3bdf..d64d9df059 100644 --- a/api/controllers/service_api/dataset/document.py +++ b/api/controllers/service_api/dataset/document.py @@ -3,7 +3,7 @@ import json from flask import request from flask_restful import marshal, reqparse from sqlalchemy import desc, select -from werkzeug.exceptions import NotFound +from werkzeug.exceptions import Forbidden, NotFound import services from controllers.common.errors import FilenameNotExistsError @@ -18,6 +18,7 @@ from controllers.service_api.app.error import ( from controllers.service_api.dataset.error import ( ArchivedDocumentImmutableError, DocumentIndexingError, + InvalidMetadataError, ) from controllers.service_api.wraps import ( DatasetApiResource, @@ -29,7 +30,7 @@ from extensions.ext_database import db from fields.document_fields import document_fields, document_status_fields from libs.login import current_user from models.dataset import Dataset, Document, DocumentSegment -from services.dataset_service import DocumentService +from services.dataset_service import DatasetService, DocumentService from services.entities.knowledge_entities.knowledge_entities import KnowledgeConfig from services.file_service import FileService @@ -59,6 +60,7 @@ class DocumentAddByTextApi(DatasetApiResource): parser.add_argument("embedding_model_provider", type=str, required=False, nullable=True, location="json") args = parser.parse_args() + dataset_id = str(dataset_id) tenant_id = str(tenant_id) dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first() @@ -74,6 +76,21 @@ class DocumentAddByTextApi(DatasetApiResource): if text is None or name is None: raise ValueError("Both 'text' and 'name' must be non-null values.") + if args.get("embedding_model_provider"): + DatasetService.check_embedding_model_setting( + tenant_id, args.get("embedding_model_provider"), args.get("embedding_model") + ) + if ( + args.get("retrieval_model") + and args.get("retrieval_model").get("reranking_model") + and args.get("retrieval_model").get("reranking_model").get("reranking_provider_name") + ): + DatasetService.check_reranking_model_setting( + tenant_id, + args.get("retrieval_model").get("reranking_model").get("reranking_provider_name"), + args.get("retrieval_model").get("reranking_model").get("reranking_model_name"), + ) + upload_file = FileService.upload_text(text=str(text), text_name=str(name)) data_source = { "type": "upload_file", @@ -124,6 +141,17 @@ class DocumentUpdateByTextApi(DatasetApiResource): if not dataset: raise ValueError("Dataset does not exist.") + if ( + args.get("retrieval_model") + and args.get("retrieval_model").get("reranking_model") + and args.get("retrieval_model").get("reranking_model").get("reranking_provider_name") + ): + DatasetService.check_reranking_model_setting( + tenant_id, + args.get("retrieval_model").get("reranking_model").get("reranking_provider_name"), + args.get("retrieval_model").get("reranking_model").get("reranking_model_name"), + ) + # indexing_technique is already set in dataset since this is an update args["indexing_technique"] = dataset.indexing_technique @@ -188,6 +216,21 @@ class DocumentAddByFileApi(DatasetApiResource): raise ValueError("indexing_technique is required.") args["indexing_technique"] = indexing_technique + if "embedding_model_provider" in args: + DatasetService.check_embedding_model_setting( + tenant_id, args["embedding_model_provider"], args["embedding_model"] + ) + if ( + "retrieval_model" in args + and args["retrieval_model"].get("reranking_model") + and args["retrieval_model"].get("reranking_model").get("reranking_provider_name") + ): + DatasetService.check_reranking_model_setting( + tenant_id, + args["retrieval_model"].get("reranking_model").get("reranking_provider_name"), + args["retrieval_model"].get("reranking_model").get("reranking_model_name"), + ) + # save file info file = request.files["file"] # check file @@ -424,6 +467,101 @@ class DocumentIndexingStatusApi(DatasetApiResource): return data +class DocumentDetailApi(DatasetApiResource): + METADATA_CHOICES = {"all", "only", "without"} + + def get(self, tenant_id, dataset_id, document_id): + dataset_id = str(dataset_id) + document_id = str(document_id) + + dataset = self.get_dataset(dataset_id, tenant_id) + + document = DocumentService.get_document(dataset.id, document_id) + + if not document: + raise NotFound("Document not found.") + + if document.tenant_id != str(tenant_id): + raise Forbidden("No permission.") + + metadata = request.args.get("metadata", "all") + if metadata not in self.METADATA_CHOICES: + raise InvalidMetadataError(f"Invalid metadata value: {metadata}") + + if metadata == "only": + response = {"id": document.id, "doc_type": document.doc_type, "doc_metadata": document.doc_metadata_details} + elif metadata == "without": + dataset_process_rules = DatasetService.get_process_rules(dataset_id) + document_process_rules = document.dataset_process_rule.to_dict() + data_source_info = document.data_source_detail_dict + response = { + "id": document.id, + "position": document.position, + "data_source_type": document.data_source_type, + "data_source_info": data_source_info, + "dataset_process_rule_id": document.dataset_process_rule_id, + "dataset_process_rule": dataset_process_rules, + "document_process_rule": document_process_rules, + "name": document.name, + "created_from": document.created_from, + "created_by": document.created_by, + "created_at": document.created_at.timestamp(), + "tokens": document.tokens, + "indexing_status": document.indexing_status, + "completed_at": int(document.completed_at.timestamp()) if document.completed_at else None, + "updated_at": int(document.updated_at.timestamp()) if document.updated_at else None, + "indexing_latency": document.indexing_latency, + "error": document.error, + "enabled": document.enabled, + "disabled_at": int(document.disabled_at.timestamp()) if document.disabled_at else None, + "disabled_by": document.disabled_by, + "archived": document.archived, + "segment_count": document.segment_count, + "average_segment_length": document.average_segment_length, + "hit_count": document.hit_count, + "display_status": document.display_status, + "doc_form": document.doc_form, + "doc_language": document.doc_language, + } + else: + dataset_process_rules = DatasetService.get_process_rules(dataset_id) + document_process_rules = document.dataset_process_rule.to_dict() + data_source_info = document.data_source_detail_dict + response = { + "id": document.id, + "position": document.position, + "data_source_type": document.data_source_type, + "data_source_info": data_source_info, + "dataset_process_rule_id": document.dataset_process_rule_id, + "dataset_process_rule": dataset_process_rules, + "document_process_rule": document_process_rules, + "name": document.name, + "created_from": document.created_from, + "created_by": document.created_by, + "created_at": document.created_at.timestamp(), + "tokens": document.tokens, + "indexing_status": document.indexing_status, + "completed_at": int(document.completed_at.timestamp()) if document.completed_at else None, + "updated_at": int(document.updated_at.timestamp()) if document.updated_at else None, + "indexing_latency": document.indexing_latency, + "error": document.error, + "enabled": document.enabled, + "disabled_at": int(document.disabled_at.timestamp()) if document.disabled_at else None, + "disabled_by": document.disabled_by, + "archived": document.archived, + "doc_type": document.doc_type, + "doc_metadata": document.doc_metadata_details, + "segment_count": document.segment_count, + "average_segment_length": document.average_segment_length, + "hit_count": document.hit_count, + "display_status": document.display_status, + "doc_form": document.doc_form, + "doc_language": document.doc_language, + } + + return response + + api.add_resource( DocumentAddByTextApi, "/datasets//document/create_by_text", @@ -447,3 +585,4 @@ api.add_resource( api.add_resource(DocumentDeleteApi, "/datasets//documents/") api.add_resource(DocumentListApi, "/datasets//documents") api.add_resource(DocumentIndexingStatusApi, "/datasets//documents//indexing-status") +api.add_resource(DocumentDetailApi, "/datasets//documents/") diff --git a/api/controllers/service_api/index.py b/api/controllers/service_api/index.py index d24c4597e2..9bb5df4c4e 100644 --- a/api/controllers/service_api/index.py +++ b/api/controllers/service_api/index.py @@ -9,7 +9,7 @@ class IndexApi(Resource): return { "welcome": "Dify OpenAPI", "api_version": "v1", - "server_version": dify_config.CURRENT_VERSION, + "server_version": dify_config.project.version, } diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index d3316a5159..5b919a68d4 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -11,13 +11,13 @@ from flask_restful import Resource from pydantic import BaseModel from sqlalchemy import select, update from sqlalchemy.orm import Session -from werkzeug.exceptions import Forbidden, Unauthorized +from werkzeug.exceptions import Forbidden, NotFound, Unauthorized from extensions.ext_database import db from extensions.ext_redis import redis_client from libs.login import _get_user from models.account import Account, Tenant, TenantAccountJoin, TenantStatus -from models.dataset import RateLimitLog +from models.dataset import Dataset, RateLimitLog from models.model import ApiToken, App, EndUser from services.feature_service import FeatureService @@ -317,3 +317,11 @@ def create_or_update_end_user_for_user_id(app_model: App, user_id: Optional[str] class DatasetApiResource(Resource): method_decorators = [validate_dataset_token] + + def get_dataset(self, dataset_id: str, tenant_id: str) -> Dataset: + dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id, Dataset.tenant_id == tenant_id).first() + + if not dataset: + raise NotFound("Dataset not found.") + + return dataset diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index 06d9ad7564..2919ca9af4 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -19,7 +19,7 @@ from controllers.web.error import ( from controllers.web.wraps import WebApiResource from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError -from models.model import App, AppMode +from models.model import App from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -77,21 +77,9 @@ class TextApi(WebApiResource): message_id = args.get("message_id", None) text = args.get("text", None) - if ( - app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value} - and app_model.workflow - and app_model.workflow.features_dict - ): - text_to_speech = app_model.workflow.features_dict.get("text_to_speech", {}) - voice = args.get("voice") or text_to_speech.get("voice") - else: - try: - voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice") - except Exception: - voice = None - + voice = args.get("voice", None) response = AudioService.transcript_tts( - app_model=app_model, message_id=message_id, end_user=end_user.external_user_id, voice=voice, text=text + app_model=app_model, text=text, voice=voice, end_user=end_user.external_user_id, message_id=message_id ) return response diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index 61de9ec670..7877408cef 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -27,6 +27,9 @@ from core.ops.ops_trace_manager import TraceQueueManager from core.prompt.utils.get_thread_messages_length import get_thread_messages_length from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository +from core.workflow.repositories.draft_variable_repository import ( + DraftVariableSaverFactory, +) from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader @@ -36,7 +39,10 @@ from libs.flask_utils import preserve_flask_contexts from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom from models.enums import WorkflowRunTriggeredFrom from services.conversation_service import ConversationService -from services.workflow_draft_variable_service import DraftVarLoader, WorkflowDraftVariableService +from services.workflow_draft_variable_service import ( + DraftVarLoader, + WorkflowDraftVariableService, +) logger = logging.getLogger(__name__) @@ -450,6 +456,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): workflow_execution_repository=workflow_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository, stream=stream, + draft_var_saver_factory=self._get_draft_var_saver_factory(invoke_from), ) return AdvancedChatAppGenerateResponseConverter.convert(response=response, invoke_from=invoke_from) @@ -521,6 +528,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): user: Union[Account, EndUser], workflow_execution_repository: WorkflowExecutionRepository, workflow_node_execution_repository: WorkflowNodeExecutionRepository, + draft_var_saver_factory: DraftVariableSaverFactory, stream: bool = False, ) -> Union[ChatbotAppBlockingResponse, Generator[ChatbotAppStreamResponse, None, None]]: """ @@ -547,6 +555,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): workflow_execution_repository=workflow_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository, stream=stream, + draft_var_saver_factory=draft_var_saver_factory, ) try: diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 8c5645bbb7..4c52fc3e83 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -64,6 +64,7 @@ from core.workflow.entities.workflow_execution import WorkflowExecutionStatus, W from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes import NodeType +from core.workflow.repositories.draft_variable_repository import DraftVariableSaverFactory from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager @@ -94,6 +95,7 @@ class AdvancedChatAppGenerateTaskPipeline: dialogue_count: int, workflow_execution_repository: WorkflowExecutionRepository, workflow_node_execution_repository: WorkflowNodeExecutionRepository, + draft_var_saver_factory: DraftVariableSaverFactory, ) -> None: self._base_task_pipeline = BasedGenerateTaskPipeline( application_generate_entity=application_generate_entity, @@ -153,6 +155,7 @@ class AdvancedChatAppGenerateTaskPipeline: self._conversation_name_generate_thread: Thread | None = None self._recorded_files: list[Mapping[str, Any]] = [] self._workflow_run_id: str = "" + self._draft_var_saver_factory = draft_var_saver_factory def process(self) -> Union[ChatbotAppBlockingResponse, Generator[ChatbotAppStreamResponse, None, None]]: """ @@ -371,6 +374,7 @@ class AdvancedChatAppGenerateTaskPipeline: workflow_node_execution=workflow_node_execution, ) session.commit() + self._save_output_for_event(event, workflow_node_execution.id) if node_finish_resp: yield node_finish_resp @@ -390,6 +394,8 @@ class AdvancedChatAppGenerateTaskPipeline: task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, ) + if isinstance(event, QueueNodeExceptionEvent): + self._save_output_for_event(event, workflow_node_execution.id) if node_finish_resp: yield node_finish_resp @@ -759,3 +765,15 @@ class AdvancedChatAppGenerateTaskPipeline: if not message: raise ValueError(f"Message not found: {self._message_id}") return message + + def _save_output_for_event(self, event: QueueNodeSucceededEvent | QueueNodeExceptionEvent, node_execution_id: str): + with Session(db.engine) as session, session.begin(): + saver = self._draft_var_saver_factory( + session=session, + app_id=self._application_generate_entity.app_config.app_id, + node_id=event.node_id, + node_type=event.node_type, + node_execution_id=node_execution_id, + enclosing_node_id=event.in_loop_id or event.in_iteration_id, + ) + saver.save(event.process_data, event.outputs) diff --git a/api/core/app/apps/base_app_generator.py b/api/core/app/apps/base_app_generator.py index a83b75cc1a..beece1d77e 100644 --- a/api/core/app/apps/base_app_generator.py +++ b/api/core/app/apps/base_app_generator.py @@ -1,10 +1,20 @@ import json from collections.abc import Generator, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union, final + +from sqlalchemy.orm import Session from core.app.app_config.entities import VariableEntityType +from core.app.entities.app_invoke_entities import InvokeFrom from core.file import File, FileUploadConfig +from core.workflow.nodes.enums import NodeType +from core.workflow.repositories.draft_variable_repository import ( + DraftVariableSaver, + DraftVariableSaverFactory, + NoopDraftVariableSaver, +) from factories import file_factory +from services.workflow_draft_variable_service import DraftVariableSaver as DraftVariableSaverImpl if TYPE_CHECKING: from core.app.app_config.entities import VariableEntity @@ -159,3 +169,38 @@ class BaseAppGenerator: yield f"event: {message}\n\n" return gen() + + @final + @staticmethod + def _get_draft_var_saver_factory(invoke_from: InvokeFrom) -> DraftVariableSaverFactory: + if invoke_from == InvokeFrom.DEBUGGER: + + def draft_var_saver_factory( + session: Session, + app_id: str, + node_id: str, + node_type: NodeType, + node_execution_id: str, + enclosing_node_id: str | None = None, + ) -> DraftVariableSaver: + return DraftVariableSaverImpl( + session=session, + app_id=app_id, + node_id=node_id, + node_type=node_type, + node_execution_id=node_execution_id, + enclosing_node_id=enclosing_node_id, + ) + else: + + def draft_var_saver_factory( + session: Session, + app_id: str, + node_id: str, + node_type: NodeType, + node_execution_id: str, + enclosing_node_id: str | None = None, + ) -> DraftVariableSaver: + return NoopDraftVariableSaver() + + return draft_var_saver_factory diff --git a/api/core/app/apps/common/workflow_response_converter.py b/api/core/app/apps/common/workflow_response_converter.py index cd1d298ca2..34a1da2227 100644 --- a/api/core/app/apps/common/workflow_response_converter.py +++ b/api/core/app/apps/common/workflow_response_converter.py @@ -44,6 +44,7 @@ from core.app.entities.task_entities import ( ) from core.file import FILE_MODEL_IDENTITY, File from core.tools.tool_manager import ToolManager +from core.variables.segments import ArrayFileSegment, FileSegment, Segment from core.workflow.entities.workflow_execution import WorkflowExecution from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution, WorkflowNodeExecutionStatus from core.workflow.nodes import NodeType @@ -506,7 +507,8 @@ class WorkflowResponseConverter: # Convert to tuple to match Sequence type return tuple(flattened_files) - def _fetch_files_from_variable_value(self, value: Union[dict, list]) -> Sequence[Mapping[str, Any]]: + @classmethod + def _fetch_files_from_variable_value(cls, value: Union[dict, list, Segment]) -> Sequence[Mapping[str, Any]]: """ Fetch files from variable value :param value: variable value @@ -515,20 +517,30 @@ class WorkflowResponseConverter: if not value: return [] - files = [] - if isinstance(value, list): + files: list[Mapping[str, Any]] = [] + if isinstance(value, FileSegment): + files.append(value.value.to_dict()) + elif isinstance(value, ArrayFileSegment): + files.extend([i.to_dict() for i in value.value]) + elif isinstance(value, File): + files.append(value.to_dict()) + elif isinstance(value, list): for item in value: - file = self._get_file_var_from_value(item) + file = cls._get_file_var_from_value(item) if file: files.append(file) - elif isinstance(value, dict): - file = self._get_file_var_from_value(value) + elif isinstance( + value, + dict, + ): + file = cls._get_file_var_from_value(value) if file: files.append(file) return files - def _get_file_var_from_value(self, value: Union[dict, list]) -> Mapping[str, Any] | None: + @classmethod + def _get_file_var_from_value(cls, value: Union[dict, list]) -> Mapping[str, Any] | None: """ Get file var from value :param value: variable value diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 369fa0e48c..40a1e272a7 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -25,6 +25,7 @@ from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository +from core.workflow.repositories.draft_variable_repository import DraftVariableSaverFactory from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader @@ -219,6 +220,9 @@ class WorkflowAppGenerator(BaseAppGenerator): # new thread with request context and contextvars context = contextvars.copy_context() + # release database connection, because the following new thread operations may take a long time + db.session.close() + worker_thread = threading.Thread( target=self._generate_worker, kwargs={ @@ -233,6 +237,10 @@ class WorkflowAppGenerator(BaseAppGenerator): worker_thread.start() + draft_var_saver_factory = self._get_draft_var_saver_factory( + invoke_from, + ) + # return response or stream generator response = self._handle_response( application_generate_entity=application_generate_entity, @@ -241,6 +249,7 @@ class WorkflowAppGenerator(BaseAppGenerator): user=user, workflow_execution_repository=workflow_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository, + draft_var_saver_factory=draft_var_saver_factory, stream=streaming, ) @@ -471,6 +480,7 @@ class WorkflowAppGenerator(BaseAppGenerator): user: Union[Account, EndUser], workflow_execution_repository: WorkflowExecutionRepository, workflow_node_execution_repository: WorkflowNodeExecutionRepository, + draft_var_saver_factory: DraftVariableSaverFactory, stream: bool = False, ) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]: """ @@ -491,6 +501,7 @@ class WorkflowAppGenerator(BaseAppGenerator): user=user, workflow_execution_repository=workflow_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository, + draft_var_saver_factory=draft_var_saver_factory, stream=stream, ) diff --git a/api/core/app/apps/workflow/generate_task_pipeline.py b/api/core/app/apps/workflow/generate_task_pipeline.py index 1734dbb598..2a85cd5e3d 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/app/apps/workflow/generate_task_pipeline.py @@ -56,6 +56,7 @@ from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.ops.ops_trace_manager import TraceQueueManager from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType from core.workflow.enums import SystemVariableKey +from core.workflow.repositories.draft_variable_repository import DraftVariableSaverFactory from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager @@ -87,6 +88,7 @@ class WorkflowAppGenerateTaskPipeline: stream: bool, workflow_execution_repository: WorkflowExecutionRepository, workflow_node_execution_repository: WorkflowNodeExecutionRepository, + draft_var_saver_factory: DraftVariableSaverFactory, ) -> None: self._base_task_pipeline = BasedGenerateTaskPipeline( application_generate_entity=application_generate_entity, @@ -131,6 +133,8 @@ class WorkflowAppGenerateTaskPipeline: self._application_generate_entity = application_generate_entity self._workflow_features_dict = workflow.features_dict self._workflow_run_id = "" + self._invoke_from = queue_manager._invoke_from + self._draft_var_saver_factory = draft_var_saver_factory def process(self) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]: """ @@ -322,6 +326,8 @@ class WorkflowAppGenerateTaskPipeline: workflow_node_execution=workflow_node_execution, ) + self._save_output_for_event(event, workflow_node_execution.id) + if node_success_response: yield node_success_response elif isinstance( @@ -339,6 +345,8 @@ class WorkflowAppGenerateTaskPipeline: task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, ) + if isinstance(event, QueueNodeExceptionEvent): + self._save_output_for_event(event, workflow_node_execution.id) if node_failed_response: yield node_failed_response @@ -593,3 +601,15 @@ class WorkflowAppGenerateTaskPipeline: ) return response + + def _save_output_for_event(self, event: QueueNodeSucceededEvent | QueueNodeExceptionEvent, node_execution_id: str): + with Session(db.engine) as session, session.begin(): + saver = self._draft_var_saver_factory( + session=session, + app_id=self._application_generate_entity.app_config.app_id, + node_id=event.node_id, + node_type=event.node_type, + node_execution_id=node_execution_id, + enclosing_node_id=event.in_loop_id or event.in_iteration_id, + ) + saver.save(event.process_data, event.outputs) diff --git a/api/core/app/apps/workflow_app_runner.py b/api/core/app/apps/workflow_app_runner.py index dc6c381e86..17b9ac5827 100644 --- a/api/core/app/apps/workflow_app_runner.py +++ b/api/core/app/apps/workflow_app_runner.py @@ -1,8 +1,6 @@ from collections.abc import Mapping from typing import Any, Optional, cast -from sqlalchemy.orm import Session - from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.apps.base_app_runner import AppRunner from core.app.entities.queue_entities import ( @@ -35,7 +33,6 @@ from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey from core.workflow.graph_engine.entities.event import ( AgentLogEvent, - BaseNodeEvent, GraphEngineEvent, GraphRunFailedEvent, GraphRunPartialSucceededEvent, @@ -70,9 +67,6 @@ from core.workflow.workflow_entry import WorkflowEntry from extensions.ext_database import db from models.model import App from models.workflow import Workflow -from services.workflow_draft_variable_service import ( - DraftVariableSaver, -) class WorkflowBasedAppRunner(AppRunner): @@ -400,7 +394,6 @@ class WorkflowBasedAppRunner(AppRunner): in_loop_id=event.in_loop_id, ) ) - self._save_draft_var_for_event(event) elif isinstance(event, NodeRunFailedEvent): self._publish_event( @@ -464,7 +457,6 @@ class WorkflowBasedAppRunner(AppRunner): in_loop_id=event.in_loop_id, ) ) - self._save_draft_var_for_event(event) elif isinstance(event, NodeInIterationFailedEvent): self._publish_event( @@ -718,30 +710,3 @@ class WorkflowBasedAppRunner(AppRunner): def _publish_event(self, event: AppQueueEvent) -> None: self.queue_manager.publish(event, PublishFrom.APPLICATION_MANAGER) - - def _save_draft_var_for_event(self, event: BaseNodeEvent): - run_result = event.route_node_state.node_run_result - if run_result is None: - return - process_data = run_result.process_data - outputs = run_result.outputs - with Session(bind=db.engine) as session, session.begin(): - draft_var_saver = DraftVariableSaver( - session=session, - app_id=self._get_app_id(), - node_id=event.node_id, - node_type=event.node_type, - # FIXME(QuantumGhost): rely on private state of queue_manager is not ideal. - invoke_from=self.queue_manager._invoke_from, - node_execution_id=event.id, - enclosing_node_id=event.in_loop_id or event.in_iteration_id or None, - ) - draft_var_saver.save(process_data=process_data, outputs=outputs) - - -def _remove_first_element_from_variable_string(key: str) -> str: - """ - Remove the first element from the prefix. - """ - prefix, remaining = key.split(".", maxsplit=1) - return remaining diff --git a/api/core/app/task_pipeline/based_generate_task_pipeline.py b/api/core/app/task_pipeline/based_generate_task_pipeline.py index 5331c0cc94..3ed0c3352f 100644 --- a/api/core/app/task_pipeline/based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/based_generate_task_pipeline.py @@ -19,6 +19,7 @@ from core.app.entities.task_entities import ( from core.errors.error import QuotaExceededError from core.model_runtime.errors.invoke import InvokeAuthorizationError, InvokeError from core.moderation.output_moderation import ModerationRule, OutputModeration +from models.enums import MessageStatus from models.model import Message logger = logging.getLogger(__name__) @@ -62,7 +63,7 @@ class BasedGenerateTaskPipeline: return err err_desc = self._error_to_desc(err) - message.status = "error" + message.status = MessageStatus.ERROR message.error = err_desc return err diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index d535e1f835..3c8c7bb5a2 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -395,6 +395,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline): message.provider_response_latency = time.perf_counter() - self._start_at message.total_price = usage.total_price message.currency = usage.currency + self._task_state.llm_result.usage.latency = message.provider_response_latency message.message_metadata = self._task_state.metadata.model_dump_json() if trace_manager: diff --git a/api/core/entities/parameter_entities.py b/api/core/entities/parameter_entities.py index 36800bc263..b071bfa5b1 100644 --- a/api/core/entities/parameter_entities.py +++ b/api/core/entities/parameter_entities.py @@ -15,6 +15,11 @@ class CommonParameterType(StrEnum): MODEL_SELECTOR = "model-selector" TOOLS_SELECTOR = "array[tools]" + # Dynamic select parameter + # Once you are not sure about the available options until authorization is done + # eg: Select a Slack channel from a Slack workspace + DYNAMIC_SELECT = "dynamic-select" + # TOOL_SELECTOR = "tool-selector" diff --git a/api/core/llm_generator/output_parser/structured_output.py b/api/core/llm_generator/output_parser/structured_output.py new file mode 100644 index 0000000000..151cef1bc3 --- /dev/null +++ b/api/core/llm_generator/output_parser/structured_output.py @@ -0,0 +1,380 @@ +import json +from collections.abc import Generator, Mapping, Sequence +from copy import deepcopy +from enum import StrEnum +from typing import Any, Literal, Optional, cast, overload + +import json_repair +from pydantic import TypeAdapter, ValidationError + +from core.llm_generator.output_parser.errors import OutputParserError +from core.llm_generator.prompts import STRUCTURED_OUTPUT_PROMPT +from core.model_manager import ModelInstance +from core.model_runtime.callbacks.base_callback import Callback +from core.model_runtime.entities.llm_entities import ( + LLMResult, + LLMResultChunk, + LLMResultChunkDelta, + LLMResultChunkWithStructuredOutput, + LLMResultWithStructuredOutput, +) +from core.model_runtime.entities.message_entities import ( + AssistantPromptMessage, + PromptMessage, + PromptMessageTool, + SystemPromptMessage, + TextPromptMessageContent, +) +from core.model_runtime.entities.model_entities import AIModelEntity, ParameterRule + + +class ResponseFormat(StrEnum): + """Constants for model response formats""" + + JSON_SCHEMA = "json_schema" # model's structured output mode. some model like gemini, gpt-4o, support this mode. + JSON = "JSON" # model's json mode. some model like claude support this mode. + JSON_OBJECT = "json_object" # json mode's another alias. some model like deepseek-chat, qwen use this alias. + + +class SpecialModelType(StrEnum): + """Constants for identifying model types""" + + GEMINI = "gemini" + OLLAMA = "ollama" + + +@overload +def invoke_llm_with_structured_output( + provider: str, + model_schema: AIModelEntity, + model_instance: ModelInstance, + prompt_messages: Sequence[PromptMessage], + json_schema: Mapping[str, Any], + model_parameters: Optional[Mapping] = None, + tools: Sequence[PromptMessageTool] | None = None, + stop: Optional[list[str]] = None, + stream: Literal[True] = True, + user: Optional[str] = None, + callbacks: Optional[list[Callback]] = None, +) -> Generator[LLMResultChunkWithStructuredOutput, None, None]: ... + + +@overload +def invoke_llm_with_structured_output( + provider: str, + model_schema: AIModelEntity, + model_instance: ModelInstance, + prompt_messages: Sequence[PromptMessage], + json_schema: Mapping[str, Any], + model_parameters: Optional[Mapping] = None, + tools: Sequence[PromptMessageTool] | None = None, + stop: Optional[list[str]] = None, + stream: Literal[False] = False, + user: Optional[str] = None, + callbacks: Optional[list[Callback]] = None, +) -> LLMResultWithStructuredOutput: ... + + +@overload +def invoke_llm_with_structured_output( + provider: str, + model_schema: AIModelEntity, + model_instance: ModelInstance, + prompt_messages: Sequence[PromptMessage], + json_schema: Mapping[str, Any], + model_parameters: Optional[Mapping] = None, + tools: Sequence[PromptMessageTool] | None = None, + stop: Optional[list[str]] = None, + stream: bool = True, + user: Optional[str] = None, + callbacks: Optional[list[Callback]] = None, +) -> LLMResultWithStructuredOutput | Generator[LLMResultChunkWithStructuredOutput, None, None]: ... + + +def invoke_llm_with_structured_output( + provider: str, + model_schema: AIModelEntity, + model_instance: ModelInstance, + prompt_messages: Sequence[PromptMessage], + json_schema: Mapping[str, Any], + model_parameters: Optional[Mapping] = None, + tools: Sequence[PromptMessageTool] | None = None, + stop: Optional[list[str]] = None, + stream: bool = True, + user: Optional[str] = None, + callbacks: Optional[list[Callback]] = None, +) -> LLMResultWithStructuredOutput | Generator[LLMResultChunkWithStructuredOutput, None, None]: + """ + Invoke large language model with structured output + 1. This method invokes model_instance.invoke_llm with json_schema + 2. Try to parse the result as structured output + + :param prompt_messages: prompt messages + :param json_schema: json schema + :param model_parameters: model parameters + :param tools: tools for tool calling + :param stop: stop words + :param stream: is stream response + :param user: unique user id + :param callbacks: callbacks + :return: full response or stream response chunk generator result + """ + + # handle native json schema + model_parameters_with_json_schema: dict[str, Any] = { + **(model_parameters or {}), + } + + if model_schema.support_structure_output: + model_parameters = _handle_native_json_schema( + provider, model_schema, json_schema, model_parameters_with_json_schema, model_schema.parameter_rules + ) + else: + # Set appropriate response format based on model capabilities + _set_response_format(model_parameters_with_json_schema, model_schema.parameter_rules) + + # handle prompt based schema + prompt_messages = _handle_prompt_based_schema( + prompt_messages=prompt_messages, + structured_output_schema=json_schema, + ) + + llm_result = model_instance.invoke_llm( + prompt_messages=list(prompt_messages), + model_parameters=model_parameters_with_json_schema, + tools=tools, + stop=stop, + stream=stream, + user=user, + callbacks=callbacks, + ) + + if isinstance(llm_result, LLMResult): + if not isinstance(llm_result.message.content, str): + raise OutputParserError( + f"Failed to parse structured output, LLM result is not a string: {llm_result.message.content}" + ) + + return LLMResultWithStructuredOutput( + structured_output=_parse_structured_output(llm_result.message.content), + model=llm_result.model, + message=llm_result.message, + usage=llm_result.usage, + system_fingerprint=llm_result.system_fingerprint, + prompt_messages=llm_result.prompt_messages, + ) + else: + + def generator() -> Generator[LLMResultChunkWithStructuredOutput, None, None]: + result_text: str = "" + prompt_messages: Sequence[PromptMessage] = [] + system_fingerprint: Optional[str] = None + for event in llm_result: + if isinstance(event, LLMResultChunk): + prompt_messages = event.prompt_messages + system_fingerprint = event.system_fingerprint + + if isinstance(event.delta.message.content, str): + result_text += event.delta.message.content + elif isinstance(event.delta.message.content, list): + for item in event.delta.message.content: + if isinstance(item, TextPromptMessageContent): + result_text += item.data + + yield LLMResultChunkWithStructuredOutput( + model=model_schema.model, + prompt_messages=prompt_messages, + system_fingerprint=system_fingerprint, + delta=event.delta, + ) + + yield LLMResultChunkWithStructuredOutput( + structured_output=_parse_structured_output(result_text), + model=model_schema.model, + prompt_messages=prompt_messages, + system_fingerprint=system_fingerprint, + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage(content=""), + usage=None, + finish_reason=None, + ), + ) + + return generator() + + +def _handle_native_json_schema( + provider: str, + model_schema: AIModelEntity, + structured_output_schema: Mapping, + model_parameters: dict, + rules: list[ParameterRule], +) -> dict: + """ + Handle structured output for models with native JSON schema support. + + :param model_parameters: Model parameters to update + :param rules: Model parameter rules + :return: Updated model parameters with JSON schema configuration + """ + # Process schema according to model requirements + schema_json = _prepare_schema_for_model(provider, model_schema, structured_output_schema) + + # Set JSON schema in parameters + model_parameters["json_schema"] = json.dumps(schema_json, ensure_ascii=False) + + # Set appropriate response format if required by the model + for rule in rules: + if rule.name == "response_format" and ResponseFormat.JSON_SCHEMA.value in rule.options: + model_parameters["response_format"] = ResponseFormat.JSON_SCHEMA.value + + return model_parameters + + +def _set_response_format(model_parameters: dict, rules: list) -> None: + """ + Set the appropriate response format parameter based on model rules. + + :param model_parameters: Model parameters to update + :param rules: Model parameter rules + """ + for rule in rules: + if rule.name == "response_format": + if ResponseFormat.JSON.value in rule.options: + model_parameters["response_format"] = ResponseFormat.JSON.value + elif ResponseFormat.JSON_OBJECT.value in rule.options: + model_parameters["response_format"] = ResponseFormat.JSON_OBJECT.value + + +def _handle_prompt_based_schema( + prompt_messages: Sequence[PromptMessage], structured_output_schema: Mapping +) -> list[PromptMessage]: + """ + Handle structured output for models without native JSON schema support. + This function modifies the prompt messages to include schema-based output requirements. + + Args: + prompt_messages: Original sequence of prompt messages + + Returns: + list[PromptMessage]: Updated prompt messages with structured output requirements + """ + # Convert schema to string format + schema_str = json.dumps(structured_output_schema, ensure_ascii=False) + + # Find existing system prompt with schema placeholder + system_prompt = next( + (prompt for prompt in prompt_messages if isinstance(prompt, SystemPromptMessage)), + None, + ) + structured_output_prompt = STRUCTURED_OUTPUT_PROMPT.replace("{{schema}}", schema_str) + # Prepare system prompt content + system_prompt_content = ( + structured_output_prompt + "\n\n" + system_prompt.content + if system_prompt and isinstance(system_prompt.content, str) + else structured_output_prompt + ) + system_prompt = SystemPromptMessage(content=system_prompt_content) + + # Extract content from the last user message + + filtered_prompts = [prompt for prompt in prompt_messages if not isinstance(prompt, SystemPromptMessage)] + updated_prompt = [system_prompt] + filtered_prompts + + return updated_prompt + + +def _parse_structured_output(result_text: str) -> Mapping[str, Any]: + structured_output: Mapping[str, Any] = {} + parsed: Mapping[str, Any] = {} + try: + parsed = TypeAdapter(Mapping).validate_json(result_text) + if not isinstance(parsed, dict): + raise OutputParserError(f"Failed to parse structured output: {result_text}") + structured_output = parsed + except ValidationError: + # if the result_text is not a valid json, try to repair it + temp_parsed = json_repair.loads(result_text) + if not isinstance(temp_parsed, dict): + # handle reasoning model like deepseek-r1 got '\n\n\n' prefix + if isinstance(temp_parsed, list): + temp_parsed = next((item for item in temp_parsed if isinstance(item, dict)), {}) + else: + raise OutputParserError(f"Failed to parse structured output: {result_text}") + structured_output = cast(dict, temp_parsed) + return structured_output + + +def _prepare_schema_for_model(provider: str, model_schema: AIModelEntity, schema: Mapping) -> dict: + """ + Prepare JSON schema based on model requirements. + + Different models have different requirements for JSON schema formatting. + This function handles these differences. + + :param schema: The original JSON schema + :return: Processed schema compatible with the current model + """ + + # Deep copy to avoid modifying the original schema + processed_schema = dict(deepcopy(schema)) + + # Convert boolean types to string types (common requirement) + convert_boolean_to_string(processed_schema) + + # Apply model-specific transformations + if SpecialModelType.GEMINI in model_schema.model: + remove_additional_properties(processed_schema) + return processed_schema + elif SpecialModelType.OLLAMA in provider: + return processed_schema + else: + # Default format with name field + return {"schema": processed_schema, "name": "llm_response"} + + +def remove_additional_properties(schema: dict) -> None: + """ + Remove additionalProperties fields from JSON schema. + Used for models like Gemini that don't support this property. + + :param schema: JSON schema to modify in-place + """ + if not isinstance(schema, dict): + return + + # Remove additionalProperties at current level + schema.pop("additionalProperties", None) + + # Process nested structures recursively + for value in schema.values(): + if isinstance(value, dict): + remove_additional_properties(value) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + remove_additional_properties(item) + + +def convert_boolean_to_string(schema: dict) -> None: + """ + Convert boolean type specifications to string in JSON schema. + + :param schema: JSON schema to modify in-place + """ + if not isinstance(schema, dict): + return + + # Check for boolean type at current level + if schema.get("type") == "boolean": + schema["type"] = "string" + + # Process nested dictionaries and lists recursively + for value in schema.values(): + if isinstance(value, dict): + convert_boolean_to_string(value) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + convert_boolean_to_string(item) diff --git a/api/core/llm_generator/prompts.py b/api/core/llm_generator/prompts.py index ddfa1e7a66..ef81e38dc5 100644 --- a/api/core/llm_generator/prompts.py +++ b/api/core/llm_generator/prompts.py @@ -291,3 +291,21 @@ Your task is to convert simple user descriptions into properly formatted JSON Sc Now, generate a JSON Schema based on my description """ # noqa: E501 + +STRUCTURED_OUTPUT_PROMPT = """You’re a helpful AI assistant. You could answer questions and output in JSON format. +constraints: + - You must output in JSON format. + - Do not output boolean value, use string type instead. + - Do not output integer or float value, use number type instead. +eg: + Here is the JSON schema: + {"additionalProperties": false, "properties": {"age": {"type": "number"}, "name": {"type": "string"}}, "required": ["name", "age"], "type": "object"} + + Here is the user's question: + My name is John Doe and I am 30 years old. + + output: + {"name": "John Doe", "age": 30} +Here is the JSON schema: +{{schema}} +""" # noqa: E501 diff --git a/api/core/model_runtime/entities/llm_entities.py b/api/core/model_runtime/entities/llm_entities.py index de5a748d4f..e52b0eba55 100644 --- a/api/core/model_runtime/entities/llm_entities.py +++ b/api/core/model_runtime/entities/llm_entities.py @@ -1,7 +1,7 @@ -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from decimal import Decimal from enum import StrEnum -from typing import Optional +from typing import Any, Optional from pydantic import BaseModel, Field @@ -101,6 +101,20 @@ class LLMResult(BaseModel): system_fingerprint: Optional[str] = None +class LLMStructuredOutput(BaseModel): + """ + Model class for llm structured output. + """ + + structured_output: Optional[Mapping[str, Any]] = None + + +class LLMResultWithStructuredOutput(LLMResult, LLMStructuredOutput): + """ + Model class for llm result with structured output. + """ + + class LLMResultChunkDelta(BaseModel): """ Model class for llm result chunk delta. @@ -123,6 +137,12 @@ class LLMResultChunk(BaseModel): delta: LLMResultChunkDelta +class LLMResultChunkWithStructuredOutput(LLMResultChunk, LLMStructuredOutput): + """ + Model class for llm result chunk with structured output. + """ + + class NumTokensResult(PriceInfo): """ Model class for number of tokens result. diff --git a/api/core/ops/arize_phoenix_trace/__init__.py b/api/core/ops/arize_phoenix_trace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py new file mode 100644 index 0000000000..0b6834acf3 --- /dev/null +++ b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py @@ -0,0 +1,720 @@ +import hashlib +import json +import logging +import os +from datetime import datetime, timedelta +from typing import Optional, Union, cast + +from openinference.semconv.trace import OpenInferenceSpanKindValues, SpanAttributes +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GrpcOTLPSpanExporter +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HttpOTLPSpanExporter +from opentelemetry.sdk import trace as trace_sdk +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from opentelemetry.sdk.trace.id_generator import RandomIdGenerator +from opentelemetry.trace import SpanContext, TraceFlags, TraceState + +from core.ops.base_trace_instance import BaseTraceInstance +from core.ops.entities.config_entity import ArizeConfig, PhoenixConfig +from core.ops.entities.trace_entity import ( + BaseTraceInfo, + DatasetRetrievalTraceInfo, + GenerateNameTraceInfo, + MessageTraceInfo, + ModerationTraceInfo, + SuggestedQuestionTraceInfo, + ToolTraceInfo, + TraceTaskName, + WorkflowTraceInfo, +) +from extensions.ext_database import db +from models.model import EndUser, MessageFile +from models.workflow import WorkflowNodeExecutionModel + +logger = logging.getLogger(__name__) + + +def setup_tracer(arize_phoenix_config: ArizeConfig | PhoenixConfig) -> tuple[trace_sdk.Tracer, SimpleSpanProcessor]: + """Configure OpenTelemetry tracer with OTLP exporter for Arize/Phoenix.""" + try: + # Choose the appropriate exporter based on config type + exporter: Union[GrpcOTLPSpanExporter, HttpOTLPSpanExporter] + if isinstance(arize_phoenix_config, ArizeConfig): + arize_endpoint = f"{arize_phoenix_config.endpoint}/v1" + arize_headers = { + "api_key": arize_phoenix_config.api_key or "", + "space_id": arize_phoenix_config.space_id or "", + "authorization": f"Bearer {arize_phoenix_config.api_key or ''}", + } + exporter = GrpcOTLPSpanExporter( + endpoint=arize_endpoint, + headers=arize_headers, + timeout=30, + ) + else: + phoenix_endpoint = f"{arize_phoenix_config.endpoint}/v1/traces" + phoenix_headers = { + "api_key": arize_phoenix_config.api_key or "", + "authorization": f"Bearer {arize_phoenix_config.api_key or ''}", + } + exporter = HttpOTLPSpanExporter( + endpoint=phoenix_endpoint, + headers=phoenix_headers, + timeout=30, + ) + + attributes = { + "openinference.project.name": arize_phoenix_config.project or "", + "model_id": arize_phoenix_config.project or "", + } + resource = Resource(attributes=attributes) + provider = trace_sdk.TracerProvider(resource=resource) + processor = SimpleSpanProcessor( + exporter, + ) + provider.add_span_processor(processor) + + # Create a named tracer instead of setting the global provider + tracer_name = f"arize_phoenix_tracer_{arize_phoenix_config.project}" + logger.info(f"[Arize/Phoenix] Created tracer with name: {tracer_name}") + return cast(trace_sdk.Tracer, provider.get_tracer(tracer_name)), processor + except Exception as e: + logger.error(f"[Arize/Phoenix] Failed to setup the tracer: {str(e)}", exc_info=True) + raise + + +def datetime_to_nanos(dt: Optional[datetime]) -> int: + """Convert datetime to nanoseconds since epoch. If None, use current time.""" + if dt is None: + dt = datetime.now() + return int(dt.timestamp() * 1_000_000_000) + + +def uuid_to_trace_id(string: Optional[str]) -> int: + """Convert UUID string to a valid trace ID (16-byte integer).""" + if string is None: + string = "" + hash_object = hashlib.sha256(string.encode()) + + # Take the first 16 bytes (128 bits) of the hash + digest = hash_object.digest()[:16] + + # Convert to integer (128 bits) + return int.from_bytes(digest, byteorder="big") + + +class ArizePhoenixDataTrace(BaseTraceInstance): + def __init__( + self, + arize_phoenix_config: ArizeConfig | PhoenixConfig, + ): + super().__init__(arize_phoenix_config) + import logging + + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + self.arize_phoenix_config = arize_phoenix_config + self.tracer, self.processor = setup_tracer(arize_phoenix_config) + self.project = arize_phoenix_config.project + self.file_base_url = os.getenv("FILES_URL", "http://127.0.0.1:5001") + + def trace(self, trace_info: BaseTraceInfo): + logger.info(f"[Arize/Phoenix] Trace: {trace_info}") + try: + if isinstance(trace_info, WorkflowTraceInfo): + self.workflow_trace(trace_info) + if isinstance(trace_info, MessageTraceInfo): + self.message_trace(trace_info) + if isinstance(trace_info, ModerationTraceInfo): + self.moderation_trace(trace_info) + if isinstance(trace_info, SuggestedQuestionTraceInfo): + self.suggested_question_trace(trace_info) + if isinstance(trace_info, DatasetRetrievalTraceInfo): + self.dataset_retrieval_trace(trace_info) + if isinstance(trace_info, ToolTraceInfo): + self.tool_trace(trace_info) + if isinstance(trace_info, GenerateNameTraceInfo): + self.generate_name_trace(trace_info) + + except Exception as e: + logger.error(f"[Arize/Phoenix] Error in the trace: {str(e)}", exc_info=True) + raise + + def workflow_trace(self, trace_info: WorkflowTraceInfo): + if trace_info.message_data is None: + return + + workflow_metadata = { + "workflow_id": trace_info.workflow_run_id or "", + "message_id": trace_info.message_id or "", + "workflow_app_log_id": trace_info.workflow_app_log_id or "", + "status": trace_info.workflow_run_status or "", + "status_message": trace_info.error or "", + "level": "ERROR" if trace_info.error else "DEFAULT", + "total_tokens": trace_info.total_tokens or 0, + } + workflow_metadata.update(trace_info.metadata) + + trace_id = uuid_to_trace_id(trace_info.message_id) + span_id = RandomIdGenerator().generate_span_id() + context = SpanContext( + trace_id=trace_id, + span_id=span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + workflow_span = self.tracer.start_span( + name=TraceTaskName.WORKFLOW_TRACE.value, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.workflow_run_inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value, + SpanAttributes.METADATA: json.dumps(workflow_metadata, ensure_ascii=False), + SpanAttributes.SESSION_ID: trace_info.conversation_id or "", + }, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(context)), + ) + + try: + # Process workflow nodes + for node_execution in self._get_workflow_nodes(trace_info.workflow_run_id): + created_at = node_execution.created_at or datetime.now() + elapsed_time = node_execution.elapsed_time + finished_at = created_at + timedelta(seconds=elapsed_time) + + process_data = json.loads(node_execution.process_data) if node_execution.process_data else {} + + node_metadata = { + "node_id": node_execution.id, + "node_type": node_execution.node_type, + "node_status": node_execution.status, + "tenant_id": node_execution.tenant_id, + "app_id": node_execution.app_id, + "app_name": node_execution.title, + "status": node_execution.status, + "level": "ERROR" if node_execution.status != "succeeded" else "DEFAULT", + } + + if node_execution.execution_metadata: + node_metadata.update(json.loads(node_execution.execution_metadata)) + + # Determine the correct span kind based on node type + span_kind = OpenInferenceSpanKindValues.CHAIN.value + if node_execution.node_type == "llm": + span_kind = OpenInferenceSpanKindValues.LLM.value + provider = process_data.get("model_provider") + model = process_data.get("model_name") + if provider: + node_metadata["ls_provider"] = provider + if model: + node_metadata["ls_model_name"] = model + + usage = json.loads(node_execution.outputs).get("usage", {}) if node_execution.outputs else {} + if usage: + node_metadata["total_tokens"] = usage.get("total_tokens", 0) + node_metadata["prompt_tokens"] = usage.get("prompt_tokens", 0) + node_metadata["completion_tokens"] = usage.get("completion_tokens", 0) + elif node_execution.node_type == "dataset_retrieval": + span_kind = OpenInferenceSpanKindValues.RETRIEVER.value + elif node_execution.node_type == "tool": + span_kind = OpenInferenceSpanKindValues.TOOL.value + else: + span_kind = OpenInferenceSpanKindValues.CHAIN.value + + node_span = self.tracer.start_span( + name=node_execution.node_type, + attributes={ + SpanAttributes.INPUT_VALUE: node_execution.inputs or "{}", + SpanAttributes.OUTPUT_VALUE: node_execution.outputs or "{}", + SpanAttributes.OPENINFERENCE_SPAN_KIND: span_kind, + SpanAttributes.METADATA: json.dumps(node_metadata, ensure_ascii=False), + SpanAttributes.SESSION_ID: trace_info.conversation_id or "", + }, + start_time=datetime_to_nanos(created_at), + ) + + try: + if node_execution.node_type == "llm": + provider = process_data.get("model_provider") + model = process_data.get("model_name") + if provider: + node_span.set_attribute(SpanAttributes.LLM_PROVIDER, provider) + if model: + node_span.set_attribute(SpanAttributes.LLM_MODEL_NAME, model) + + usage = json.loads(node_execution.outputs).get("usage", {}) if node_execution.outputs else {} + if usage: + node_span.set_attribute(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, usage.get("total_tokens", 0)) + node_span.set_attribute( + SpanAttributes.LLM_TOKEN_COUNT_PROMPT, usage.get("prompt_tokens", 0) + ) + node_span.set_attribute( + SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, usage.get("completion_tokens", 0) + ) + finally: + node_span.end(end_time=datetime_to_nanos(finished_at)) + finally: + workflow_span.end(end_time=datetime_to_nanos(trace_info.end_time)) + + def message_trace(self, trace_info: MessageTraceInfo): + if trace_info.message_data is None: + return + + file_list = cast(list[str], trace_info.file_list) or [] + message_file_data: Optional[MessageFile] = trace_info.message_file_data + + if message_file_data is not None: + file_url = f"{self.file_base_url}/{message_file_data.url}" if message_file_data else "" + file_list.append(file_url) + + message_metadata = { + "message_id": trace_info.message_id or "", + "conversation_mode": str(trace_info.conversation_mode or ""), + "user_id": trace_info.message_data.from_account_id or "", + "file_list": json.dumps(file_list), + "status": trace_info.message_data.status or "", + "status_message": trace_info.error or "", + "level": "ERROR" if trace_info.error else "DEFAULT", + "total_tokens": trace_info.total_tokens or 0, + "prompt_tokens": trace_info.message_tokens or 0, + "completion_tokens": trace_info.answer_tokens or 0, + "ls_provider": trace_info.message_data.model_provider or "", + "ls_model_name": trace_info.message_data.model_id or "", + } + message_metadata.update(trace_info.metadata) + + # Add end user data if available + if trace_info.message_data.from_end_user_id: + end_user_data: Optional[EndUser] = ( + db.session.query(EndUser).filter(EndUser.id == trace_info.message_data.from_end_user_id).first() + ) + if end_user_data is not None: + message_metadata["end_user_id"] = end_user_data.session_id + + attributes = { + SpanAttributes.INPUT_VALUE: trace_info.message_data.query, + SpanAttributes.OUTPUT_VALUE: trace_info.message_data.answer, + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value, + SpanAttributes.METADATA: json.dumps(message_metadata, ensure_ascii=False), + SpanAttributes.SESSION_ID: trace_info.message_data.conversation_id, + } + + trace_id = uuid_to_trace_id(trace_info.message_id) + message_span_id = RandomIdGenerator().generate_span_id() + span_context = SpanContext( + trace_id=trace_id, + span_id=message_span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + message_span = self.tracer.start_span( + name=TraceTaskName.MESSAGE_TRACE.value, + attributes=attributes, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(span_context)), + ) + + try: + if trace_info.error: + message_span.add_event( + "exception", + attributes={ + "exception.message": trace_info.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.error, + }, + ) + + # Convert outputs to string based on type + if isinstance(trace_info.outputs, dict | list): + outputs_str = json.dumps(trace_info.outputs, ensure_ascii=False) + elif isinstance(trace_info.outputs, str): + outputs_str = trace_info.outputs + else: + outputs_str = str(trace_info.outputs) + + llm_attributes = { + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.LLM.value, + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: outputs_str, + SpanAttributes.METADATA: json.dumps(message_metadata, ensure_ascii=False), + SpanAttributes.SESSION_ID: trace_info.message_data.conversation_id, + } + + if isinstance(trace_info.inputs, list): + for i, msg in enumerate(trace_info.inputs): + if isinstance(msg, dict): + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.{i}.message.content"] = msg.get("text", "") + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.{i}.message.role"] = msg.get( + "role", "user" + ) + # todo: handle assistant and tool role messages, as they don't always + # have a text field, but may have a tool_calls field instead + # e.g. 'tool_calls': [{'id': '98af3a29-b066-45a5-b4b1-46c74ddafc58', + # 'type': 'function', 'function': {'name': 'current_time', 'arguments': '{}'}}]} + elif isinstance(trace_info.inputs, dict): + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.message.content"] = json.dumps(trace_info.inputs) + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.message.role"] = "user" + elif isinstance(trace_info.inputs, str): + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.message.content"] = trace_info.inputs + llm_attributes[f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.message.role"] = "user" + + if trace_info.total_tokens is not None and trace_info.total_tokens > 0: + llm_attributes[SpanAttributes.LLM_TOKEN_COUNT_TOTAL] = trace_info.total_tokens + if trace_info.message_tokens is not None and trace_info.message_tokens > 0: + llm_attributes[SpanAttributes.LLM_TOKEN_COUNT_PROMPT] = trace_info.message_tokens + if trace_info.answer_tokens is not None and trace_info.answer_tokens > 0: + llm_attributes[SpanAttributes.LLM_TOKEN_COUNT_COMPLETION] = trace_info.answer_tokens + + if trace_info.message_data.model_id is not None: + llm_attributes[SpanAttributes.LLM_MODEL_NAME] = trace_info.message_data.model_id + if trace_info.message_data.model_provider is not None: + llm_attributes[SpanAttributes.LLM_PROVIDER] = trace_info.message_data.model_provider + + if trace_info.message_data and trace_info.message_data.message_metadata: + metadata_dict = json.loads(trace_info.message_data.message_metadata) + if model_params := metadata_dict.get("model_parameters"): + llm_attributes[SpanAttributes.LLM_INVOCATION_PARAMETERS] = json.dumps(model_params) + + llm_span = self.tracer.start_span( + name="llm", + attributes=llm_attributes, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(span_context)), + ) + + try: + if trace_info.error: + llm_span.add_event( + "exception", + attributes={ + "exception.message": trace_info.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.error, + }, + ) + finally: + llm_span.end(end_time=datetime_to_nanos(trace_info.end_time)) + finally: + message_span.end(end_time=datetime_to_nanos(trace_info.end_time)) + + def moderation_trace(self, trace_info: ModerationTraceInfo): + if trace_info.message_data is None: + return + + metadata = { + "message_id": trace_info.message_id, + "tool_name": "moderation", + "status": trace_info.message_data.status, + "status_message": trace_info.message_data.error or "", + "level": "ERROR" if trace_info.message_data.error else "DEFAULT", + } + metadata.update(trace_info.metadata) + + trace_id = uuid_to_trace_id(trace_info.message_id) + span_id = RandomIdGenerator().generate_span_id() + context = SpanContext( + trace_id=trace_id, + span_id=span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + span = self.tracer.start_span( + name=TraceTaskName.MODERATION_TRACE.value, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: json.dumps( + { + "action": trace_info.action, + "flagged": trace_info.flagged, + "preset_response": trace_info.preset_response, + "inputs": trace_info.inputs, + }, + ensure_ascii=False, + ), + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value, + SpanAttributes.METADATA: json.dumps(metadata, ensure_ascii=False), + }, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(context)), + ) + + try: + if trace_info.message_data.error: + span.add_event( + "exception", + attributes={ + "exception.message": trace_info.message_data.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.message_data.error, + }, + ) + finally: + span.end(end_time=datetime_to_nanos(trace_info.end_time)) + + def suggested_question_trace(self, trace_info: SuggestedQuestionTraceInfo): + if trace_info.message_data is None: + return + + start_time = trace_info.start_time or trace_info.message_data.created_at + end_time = trace_info.end_time or trace_info.message_data.updated_at + + metadata = { + "message_id": trace_info.message_id, + "tool_name": "suggested_question", + "status": trace_info.status, + "status_message": trace_info.error or "", + "level": "ERROR" if trace_info.error else "DEFAULT", + "total_tokens": trace_info.total_tokens, + "ls_provider": trace_info.model_provider or "", + "ls_model_name": trace_info.model_id or "", + } + metadata.update(trace_info.metadata) + + trace_id = uuid_to_trace_id(trace_info.message_id) + span_id = RandomIdGenerator().generate_span_id() + context = SpanContext( + trace_id=trace_id, + span_id=span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + span = self.tracer.start_span( + name=TraceTaskName.SUGGESTED_QUESTION_TRACE.value, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: json.dumps(trace_info.suggested_question, ensure_ascii=False), + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value, + SpanAttributes.METADATA: json.dumps(metadata, ensure_ascii=False), + }, + start_time=datetime_to_nanos(start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(context)), + ) + + try: + if trace_info.error: + span.add_event( + "exception", + attributes={ + "exception.message": trace_info.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.error, + }, + ) + finally: + span.end(end_time=datetime_to_nanos(end_time)) + + def dataset_retrieval_trace(self, trace_info: DatasetRetrievalTraceInfo): + if trace_info.message_data is None: + return + + start_time = trace_info.start_time or trace_info.message_data.created_at + end_time = trace_info.end_time or trace_info.message_data.updated_at + + metadata = { + "message_id": trace_info.message_id, + "tool_name": "dataset_retrieval", + "status": trace_info.message_data.status, + "status_message": trace_info.message_data.error or "", + "level": "ERROR" if trace_info.message_data.error else "DEFAULT", + "ls_provider": trace_info.message_data.model_provider or "", + "ls_model_name": trace_info.message_data.model_id or "", + } + metadata.update(trace_info.metadata) + + trace_id = uuid_to_trace_id(trace_info.message_id) + span_id = RandomIdGenerator().generate_span_id() + context = SpanContext( + trace_id=trace_id, + span_id=span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + span = self.tracer.start_span( + name=TraceTaskName.DATASET_RETRIEVAL_TRACE.value, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: json.dumps({"documents": trace_info.documents}, ensure_ascii=False), + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.RETRIEVER.value, + SpanAttributes.METADATA: json.dumps(metadata, ensure_ascii=False), + "start_time": start_time.isoformat() if start_time else "", + "end_time": end_time.isoformat() if end_time else "", + }, + start_time=datetime_to_nanos(start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(context)), + ) + + try: + if trace_info.message_data.error: + span.add_event( + "exception", + attributes={ + "exception.message": trace_info.message_data.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.message_data.error, + }, + ) + finally: + span.end(end_time=datetime_to_nanos(end_time)) + + def tool_trace(self, trace_info: ToolTraceInfo): + if trace_info.message_data is None: + logger.warning("[Arize/Phoenix] Message data is None, skipping tool trace.") + return + + metadata = { + "message_id": trace_info.message_id, + "tool_config": json.dumps(trace_info.tool_config, ensure_ascii=False), + } + + trace_id = uuid_to_trace_id(trace_info.message_id) + tool_span_id = RandomIdGenerator().generate_span_id() + logger.info(f"[Arize/Phoenix] Creating tool trace with trace_id: {trace_id}, span_id: {tool_span_id}") + + # Create span context with the same trace_id as the parent + # todo: Create with the appropriate parent span context, so that the tool span is + # a child of the appropriate span (e.g. message span) + span_context = SpanContext( + trace_id=trace_id, + span_id=tool_span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + tool_params_str = ( + json.dumps(trace_info.tool_parameters, ensure_ascii=False) + if isinstance(trace_info.tool_parameters, dict) + else str(trace_info.tool_parameters) + ) + + span = self.tracer.start_span( + name=trace_info.tool_name, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.tool_inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: trace_info.tool_outputs, + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.TOOL.value, + SpanAttributes.METADATA: json.dumps(metadata, ensure_ascii=False), + SpanAttributes.TOOL_NAME: trace_info.tool_name, + SpanAttributes.TOOL_PARAMETERS: tool_params_str, + }, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(span_context)), + ) + + try: + if trace_info.error: + span.add_event( + "exception", + attributes={ + "exception.message": trace_info.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.error, + }, + ) + finally: + span.end(end_time=datetime_to_nanos(trace_info.end_time)) + + def generate_name_trace(self, trace_info: GenerateNameTraceInfo): + if trace_info.message_data is None: + return + + metadata = { + "project_name": self.project, + "message_id": trace_info.message_id, + "status": trace_info.message_data.status, + "status_message": trace_info.message_data.error or "", + "level": "ERROR" if trace_info.message_data.error else "DEFAULT", + } + metadata.update(trace_info.metadata) + + trace_id = uuid_to_trace_id(trace_info.message_id) + span_id = RandomIdGenerator().generate_span_id() + context = SpanContext( + trace_id=trace_id, + span_id=span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + trace_state=TraceState(), + ) + + span = self.tracer.start_span( + name=TraceTaskName.GENERATE_NAME_TRACE.value, + attributes={ + SpanAttributes.INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), + SpanAttributes.OUTPUT_VALUE: json.dumps(trace_info.outputs, ensure_ascii=False), + SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value, + SpanAttributes.METADATA: json.dumps(metadata, ensure_ascii=False), + SpanAttributes.SESSION_ID: trace_info.message_data.conversation_id, + "start_time": trace_info.start_time.isoformat() if trace_info.start_time else "", + "end_time": trace_info.end_time.isoformat() if trace_info.end_time else "", + }, + start_time=datetime_to_nanos(trace_info.start_time), + context=trace.set_span_in_context(trace.NonRecordingSpan(context)), + ) + + try: + if trace_info.message_data.error: + span.add_event( + "exception", + attributes={ + "exception.message": trace_info.message_data.error, + "exception.type": "Error", + "exception.stacktrace": trace_info.message_data.error, + }, + ) + finally: + span.end(end_time=datetime_to_nanos(trace_info.end_time)) + + def api_check(self): + try: + with self.tracer.start_span("api_check") as span: + span.set_attribute("test", "true") + return True + except Exception as e: + logger.info(f"[Arize/Phoenix] API check failed: {str(e)}", exc_info=True) + raise ValueError(f"[Arize/Phoenix] API check failed: {str(e)}") + + def get_project_url(self): + try: + if self.arize_phoenix_config.endpoint == "https://otlp.arize.com": + return "https://app.arize.com/" + else: + return f"{self.arize_phoenix_config.endpoint}/projects/" + except Exception as e: + logger.info(f"[Arize/Phoenix] Get run url failed: {str(e)}", exc_info=True) + raise ValueError(f"[Arize/Phoenix] Get run url failed: {str(e)}") + + def _get_workflow_nodes(self, workflow_run_id: str): + """Helper method to get workflow nodes""" + workflow_nodes = ( + db.session.query( + WorkflowNodeExecutionModel.id, + WorkflowNodeExecutionModel.tenant_id, + WorkflowNodeExecutionModel.app_id, + WorkflowNodeExecutionModel.title, + WorkflowNodeExecutionModel.node_type, + WorkflowNodeExecutionModel.status, + WorkflowNodeExecutionModel.inputs, + WorkflowNodeExecutionModel.outputs, + WorkflowNodeExecutionModel.created_at, + WorkflowNodeExecutionModel.elapsed_time, + WorkflowNodeExecutionModel.process_data, + WorkflowNodeExecutionModel.execution_metadata, + ) + .filter(WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id) + .all() + ) + return workflow_nodes diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index c988bf48d1..8a2ce58539 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -4,6 +4,8 @@ from pydantic import BaseModel, ValidationInfo, field_validator class TracingProviderEnum(StrEnum): + ARIZE = "arize" + PHOENIX = "phoenix" LANGFUSE = "langfuse" LANGSMITH = "langsmith" OPIK = "opik" @@ -18,6 +20,69 @@ class BaseTracingConfig(BaseModel): ... +class ArizeConfig(BaseTracingConfig): + """ + Model class for Arize tracing config. + """ + + api_key: str | None = None + space_id: str | None = None + project: str | None = None + endpoint: str = "https://otlp.arize.com" + + @field_validator("project") + @classmethod + def project_validator(cls, v, info: ValidationInfo): + if v is None or v == "": + v = "default" + + return v + + @field_validator("endpoint") + @classmethod + def endpoint_validator(cls, v, info: ValidationInfo): + if v is None or v == "": + v = "https://otlp.arize.com" + if not v.startswith(("https://", "http://")): + raise ValueError("endpoint must start with https:// or http://") + if "/" in v[8:]: + parts = v.split("/") + v = parts[0] + "//" + parts[2] + + return v + + +class PhoenixConfig(BaseTracingConfig): + """ + Model class for Phoenix tracing config. + """ + + api_key: str | None = None + project: str | None = None + endpoint: str = "https://app.phoenix.arize.com" + + @field_validator("project") + @classmethod + def project_validator(cls, v, info: ValidationInfo): + if v is None or v == "": + v = "default" + + return v + + @field_validator("endpoint") + @classmethod + def endpoint_validator(cls, v, info: ValidationInfo): + if v is None or v == "": + v = "https://app.phoenix.arize.com" + if not v.startswith(("https://", "http://")): + raise ValueError("endpoint must start with https:// or http://") + if "/" in v[8:]: + parts = v.split("/") + v = parts[0] + "//" + parts[2] + + return v + + class LangfuseConfig(BaseTracingConfig): """ Model class for Langfuse tracing config. diff --git a/api/core/ops/langfuse_trace/langfuse_trace.py b/api/core/ops/langfuse_trace/langfuse_trace.py index 0ea74e9ef0..1d4ae49fc7 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -32,6 +32,7 @@ from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.nodes.enums import NodeType from extensions.ext_database import db from models import EndUser, WorkflowNodeExecutionTriggeredFrom +from models.enums import MessageStatus logger = logging.getLogger(__name__) @@ -83,6 +84,7 @@ class LangFuseDataTrace(BaseTraceInstance): metadata=metadata, session_id=trace_info.conversation_id, tags=["message", "workflow"], + version=trace_info.workflow_run_version, ) self.add_trace(langfuse_trace_data=trace_data) workflow_span_data = LangfuseSpan( @@ -108,6 +110,7 @@ class LangFuseDataTrace(BaseTraceInstance): metadata=metadata, session_id=trace_info.conversation_id, tags=["workflow"], + version=trace_info.workflow_run_version, ) self.add_trace(langfuse_trace_data=trace_data) @@ -172,37 +175,7 @@ class LangFuseDataTrace(BaseTraceInstance): } ) - # add span - if trace_info.message_id: - span_data = LangfuseSpan( - id=node_execution_id, - name=node_type, - input=inputs, - output=outputs, - trace_id=trace_id, - start_time=created_at, - end_time=finished_at, - metadata=metadata, - level=(LevelEnum.DEFAULT if status == "succeeded" else LevelEnum.ERROR), - status_message=trace_info.error or "", - parent_observation_id=trace_info.workflow_run_id, - ) - else: - span_data = LangfuseSpan( - id=node_execution_id, - name=node_type, - input=inputs, - output=outputs, - trace_id=trace_id, - start_time=created_at, - end_time=finished_at, - metadata=metadata, - level=(LevelEnum.DEFAULT if status == "succeeded" else LevelEnum.ERROR), - status_message=trace_info.error or "", - ) - - self.add_span(langfuse_span_data=span_data) - + # add generation span if process_data and process_data.get("model_mode") == "chat": total_token = metadata.get("total_tokens", 0) prompt_tokens = 0 @@ -226,10 +199,10 @@ class LangFuseDataTrace(BaseTraceInstance): ) node_generation_data = LangfuseGeneration( - name="llm", + id=node_execution_id, + name=node_name, trace_id=trace_id, model=process_data.get("model_name"), - parent_observation_id=node_execution_id, start_time=created_at, end_time=finished_at, input=inputs, @@ -237,11 +210,30 @@ class LangFuseDataTrace(BaseTraceInstance): metadata=metadata, level=(LevelEnum.DEFAULT if status == "succeeded" else LevelEnum.ERROR), status_message=trace_info.error or "", + parent_observation_id=trace_info.workflow_run_id if trace_info.message_id else None, usage=generation_usage, ) self.add_generation(langfuse_generation_data=node_generation_data) + # add normal span + else: + span_data = LangfuseSpan( + id=node_execution_id, + name=node_name, + input=inputs, + output=outputs, + trace_id=trace_id, + start_time=created_at, + end_time=finished_at, + metadata=metadata, + level=(LevelEnum.DEFAULT if status == "succeeded" else LevelEnum.ERROR), + status_message=trace_info.error or "", + parent_observation_id=trace_info.workflow_run_id if trace_info.message_id else None, + ) + + self.add_span(langfuse_span_data=span_data) + def message_trace(self, trace_info: MessageTraceInfo, **kwargs): # get message file data file_list = trace_info.file_list @@ -284,7 +276,7 @@ class LangFuseDataTrace(BaseTraceInstance): ) self.add_trace(langfuse_trace_data=trace_data) - # start add span + # add generation generation_usage = GenerationUsage( input=trace_info.message_tokens, output=trace_info.answer_tokens, @@ -302,7 +294,7 @@ class LangFuseDataTrace(BaseTraceInstance): input=trace_info.inputs, output=message_data.answer, metadata=metadata, - level=(LevelEnum.DEFAULT if message_data.status != "error" else LevelEnum.ERROR), + level=(LevelEnum.DEFAULT if message_data.status != MessageStatus.ERROR else LevelEnum.ERROR), status_message=message_data.error or "", usage=generation_usage, ) @@ -348,7 +340,7 @@ class LangFuseDataTrace(BaseTraceInstance): start_time=trace_info.start_time, end_time=trace_info.end_time, metadata=trace_info.metadata, - level=(LevelEnum.DEFAULT if message_data.status != "error" else LevelEnum.ERROR), + level=(LevelEnum.DEFAULT if message_data.status != MessageStatus.ERROR else LevelEnum.ERROR), status_message=message_data.error or "", usage=generation_usage, ) diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index a98904102c..d6d6b4a1d4 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -41,6 +41,28 @@ from tasks.ops_trace_task import process_trace_tasks class OpsTraceProviderConfigMap(dict[str, dict[str, Any]]): def __getitem__(self, provider: str) -> dict[str, Any]: match provider: + case TracingProviderEnum.ARIZE: + from core.ops.arize_phoenix_trace.arize_phoenix_trace import ArizePhoenixDataTrace + from core.ops.entities.config_entity import ArizeConfig + + return { + "config_class": ArizeConfig, + "secret_keys": ["api_key", "space_id"], + "other_keys": ["project", "endpoint"], + "trace_instance": ArizePhoenixDataTrace, + } + + case TracingProviderEnum.PHOENIX: + from core.ops.arize_phoenix_trace.arize_phoenix_trace import ArizePhoenixDataTrace + from core.ops.entities.config_entity import PhoenixConfig + + return { + "config_class": PhoenixConfig, + "secret_keys": ["api_key"], + "other_keys": ["project", "endpoint"], + "trace_instance": ArizePhoenixDataTrace, + } + case TracingProviderEnum.LANGFUSE: from core.ops.entities.config_entity import LangfuseConfig from core.ops.langfuse_trace.langfuse_trace import LangFuseDataTrace @@ -84,7 +106,26 @@ class OpsTraceProviderConfigMap(dict[str, dict[str, Any]]): "other_keys": ["project", "entity", "endpoint", "host"], "trace_instance": WeaveDataTrace, } + case TracingProviderEnum.ARIZE: + from core.ops.arize_phoenix_trace.arize_phoenix_trace import ArizePhoenixDataTrace + from core.ops.entities.config_entity import ArizeConfig + + return { + "config_class": ArizeConfig, + "secret_keys": ["api_key", "space_id"], + "other_keys": ["project", "endpoint"], + "trace_instance": ArizePhoenixDataTrace, + } + case TracingProviderEnum.PHOENIX: + from core.ops.arize_phoenix_trace.arize_phoenix_trace import ArizePhoenixDataTrace + from core.ops.entities.config_entity import PhoenixConfig + return { + "config_class": PhoenixConfig, + "secret_keys": ["api_key"], + "other_keys": ["project", "endpoint"], + "trace_instance": ArizePhoenixDataTrace, + } case _: raise KeyError(f"Unsupported tracing provider: {provider}") diff --git a/api/core/plugin/backwards_invocation/model.py b/api/core/plugin/backwards_invocation/model.py index 072644e53b..d07ab3d0c4 100644 --- a/api/core/plugin/backwards_invocation/model.py +++ b/api/core/plugin/backwards_invocation/model.py @@ -2,8 +2,15 @@ import tempfile from binascii import hexlify, unhexlify from collections.abc import Generator +from core.llm_generator.output_parser.structured_output import invoke_llm_with_structured_output from core.model_manager import ModelManager -from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta +from core.model_runtime.entities.llm_entities import ( + LLMResult, + LLMResultChunk, + LLMResultChunkDelta, + LLMResultChunkWithStructuredOutput, + LLMResultWithStructuredOutput, +) from core.model_runtime.entities.message_entities import ( PromptMessage, SystemPromptMessage, @@ -12,6 +19,7 @@ from core.model_runtime.entities.message_entities import ( from core.plugin.backwards_invocation.base import BaseBackwardsInvocation from core.plugin.entities.request import ( RequestInvokeLLM, + RequestInvokeLLMWithStructuredOutput, RequestInvokeModeration, RequestInvokeRerank, RequestInvokeSpeech2Text, @@ -81,6 +89,72 @@ class PluginModelBackwardsInvocation(BaseBackwardsInvocation): return handle_non_streaming(response) + @classmethod + def invoke_llm_with_structured_output( + cls, user_id: str, tenant: Tenant, payload: RequestInvokeLLMWithStructuredOutput + ): + """ + invoke llm with structured output + """ + model_instance = ModelManager().get_model_instance( + tenant_id=tenant.id, + provider=payload.provider, + model_type=payload.model_type, + model=payload.model, + ) + + model_schema = model_instance.model_type_instance.get_model_schema(payload.model, model_instance.credentials) + + if not model_schema: + raise ValueError(f"Model schema not found for {payload.model}") + + response = invoke_llm_with_structured_output( + provider=payload.provider, + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=payload.prompt_messages, + json_schema=payload.structured_output_schema, + tools=payload.tools, + stop=payload.stop, + stream=True if payload.stream is None else payload.stream, + user=user_id, + model_parameters=payload.completion_params, + ) + + if isinstance(response, Generator): + + def handle() -> Generator[LLMResultChunkWithStructuredOutput, None, None]: + for chunk in response: + if chunk.delta.usage: + llm_utils.deduct_llm_quota( + tenant_id=tenant.id, model_instance=model_instance, usage=chunk.delta.usage + ) + chunk.prompt_messages = [] + yield chunk + + return handle() + else: + if response.usage: + llm_utils.deduct_llm_quota(tenant_id=tenant.id, model_instance=model_instance, usage=response.usage) + + def handle_non_streaming( + response: LLMResultWithStructuredOutput, + ) -> Generator[LLMResultChunkWithStructuredOutput, None, None]: + yield LLMResultChunkWithStructuredOutput( + model=response.model, + prompt_messages=[], + system_fingerprint=response.system_fingerprint, + structured_output=response.structured_output, + delta=LLMResultChunkDelta( + index=0, + message=response.message, + usage=response.usage, + finish_reason="", + ), + ) + + return handle_non_streaming(response) + @classmethod def invoke_text_embedding(cls, user_id: str, tenant: Tenant, payload: RequestInvokeTextEmbedding): """ diff --git a/api/core/plugin/entities/parameters.py b/api/core/plugin/entities/parameters.py index 895dd0d0fc..2b438a3c33 100644 --- a/api/core/plugin/entities/parameters.py +++ b/api/core/plugin/entities/parameters.py @@ -10,6 +10,9 @@ from core.tools.entities.common_entities import I18nObject class PluginParameterOption(BaseModel): value: str = Field(..., description="The value of the option") label: I18nObject = Field(..., description="The label of the option") + icon: Optional[str] = Field( + default=None, description="The icon of the option, can be a url or a base64 encoded image" + ) @field_validator("value", mode="before") @classmethod @@ -35,6 +38,7 @@ class PluginParameterType(enum.StrEnum): APP_SELECTOR = CommonParameterType.APP_SELECTOR.value MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value + DYNAMIC_SELECT = CommonParameterType.DYNAMIC_SELECT.value # deprecated, should not use. SYSTEM_FILES = CommonParameterType.SYSTEM_FILES.value diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index e0d2857e97..592b42c0da 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -1,4 +1,4 @@ -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from datetime import datetime from enum import StrEnum from typing import Any, Generic, Optional, TypeVar @@ -9,6 +9,7 @@ from core.agent.plugin_entities import AgentProviderEntityWithPlugin from core.model_runtime.entities.model_entities import AIModelEntity from core.model_runtime.entities.provider_entities import ProviderEntity from core.plugin.entities.base import BasePluginEntity +from core.plugin.entities.parameters import PluginParameterOption from core.plugin.entities.plugin import PluginDeclaration, PluginEntity from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin @@ -186,3 +187,7 @@ class PluginOAuthCredentialsResponse(BaseModel): class PluginListResponse(BaseModel): list: list[PluginEntity] total: int + + +class PluginDynamicSelectOptionsResponse(BaseModel): + options: Sequence[PluginParameterOption] = Field(description="The options of the dynamic select.") diff --git a/api/core/plugin/entities/request.py b/api/core/plugin/entities/request.py index 1692020ec8..f9c81ed4d5 100644 --- a/api/core/plugin/entities/request.py +++ b/api/core/plugin/entities/request.py @@ -82,6 +82,16 @@ class RequestInvokeLLM(BaseRequestInvokeModel): return v +class RequestInvokeLLMWithStructuredOutput(RequestInvokeLLM): + """ + Request to invoke LLM with structured output + """ + + structured_output_schema: dict[str, Any] = Field( + default_factory=dict, description="The schema of the structured output in JSON schema format" + ) + + class RequestInvokeTextEmbedding(BaseRequestInvokeModel): """ Request to invoke text embedding diff --git a/api/core/plugin/impl/dynamic_select.py b/api/core/plugin/impl/dynamic_select.py new file mode 100644 index 0000000000..004412afd7 --- /dev/null +++ b/api/core/plugin/impl/dynamic_select.py @@ -0,0 +1,45 @@ +from collections.abc import Mapping +from typing import Any + +from core.plugin.entities.plugin import GenericProviderID +from core.plugin.entities.plugin_daemon import PluginDynamicSelectOptionsResponse +from core.plugin.impl.base import BasePluginClient + + +class DynamicSelectClient(BasePluginClient): + def fetch_dynamic_select_options( + self, + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + action: str, + credentials: Mapping[str, Any], + parameter: str, + ) -> PluginDynamicSelectOptionsResponse: + """ + Fetch dynamic select options for a plugin parameter. + """ + response = self._request_with_plugin_daemon_response_stream( + "POST", + f"plugin/{tenant_id}/dispatch/dynamic_select/fetch_parameter_options", + PluginDynamicSelectOptionsResponse, + data={ + "user_id": user_id, + "data": { + "provider": GenericProviderID(provider).provider_name, + "credentials": credentials, + "provider_action": action, + "parameter": parameter, + }, + }, + headers={ + "X-Plugin-ID": plugin_id, + "Content-Type": "application/json", + }, + ) + + for options in response: + return options + + raise ValueError(f"Plugin service returned no options for parameter '{parameter}' in provider '{provider}'") diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 38c0b540d5..3fca48be22 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -1010,6 +1010,9 @@ class DatasetRetrieval: def _process_metadata_filter_func( self, sequence: int, condition: str, metadata_name: str, value: Optional[Any], filters: list ): + if value is None: + return + key = f"{metadata_name}_{sequence}" key_value = f"{metadata_name}_{sequence}_value" match condition: diff --git a/api/core/tools/builtin_tool/providers/code/tools/simple_code.py b/api/core/tools/builtin_tool/providers/code/tools/simple_code.py index ab0e155b98..b4e650e0ed 100644 --- a/api/core/tools/builtin_tool/providers/code/tools/simple_code.py +++ b/api/core/tools/builtin_tool/providers/code/tools/simple_code.py @@ -4,6 +4,7 @@ from typing import Any, Optional from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage from core.tools.builtin_tool.tool import BuiltinTool from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.errors import ToolInvokeError class SimpleCode(BuiltinTool): @@ -25,6 +26,8 @@ class SimpleCode(BuiltinTool): if language not in {CodeLanguage.PYTHON3, CodeLanguage.JAVASCRIPT}: raise ValueError(f"Only python3 and javascript are supported, not {language}") - result = CodeExecutor.execute_code(language, "", code) - - yield self.create_text_message(result) + try: + result = CodeExecutor.execute_code(language, "", code) + yield self.create_text_message(result) + except Exception as e: + raise ToolInvokeError(str(e)) diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index 03047c0545..d2c28076ae 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -240,6 +240,7 @@ class ToolParameter(PluginParameter): FILES = PluginParameterType.FILES.value APP_SELECTOR = PluginParameterType.APP_SELECTOR.value MODEL_SELECTOR = PluginParameterType.MODEL_SELECTOR.value + DYNAMIC_SELECT = PluginParameterType.DYNAMIC_SELECT.value # deprecated, should not use. SYSTEM_FILES = PluginParameterType.SYSTEM_FILES.value diff --git a/api/core/tools/utils/configuration.py b/api/core/tools/utils/configuration.py index 6a5fba65bd..1f23e90351 100644 --- a/api/core/tools/utils/configuration.py +++ b/api/core/tools/utils/configuration.py @@ -86,6 +86,7 @@ class ProviderConfigEncrypter(BaseModel): cached_credentials = cache.get() if cached_credentials: return cached_credentials + data = self._deep_copy(data) # get fields need to be decrypted fields = dict[str, BasicProviderConfig]() diff --git a/api/core/workflow/entities/workflow_node_execution.py b/api/core/workflow/entities/workflow_node_execution.py index 773f5b777b..09a408f4d7 100644 --- a/api/core/workflow/entities/workflow_node_execution.py +++ b/api/core/workflow/entities/workflow_node_execution.py @@ -66,11 +66,21 @@ class WorkflowNodeExecution(BaseModel): but they are not stored in the model. """ - # Core identification fields - id: str # Unique identifier for this execution record - node_execution_id: Optional[str] = None # Optional secondary ID for cross-referencing + # --------- Core identification fields --------- + + # Unique identifier for this execution record, used when persisting to storage. + # Value is a UUID string (e.g., '09b3e04c-f9ae-404c-ad82-290b8d7bd382'). + id: str + + # Optional secondary ID for cross-referencing purposes. + # + # NOTE: For referencing the persisted record, use `id` rather than `node_execution_id`. + # While `node_execution_id` may sometimes be a UUID string, this is not guaranteed. + # In most scenarios, `id` should be used as the primary identifier. + node_execution_id: Optional[str] = None workflow_id: str # ID of the workflow this node belongs to workflow_execution_id: Optional[str] = None # ID of the specific workflow run (null for single-step debugging) + # --------- Core identification fields ends --------- # Execution positioning and flow index: int # Sequence number for ordering in trace visualization diff --git a/api/core/workflow/nodes/agent/agent_node.py b/api/core/workflow/nodes/agent/agent_node.py index 2f28363955..987f670acb 100644 --- a/api/core/workflow/nodes/agent/agent_node.py +++ b/api/core/workflow/nodes/agent/agent_node.py @@ -158,7 +158,10 @@ class AgentNode(ToolNode): # variable_pool.convert_template expects a string template, # but if passing a dict, convert to JSON string first before rendering try: - parameter_value = json.dumps(agent_input.value, ensure_ascii=False) + if not isinstance(agent_input.value, str): + parameter_value = json.dumps(agent_input.value, ensure_ascii=False) + else: + parameter_value = str(agent_input.value) except TypeError: parameter_value = str(agent_input.value) segment_group = variable_pool.convert_template(parameter_value) @@ -166,7 +169,8 @@ class AgentNode(ToolNode): # variable_pool.convert_template returns a string, # so we need to convert it back to a dictionary try: - parameter_value = json.loads(parameter_value) + if not isinstance(agent_input.value, str): + parameter_value = json.loads(parameter_value) except json.JSONDecodeError: parameter_value = parameter_value else: diff --git a/api/core/workflow/nodes/answer/answer_stream_processor.py b/api/core/workflow/nodes/answer/answer_stream_processor.py index f3e4a62ade..97666fad05 100644 --- a/api/core/workflow/nodes/answer/answer_stream_processor.py +++ b/api/core/workflow/nodes/answer/answer_stream_processor.py @@ -2,7 +2,6 @@ import logging from collections.abc import Generator from typing import cast -from core.file import FILE_MODEL_IDENTITY, File from core.workflow.entities.variable_pool import VariablePool from core.workflow.graph_engine.entities.event import ( GraphEngineEvent, @@ -201,44 +200,3 @@ class AnswerStreamProcessor(StreamProcessor): stream_out_answer_node_ids.append(answer_node_id) return stream_out_answer_node_ids - - @classmethod - def _fetch_files_from_variable_value(cls, value: dict | list) -> list[dict]: - """ - Fetch files from variable value - :param value: variable value - :return: - """ - if not value: - return [] - - files = [] - if isinstance(value, list): - for item in value: - file_var = cls._get_file_var_from_value(item) - if file_var: - files.append(file_var) - elif isinstance(value, dict): - file_var = cls._get_file_var_from_value(value) - if file_var: - files.append(file_var) - - return files - - @classmethod - def _get_file_var_from_value(cls, value: dict | list): - """ - Get file var from value - :param value: variable value - :return: - """ - if not value: - return None - - if isinstance(value, dict): - if "dify_model_identity" in value and value["dify_model_identity"] == FILE_MODEL_IDENTITY: - return value - elif isinstance(value, File): - return value.to_dict() - - return None diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 2c83b00d4a..b0a14229c5 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -333,7 +333,7 @@ class Executor: try: response = getattr(ssrf_proxy, self.method.lower())(**request_args) except (ssrf_proxy.MaxRetriesExceededError, httpx.RequestError) as e: - raise HttpRequestNodeError(str(e)) + raise HttpRequestNodeError(str(e)) from e # FIXME: fix type ignore, this maybe httpx type issue return response # type: ignore diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 0b9e98f28a..b34d62d669 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -490,6 +490,9 @@ class KnowledgeRetrievalNode(LLMNode): def _process_metadata_filter_func( self, sequence: int, condition: str, metadata_name: str, value: Optional[Any], filters: list ): + if value is None: + return + key = f"{metadata_name}_{sequence}" key_value = f"{metadata_name}_{sequence}_value" match condition: diff --git a/api/core/workflow/nodes/llm/node.py b/api/core/workflow/nodes/llm/node.py index 124ae6d75d..b5225ce548 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -5,11 +5,11 @@ import logging from collections.abc import Generator, Mapping, Sequence from typing import TYPE_CHECKING, Any, Optional, cast -import json_repair - from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity from core.file import FileType, file_manager from core.helper.code_executor import CodeExecutor, CodeLanguage +from core.llm_generator.output_parser.errors import OutputParserError +from core.llm_generator.output_parser.structured_output import invoke_llm_with_structured_output from core.memory.token_buffer_memory import TokenBufferMemory from core.model_manager import ModelInstance, ModelManager from core.model_runtime.entities import ( @@ -18,7 +18,13 @@ from core.model_runtime.entities import ( PromptMessageContentType, TextPromptMessageContent, ) -from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMUsage +from core.model_runtime.entities.llm_entities import ( + LLMResult, + LLMResultChunk, + LLMResultChunkWithStructuredOutput, + LLMStructuredOutput, + LLMUsage, +) from core.model_runtime.entities.message_entities import ( AssistantPromptMessage, PromptMessageContentUnionTypes, @@ -31,7 +37,6 @@ from core.model_runtime.entities.model_entities import ( ModelFeature, ModelPropertyKey, ModelType, - ParameterRule, ) from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.utils.encoders import jsonable_encoder @@ -62,11 +67,6 @@ from core.workflow.nodes.event import ( RunRetrieverResourceEvent, RunStreamChunkEvent, ) -from core.workflow.utils.structured_output.entities import ( - ResponseFormat, - SpecialModelType, -) -from core.workflow.utils.structured_output.prompt import STRUCTURED_OUTPUT_PROMPT from core.workflow.utils.variable_template_parser import VariableTemplateParser from . import llm_utils @@ -143,12 +143,6 @@ class LLMNode(BaseNode[LLMNodeData]): return "1" def _run(self) -> Generator[NodeEvent | InNodeEvent, None, None]: - def process_structured_output(text: str) -> Optional[dict[str, Any]]: - """Process structured output if enabled""" - if not self.node_data.structured_output_enabled or not self.node_data.structured_output: - return None - return self._parse_structured_output(text) - node_inputs: Optional[dict[str, Any]] = None process_data = None result_text = "" @@ -244,6 +238,8 @@ class LLMNode(BaseNode[LLMNodeData]): stop=stop, ) + structured_output: LLMStructuredOutput | None = None + for event in generator: if isinstance(event, RunStreamChunkEvent): yield event @@ -254,10 +250,12 @@ class LLMNode(BaseNode[LLMNodeData]): # deduct quota llm_utils.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) break + elif isinstance(event, LLMStructuredOutput): + structured_output = event + outputs = {"text": result_text, "usage": jsonable_encoder(usage), "finish_reason": finish_reason} - structured_output = process_structured_output(result_text) if structured_output: - outputs["structured_output"] = structured_output + outputs["structured_output"] = structured_output.structured_output if self._file_outputs is not None: outputs["files"] = ArrayFileSegment(value=self._file_outputs) @@ -302,20 +300,40 @@ class LLMNode(BaseNode[LLMNodeData]): model_instance: ModelInstance, prompt_messages: Sequence[PromptMessage], stop: Optional[Sequence[str]] = None, - ) -> Generator[NodeEvent, None, None]: - invoke_result = model_instance.invoke_llm( - prompt_messages=list(prompt_messages), - model_parameters=node_data_model.completion_params, - stop=list(stop or []), - stream=True, - user=self.user_id, + ) -> Generator[NodeEvent | LLMStructuredOutput, None, None]: + model_schema = model_instance.model_type_instance.get_model_schema( + node_data_model.name, model_instance.credentials ) + if not model_schema: + raise ValueError(f"Model schema not found for {node_data_model.name}") + + if self.node_data.structured_output_enabled: + output_schema = self._fetch_structured_output_schema() + invoke_result = invoke_llm_with_structured_output( + provider=model_instance.provider, + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=output_schema, + model_parameters=node_data_model.completion_params, + stop=list(stop or []), + stream=True, + user=self.user_id, + ) + else: + invoke_result = model_instance.invoke_llm( + prompt_messages=list(prompt_messages), + model_parameters=node_data_model.completion_params, + stop=list(stop or []), + stream=True, + user=self.user_id, + ) return self._handle_invoke_result(invoke_result=invoke_result) def _handle_invoke_result( - self, invoke_result: LLMResult | Generator[LLMResultChunk, None, None] - ) -> Generator[NodeEvent, None, None]: + self, invoke_result: LLMResult | Generator[LLMResultChunk | LLMStructuredOutput, None, None] + ) -> Generator[NodeEvent | LLMStructuredOutput, None, None]: # For blocking mode if isinstance(invoke_result, LLMResult): event = self._handle_blocking_result(invoke_result=invoke_result) @@ -329,23 +347,32 @@ class LLMNode(BaseNode[LLMNodeData]): usage = LLMUsage.empty_usage() finish_reason = None full_text_buffer = io.StringIO() - for result in invoke_result: - contents = result.delta.message.content - for text_part in self._save_multimodal_output_and_convert_result_to_markdown(contents): - full_text_buffer.write(text_part) - yield RunStreamChunkEvent(chunk_content=text_part, from_variable_selector=[self.node_id, "text"]) - - # Update the whole metadata - if not model and result.model: - model = result.model - if len(prompt_messages) == 0: - # TODO(QuantumGhost): it seems that this update has no visable effect. - # What's the purpose of the line below? - prompt_messages = list(result.prompt_messages) - if usage.prompt_tokens == 0 and result.delta.usage: - usage = result.delta.usage - if finish_reason is None and result.delta.finish_reason: - finish_reason = result.delta.finish_reason + # Consume the invoke result and handle generator exception + try: + for result in invoke_result: + if isinstance(result, LLMResultChunkWithStructuredOutput): + yield result + if isinstance(result, LLMResultChunk): + contents = result.delta.message.content + for text_part in self._save_multimodal_output_and_convert_result_to_markdown(contents): + full_text_buffer.write(text_part) + yield RunStreamChunkEvent( + chunk_content=text_part, from_variable_selector=[self.node_id, "text"] + ) + + # Update the whole metadata + if not model and result.model: + model = result.model + if len(prompt_messages) == 0: + # TODO(QuantumGhost): it seems that this update has no visable effect. + # What's the purpose of the line below? + prompt_messages = list(result.prompt_messages) + if usage.prompt_tokens == 0 and result.delta.usage: + usage = result.delta.usage + if finish_reason is None and result.delta.finish_reason: + finish_reason = result.delta.finish_reason + except OutputParserError as e: + raise LLMNodeError(f"Failed to parse structured output: {e}") yield ModelInvokeCompletedEvent(text=full_text_buffer.getvalue(), usage=usage, finish_reason=finish_reason) @@ -522,12 +549,6 @@ class LLMNode(BaseNode[LLMNodeData]): if not model_schema: raise ModelNotExistError(f"Model {node_data_model.name} not exist.") - if self.node_data.structured_output_enabled: - if model_schema.support_structure_output: - completion_params = self._handle_native_json_schema(completion_params, model_schema.parameter_rules) - else: - # Set appropriate response format based on model capabilities - self._set_response_format(completion_params, model_schema.parameter_rules) model_config_with_cred.parameters = completion_params # NOTE(-LAN-): This line modify the `self.node_data.model`, which is used in `_invoke_llm()`. node_data_model.completion_params = completion_params @@ -719,32 +740,8 @@ class LLMNode(BaseNode[LLMNodeData]): ) if not model_schema: raise ModelNotExistError(f"Model {model_config.model} not exist.") - if self.node_data.structured_output_enabled: - if not model_schema.support_structure_output: - filtered_prompt_messages = self._handle_prompt_based_schema( - prompt_messages=filtered_prompt_messages, - ) return filtered_prompt_messages, model_config.stop - def _parse_structured_output(self, result_text: str) -> dict[str, Any]: - structured_output: dict[str, Any] = {} - try: - parsed = json.loads(result_text) - if not isinstance(parsed, dict): - raise LLMNodeError(f"Failed to parse structured output: {result_text}") - structured_output = parsed - except json.JSONDecodeError as e: - # if the result_text is not a valid json, try to repair it - parsed = json_repair.loads(result_text) - if not isinstance(parsed, dict): - # handle reasoning model like deepseek-r1 got '\n\n\n' prefix - if isinstance(parsed, list): - parsed = next((item for item in parsed if isinstance(item, dict)), {}) - else: - raise LLMNodeError(f"Failed to parse structured output: {result_text}") - structured_output = parsed - return structured_output - @classmethod def _extract_variable_selector_to_variable_mapping( cls, @@ -934,104 +931,6 @@ class LLMNode(BaseNode[LLMNodeData]): self._file_outputs.append(saved_file) return saved_file - def _handle_native_json_schema(self, model_parameters: dict, rules: list[ParameterRule]) -> dict: - """ - Handle structured output for models with native JSON schema support. - - :param model_parameters: Model parameters to update - :param rules: Model parameter rules - :return: Updated model parameters with JSON schema configuration - """ - # Process schema according to model requirements - schema = self._fetch_structured_output_schema() - schema_json = self._prepare_schema_for_model(schema) - - # Set JSON schema in parameters - model_parameters["json_schema"] = json.dumps(schema_json, ensure_ascii=False) - - # Set appropriate response format if required by the model - for rule in rules: - if rule.name == "response_format" and ResponseFormat.JSON_SCHEMA.value in rule.options: - model_parameters["response_format"] = ResponseFormat.JSON_SCHEMA.value - - return model_parameters - - def _handle_prompt_based_schema(self, prompt_messages: Sequence[PromptMessage]) -> list[PromptMessage]: - """ - Handle structured output for models without native JSON schema support. - This function modifies the prompt messages to include schema-based output requirements. - - Args: - prompt_messages: Original sequence of prompt messages - - Returns: - list[PromptMessage]: Updated prompt messages with structured output requirements - """ - # Convert schema to string format - schema_str = json.dumps(self._fetch_structured_output_schema(), ensure_ascii=False) - - # Find existing system prompt with schema placeholder - system_prompt = next( - (prompt for prompt in prompt_messages if isinstance(prompt, SystemPromptMessage)), - None, - ) - structured_output_prompt = STRUCTURED_OUTPUT_PROMPT.replace("{{schema}}", schema_str) - # Prepare system prompt content - system_prompt_content = ( - structured_output_prompt + "\n\n" + system_prompt.content - if system_prompt and isinstance(system_prompt.content, str) - else structured_output_prompt - ) - system_prompt = SystemPromptMessage(content=system_prompt_content) - - # Extract content from the last user message - - filtered_prompts = [prompt for prompt in prompt_messages if not isinstance(prompt, SystemPromptMessage)] - updated_prompt = [system_prompt] + filtered_prompts - - return updated_prompt - - def _set_response_format(self, model_parameters: dict, rules: list) -> None: - """ - Set the appropriate response format parameter based on model rules. - - :param model_parameters: Model parameters to update - :param rules: Model parameter rules - """ - for rule in rules: - if rule.name == "response_format": - if ResponseFormat.JSON.value in rule.options: - model_parameters["response_format"] = ResponseFormat.JSON.value - elif ResponseFormat.JSON_OBJECT.value in rule.options: - model_parameters["response_format"] = ResponseFormat.JSON_OBJECT.value - - def _prepare_schema_for_model(self, schema: dict) -> dict: - """ - Prepare JSON schema based on model requirements. - - Different models have different requirements for JSON schema formatting. - This function handles these differences. - - :param schema: The original JSON schema - :return: Processed schema compatible with the current model - """ - - # Deep copy to avoid modifying the original schema - processed_schema = schema.copy() - - # Convert boolean types to string types (common requirement) - convert_boolean_to_string(processed_schema) - - # Apply model-specific transformations - if SpecialModelType.GEMINI in self.node_data.model.name: - remove_additional_properties(processed_schema) - return processed_schema - elif SpecialModelType.OLLAMA in self.node_data.model.provider: - return processed_schema - else: - # Default format with name field - return {"schema": processed_schema, "name": "llm_response"} - def _fetch_model_schema(self, provider: str) -> AIModelEntity | None: """ Fetch model schema @@ -1243,49 +1142,3 @@ def _handle_completion_template( ) prompt_messages.append(prompt_message) return prompt_messages - - -def remove_additional_properties(schema: dict) -> None: - """ - Remove additionalProperties fields from JSON schema. - Used for models like Gemini that don't support this property. - - :param schema: JSON schema to modify in-place - """ - if not isinstance(schema, dict): - return - - # Remove additionalProperties at current level - schema.pop("additionalProperties", None) - - # Process nested structures recursively - for value in schema.values(): - if isinstance(value, dict): - remove_additional_properties(value) - elif isinstance(value, list): - for item in value: - if isinstance(item, dict): - remove_additional_properties(item) - - -def convert_boolean_to_string(schema: dict) -> None: - """ - Convert boolean type specifications to string in JSON schema. - - :param schema: JSON schema to modify in-place - """ - if not isinstance(schema, dict): - return - - # Check for boolean type at current level - if schema.get("type") == "boolean": - schema["type"] = "string" - - # Process nested dictionaries and lists recursively - for value in schema.values(): - if isinstance(value, dict): - convert_boolean_to_string(value) - elif isinstance(value, list): - for item in value: - if isinstance(item, dict): - convert_boolean_to_string(item) diff --git a/api/core/workflow/nodes/tool/tool_node.py b/api/core/workflow/nodes/tool/tool_node.py index aa15d69931..4d15d78a95 100644 --- a/api/core/workflow/nodes/tool/tool_node.py +++ b/api/core/workflow/nodes/tool/tool_node.py @@ -167,7 +167,9 @@ class ToolNode(BaseNode[ToolNodeData]): if tool_input.type == "variable": variable = variable_pool.get(tool_input.value) if variable is None: - raise ToolParameterError(f"Variable {tool_input.value} does not exist") + if parameter.required: + raise ToolParameterError(f"Variable {tool_input.value} does not exist") + continue parameter_value = variable.value elif tool_input.type in {"mixed", "constant"}: segment_group = variable_pool.convert_template(str(tool_input.value)) diff --git a/api/core/workflow/repositories/draft_variable_repository.py b/api/core/workflow/repositories/draft_variable_repository.py new file mode 100644 index 0000000000..cadc23f845 --- /dev/null +++ b/api/core/workflow/repositories/draft_variable_repository.py @@ -0,0 +1,32 @@ +import abc +from collections.abc import Mapping +from typing import Any, Protocol + +from sqlalchemy.orm import Session + +from core.workflow.nodes.enums import NodeType + + +class DraftVariableSaver(Protocol): + @abc.abstractmethod + def save(self, process_data: Mapping[str, Any] | None, outputs: Mapping[str, Any] | None): + pass + + +class DraftVariableSaverFactory(Protocol): + @abc.abstractmethod + def __call__( + self, + session: Session, + app_id: str, + node_id: str, + node_type: NodeType, + node_execution_id: str, + enclosing_node_id: str | None = None, + ) -> "DraftVariableSaver": + pass + + +class NoopDraftVariableSaver(DraftVariableSaver): + def save(self, process_data: Mapping[str, Any] | None, outputs: Mapping[str, Any] | None): + pass diff --git a/api/core/workflow/utils/structured_output/entities.py b/api/core/workflow/utils/structured_output/entities.py deleted file mode 100644 index 6491042bfe..0000000000 --- a/api/core/workflow/utils/structured_output/entities.py +++ /dev/null @@ -1,16 +0,0 @@ -from enum import StrEnum - - -class ResponseFormat(StrEnum): - """Constants for model response formats""" - - JSON_SCHEMA = "json_schema" # model's structured output mode. some model like gemini, gpt-4o, support this mode. - JSON = "JSON" # model's json mode. some model like claude support this mode. - JSON_OBJECT = "json_object" # json mode's another alias. some model like deepseek-chat, qwen use this alias. - - -class SpecialModelType(StrEnum): - """Constants for identifying model types""" - - GEMINI = "gemini" - OLLAMA = "ollama" diff --git a/api/core/workflow/utils/structured_output/prompt.py b/api/core/workflow/utils/structured_output/prompt.py deleted file mode 100644 index 06d9b2056e..0000000000 --- a/api/core/workflow/utils/structured_output/prompt.py +++ /dev/null @@ -1,17 +0,0 @@ -STRUCTURED_OUTPUT_PROMPT = """You’re a helpful AI assistant. You could answer questions and output in JSON format. -constraints: - - You must output in JSON format. - - Do not output boolean value, use string type instead. - - Do not output integer or float value, use number type instead. -eg: - Here is the JSON schema: - {"additionalProperties": false, "properties": {"age": {"type": "number"}, "name": {"type": "string"}}, "required": ["name", "age"], "type": "object"} - - Here is the user's question: - My name is John Doe and I am 30 years old. - - output: - {"name": "John Doe", "age": 30} -Here is the JSON schema: -{{schema}} -""" # noqa: E501 diff --git a/api/core/workflow/workflow_cycle_manager.py b/api/core/workflow/workflow_cycle_manager.py index 6ee562fc8d..0aab2426af 100644 --- a/api/core/workflow/workflow_cycle_manager.py +++ b/api/core/workflow/workflow_cycle_manager.py @@ -27,6 +27,7 @@ from core.workflow.enums import SystemVariableKey from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.workflow_entry import WorkflowEntry +from libs.datetime_utils import naive_utc_now @dataclass @@ -160,12 +161,13 @@ class WorkflowCycleManager: exceptions_count: int = 0, ) -> WorkflowExecution: workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) + now = naive_utc_now() workflow_execution.status = WorkflowExecutionStatus(status.value) workflow_execution.error_message = error_message workflow_execution.total_tokens = total_tokens workflow_execution.total_steps = total_steps - workflow_execution.finished_at = datetime.now(UTC).replace(tzinfo=None) + workflow_execution.finished_at = now workflow_execution.exceptions_count = exceptions_count # Use the instance repository to find running executions for a workflow run @@ -174,7 +176,6 @@ class WorkflowCycleManager: ) # Update the domain models - now = datetime.now(UTC).replace(tzinfo=None) for node_execution in running_node_executions: if node_execution.node_execution_id: # Update the domain model diff --git a/api/extensions/ext_app_metrics.py b/api/extensions/ext_app_metrics.py index b7d412d68d..56a69a1862 100644 --- a/api/extensions/ext_app_metrics.py +++ b/api/extensions/ext_app_metrics.py @@ -12,14 +12,14 @@ def init_app(app: DifyApp): @app.after_request def after_request(response): """Add Version headers to the response.""" - response.headers.add("X-Version", dify_config.CURRENT_VERSION) + response.headers.add("X-Version", dify_config.project.version) response.headers.add("X-Env", dify_config.DEPLOY_ENV) return response @app.route("/health") def health(): return Response( - json.dumps({"pid": os.getpid(), "status": "ok", "version": dify_config.CURRENT_VERSION}), + json.dumps({"pid": os.getpid(), "status": "ok", "version": dify_config.project.version}), status=200, content_type="application/json", ) diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py index a837552007..6279b1ad36 100644 --- a/api/extensions/ext_celery.py +++ b/api/extensions/ext_celery.py @@ -21,6 +21,7 @@ def init_app(app: DifyApp) -> Celery: "master_name": dify_config.CELERY_SENTINEL_MASTER_NAME, "sentinel_kwargs": { "socket_timeout": dify_config.CELERY_SENTINEL_SOCKET_TIMEOUT, + "password": dify_config.CELERY_SENTINEL_PASSWORD, }, } diff --git a/api/extensions/ext_otel.py b/api/extensions/ext_otel.py index 6dcfa7bec6..23cf4c5cab 100644 --- a/api/extensions/ext_otel.py +++ b/api/extensions/ext_otel.py @@ -49,7 +49,7 @@ def init_app(app: DifyApp): logging.getLogger().addHandler(exception_handler) def init_flask_instrumentor(app: DifyApp): - meter = get_meter("http_metrics", version=dify_config.CURRENT_VERSION) + meter = get_meter("http_metrics", version=dify_config.project.version) _http_response_counter = meter.create_counter( "http.server.response.count", description="Total number of HTTP responses by status code, method and target", @@ -163,7 +163,7 @@ def init_app(app: DifyApp): resource = Resource( attributes={ ResourceAttributes.SERVICE_NAME: dify_config.APPLICATION_NAME, - ResourceAttributes.SERVICE_VERSION: f"dify-{dify_config.CURRENT_VERSION}-{dify_config.COMMIT_SHA}", + ResourceAttributes.SERVICE_VERSION: f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}", ResourceAttributes.PROCESS_PID: os.getpid(), ResourceAttributes.DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}", ResourceAttributes.HOST_NAME: socket.gethostname(), diff --git a/api/extensions/ext_sentry.py b/api/extensions/ext_sentry.py index 3a74aace6a..82aed0d98d 100644 --- a/api/extensions/ext_sentry.py +++ b/api/extensions/ext_sentry.py @@ -35,6 +35,6 @@ def init_app(app: DifyApp): traces_sample_rate=dify_config.SENTRY_TRACES_SAMPLE_RATE, profiles_sample_rate=dify_config.SENTRY_PROFILES_SAMPLE_RATE, environment=dify_config.DEPLOY_ENV, - release=f"dify-{dify_config.CURRENT_VERSION}-{dify_config.COMMIT_SHA}", + release=f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}", before_send=before_send, ) diff --git a/api/libs/file_utils.py b/api/libs/file_utils.py new file mode 100644 index 0000000000..982b2cc1ac --- /dev/null +++ b/api/libs/file_utils.py @@ -0,0 +1,30 @@ +from pathlib import Path + + +def search_file_upwards( + base_dir_path: Path, + target_file_name: str, + max_search_parent_depth: int, +) -> Path: + """ + Find a target file in the current directory or its parent directories up to a specified depth. + :param base_dir_path: Starting directory path to search from. + :param target_file_name: Name of the file to search for. + :param max_search_parent_depth: Maximum number of parent directories to search upwards. + :return: Path of the file if found, otherwise None. + """ + current_path = base_dir_path.resolve() + for _ in range(max_search_parent_depth): + candidate_path = current_path / target_file_name + if candidate_path.is_file(): + return candidate_path + parent_path = current_path.parent + if parent_path == current_path: # reached the root directory + break + else: + current_path = parent_path + + raise ValueError( + f"File '{target_file_name}' not found in the directory '{base_dir_path.resolve()}' or its parent directories" + f" in depth of {max_search_parent_depth}." + ) diff --git a/api/models/dataset.py b/api/models/dataset.py index ad43d6f371..1ec27203a0 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -140,7 +140,7 @@ class Dataset(Base): def word_count(self): return ( db.session.query(Document) - .with_entities(func.coalesce(func.sum(Document.word_count))) + .with_entities(func.coalesce(func.sum(Document.word_count), 0)) .filter(Document.dataset_id == self.id) .scalar() ) @@ -448,7 +448,7 @@ class Document(Base): def hit_count(self): return ( db.session.query(DocumentSegment) - .with_entities(func.coalesce(func.sum(DocumentSegment.hit_count))) + .with_entities(func.coalesce(func.sum(DocumentSegment.hit_count), 0)) .filter(DocumentSegment.document_id == self.id) .scalar() ) diff --git a/api/models/enums.py b/api/models/enums.py index 4434c3fec8..cc9f28a7bb 100644 --- a/api/models/enums.py +++ b/api/models/enums.py @@ -21,3 +21,12 @@ class DraftVariableType(StrEnum): NODE = "node" SYS = "sys" CONVERSATION = "conversation" + + +class MessageStatus(StrEnum): + """ + Message Status Enum + """ + + NORMAL = "normal" + ERROR = "error" diff --git a/api/models/model.py b/api/models/model.py index ce5f449f87..93737043d5 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -676,7 +676,7 @@ class Conversation(Base): if isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY: if value["transfer_method"] == FileTransferMethod.TOOL_FILE: value["tool_file_id"] = value["related_id"] - elif value["transfer_method"] == FileTransferMethod.LOCAL_FILE: + elif value["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]: value["upload_file_id"] = value["related_id"] inputs[key] = file_factory.build_from_mapping(mapping=value, tenant_id=value["tenant_id"]) elif isinstance(value, list) and all( @@ -686,7 +686,7 @@ class Conversation(Base): for item in value: if item["transfer_method"] == FileTransferMethod.TOOL_FILE: item["tool_file_id"] = item["related_id"] - elif item["transfer_method"] == FileTransferMethod.LOCAL_FILE: + elif item["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]: item["upload_file_id"] = item["related_id"] inputs[key].append(file_factory.build_from_mapping(mapping=item, tenant_id=item["tenant_id"])) @@ -946,7 +946,7 @@ class Message(Base): if isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY: if value["transfer_method"] == FileTransferMethod.TOOL_FILE: value["tool_file_id"] = value["related_id"] - elif value["transfer_method"] == FileTransferMethod.LOCAL_FILE: + elif value["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]: value["upload_file_id"] = value["related_id"] inputs[key] = file_factory.build_from_mapping(mapping=value, tenant_id=value["tenant_id"]) elif isinstance(value, list) and all( @@ -956,7 +956,7 @@ class Message(Base): for item in value: if item["transfer_method"] == FileTransferMethod.TOOL_FILE: item["tool_file_id"] = item["related_id"] - elif item["transfer_method"] == FileTransferMethod.LOCAL_FILE: + elif item["transfer_method"] in [FileTransferMethod.LOCAL_FILE, FileTransferMethod.REMOTE_URL]: item["upload_file_id"] = item["related_id"] inputs[key].append(file_factory.build_from_mapping(mapping=item, tenant_id=item["tenant_id"])) return inputs diff --git a/api/pyproject.toml b/api/pyproject.toml index 6033b3a670..d33806d0ae 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,9 +1,10 @@ [project] name = "dify-api" -dynamic = ["version"] +version = "1.5.1" requires-python = ">=3.11,<3.13" dependencies = [ + "arize-phoenix-otel~=0.9.2", "authlib==1.3.1", "azure-identity==1.16.1", "beautifulsoup4==4.12.2", @@ -198,7 +199,7 @@ vdb = [ "pymochow==1.3.1", "pyobvector~=0.1.6", "qdrant-client==1.9.0", - "tablestore==6.1.0", + "tablestore==6.2.0", "tcvectordb~=1.6.4", "tidb-vector==0.0.9", "upstash-vector==0.6.0", diff --git a/api/services/account_service.py b/api/services/account_service.py index 14d238467d..3fdbda48a6 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -889,7 +889,7 @@ class RegisterService: TenantService.create_owner_tenant_if_not_exist(account=account, is_setup=True) - dify_setup = DifySetup(version=dify_config.CURRENT_VERSION) + dify_setup = DifySetup(version=dify_config.project.version) db.session.add(dify_setup) db.session.commit() except Exception as e: diff --git a/api/services/audio_service.py b/api/services/audio_service.py index a259f5a4c4..e8923eb51b 100644 --- a/api/services/audio_service.py +++ b/api/services/audio_service.py @@ -1,13 +1,17 @@ import io import logging import uuid +from collections.abc import Generator from typing import Optional +from flask import Response, stream_with_context from werkzeug.datastructures import FileStorage from constants import AUDIO_EXTENSIONS from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType +from extensions.ext_database import db +from models.enums import MessageStatus from models.model import App, AppMode, AppModelConfig, Message from services.errors.audio import ( AudioTooLargeServiceError, @@ -16,6 +20,7 @@ from services.errors.audio import ( ProviderNotSupportTextToSpeechServiceError, UnsupportedAudioTypeServiceError, ) +from services.workflow_service import WorkflowService FILE_SIZE = 30 FILE_SIZE_LIMIT = FILE_SIZE * 1024 * 1024 @@ -74,35 +79,36 @@ class AudioService: voice: Optional[str] = None, end_user: Optional[str] = None, message_id: Optional[str] = None, + is_draft: bool = False, ): - from collections.abc import Generator - - from flask import Response, stream_with_context - from app import app - from extensions.ext_database import db - def invoke_tts(text_content: str, app_model: App, voice: Optional[str] = None): + def invoke_tts(text_content: str, app_model: App, voice: Optional[str] = None, is_draft: bool = False): with app.app_context(): - if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}: - workflow = app_model.workflow - if workflow is None: - raise ValueError("TTS is not enabled") - - features_dict = workflow.features_dict - if "text_to_speech" not in features_dict or not features_dict["text_to_speech"].get("enabled"): - raise ValueError("TTS is not enabled") - - voice = features_dict["text_to_speech"].get("voice") if voice is None else voice - else: - if app_model.app_model_config is None: - raise ValueError("AppModelConfig not found") - text_to_speech_dict = app_model.app_model_config.text_to_speech_dict - - if not text_to_speech_dict.get("enabled"): - raise ValueError("TTS is not enabled") - - voice = text_to_speech_dict.get("voice") if voice is None else voice + if voice is None: + if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}: + if is_draft: + workflow = WorkflowService().get_draft_workflow(app_model=app_model) + else: + workflow = app_model.workflow + if ( + workflow is None + or "text_to_speech" not in workflow.features_dict + or not workflow.features_dict["text_to_speech"].get("enabled") + ): + raise ValueError("TTS is not enabled") + + voice = workflow.features_dict["text_to_speech"].get("voice") + else: + if not is_draft: + if app_model.app_model_config is None: + raise ValueError("AppModelConfig not found") + text_to_speech_dict = app_model.app_model_config.text_to_speech_dict + + if not text_to_speech_dict.get("enabled"): + raise ValueError("TTS is not enabled") + + voice = text_to_speech_dict.get("voice") model_manager = ModelManager() model_instance = model_manager.get_default_model_instance( @@ -132,18 +138,18 @@ class AudioService: message = db.session.query(Message).filter(Message.id == message_id).first() if message is None: return None - if message.answer == "" and message.status == "normal": + if message.answer == "" and message.status == MessageStatus.NORMAL: return None else: - response = invoke_tts(message.answer, app_model=app_model, voice=voice) + response = invoke_tts(text_content=message.answer, app_model=app_model, voice=voice, is_draft=is_draft) if isinstance(response, Generator): return Response(stream_with_context(response), content_type="audio/mpeg") return response else: if text is None: raise ValueError("Text is required") - response = invoke_tts(text, app_model, voice) + response = invoke_tts(text_content=text, app_model=app_model, voice=voice, is_draft=is_draft) if isinstance(response, Generator): return Response(stream_with_context(response), content_type="audio/mpeg") return response diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index a697c9ab7f..e42b5ace75 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -278,6 +278,23 @@ class DatasetService: except ProviderTokenNotInitError as ex: raise ValueError(ex.description) + @staticmethod + def check_reranking_model_setting(tenant_id: str, reranking_model_provider: str, reranking_model: str): + try: + model_manager = ModelManager() + model_manager.get_model_instance( + tenant_id=tenant_id, + provider=reranking_model_provider, + model_type=ModelType.RERANK, + model=reranking_model, + ) + except LLMBadRequestError: + raise ValueError( + "No Rerank Model available. Please configure a valid provider in the Settings -> Model Provider." + ) + except ProviderTokenNotInitError as ex: + raise ValueError(ex.description) + @staticmethod def update_dataset(dataset_id, data, user): """ @@ -2207,6 +2224,7 @@ class SegmentService: # calc embedding use tokens if document.doc_form == "qa_model": + segment.answer = args.answer tokens = embedding_model.get_text_embedding_num_tokens(texts=[content + segment.answer])[0] else: tokens = embedding_model.get_text_embedding_num_tokens(texts=[content])[0] diff --git a/api/services/ops_service.py b/api/services/ops_service.py index 792f50703e..c88accb9a5 100644 --- a/api/services/ops_service.py +++ b/api/services/ops_service.py @@ -34,6 +34,24 @@ class OpsService: ) new_decrypt_tracing_config = OpsTraceManager.obfuscated_decrypt_token(tracing_provider, decrypt_tracing_config) + if tracing_provider == "arize" and ( + "project_url" not in decrypt_tracing_config or not decrypt_tracing_config.get("project_url") + ): + try: + project_url = OpsTraceManager.get_trace_config_project_url(decrypt_tracing_config, tracing_provider) + new_decrypt_tracing_config.update({"project_url": project_url}) + except Exception: + new_decrypt_tracing_config.update({"project_url": "https://app.arize.com/"}) + + if tracing_provider == "phoenix" and ( + "project_url" not in decrypt_tracing_config or not decrypt_tracing_config.get("project_url") + ): + try: + project_url = OpsTraceManager.get_trace_config_project_url(decrypt_tracing_config, tracing_provider) + new_decrypt_tracing_config.update({"project_url": project_url}) + except Exception: + new_decrypt_tracing_config.update({"project_url": "https://app.phoenix.arize.com/projects/"}) + if tracing_provider == "langfuse" and ( "project_key" not in decrypt_tracing_config or not decrypt_tracing_config.get("project_key") ): @@ -107,7 +125,9 @@ class OpsService: return {"error": "Invalid Credentials"} # get project url - if tracing_provider == "langfuse": + if tracing_provider in ("arize", "phoenix"): + project_url = OpsTraceManager.get_trace_config_project_url(tracing_config, tracing_provider) + elif tracing_provider == "langfuse": project_key = OpsTraceManager.get_trace_config_project_key(tracing_config, tracing_provider) project_url = "{host}/project/{key}".format(host=tracing_config.get("host"), key=project_key) elif tracing_provider in ("langsmith", "opik"): diff --git a/api/services/plugin/plugin_parameter_service.py b/api/services/plugin/plugin_parameter_service.py new file mode 100644 index 0000000000..393213c0e2 --- /dev/null +++ b/api/services/plugin/plugin_parameter_service.py @@ -0,0 +1,74 @@ +from collections.abc import Mapping, Sequence +from typing import Any, Literal + +from sqlalchemy.orm import Session + +from core.plugin.entities.parameters import PluginParameterOption +from core.plugin.impl.dynamic_select import DynamicSelectClient +from core.tools.tool_manager import ToolManager +from core.tools.utils.configuration import ProviderConfigEncrypter +from extensions.ext_database import db +from models.tools import BuiltinToolProvider + + +class PluginParameterService: + @staticmethod + def get_dynamic_select_options( + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + action: str, + parameter: str, + provider_type: Literal["tool"], + ) -> Sequence[PluginParameterOption]: + """ + Get dynamic select options for a plugin parameter. + + Args: + tenant_id: The tenant ID. + plugin_id: The plugin ID. + provider: The provider name. + action: The action name. + parameter: The parameter name. + """ + credentials: Mapping[str, Any] = {} + + match provider_type: + case "tool": + provider_controller = ToolManager.get_builtin_provider(provider, tenant_id) + # init tool configuration + tool_configuration = ProviderConfigEncrypter( + tenant_id=tenant_id, + config=[x.to_basic_provider_config() for x in provider_controller.get_credentials_schema()], + provider_type=provider_controller.provider_type.value, + provider_identity=provider_controller.entity.identity.name, + ) + + # check if credentials are required + if not provider_controller.need_credentials: + credentials = {} + else: + # fetch credentials from db + with Session(db.engine) as session: + db_record = ( + session.query(BuiltinToolProvider) + .filter( + BuiltinToolProvider.tenant_id == tenant_id, + BuiltinToolProvider.provider == provider, + ) + .first() + ) + + if db_record is None: + raise ValueError(f"Builtin provider {provider} not found when fetching credentials") + + credentials = tool_configuration.decrypt(db_record.credentials) + case _: + raise ValueError(f"Invalid provider type: {provider_type}") + + return ( + DynamicSelectClient() + .fetch_dynamic_select_options(tenant_id, user_id, plugin_id, provider, action, credentials, parameter) + .options + ) diff --git a/api/services/workflow_draft_variable_service.py b/api/services/workflow_draft_variable_service.py index 164693c2e1..44fd72b5e4 100644 --- a/api/services/workflow_draft_variable_service.py +++ b/api/services/workflow_draft_variable_service.py @@ -154,7 +154,7 @@ class WorkflowDraftVariableService: variables = ( # Do not load the `value` field. query.options(orm.defer(WorkflowDraftVariable.value)) - .order_by(WorkflowDraftVariable.id.desc()) + .order_by(WorkflowDraftVariable.created_at.desc()) .limit(limit) .offset((page - 1) * limit) .all() @@ -168,7 +168,7 @@ class WorkflowDraftVariableService: WorkflowDraftVariable.node_id == node_id, ) query = self._session.query(WorkflowDraftVariable).filter(*criteria) - variables = query.order_by(WorkflowDraftVariable.id.desc()).all() + variables = query.order_by(WorkflowDraftVariable.created_at.desc()).all() return WorkflowDraftVariableList(variables=variables) def list_node_variables(self, app_id: str, node_id: str) -> WorkflowDraftVariableList: @@ -235,7 +235,9 @@ class WorkflowDraftVariableService: self._session.flush() return variable - def _reset_node_var(self, workflow: Workflow, variable: WorkflowDraftVariable) -> WorkflowDraftVariable | None: + def _reset_node_var_or_sys_var( + self, workflow: Workflow, variable: WorkflowDraftVariable + ) -> WorkflowDraftVariable | None: # If a variable does not allow updating, it makes no sence to resetting it. if not variable.editable: return variable @@ -259,28 +261,35 @@ class WorkflowDraftVariableService: self._session.flush() return None - # Get node type for proper value extraction - node_config = workflow.get_node_config_by_id(variable.node_id) - node_type = workflow.get_node_type_from_node_config(node_config) - outputs_dict = node_exec.outputs_dict or {} + # a sentinel value used to check the absent of the output variable key. + absent = object() + + if variable.get_variable_type() == DraftVariableType.NODE: + # Get node type for proper value extraction + node_config = workflow.get_node_config_by_id(variable.node_id) + node_type = workflow.get_node_type_from_node_config(node_config) + + # Note: Based on the implementation in `_build_from_variable_assigner_mapping`, + # VariableAssignerNode (both v1 and v2) can only create conversation draft variables. + # For consistency, we should simply return when processing VARIABLE_ASSIGNER nodes. + # + # This implementation must remain synchronized with the `_build_from_variable_assigner_mapping` + # and `save` methods. + if node_type == NodeType.VARIABLE_ASSIGNER: + return variable + output_value = outputs_dict.get(variable.name, absent) + else: + output_value = outputs_dict.get(f"sys.{variable.name}", absent) - # Note: Based on the implementation in `_build_from_variable_assigner_mapping`, - # VariableAssignerNode (both v1 and v2) can only create conversation draft variables. - # For consistency, we should simply return when processing VARIABLE_ASSIGNER nodes. - # - # This implementation must remain synchronized with the `_build_from_variable_assigner_mapping` - # and `save` methods. - if node_type == NodeType.VARIABLE_ASSIGNER: - return variable - - if variable.name not in outputs_dict: + # We cannot use `is None` to check the existence of an output variable here as + # the value of the output may be `None`. + if output_value is absent: # If variable not found in execution data, delete the variable self._session.delete(instance=variable) self._session.flush() return None - value = outputs_dict[variable.name] - value_seg = WorkflowDraftVariable.build_segment_with_type(variable.value_type, value) + value_seg = WorkflowDraftVariable.build_segment_with_type(variable.value_type, output_value) # Extract variable value using unified logic variable.set_value(value_seg) variable.last_edited_at = None # Reset to indicate this is a reset operation @@ -291,10 +300,8 @@ class WorkflowDraftVariableService: variable_type = variable.get_variable_type() if variable_type == DraftVariableType.CONVERSATION: return self._reset_conv_var(workflow, variable) - elif variable_type == DraftVariableType.NODE: - return self._reset_node_var(workflow, variable) else: - raise VariableResetError(f"cannot reset system variable, variable_id={variable.id}") + return self._reset_node_var_or_sys_var(workflow, variable) def delete_variable(self, variable: WorkflowDraftVariable): self._session.delete(variable) @@ -439,6 +446,9 @@ def _batch_upsert_draft_varaible( stmt = stmt.on_conflict_do_update( index_elements=WorkflowDraftVariable.unique_app_id_node_id_name(), set_={ + # Refresh creation timestamp to ensure updated variables + # appear first in chronologically sorted result sets. + "created_at": stmt.excluded.created_at, "updated_at": stmt.excluded.updated_at, "last_edited_at": stmt.excluded.last_edited_at, "description": stmt.excluded.description, @@ -525,9 +535,6 @@ class DraftVariableSaver: # The type of the current node (see NodeType). _node_type: NodeType - # Indicates how the workflow execution was triggered (see InvokeFrom). - _invoke_from: InvokeFrom - # _node_execution_id: str @@ -546,15 +553,16 @@ class DraftVariableSaver: app_id: str, node_id: str, node_type: NodeType, - invoke_from: InvokeFrom, node_execution_id: str, enclosing_node_id: str | None = None, ): + # Important: `node_execution_id` parameter refers to the primary key (`id`) of the + # WorkflowNodeExecutionModel/WorkflowNodeExecution, not their `node_execution_id` + # field. These are distinct database fields with different purposes. self._session = session self._app_id = app_id self._node_id = node_id self._node_type = node_type - self._invoke_from = invoke_from self._node_execution_id = node_execution_id self._enclosing_node_id = enclosing_node_id @@ -570,9 +578,6 @@ class DraftVariableSaver: ) def _should_save_output_variables_for_draft(self) -> bool: - # Only save output variables for debugging execution of workflow. - if self._invoke_from != InvokeFrom.DEBUGGER: - return False if self._enclosing_node_id is not None and self._node_type != NodeType.VARIABLE_ASSIGNER: # Currently we do not save output variables for nodes inside loop or iteration. return False diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 0fd94ac86e..2be57fd51c 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -12,7 +12,6 @@ from sqlalchemy.orm import Session from core.app.app_config.entities import VariableEntityType from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager -from core.app.entities.app_invoke_entities import InvokeFrom from core.file import File from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.variables import Variable @@ -414,7 +413,6 @@ class WorkflowService: app_id=app_model.id, node_id=workflow_node_execution.node_id, node_type=NodeType(workflow_node_execution.node_type), - invoke_from=InvokeFrom.DEBUGGER, enclosing_node_id=enclosing_node_id, node_execution_id=node_execution.id, ) diff --git a/api/tests/integration_tests/workflow/nodes/test_llm.py b/api/tests/integration_tests/workflow/nodes/test_llm.py index a3b2fdc376..389d1071f3 100644 --- a/api/tests/integration_tests/workflow/nodes/test_llm.py +++ b/api/tests/integration_tests/workflow/nodes/test_llm.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock, patch import pytest from core.app.entities.app_invoke_entities import InvokeFrom +from core.llm_generator.output_parser.structured_output import _parse_structured_output from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage from core.model_runtime.entities.message_entities import AssistantPromptMessage from core.workflow.entities.variable_pool import VariablePool @@ -277,29 +278,6 @@ def test_execute_llm_with_jinja2(flask_req_ctx, setup_code_executor_mock): def test_extract_json(): - node = init_llm_node( - config={ - "id": "llm", - "data": { - "title": "123", - "type": "llm", - "model": {"provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", "completion_params": {}}, - "prompt_config": { - "structured_output": { - "enabled": True, - "schema": { - "type": "object", - "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, - }, - } - }, - "prompt_template": [{"role": "user", "text": "{{#sys.query#}}"}], - "memory": None, - "context": {"enabled": False}, - "vision": {"enabled": False}, - }, - }, - ) llm_texts = [ '\n\n{"name": "test", "age": 123', # resoning model (deepseek-r1) '{"name":"test","age":123}', # json schema model (gpt-4o) @@ -308,4 +286,4 @@ def test_extract_json(): '{"name":"test",age:123}', # without quotes (qwen-2.5-0.5b) ] result = {"name": "test", "age": 123} - assert all(node._parse_structured_output(item) == result for item in llm_texts) + assert all(_parse_structured_output(item) == result for item in llm_texts) diff --git a/api/tests/unit_tests/core/app/apps/common/test_workflow_response_converter.py b/api/tests/unit_tests/core/app/apps/common/test_workflow_response_converter.py new file mode 100644 index 0000000000..b88a57bfd4 --- /dev/null +++ b/api/tests/unit_tests/core/app/apps/common/test_workflow_response_converter.py @@ -0,0 +1,259 @@ +from collections.abc import Mapping, Sequence + +from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter +from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod, FileType +from core.variables.segments import ArrayFileSegment, FileSegment + + +class TestWorkflowResponseConverterFetchFilesFromVariableValue: + """Test class for WorkflowResponseConverter._fetch_files_from_variable_value method""" + + def create_test_file(self, file_id: str = "test_file_1") -> File: + """Create a test File object""" + return File( + id=file_id, + tenant_id="test_tenant", + type=FileType.DOCUMENT, + transfer_method=FileTransferMethod.LOCAL_FILE, + related_id="related_123", + filename=f"{file_id}.txt", + extension=".txt", + mime_type="text/plain", + size=1024, + storage_key="storage_key_123", + ) + + def create_file_dict(self, file_id: str = "test_file_dict") -> dict: + """Create a file dictionary with correct dify_model_identity""" + return { + "dify_model_identity": FILE_MODEL_IDENTITY, + "id": file_id, + "tenant_id": "test_tenant", + "type": "document", + "transfer_method": "local_file", + "related_id": "related_456", + "filename": f"{file_id}.txt", + "extension": ".txt", + "mime_type": "text/plain", + "size": 2048, + "url": "http://example.com/file.txt", + } + + def test_fetch_files_from_variable_value_with_none(self): + """Test with None input""" + # The method signature expects Union[dict, list, Segment], but implementation handles None + # We'll test the actual behavior by passing an empty dict instead + result = WorkflowResponseConverter._fetch_files_from_variable_value(None) # type: ignore + assert result == [] + + def test_fetch_files_from_variable_value_with_empty_dict(self): + """Test with empty dictionary""" + result = WorkflowResponseConverter._fetch_files_from_variable_value({}) + assert result == [] + + def test_fetch_files_from_variable_value_with_empty_list(self): + """Test with empty list""" + result = WorkflowResponseConverter._fetch_files_from_variable_value([]) + assert result == [] + + def test_fetch_files_from_variable_value_with_file_segment(self): + """Test with valid FileSegment""" + test_file = self.create_test_file("segment_file") + file_segment = FileSegment(value=test_file) + + result = WorkflowResponseConverter._fetch_files_from_variable_value(file_segment) + + assert len(result) == 1 + assert isinstance(result[0], dict) + assert result[0]["id"] == "segment_file" + assert result[0]["dify_model_identity"] == FILE_MODEL_IDENTITY + + def test_fetch_files_from_variable_value_with_array_file_segment_single(self): + """Test with ArrayFileSegment containing single file""" + test_file = self.create_test_file("array_file_1") + array_segment = ArrayFileSegment(value=[test_file]) + + result = WorkflowResponseConverter._fetch_files_from_variable_value(array_segment) + + assert len(result) == 1 + assert isinstance(result[0], dict) + assert result[0]["id"] == "array_file_1" + + def test_fetch_files_from_variable_value_with_array_file_segment_multiple(self): + """Test with ArrayFileSegment containing multiple files""" + test_file_1 = self.create_test_file("array_file_1") + test_file_2 = self.create_test_file("array_file_2") + array_segment = ArrayFileSegment(value=[test_file_1, test_file_2]) + + result = WorkflowResponseConverter._fetch_files_from_variable_value(array_segment) + + assert len(result) == 2 + assert result[0]["id"] == "array_file_1" + assert result[1]["id"] == "array_file_2" + + def test_fetch_files_from_variable_value_with_array_file_segment_empty(self): + """Test with ArrayFileSegment containing empty array""" + array_segment = ArrayFileSegment(value=[]) + + result = WorkflowResponseConverter._fetch_files_from_variable_value(array_segment) + + assert result == [] + + def test_fetch_files_from_variable_value_with_list_of_file_dicts(self): + """Test with list containing file dictionaries""" + file_dict_1 = self.create_file_dict("list_file_1") + file_dict_2 = self.create_file_dict("list_file_2") + test_list = [file_dict_1, file_dict_2] + + result = WorkflowResponseConverter._fetch_files_from_variable_value(test_list) + + assert len(result) == 2 + assert result[0]["id"] == "list_file_1" + assert result[1]["id"] == "list_file_2" + + def test_fetch_files_from_variable_value_with_list_of_file_objects(self): + """Test with list containing File objects""" + file_obj_1 = self.create_test_file("list_obj_1") + file_obj_2 = self.create_test_file("list_obj_2") + test_list = [file_obj_1, file_obj_2] + + result = WorkflowResponseConverter._fetch_files_from_variable_value(test_list) + + assert len(result) == 2 + assert result[0]["id"] == "list_obj_1" + assert result[1]["id"] == "list_obj_2" + + def test_fetch_files_from_variable_value_with_list_mixed_valid_invalid(self): + """Test with list containing mix of valid files and invalid items""" + file_dict = self.create_file_dict("mixed_file") + invalid_dict = {"not_a_file": "value"} + test_list = [file_dict, invalid_dict, "string_item", 123] + + result = WorkflowResponseConverter._fetch_files_from_variable_value(test_list) + + assert len(result) == 1 + assert result[0]["id"] == "mixed_file" + + def test_fetch_files_from_variable_value_with_list_nested_structures(self): + """Test with list containing nested structures""" + file_dict = self.create_file_dict("nested_file") + nested_list = [file_dict, ["inner_list"]] + test_list = [nested_list, {"nested": "dict"}] + + result = WorkflowResponseConverter._fetch_files_from_variable_value(test_list) + + # Should not process nested structures in list items + assert result == [] + + def test_fetch_files_from_variable_value_with_dict_incorrect_identity(self): + """Test with dictionary having incorrect dify_model_identity""" + invalid_dict = {"dify_model_identity": "wrong_identity", "id": "invalid_file", "filename": "test.txt"} + + result = WorkflowResponseConverter._fetch_files_from_variable_value(invalid_dict) + + assert result == [] + + def test_fetch_files_from_variable_value_with_dict_missing_identity(self): + """Test with dictionary missing dify_model_identity""" + invalid_dict = {"id": "no_identity_file", "filename": "test.txt"} + + result = WorkflowResponseConverter._fetch_files_from_variable_value(invalid_dict) + + assert result == [] + + def test_fetch_files_from_variable_value_with_dict_file_object(self): + """Test with dictionary containing File object""" + file_obj = self.create_test_file("dict_obj_file") + test_dict = {"file_key": file_obj} + + result = WorkflowResponseConverter._fetch_files_from_variable_value(test_dict) + + # Should not extract File objects from dict values + assert result == [] + + def test_fetch_files_from_variable_value_with_mixed_data_types(self): + """Test with various mixed data types""" + mixed_data = {"string": "text", "number": 42, "boolean": True, "null": None, "dify_model_identity": "wrong"} + + result = WorkflowResponseConverter._fetch_files_from_variable_value(mixed_data) + + assert result == [] + + def test_fetch_files_from_variable_value_with_invalid_objects(self): + """Test with invalid objects that are not supported types""" + # Test with an invalid dict that doesn't match expected patterns + invalid_dict = {"custom_key": "custom_value"} + + result = WorkflowResponseConverter._fetch_files_from_variable_value(invalid_dict) + + assert result == [] + + def test_fetch_files_from_variable_value_with_string_input(self): + """Test with string input (unsupported type)""" + # Since method expects Union[dict, list, Segment], test with empty list instead + result = WorkflowResponseConverter._fetch_files_from_variable_value([]) + + assert result == [] + + def test_fetch_files_from_variable_value_with_number_input(self): + """Test with number input (unsupported type)""" + # Test with list containing numbers (should be ignored) + result = WorkflowResponseConverter._fetch_files_from_variable_value([42, "string", None]) + + assert result == [] + + def test_fetch_files_from_variable_value_return_type_is_sequence(self): + """Test that return type is Sequence[Mapping[str, Any]]""" + file_dict = self.create_file_dict("type_test_file") + + result = WorkflowResponseConverter._fetch_files_from_variable_value(file_dict) + + assert isinstance(result, Sequence) + assert len(result) == 1 + assert isinstance(result[0], Mapping) + assert all(isinstance(key, str) for key in result[0]) + + def test_fetch_files_from_variable_value_preserves_file_properties(self): + """Test that all file properties are preserved in the result""" + original_file = self.create_test_file("property_test") + file_segment = FileSegment(value=original_file) + + result = WorkflowResponseConverter._fetch_files_from_variable_value(file_segment) + + assert len(result) == 1 + file_dict = result[0] + assert file_dict["id"] == "property_test" + assert file_dict["tenant_id"] == "test_tenant" + assert file_dict["type"] == "document" + assert file_dict["transfer_method"] == "local_file" + assert file_dict["filename"] == "property_test.txt" + assert file_dict["extension"] == ".txt" + assert file_dict["mime_type"] == "text/plain" + assert file_dict["size"] == 1024 + + def test_fetch_files_from_variable_value_with_complex_nested_scenario(self): + """Test complex scenario with nested valid and invalid data""" + file_dict = self.create_file_dict("complex_file") + file_obj = self.create_test_file("complex_obj") + + # Complex nested structure + complex_data = [ + file_dict, # Valid file dict + file_obj, # Valid file object + { # Invalid dict + "not_file": "data", + "nested": {"deep": "value"}, + }, + [ # Nested list (should be ignored) + self.create_file_dict("nested_file") + ], + "string", # Invalid string + None, # None value + 42, # Invalid number + ] + + result = WorkflowResponseConverter._fetch_files_from_variable_value(complex_data) + + assert len(result) == 2 + assert result[0]["id"] == "complex_file" + assert result[1]["id"] == "complex_obj" diff --git a/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py b/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py index 8ae69c8d64..c5c9cf1050 100644 --- a/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py +++ b/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py @@ -6,12 +6,11 @@ from unittest.mock import Mock, patch import pytest from sqlalchemy.orm import Session -from core.app.entities.app_invoke_entities import InvokeFrom -from core.variables.types import SegmentType +from core.variables import StringSegment from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID from core.workflow.nodes import NodeType from models.enums import DraftVariableType -from models.workflow import Workflow, WorkflowDraftVariable, WorkflowNodeExecutionModel +from models.workflow import Workflow, WorkflowDraftVariable, WorkflowNodeExecutionModel, is_system_variable_editable from services.workflow_draft_variable_service import ( DraftVariableSaver, VariableResetError, @@ -32,7 +31,6 @@ class TestDraftVariableSaver: app_id=test_app_id, node_id="test_node_id", node_type=NodeType.START, - invoke_from=InvokeFrom.DEBUGGER, node_execution_id="test_execution_id", ) assert saver._should_variable_be_visible("123_456", NodeType.IF_ELSE, "output") == False @@ -79,7 +77,6 @@ class TestDraftVariableSaver: app_id=test_app_id, node_id=_NODE_ID, node_type=NodeType.START, - invoke_from=InvokeFrom.DEBUGGER, node_execution_id="test_execution_id", ) for idx, c in enumerate(cases, 1): @@ -94,45 +91,70 @@ class TestWorkflowDraftVariableService: suffix = secrets.token_hex(6) return f"test_app_id_{suffix}" + def _create_test_workflow(self, app_id: str) -> Workflow: + """Create a real Workflow instance for testing""" + return Workflow.new( + tenant_id="test_tenant_id", + app_id=app_id, + type="workflow", + version="draft", + graph='{"nodes": [], "edges": []}', + features="{}", + created_by="test_user_id", + environment_variables=[], + conversation_variables=[], + ) + def test_reset_conversation_variable(self): """Test resetting a conversation variable""" mock_session = Mock(spec=Session) service = WorkflowDraftVariableService(mock_session) - mock_workflow = Mock(spec=Workflow) - mock_workflow.app_id = self._get_test_app_id() - # Create mock variable - mock_variable = Mock(spec=WorkflowDraftVariable) - mock_variable.get_variable_type.return_value = DraftVariableType.CONVERSATION - mock_variable.id = "var-id" - mock_variable.name = "test_var" + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create real conversation variable + test_value = StringSegment(value="test_value") + variable = WorkflowDraftVariable.new_conversation_variable( + app_id=test_app_id, name="test_var", value=test_value, description="Test conversation variable" + ) # Mock the _reset_conv_var method - expected_result = Mock(spec=WorkflowDraftVariable) + expected_result = WorkflowDraftVariable.new_conversation_variable( + app_id=test_app_id, + name="test_var", + value=StringSegment(value="reset_value"), + ) with patch.object(service, "_reset_conv_var", return_value=expected_result) as mock_reset_conv: - result = service.reset_variable(mock_workflow, mock_variable) + result = service.reset_variable(workflow, variable) - mock_reset_conv.assert_called_once_with(mock_workflow, mock_variable) + mock_reset_conv.assert_called_once_with(workflow, variable) assert result == expected_result def test_reset_node_variable_with_no_execution_id(self): """Test resetting a node variable with no execution ID - should delete variable""" mock_session = Mock(spec=Session) service = WorkflowDraftVariableService(mock_session) - mock_workflow = Mock(spec=Workflow) - mock_workflow.app_id = self._get_test_app_id() - # Create mock variable with no execution ID - mock_variable = Mock(spec=WorkflowDraftVariable) - mock_variable.get_variable_type.return_value = DraftVariableType.NODE - mock_variable.node_execution_id = None - mock_variable.id = "var-id" - mock_variable.name = "test_var" + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create real node variable with no execution ID + test_value = StringSegment(value="test_value") + variable = WorkflowDraftVariable.new_node_variable( + app_id=test_app_id, + node_id="test_node_id", + name="test_var", + value=test_value, + node_execution_id="exec-id", # Set initially + ) + # Manually set to None to simulate the test condition + variable.node_execution_id = None - result = service._reset_node_var(mock_workflow, mock_variable) + result = service._reset_node_var_or_sys_var(workflow, variable) # Should delete the variable and return None - mock_session.delete.assert_called_once_with(instance=mock_variable) + mock_session.delete.assert_called_once_with(instance=variable) mock_session.flush.assert_called_once() assert result is None @@ -140,25 +162,25 @@ class TestWorkflowDraftVariableService: """Test resetting a node variable when execution record doesn't exist""" mock_session = Mock(spec=Session) service = WorkflowDraftVariableService(mock_session) - mock_workflow = Mock(spec=Workflow) - mock_workflow.app_id = self._get_test_app_id() - # Create mock variable with execution ID - mock_variable = Mock(spec=WorkflowDraftVariable) - mock_variable.get_variable_type.return_value = DraftVariableType.NODE - mock_variable.node_execution_id = "exec-id" - mock_variable.id = "var-id" - mock_variable.name = "test_var" + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create real node variable with execution ID + test_value = StringSegment(value="test_value") + variable = WorkflowDraftVariable.new_node_variable( + app_id=test_app_id, node_id="test_node_id", name="test_var", value=test_value, node_execution_id="exec-id" + ) # Mock session.scalars to return None (no execution record found) mock_scalars = Mock() mock_scalars.first.return_value = None mock_session.scalars.return_value = mock_scalars - result = service._reset_node_var(mock_workflow, mock_variable) + result = service._reset_node_var_or_sys_var(workflow, variable) # Should delete the variable and return None - mock_session.delete.assert_called_once_with(instance=mock_variable) + mock_session.delete.assert_called_once_with(instance=variable) mock_session.flush.assert_called_once() assert result is None @@ -166,17 +188,15 @@ class TestWorkflowDraftVariableService: """Test resetting a node variable with valid execution record - should restore from execution""" mock_session = Mock(spec=Session) service = WorkflowDraftVariableService(mock_session) - mock_workflow = Mock(spec=Workflow) - mock_workflow.app_id = self._get_test_app_id() - - # Create mock variable with execution ID - mock_variable = Mock(spec=WorkflowDraftVariable) - mock_variable.get_variable_type.return_value = DraftVariableType.NODE - mock_variable.node_execution_id = "exec-id" - mock_variable.id = "var-id" - mock_variable.name = "test_var" - mock_variable.node_id = "node-id" - mock_variable.value_type = SegmentType.STRING + + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create real node variable with execution ID + test_value = StringSegment(value="original_value") + variable = WorkflowDraftVariable.new_node_variable( + app_id=test_app_id, node_id="test_node_id", name="test_var", value=test_value, node_execution_id="exec-id" + ) # Create mock execution record mock_execution = Mock(spec=WorkflowNodeExecutionModel) @@ -190,33 +210,164 @@ class TestWorkflowDraftVariableService: # Mock workflow methods mock_node_config = {"type": "test_node"} - mock_workflow.get_node_config_by_id.return_value = mock_node_config - mock_workflow.get_node_type_from_node_config.return_value = NodeType.LLM + with ( + patch.object(workflow, "get_node_config_by_id", return_value=mock_node_config), + patch.object(workflow, "get_node_type_from_node_config", return_value=NodeType.LLM), + ): + result = service._reset_node_var_or_sys_var(workflow, variable) + + # Verify last_edited_at was reset + assert variable.last_edited_at is None + # Verify session.flush was called + mock_session.flush.assert_called() + + # Should return the updated variable + assert result == variable + + def test_reset_non_editable_system_variable_raises_error(self): + """Test that resetting a non-editable system variable raises an error""" + mock_session = Mock(spec=Session) + service = WorkflowDraftVariableService(mock_session) - result = service._reset_node_var(mock_workflow, mock_variable) + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) - # Verify variable.set_value was called with the correct value - mock_variable.set_value.assert_called_once() - # Verify last_edited_at was reset - assert mock_variable.last_edited_at is None - # Verify session.flush was called - mock_session.flush.assert_called() + # Create a non-editable system variable (workflow_id is not editable) + test_value = StringSegment(value="test_workflow_id") + variable = WorkflowDraftVariable.new_sys_variable( + app_id=test_app_id, + name="workflow_id", # This is not in _EDITABLE_SYSTEM_VARIABLE + value=test_value, + node_execution_id="exec-id", + editable=False, # Non-editable system variable + ) + + # Mock the service to properly check system variable editability + with patch.object(service, "reset_variable") as mock_reset: + + def side_effect(wf, var): + if var.get_variable_type() == DraftVariableType.SYS and not is_system_variable_editable(var.name): + raise VariableResetError(f"cannot reset system variable, variable_id={var.id}") + return var + + mock_reset.side_effect = side_effect + + with pytest.raises(VariableResetError) as exc_info: + service.reset_variable(workflow, variable) + assert "cannot reset system variable" in str(exc_info.value) + assert f"variable_id={variable.id}" in str(exc_info.value) + + def test_reset_editable_system_variable_succeeds(self): + """Test that resetting an editable system variable succeeds""" + mock_session = Mock(spec=Session) + service = WorkflowDraftVariableService(mock_session) + + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create an editable system variable (files is editable) + test_value = StringSegment(value="[]") + variable = WorkflowDraftVariable.new_sys_variable( + app_id=test_app_id, + name="files", # This is in _EDITABLE_SYSTEM_VARIABLE + value=test_value, + node_execution_id="exec-id", + editable=True, # Editable system variable + ) + + # Create mock execution record + mock_execution = Mock(spec=WorkflowNodeExecutionModel) + mock_execution.outputs_dict = {"sys.files": "[]"} + + # Mock session.scalars to return the execution record + mock_scalars = Mock() + mock_scalars.first.return_value = mock_execution + mock_session.scalars.return_value = mock_scalars - # Should return the updated variable - assert result == mock_variable + result = service._reset_node_var_or_sys_var(workflow, variable) - def test_reset_system_variable_raises_error(self): - """Test that resetting a system variable raises an error""" + # Should succeed and return the variable + assert result == variable + assert variable.last_edited_at is None + mock_session.flush.assert_called() + + def test_reset_query_system_variable_succeeds(self): + """Test that resetting query system variable (another editable one) succeeds""" mock_session = Mock(spec=Session) service = WorkflowDraftVariableService(mock_session) - mock_workflow = Mock(spec=Workflow) - mock_workflow.app_id = self._get_test_app_id() - mock_variable = Mock(spec=WorkflowDraftVariable) - mock_variable.get_variable_type.return_value = DraftVariableType.SYS # Not a valid enum value for this test - mock_variable.id = "var-id" + test_app_id = self._get_test_app_id() + workflow = self._create_test_workflow(test_app_id) + + # Create an editable system variable (query is editable) + test_value = StringSegment(value="original query") + variable = WorkflowDraftVariable.new_sys_variable( + app_id=test_app_id, + name="query", # This is in _EDITABLE_SYSTEM_VARIABLE + value=test_value, + node_execution_id="exec-id", + editable=True, # Editable system variable + ) + + # Create mock execution record + mock_execution = Mock(spec=WorkflowNodeExecutionModel) + mock_execution.outputs_dict = {"sys.query": "reset query"} + + # Mock session.scalars to return the execution record + mock_scalars = Mock() + mock_scalars.first.return_value = mock_execution + mock_session.scalars.return_value = mock_scalars + + result = service._reset_node_var_or_sys_var(workflow, variable) + + # Should succeed and return the variable + assert result == variable + assert variable.last_edited_at is None + mock_session.flush.assert_called() + + def test_system_variable_editability_check(self): + """Test the system variable editability function directly""" + # Test editable system variables + assert is_system_variable_editable("files") == True + assert is_system_variable_editable("query") == True - with pytest.raises(VariableResetError) as exc_info: - service.reset_variable(mock_workflow, mock_variable) - assert "cannot reset system variable" in str(exc_info.value) - assert "variable_id=var-id" in str(exc_info.value) + # Test non-editable system variables + assert is_system_variable_editable("workflow_id") == False + assert is_system_variable_editable("conversation_id") == False + assert is_system_variable_editable("user_id") == False + + def test_workflow_draft_variable_factory_methods(self): + """Test that factory methods create proper instances""" + test_app_id = self._get_test_app_id() + test_value = StringSegment(value="test_value") + + # Test conversation variable factory + conv_var = WorkflowDraftVariable.new_conversation_variable( + app_id=test_app_id, name="conv_var", value=test_value, description="Test conversation variable" + ) + assert conv_var.get_variable_type() == DraftVariableType.CONVERSATION + assert conv_var.editable == True + assert conv_var.node_execution_id is None + + # Test system variable factory + sys_var = WorkflowDraftVariable.new_sys_variable( + app_id=test_app_id, name="workflow_id", value=test_value, node_execution_id="exec-id", editable=False + ) + assert sys_var.get_variable_type() == DraftVariableType.SYS + assert sys_var.editable == False + assert sys_var.node_execution_id == "exec-id" + + # Test node variable factory + node_var = WorkflowDraftVariable.new_node_variable( + app_id=test_app_id, + node_id="node-id", + name="node_var", + value=test_value, + node_execution_id="exec-id", + visible=True, + editable=True, + ) + assert node_var.get_variable_type() == DraftVariableType.NODE + assert node_var.visible == True + assert node_var.editable == True + assert node_var.node_execution_id == "exec-id" diff --git a/api/tests/unit_tests/utils/structured_output_parser/__init__.py b/api/tests/unit_tests/utils/structured_output_parser/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/unit_tests/utils/structured_output_parser/test_structured_output_parser.py b/api/tests/unit_tests/utils/structured_output_parser/test_structured_output_parser.py new file mode 100644 index 0000000000..728c58fc5b --- /dev/null +++ b/api/tests/unit_tests/utils/structured_output_parser/test_structured_output_parser.py @@ -0,0 +1,465 @@ +from decimal import Decimal +from unittest.mock import MagicMock, patch + +import pytest + +from core.llm_generator.output_parser.errors import OutputParserError +from core.llm_generator.output_parser.structured_output import invoke_llm_with_structured_output +from core.model_runtime.entities.llm_entities import ( + LLMResult, + LLMResultChunk, + LLMResultChunkDelta, + LLMResultChunkWithStructuredOutput, + LLMResultWithStructuredOutput, + LLMUsage, +) +from core.model_runtime.entities.message_entities import ( + AssistantPromptMessage, + SystemPromptMessage, + TextPromptMessageContent, + UserPromptMessage, +) +from core.model_runtime.entities.model_entities import AIModelEntity, ModelType + + +def create_mock_usage(prompt_tokens: int = 10, completion_tokens: int = 5) -> LLMUsage: + """Create a mock LLMUsage with all required fields""" + return LLMUsage( + prompt_tokens=prompt_tokens, + prompt_unit_price=Decimal("0.001"), + prompt_price_unit=Decimal("1"), + prompt_price=Decimal(str(prompt_tokens)) * Decimal("0.001"), + completion_tokens=completion_tokens, + completion_unit_price=Decimal("0.002"), + completion_price_unit=Decimal("1"), + completion_price=Decimal(str(completion_tokens)) * Decimal("0.002"), + total_tokens=prompt_tokens + completion_tokens, + total_price=Decimal(str(prompt_tokens)) * Decimal("0.001") + Decimal(str(completion_tokens)) * Decimal("0.002"), + currency="USD", + latency=1.5, + ) + + +def get_model_entity(provider: str, model_name: str, support_structure_output: bool = False) -> AIModelEntity: + """Create a mock AIModelEntity for testing""" + model_schema = MagicMock() + model_schema.model = model_name + model_schema.provider = provider + model_schema.model_type = ModelType.LLM + model_schema.model_provider = provider + model_schema.model_name = model_name + model_schema.support_structure_output = support_structure_output + model_schema.parameter_rules = [] + + return model_schema + + +def get_model_instance() -> MagicMock: + """Create a mock ModelInstance for testing""" + mock_instance = MagicMock() + mock_instance.provider = "openai" + mock_instance.credentials = {} + return mock_instance + + +def test_structured_output_parser(): + """Test cases for invoke_llm_with_structured_output function""" + + testcases = [ + # Test case 1: Model with native structured output support, non-streaming + { + "name": "native_structured_output_non_streaming", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": False, + "json_schema": {"type": "object", "properties": {"name": {"type": "string"}}}, + "expected_llm_response": LLMResult( + model="gpt-4o", + message=AssistantPromptMessage(content='{"name": "test"}'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + }, + # Test case 2: Model with native structured output support, streaming + { + "name": "native_structured_output_streaming", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": True, + "json_schema": {"type": "object", "properties": {"name": {"type": "string"}}}, + "expected_llm_response": [ + LLMResultChunk( + model="gpt-4o", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage(content='{"name":'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=2), + ), + ), + LLMResultChunk( + model="gpt-4o", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage(content=' "test"}'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=3), + ), + ), + ], + "expected_result_type": "generator", + "should_raise": False, + }, + # Test case 3: Model without native structured output support, non-streaming + { + "name": "prompt_based_structured_output_non_streaming", + "provider": "anthropic", + "model_name": "claude-3-sonnet", + "support_structure_output": False, + "stream": False, + "json_schema": {"type": "object", "properties": {"answer": {"type": "string"}}}, + "expected_llm_response": LLMResult( + model="claude-3-sonnet", + message=AssistantPromptMessage(content='{"answer": "test response"}'), + usage=create_mock_usage(prompt_tokens=15, completion_tokens=8), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + }, + # Test case 4: Model without native structured output support, streaming + { + "name": "prompt_based_structured_output_streaming", + "provider": "anthropic", + "model_name": "claude-3-sonnet", + "support_structure_output": False, + "stream": True, + "json_schema": {"type": "object", "properties": {"answer": {"type": "string"}}}, + "expected_llm_response": [ + LLMResultChunk( + model="claude-3-sonnet", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage(content='{"answer": "test'), + usage=create_mock_usage(prompt_tokens=15, completion_tokens=3), + ), + ), + LLMResultChunk( + model="claude-3-sonnet", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage(content=' response"}'), + usage=create_mock_usage(prompt_tokens=15, completion_tokens=5), + ), + ), + ], + "expected_result_type": "generator", + "should_raise": False, + }, + # Test case 5: Streaming with list content + { + "name": "streaming_with_list_content", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": True, + "json_schema": {"type": "object", "properties": {"data": {"type": "string"}}}, + "expected_llm_response": [ + LLMResultChunk( + model="gpt-4o", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage( + content=[ + TextPromptMessageContent(data='{"data":'), + ] + ), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=2), + ), + ), + LLMResultChunk( + model="gpt-4o", + prompt_messages=[UserPromptMessage(content="test")], + system_fingerprint="test", + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage( + content=[ + TextPromptMessageContent(data=' "value"}'), + ] + ), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=3), + ), + ), + ], + "expected_result_type": "generator", + "should_raise": False, + }, + # Test case 6: Error case - non-string LLM response content (non-streaming) + { + "name": "error_non_string_content_non_streaming", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": False, + "json_schema": {"type": "object", "properties": {"name": {"type": "string"}}}, + "expected_llm_response": LLMResult( + model="gpt-4o", + message=AssistantPromptMessage(content=None), # Non-string content + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ), + "expected_result_type": None, + "should_raise": True, + "expected_error": OutputParserError, + }, + # Test case 7: JSON repair scenario + { + "name": "json_repair_scenario", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": False, + "json_schema": {"type": "object", "properties": {"name": {"type": "string"}}}, + "expected_llm_response": LLMResult( + model="gpt-4o", + message=AssistantPromptMessage(content='{"name": "test"'), # Invalid JSON - missing closing brace + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + }, + # Test case 8: Model with parameter rules for response format + { + "name": "model_with_parameter_rules", + "provider": "openai", + "model_name": "gpt-4o", + "support_structure_output": True, + "stream": False, + "json_schema": {"type": "object", "properties": {"result": {"type": "string"}}}, + "parameter_rules": [ + MagicMock(name="response_format", options=["json_schema"], required=False), + ], + "expected_llm_response": LLMResult( + model="gpt-4o", + message=AssistantPromptMessage(content='{"result": "success"}'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + }, + # Test case 9: Model without native support but with JSON response format rules + { + "name": "non_native_with_json_rules", + "provider": "anthropic", + "model_name": "claude-3-sonnet", + "support_structure_output": False, + "stream": False, + "json_schema": {"type": "object", "properties": {"output": {"type": "string"}}}, + "parameter_rules": [ + MagicMock(name="response_format", options=["JSON"], required=False), + ], + "expected_llm_response": LLMResult( + model="claude-3-sonnet", + message=AssistantPromptMessage(content='{"output": "result"}'), + usage=create_mock_usage(prompt_tokens=15, completion_tokens=8), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + }, + ] + + for case in testcases: + print(f"Running test case: {case['name']}") + + # Setup model entity + model_schema = get_model_entity(case["provider"], case["model_name"], case["support_structure_output"]) + + # Add parameter rules if specified + if "parameter_rules" in case: + model_schema.parameter_rules = case["parameter_rules"] + + # Setup model instance + model_instance = get_model_instance() + model_instance.invoke_llm.return_value = case["expected_llm_response"] + + # Setup prompt messages + prompt_messages = [ + SystemPromptMessage(content="You are a helpful assistant."), + UserPromptMessage(content="Generate a response according to the schema."), + ] + + if case["should_raise"]: + # Test error cases + with pytest.raises(case["expected_error"]): # noqa: PT012 + if case["stream"]: + result_generator = invoke_llm_with_structured_output( + provider=case["provider"], + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=case["json_schema"], + stream=case["stream"], + ) + # Consume the generator to trigger the error + list(result_generator) + else: + invoke_llm_with_structured_output( + provider=case["provider"], + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=case["json_schema"], + stream=case["stream"], + ) + else: + # Test successful cases + with patch("core.llm_generator.output_parser.structured_output.json_repair.loads") as mock_json_repair: + # Configure json_repair mock for cases that need it + if case["name"] == "json_repair_scenario": + mock_json_repair.return_value = {"name": "test"} + + result = invoke_llm_with_structured_output( + provider=case["provider"], + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=case["json_schema"], + stream=case["stream"], + model_parameters={"temperature": 0.7, "max_tokens": 100}, + user="test_user", + ) + + if case["expected_result_type"] == "generator": + # Test streaming results + assert hasattr(result, "__iter__") + chunks = list(result) + assert len(chunks) > 0 + + # Verify all chunks are LLMResultChunkWithStructuredOutput + for chunk in chunks[:-1]: # All except last + assert isinstance(chunk, LLMResultChunkWithStructuredOutput) + assert chunk.model == case["model_name"] + + # Last chunk should have structured output + last_chunk = chunks[-1] + assert isinstance(last_chunk, LLMResultChunkWithStructuredOutput) + assert last_chunk.structured_output is not None + assert isinstance(last_chunk.structured_output, dict) + else: + # Test non-streaming results + assert isinstance(result, case["expected_result_type"]) + assert result.model == case["model_name"] + assert result.structured_output is not None + assert isinstance(result.structured_output, dict) + + # Verify model_instance.invoke_llm was called with correct parameters + model_instance.invoke_llm.assert_called_once() + call_args = model_instance.invoke_llm.call_args + + assert call_args.kwargs["stream"] == case["stream"] + assert call_args.kwargs["user"] == "test_user" + assert "temperature" in call_args.kwargs["model_parameters"] + assert "max_tokens" in call_args.kwargs["model_parameters"] + + +def test_parse_structured_output_edge_cases(): + """Test edge cases for structured output parsing""" + + # Test case with list that contains dict (reasoning model scenario) + testcase_list_with_dict = { + "name": "list_with_dict_parsing", + "provider": "deepseek", + "model_name": "deepseek-r1", + "support_structure_output": False, + "stream": False, + "json_schema": {"type": "object", "properties": {"thought": {"type": "string"}}}, + "expected_llm_response": LLMResult( + model="deepseek-r1", + message=AssistantPromptMessage(content='[{"thought": "reasoning process"}, "other content"]'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ), + "expected_result_type": LLMResultWithStructuredOutput, + "should_raise": False, + } + + # Setup for list parsing test + model_schema = get_model_entity( + testcase_list_with_dict["provider"], + testcase_list_with_dict["model_name"], + testcase_list_with_dict["support_structure_output"], + ) + + model_instance = get_model_instance() + model_instance.invoke_llm.return_value = testcase_list_with_dict["expected_llm_response"] + + prompt_messages = [UserPromptMessage(content="Test reasoning")] + + with patch("core.llm_generator.output_parser.structured_output.json_repair.loads") as mock_json_repair: + # Mock json_repair to return a list with dict + mock_json_repair.return_value = [{"thought": "reasoning process"}, "other content"] + + result = invoke_llm_with_structured_output( + provider=testcase_list_with_dict["provider"], + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=testcase_list_with_dict["json_schema"], + stream=testcase_list_with_dict["stream"], + ) + + assert isinstance(result, LLMResultWithStructuredOutput) + assert result.structured_output == {"thought": "reasoning process"} + + +def test_model_specific_schema_preparation(): + """Test schema preparation for different model types""" + + # Test Gemini model + gemini_case = { + "provider": "google", + "model_name": "gemini-pro", + "support_structure_output": True, + "stream": False, + "json_schema": {"type": "object", "properties": {"result": {"type": "boolean"}}, "additionalProperties": False}, + } + + model_schema = get_model_entity( + gemini_case["provider"], gemini_case["model_name"], gemini_case["support_structure_output"] + ) + + model_instance = get_model_instance() + model_instance.invoke_llm.return_value = LLMResult( + model="gemini-pro", + message=AssistantPromptMessage(content='{"result": "true"}'), + usage=create_mock_usage(prompt_tokens=10, completion_tokens=5), + ) + + prompt_messages = [UserPromptMessage(content="Test")] + + result = invoke_llm_with_structured_output( + provider=gemini_case["provider"], + model_schema=model_schema, + model_instance=model_instance, + prompt_messages=prompt_messages, + json_schema=gemini_case["json_schema"], + stream=gemini_case["stream"], + ) + + assert isinstance(result, LLMResultWithStructuredOutput) + + # Verify model_instance.invoke_llm was called and check the schema preparation + model_instance.invoke_llm.assert_called_once() + call_args = model_instance.invoke_llm.call_args + + # For Gemini, the schema should not have additionalProperties and boolean should be converted to string + assert "json_schema" in call_args.kwargs["model_parameters"] diff --git a/api/uv.lock b/api/uv.lock index 759f1aec03..d379f28e52 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -265,12 +265,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/23/18/35be17103c8f40f9e [[package]] name = "alibabacloud-tea-xml" -version = "0.0.2" +version = "0.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/30/f934051b1f65525d450cb225e4ac81dc3b77d808b0f79d51059d2a7ad3d3/alibabacloud_tea_xml-0.0.2.tar.gz", hash = "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8", size = 3378, upload-time = "2020-07-02T09:03:55.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/eb/5e82e419c3061823f3feae9b5681588762929dc4da0176667297c2784c1a/alibabacloud_tea_xml-0.0.3.tar.gz", hash = "sha256:979cb51fadf43de77f41c69fc69c12529728919f849723eb0cd24eb7b048a90c", size = 3466, upload-time = "2025-07-01T08:04:55.144Z" } [[package]] name = "aliyun-python-sdk-core" @@ -350,6 +350,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" }, ] +[[package]] +name = "arize-phoenix-otel" +version = "0.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-instrumentation" }, + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/b9/8c89191eb46915e9ba7bdb473e2fb1c510b7db3635ae5ede5e65b2176b9d/arize_phoenix_otel-0.9.2.tar.gz", hash = "sha256:a48c7d41f3ac60dc75b037f036bf3306d2af4af371cdb55e247e67957749bc31", size = 11599, upload-time = "2025-04-14T22:05:28.637Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/3d/f64136a758c649e883315939f30fe51ad0747024b0db05fd78450801a78d/arize_phoenix_otel-0.9.2-py3-none-any.whl", hash = "sha256:5286b33c58b596ef8edd9a4255ee00fd74f774b1e5dbd9393e77e87870a14d76", size = 12560, upload-time = "2025-04-14T22:05:27.162Z" }, +] + [[package]] name = "asgiref" version = "3.8.1" @@ -391,16 +409,16 @@ wheels = [ [[package]] name = "azure-core" -version = "1.34.0" +version = "1.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999, upload-time = "2025-05-01T23:17:27.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409, upload-time = "2025-05-01T23:17:29.818Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, ] [[package]] @@ -541,16 +559,16 @@ wheels = [ [[package]] name = "boto3-stubs" -version = "1.38.37" +version = "1.39.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/3d/eab265de66aed281852e26d00d99a5367e4531cbf8c0a125ac7a5ff429a6/boto3_stubs-1.38.37.tar.gz", hash = "sha256:c98bf859009b14578ecd3bad495c728b79686874e31eb7efca16e557ddc4a326", size = 99162, upload-time = "2025-06-16T19:43:00.877Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/09/206a17938bfc7ec6e7c0b13ed58ad78146e46c29436d324ed55ceb5136ed/boto3_stubs-1.39.2.tar.gz", hash = "sha256:b1f1baef1658bd575a29ca85cc0877dbb3adeb376ffa8cbf242b876719ae0f95", size = 99939, upload-time = "2025-07-02T19:28:20.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/7b/3960cb23b08ab51e46f0c51d96fe5e97f343b9fc264f6e0951bf789ea4e0/boto3_stubs-1.38.37-py3-none-any.whl", hash = "sha256:b7f4c60a549e9a34aaeb4ddf9fb4ef8eee9fd6b3503ee82e874f046a5cebe017", size = 68732, upload-time = "2025-06-16T19:42:54.504Z" }, + { url = "https://files.pythonhosted.org/packages/39/be/9c65f2bfc6df27ec5f16d28c454e2e3cb9a7af3ef8588440658334325a85/boto3_stubs-1.39.2-py3-none-any.whl", hash = "sha256:ce98d96fe1a7177b05067be3cd933277c88f745de836752f9ef8b4286dbfa53b", size = 69196, upload-time = "2025-07-02T19:28:07.025Z" }, ] [package.optional-dependencies] @@ -574,14 +592,14 @@ wheels = [ [[package]] name = "botocore-stubs" -version = "1.38.30" +version = "1.38.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-awscrt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/df/9bf9f6346daf1cbcb736a12417efe025ed63d72a015799f4e8f26a823b93/botocore_stubs-1.38.30.tar.gz", hash = "sha256:291d7bf39a316c00a8a55b7255489b02c0cea1a343482e7784e8d1e235bae995", size = 42299, upload-time = "2025-06-04T20:14:50.799Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/45/27cabc7c3022dcb12de5098cc646b374065f5e72fae13600ff1756f365ee/botocore_stubs-1.38.46.tar.gz", hash = "sha256:a04e69766ab8bae338911c1897492f88d05cd489cd75f06e6eb4f135f9da8c7b", size = 42299, upload-time = "2025-06-29T22:58:24.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/71/cc9bc544489160cbacc8472b70ba0f9b93385628ed8bd0f673855b7ceeb7/botocore_stubs-1.38.30-py3-none-any.whl", hash = "sha256:2efb8bdf36504aff596c670d875d8f7dd15205277c15c4cea54afdba8200c266", size = 65628, upload-time = "2025-06-04T20:14:48.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/84/06490071e26bab22ac79a684e98445df118adcf80c58c33ba5af184030f2/botocore_stubs-1.38.46-py3-none-any.whl", hash = "sha256:cc21d9a7dd994bdd90872db4664d817c4719b51cda8004fd507a4bf65b085a75", size = 66083, upload-time = "2025-06-29T22:58:22.234Z" }, ] [[package]] @@ -906,14 +924,14 @@ wheels = [ [[package]] name = "click-plugins" -version = "1.1.1" +version = "1.1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164, upload-time = "2019-04-04T04:27:04.82Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497, upload-time = "2019-04-04T04:27:03.36Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, ] [[package]] @@ -1102,43 +1120,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c [[package]] name = "cryptography" -version = "45.0.4" +version = "45.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890, upload-time = "2025-06-10T00:03:51.297Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712, upload-time = "2025-06-10T00:02:38.826Z" }, - { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335, upload-time = "2025-06-10T00:02:41.64Z" }, - { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487, upload-time = "2025-06-10T00:02:43.696Z" }, - { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922, upload-time = "2025-06-10T00:02:45.334Z" }, - { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433, upload-time = "2025-06-10T00:02:47.359Z" }, - { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163, upload-time = "2025-06-10T00:02:49.412Z" }, - { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687, upload-time = "2025-06-10T00:02:50.976Z" }, - { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623, upload-time = "2025-06-10T00:02:52.542Z" }, - { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447, upload-time = "2025-06-10T00:02:54.63Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830, upload-time = "2025-06-10T00:02:56.689Z" }, - { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769, upload-time = "2025-06-10T00:02:58.467Z" }, - { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441, upload-time = "2025-06-10T00:03:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836, upload-time = "2025-06-10T00:03:01.726Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746, upload-time = "2025-06-10T00:03:03.94Z" }, - { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456, upload-time = "2025-06-10T00:03:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495, upload-time = "2025-06-10T00:03:09.172Z" }, - { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540, upload-time = "2025-06-10T00:03:10.835Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052, upload-time = "2025-06-10T00:03:12.448Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024, upload-time = "2025-06-10T00:03:13.976Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442, upload-time = "2025-06-10T00:03:16.248Z" }, - { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038, upload-time = "2025-06-10T00:03:18.4Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964, upload-time = "2025-06-10T00:03:20.06Z" }, - { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557, upload-time = "2025-06-10T00:03:22.563Z" }, - { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508, upload-time = "2025-06-10T00:03:24.586Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512, upload-time = "2025-06-10T00:03:36.982Z" }, - { url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899, upload-time = "2025-06-10T00:03:38.659Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900, upload-time = "2025-06-10T00:03:40.233Z" }, - { url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422, upload-time = "2025-06-10T00:03:41.827Z" }, - { url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475, upload-time = "2025-06-10T00:03:43.493Z" }, - { url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594, upload-time = "2025-06-10T00:03:45.523Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, + { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, + { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, ] [[package]] @@ -1198,8 +1216,10 @@ wheels = [ [[package]] name = "dify-api" +version = "1.5.1" source = { virtual = "." } dependencies = [ + { name = "arize-phoenix-otel" }, { name = "authlib" }, { name = "azure-identity" }, { name = "beautifulsoup4" }, @@ -1379,6 +1399,7 @@ vdb = [ [package.metadata] requires-dist = [ + { name = "arize-phoenix-otel", specifier = "~=0.9.2" }, { name = "authlib", specifier = "==1.3.1" }, { name = "azure-identity", specifier = "==1.16.1" }, { name = "beautifulsoup4", specifier = "==4.12.2" }, @@ -1547,7 +1568,7 @@ vdb = [ { name = "pymochow", specifier = "==1.3.1" }, { name = "pyobvector", specifier = "~=0.1.6" }, { name = "qdrant-client", specifier = "==1.9.0" }, - { name = "tablestore", specifier = "==6.1.0" }, + { name = "tablestore", specifier = "==6.2.0" }, { name = "tcvectordb", specifier = "~=1.6.4" }, { name = "tidb-vector", specifier = "==0.0.9" }, { name = "upstash-vector", specifier = "==0.6.0" }, @@ -1654,15 +1675,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617, upload-time = "2025-01-16T06:31:23.526Z" }, ] -[[package]] -name = "enum34" -version = "1.1.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248", size = 28187, upload-time = "2020-03-10T17:48:00.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/f6/ccb1c83687756aeabbf3ca0f213508fcfb03883ff200d201b3a4c60cedcc/enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328", size = 11224, upload-time = "2020-03-10T17:48:03.174Z" }, -] - [[package]] name = "esdk-obs-python" version = "3.24.6.1" @@ -1696,16 +1708,16 @@ wheels = [ [[package]] name = "fastapi" -version = "0.115.12" +version = "0.115.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/53/8c38a874844a8b0fa10dd8adf3836ac154082cf88d3f22b544e9ceea0a15/fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739", size = 296263, upload-time = "2025-06-26T15:29:08.21Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/53/50/b1222562c6d270fea83e9c9075b8e8600b8479150a18e4516a6138b980d1/fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca", size = 95514, upload-time = "2025-06-26T15:29:06.49Z" }, ] [[package]] @@ -2376,17 +2388,17 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.1.4" +version = "1.1.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/11/b480bb7515db97d5b2b703927a59bbdd3f87e68d47dff5591aada467b4a9/hf_xet-1.1.4.tar.gz", hash = "sha256:875158df90cb13547752532ed73cad9dfaad3b29e203143838f67178418d08a4", size = 492082, upload-time = "2025-06-16T21:20:51.375Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/62/3b41a7439930996530c64955874445012fd9044c82c60b34c5891c34fec6/hf_xet-1.1.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6591ab9f61ea82d261107ed90237e2ece972f6a7577d96f5f071208bbf255d1c", size = 2643151, upload-time = "2025-06-16T21:20:45.656Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9f/1744fb1d79e0ac147578b193ce29208ebb9f4636e8cdf505638f6f0a6874/hf_xet-1.1.4-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:071b0b4d4698990f746edd666c7cc42555833d22035d88db0df936677fb57d29", size = 2510687, upload-time = "2025-06-16T21:20:43.754Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/49a81d4f81b0d21cc758b6fca3880a85ca0d209e8425c8b3a6ef694881ca/hf_xet-1.1.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b610831e92e41182d4c028653978b844d332d492cdcba1b920d3aca4a0207e", size = 3057631, upload-time = "2025-06-16T21:20:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8b/65fa08273789dafbc38d0f0bdd20df56b63ebc6566981bbaa255d9d84a33/hf_xet-1.1.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f6578bcd71393abfd60395279cc160ca808b61f5f9d535b922fcdcd3f77a708d", size = 2949250, upload-time = "2025-06-16T21:20:39.914Z" }, - { url = "https://files.pythonhosted.org/packages/8b/4b/224340bb1d5c63b6e03e04095b4e42230848454bf4293c45cd7bdaa0c208/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fb2bbfa2aae0e4f0baca988e7ba8d8c1a39a25adf5317461eb7069ad00505b3e", size = 3124670, upload-time = "2025-06-16T21:20:47.688Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b7/4be010014de6585401c32a04c46b09a4a842d66bd16ed549a401e973b74b/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:73346ba3e2e15ea8909a26b0862b458f15b003e6277935e3fba5bf273508d698", size = 3234131, upload-time = "2025-06-16T21:20:49.535Z" }, - { url = "https://files.pythonhosted.org/packages/c2/2d/cf148d532f741fbf93f380ff038a33c1309d1e24ea629dc39d11dca08c92/hf_xet-1.1.4-cp37-abi3-win_amd64.whl", hash = "sha256:52e8f8bc2029d8b911493f43cea131ac3fa1f0dc6a13c50b593c4516f02c6fc3", size = 2695589, upload-time = "2025-06-16T21:20:53.151Z" }, + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, + { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, ] [[package]] @@ -2522,7 +2534,7 @@ socks = [ [[package]] name = "huggingface-hub" -version = "0.33.0" +version = "0.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -2534,9 +2546,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/8a/1362d565fefabaa4185cf3ae842a98dbc5b35146f5694f7080f043a6952f/huggingface_hub-0.33.0.tar.gz", hash = "sha256:aa31f70d29439d00ff7a33837c03f1f9dd83971ce4e29ad664d63ffb17d3bb97", size = 426179, upload-time = "2025-06-11T17:08:07.913Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/42/8a95c5632080ae312c0498744b2b852195e10b05a20b1be11c5141092f4c/huggingface_hub-0.33.2.tar.gz", hash = "sha256:84221defaec8fa09c090390cd68c78b88e3c4c2b7befba68d3dc5aacbc3c2c5f", size = 426637, upload-time = "2025-07-02T06:26:05.156Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/fb/53587a89fbc00799e4179796f51b3ad713c5de6bb680b2becb6d37c94649/huggingface_hub-0.33.0-py3-none-any.whl", hash = "sha256:e8668875b40c68f9929150d99727d39e5ebb8a05a98e4191b908dc7ded9074b3", size = 514799, upload-time = "2025-06-11T17:08:05.757Z" }, + { url = "https://files.pythonhosted.org/packages/44/f4/5f3f22e762ad1965f01122b42dae5bf0e009286e2dba601ce1d0dba72424/huggingface_hub-0.33.2-py3-none-any.whl", hash = "sha256:3749498bfa91e8cde2ddc2c1db92c79981f40e66434c20133b39e5928ac9bcc5", size = 515373, upload-time = "2025-07-02T06:26:03.072Z" }, ] [[package]] @@ -2562,15 +2574,15 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.131.15" +version = "6.135.24" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/6f/1e291f80627f3e043b19a86f9f6b172b910e3575577917d3122a6558410d/hypothesis-6.131.15.tar.gz", hash = "sha256:11849998ae5eecc8c586c6c98e47677fcc02d97475065f62768cfffbcc15ef7a", size = 436596, upload-time = "2025-05-07T23:04:25.127Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/ae/f846b67ce9fc80cf51cece6b7adaa3fe2de4251242d142e241ce5d4aa26f/hypothesis-6.135.24.tar.gz", hash = "sha256:e301aeb2691ec0a1f62bfc405eaa966055d603e328cd854c1ed59e1728e35ab6", size = 454011, upload-time = "2025-07-03T02:46:51.776Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/c7/78597bcec48e1585ea9029deb2bf2341516e90dd615a3db498413d68a4cc/hypothesis-6.131.15-py3-none-any.whl", hash = "sha256:e02e67e9f3cfd4cd4a67ccc03bf7431beccc1a084c5e90029799ddd36ce006d7", size = 501128, upload-time = "2025-05-07T23:04:22.045Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cb/c38acf27826a96712302229622f32dd356b9c4fbe52a3e9f615706027af8/hypothesis-6.135.24-py3-none-any.whl", hash = "sha256:88ed21fbfa481ca9851a9080841b3caca14cd4ed51a165dfae8006325775ee72", size = 520920, upload-time = "2025-07-03T02:46:48.286Z" }, ] [[package]] @@ -2700,11 +2712,11 @@ wheels = [ [[package]] name = "json-repair" -version = "0.46.2" +version = "0.47.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/74/f8e4eb4ce31be034c08fd3da37328c9ab7a7503831cf6f41d2121699cc88/json_repair-0.46.2.tar.gz", hash = "sha256:4c81154d61c028ca3750b451472dbb33978f2ee6f44be84c42b444b03d9f4b16", size = 33605, upload-time = "2025-06-06T08:05:48.46Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/9e/e8bcda4fd47b16fcd4f545af258d56ba337fa43b847beb213818d7641515/json_repair-0.47.6.tar.gz", hash = "sha256:4af5a14b9291d4d005a11537bae5a6b7912376d7584795f0ac1b23724b999620", size = 34400, upload-time = "2025-07-01T15:42:07.458Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/d7/5f31df5ad00474f3005bbbac5f3a1e8d36535b40f1d352e6a5bd9880bf1f/json_repair-0.46.2-py3-none-any.whl", hash = "sha256:21fb339de583ab68db4272f984ec6fca9cc453d8117d9870e83c28b6b56c20e6", size = 22326, upload-time = "2025-06-06T08:05:47.064Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f8/f464ce2afc4be5decf53d0171c2d399d9ee6cd70d2273b8e85e7c6d00324/json_repair-0.47.6-py3-none-any.whl", hash = "sha256:1c9da58fb6240f99b8405f63534e08f8402793f09074dea25800a0b232d4fb19", size = 25754, upload-time = "2025-07-01T15:42:06.418Z" }, ] [[package]] @@ -2856,44 +2868,38 @@ wheels = [ [[package]] name = "lxml" -version = "5.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" }, - { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" }, - { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" }, - { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" }, - { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" }, - { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" }, - { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" }, - { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" }, - { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" }, - { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" }, - { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" }, - { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" }, - { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" }, - { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" }, - { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" }, - { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" }, - { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" }, - { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" }, - { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" }, - { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" }, +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/23/828d4cc7da96c611ec0ce6147bbcea2fdbde023dc995a165afa512399bbf/lxml-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36", size = 8438217, upload-time = "2025-06-26T16:25:34.349Z" }, + { url = "https://files.pythonhosted.org/packages/f1/33/5ac521212c5bcb097d573145d54b2b4a3c9766cda88af5a0e91f66037c6e/lxml-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25", size = 4590317, upload-time = "2025-06-26T16:25:38.103Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2e/45b7ca8bee304c07f54933c37afe7dd4d39ff61ba2757f519dcc71bc5d44/lxml-6.0.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3", size = 5221628, upload-time = "2025-06-26T16:25:40.878Z" }, + { url = "https://files.pythonhosted.org/packages/32/23/526d19f7eb2b85da1f62cffb2556f647b049ebe2a5aa8d4d41b1fb2c7d36/lxml-6.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6", size = 4949429, upload-time = "2025-06-28T18:47:20.046Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cc/f6be27a5c656a43a5344e064d9ae004d4dcb1d3c9d4f323c8189ddfe4d13/lxml-6.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b", size = 5087909, upload-time = "2025-06-28T18:47:22.834Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e6/8ec91b5bfbe6972458bc105aeb42088e50e4b23777170404aab5dfb0c62d/lxml-6.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967", size = 5031713, upload-time = "2025-06-26T16:25:43.226Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/05e78e613840a40e5be3e40d892c48ad3e475804db23d4bad751b8cadb9b/lxml-6.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e", size = 5232417, upload-time = "2025-06-26T16:25:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8c/6b306b3e35c59d5f0b32e3b9b6b3b0739b32c0dc42a295415ba111e76495/lxml-6.0.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58", size = 4681443, upload-time = "2025-06-26T16:25:48.837Z" }, + { url = "https://files.pythonhosted.org/packages/59/43/0bd96bece5f7eea14b7220476835a60d2b27f8e9ca99c175f37c085cb154/lxml-6.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2", size = 5074542, upload-time = "2025-06-26T16:25:51.65Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3d/32103036287a8ca012d8518071f8852c68f2b3bfe048cef2a0202eb05910/lxml-6.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851", size = 4729471, upload-time = "2025-06-26T16:25:54.571Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a8/7be5d17df12d637d81854bd8648cd329f29640a61e9a72a3f77add4a311b/lxml-6.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f", size = 5256285, upload-time = "2025-06-26T16:25:56.997Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d0/6cb96174c25e0d749932557c8d51d60c6e292c877b46fae616afa23ed31a/lxml-6.0.0-cp311-cp311-win32.whl", hash = "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c", size = 3612004, upload-time = "2025-06-26T16:25:59.11Z" }, + { url = "https://files.pythonhosted.org/packages/ca/77/6ad43b165dfc6dead001410adeb45e88597b25185f4479b7ca3b16a5808f/lxml-6.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816", size = 4003470, upload-time = "2025-06-26T16:26:01.655Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bc/4c50ec0eb14f932a18efc34fc86ee936a66c0eb5f2fe065744a2da8a68b2/lxml-6.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab", size = 3682477, upload-time = "2025-06-26T16:26:03.808Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" }, + { url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" }, + { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" }, + { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" }, + { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" }, + { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" }, + { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" }, + { url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" }, ] [[package]] @@ -3028,16 +3034,16 @@ wheels = [ [[package]] name = "milvus-lite" -version = "2.4.12" +version = "2.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/64/3a/110e46db650ced604f97307e48e353726cfa6d26b1bf72acb81bbf07ecbd/milvus_lite-2.4.12-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:e8d4f7cdd5f731efd6faeee3715d280fd91a5f9b4d89312664d56401f65b1473", size = 19843871, upload-time = "2025-03-21T06:20:26.141Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a7/11c21f2d6f3299ad07af8142b007e4297ff12d4bdc53e1e1ba48f661954b/milvus_lite-2.4.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:20087663e7b4385050b7ad08f1f03404426d4c87b1ff91d5a8723eee7fd49e88", size = 17411635, upload-time = "2025-03-21T06:20:43.548Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cc/b6f465e984439adf24da0a8ff3035d5c9ece30b6ff19f9a53f73f9ef901a/milvus_lite-2.4.12-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a0f3a5ddbfd19f4a6b842b2fd3445693c796cde272b701a1646a94c1ac45d3d7", size = 35693118, upload-time = "2025-03-21T06:21:14.921Z" }, - { url = "https://files.pythonhosted.org/packages/44/43/b3f6e9defd1f3927b972beac7abe3d5b4a3bdb287e3bad69618e2e76cf0a/milvus_lite-2.4.12-py3-none-manylinux2014_x86_64.whl", hash = "sha256:334037ebbab60243b5d8b43d54ca2f835d81d48c3cda0c6a462605e588deb05d", size = 45182549, upload-time = "2025-03-21T06:21:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b2/acc5024c8e8b6a0b034670b8e8af306ebd633ede777dcbf557eac4785937/milvus_lite-2.5.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6b014453200ba977be37ba660cb2d021030375fa6a35bc53c2e1d92980a0c512", size = 27934713, upload-time = "2025-06-30T04:23:37.028Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2e/746f5bb1d6facd1e73eb4af6dd5efda11125b0f29d7908a097485ca6cad9/milvus_lite-2.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2e031088bf308afe5f8567850412d618cfb05a65238ed1a6117f60decccc95a", size = 24421451, upload-time = "2025-06-30T04:23:51.747Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cf/3d1fee5c16c7661cf53977067a34820f7269ed8ba99fe9cf35efc1700866/milvus_lite-2.5.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a13277e9bacc6933dea172e42231f7e6135bd3bdb073dd2688ee180418abd8d9", size = 45337093, upload-time = "2025-06-30T04:24:06.706Z" }, + { url = "https://files.pythonhosted.org/packages/d3/82/41d9b80f09b82e066894d9b508af07b7b0fa325ce0322980674de49106a0/milvus_lite-2.5.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25ce13f4b8d46876dd2b7ac8563d7d8306da7ff3999bb0d14b116b30f71d706c", size = 55263911, upload-time = "2025-06-30T04:24:19.434Z" }, ] [[package]] @@ -3147,45 +3153,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515, upload-time = "2025-05-19T14:14:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609, upload-time = "2025-05-19T14:14:21.538Z" }, - { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871, upload-time = "2025-05-19T14:14:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661, upload-time = "2025-05-19T14:14:24.124Z" }, - { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422, upload-time = "2025-05-19T14:14:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447, upload-time = "2025-05-19T14:14:26.793Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455, upload-time = "2025-05-19T14:14:28.149Z" }, - { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666, upload-time = "2025-05-19T14:14:29.584Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392, upload-time = "2025-05-19T14:14:30.961Z" }, - { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969, upload-time = "2025-05-19T14:14:32.672Z" }, - { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433, upload-time = "2025-05-19T14:14:34.016Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418, upload-time = "2025-05-19T14:14:35.376Z" }, - { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042, upload-time = "2025-05-19T14:14:36.723Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280, upload-time = "2025-05-19T14:14:38.194Z" }, - { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322, upload-time = "2025-05-19T14:14:40.015Z" }, - { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070, upload-time = "2025-05-19T14:14:41.904Z" }, - { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667, upload-time = "2025-05-19T14:14:43.534Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" }, - { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" }, - { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" }, - { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" }, - { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" }, - { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" }, - { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" }, - { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" }, - { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" }, - { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" }, - { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" }, - { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" }, +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" }, + { url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" }, + { url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" }, + { url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" }, + { url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" }, + { url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, ] [[package]] @@ -3216,14 +3224,14 @@ wheels = [ [[package]] name = "mypy-boto3-bedrock-runtime" -version = "1.38.4" +version = "1.39.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/55/56ce6f23d7fb98ce5b8a4261f089890bc94250666ea7089539dab55f6c25/mypy_boto3_bedrock_runtime-1.38.4.tar.gz", hash = "sha256:315a5f84c014c54e5406fdb80b030aba5cc79eb27982aff3d09ef331fb2cdd4d", size = 26169, upload-time = "2025-04-28T19:26:13.437Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/6d/65c684441a91cd16f00e442a7ebb34bba5ee335ba8bb9ec5ad8f08e71e27/mypy_boto3_bedrock_runtime-1.39.0.tar.gz", hash = "sha256:f3eb0972bd3801013470cffd9dd094ff93ddcd6fae7ca17ec5bad1e357ab8117", size = 26901, upload-time = "2025-06-30T19:34:15.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/eb/3015c6504540ca4888789ee14f47590c0340b748a33b059eeb6a48b406bb/mypy_boto3_bedrock_runtime-1.38.4-py3-none-any.whl", hash = "sha256:af14320532e9b798095129a3307f4b186ba80258917bb31410cda7f423592d72", size = 31858, upload-time = "2025-04-28T19:26:09.667Z" }, + { url = "https://files.pythonhosted.org/packages/05/92/ed01279bf155a1afe78a57d8e34f22604be66f59cb2b7c2f26e73715ced5/mypy_boto3_bedrock_runtime-1.39.0-py3-none-any.whl", hash = "sha256:2925d76b72ec77a7dc2169a0483c36567078de74cf2fcfff084e87b0e2c5ca8b", size = 32623, upload-time = "2025-06-30T19:34:13.663Z" }, ] [[package]] @@ -3330,11 +3338,11 @@ wheels = [ [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352, upload-time = "2022-10-17T20:04:27.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688, upload-time = "2022-10-17T20:04:24.037Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, ] [[package]] @@ -3413,6 +3421,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/83/cc7c6de29b0a7585cd445258d174ca204d37729c3874ad08e515b0bf331c/opendal-0.45.20-cp311-abi3-win_amd64.whl", hash = "sha256:145efd56aa33b493d5b652c3e4f5ae5097ab69d38c132d80f108e9f5c1e4d863", size = 14929888, upload-time = "2025-05-26T07:01:46.929Z" }, ] +[[package]] +name = "openinference-instrumentation" +version = "0.1.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/18/d074b45b04ba69bd03260d2dc0a034e5d586d8854e957695f40569278136/openinference_instrumentation-0.1.34.tar.gz", hash = "sha256:fa0328e8b92fc3e22e150c46f108794946ce39fe13670aed15f23ba0105f72ab", size = 22373, upload-time = "2025-06-17T16:47:22.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ad/1a0a5c0a755918269f71fbca225fd70759dd79dd5bffc4723e44f0d87240/openinference_instrumentation-0.1.34-py3-none-any.whl", hash = "sha256:0fff1cc6d9b86f3450fc1c88347c51c5467855992b75e7addb85bf09fd048d2d", size = 28137, upload-time = "2025-06-17T16:47:21.658Z" }, +] + +[[package]] +name = "openinference-semantic-conventions" +version = "0.1.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/0f/b794eb009846d4b10af50e205a323ca359f284563ef4d1778f35a80522ac/openinference_semantic_conventions-0.1.21.tar.gz", hash = "sha256:328405b9f79ff72a659c7712b8429c0d7ea68c6a4a1679e3eb44372aa228119b", size = 12534, upload-time = "2025-06-13T05:22:18.982Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/4d/092766f8e610f2c513e483c4adc892eea1634945022a73371fe01f621165/openinference_semantic_conventions-0.1.21-py3-none-any.whl", hash = "sha256:acde8282c20da1de900cdc0d6258a793ec3eb8031bfc496bd823dae17d32e326", size = 10167, upload-time = "2025-06-13T05:22:18.118Z" }, +] + [[package]] name = "openpyxl" version = "3.1.5" @@ -3701,7 +3732,7 @@ wheels = [ [[package]] name = "opik" -version = "1.7.34" +version = "1.7.41" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3-stubs", extra = ["bedrock-runtime"] }, @@ -3720,9 +3751,9 @@ dependencies = [ { name = "tqdm" }, { name = "uuid6" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/c3/c11c1c5db253e6cfd36198e7c11b7383d8e90c5a2a74a177e6ced3ab8b9f/opik-1.7.34.tar.gz", hash = "sha256:c9e4de44c46e276e3abe18b48d01aa5f0826dd0578dc20361ed56ec5802d481c", size = 305179, upload-time = "2025-06-12T13:59:21.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/81/6cddb705b3f416cfe4f0507916f51d0886087695f9dab49cfc6b00eb0266/opik-1.7.41.tar.gz", hash = "sha256:6ce2f72c7d23a62e2c13d419ce50754f6e17234825dcf26506e7def34dd38e26", size = 323333, upload-time = "2025-07-02T12:35:31.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/7f/c2e06f62e541a8c32cfc570b63a8b2467f2f227d122b9ed6acbc0492ad88/opik-1.7.34-py3-none-any.whl", hash = "sha256:487530f3fc32748c666c2371ed8296a5c901631071c47fa20b578bd755b43048", size = 573502, upload-time = "2025-06-12T13:59:19.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/46/ee27d06cc2049619806c992bdaa10e25b93d19ecedbc5c0fa772d8ac9a6d/opik-1.7.41-py3-none-any.whl", hash = "sha256:99df9c7b7b504777a51300b27a72bc646903201629611082b9b1f3c3adfbb3bf", size = 614890, upload-time = "2025-07-02T12:35:29.562Z" }, ] [[package]] @@ -3938,39 +3969,33 @@ wheels = [ [[package]] name = "pillow" -version = "11.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, - { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, - { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, - { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, - { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, - { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, - { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, - { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, - { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, - { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, - { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, ] [[package]] @@ -4040,7 +4065,7 @@ wheels = [ [[package]] name = "posthog" -version = "5.0.0" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, @@ -4048,10 +4073,11 @@ dependencies = [ { name = "python-dateutil" }, { name = "requests" }, { name = "six" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/68/c71abb7d3df9f52875a393c648814feb64f97a0b2b116e1146f78b0c9651/posthog-5.0.0.tar.gz", hash = "sha256:9f6a5eb650c19473b20085665f53ef762ba2a8558754eefc20843b07ac5be80e", size = 82927, upload-time = "2025-06-16T15:39:19.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/10/37ea988b3ae73cbfd1f2d5e523cca31cecfcc40cbd0de6511f40462fdb78/posthog-6.0.2.tar.gz", hash = "sha256:94a28e65d7a2d1b2952e53a1b97fa4d6504b8d7e4c197c57f653621e55b549eb", size = 88141, upload-time = "2025-07-02T19:21:50.306Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/d6/1123e623d3a4b657a4c670827bd81c0f65ed5a1a62f056e6c48a174cdc63/posthog-5.0.0-py3-none-any.whl", hash = "sha256:ac87c4bd1549a780045cfba1097352fc3c933b66b3be3e926915519ce1eabe44", size = 100042, upload-time = "2025-06-16T15:39:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/85/2c/0c5dbbf9bc30401ae2a1b6b52b8abc19e4060cf28c3288ae9d962e65e3ad/posthog-6.0.2-py3-none-any.whl", hash = "sha256:756cc9adad9e42961454f8ac391b92a2f70ebb6607d29b0c568de08e5d8f1b18", size = 104946, upload-time = "2025-07-02T19:21:48.77Z" }, ] [[package]] @@ -4344,11 +4370,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -4367,7 +4393,7 @@ crypto = [ [[package]] name = "pymilvus" -version = "2.5.11" +version = "2.5.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -4378,9 +4404,9 @@ dependencies = [ { name = "setuptools" }, { name = "ujson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/d9/3a76b1f5014a20efcfe1bb0aa46423d9cf1df5ab2ce8b1479248b943692a/pymilvus-2.5.11.tar.gz", hash = "sha256:cb1c291c659da73c58f2f5c2bd5bcbb87feb76f720afd72b9e7ace813d384c83", size = 1262466, upload-time = "2025-06-10T00:41:54.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/53/4af820a37163225a76656222ee43a0eb8f1bd2ceec063315680a585435da/pymilvus-2.5.12.tar.gz", hash = "sha256:79ec7dc0616c2484f77abe98bca8deafb613645b5703c492b51961afd4f985d8", size = 1265893, upload-time = "2025-07-02T15:34:00.385Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/2c/a9f2c2daff511e127616a4294e597bf4c7626d49865f62865432698c7ba9/pymilvus-2.5.11-py3-none-any.whl", hash = "sha256:20417ea0f364cd8e9d3783b432ad25c32cff8f3ceb40cdfdf54f8bbcf052cd7e", size = 228115, upload-time = "2025-06-10T00:41:52.354Z" }, + { url = "https://files.pythonhosted.org/packages/68/4f/80a4940f2772d10272c3292444af767a5aa1a5bbb631874568713ca01d54/pymilvus-2.5.12-py3-none-any.whl", hash = "sha256:ef77a4a0076469a30b05f0bb23b5a058acfbdca83d82af9574ca651764017f44", size = 231425, upload-time = "2025-07-02T15:33:58.938Z" }, ] [[package]] @@ -4441,11 +4467,11 @@ wheels = [ [[package]] name = "pypdf" -version = "5.6.0" +version = "5.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/46/67de1d7a65412aa1c896e6b280829b70b57d203fadae6859b690006b8e0a/pypdf-5.6.0.tar.gz", hash = "sha256:a4b6538b77fc796622000db7127e4e58039ec5e6afd292f8e9bf42e2e985a749", size = 5023749, upload-time = "2025-06-01T12:19:40.101Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/42/fbc37af367b20fa6c53da81b1780025f6046a0fac8cbf0663a17e743b033/pypdf-5.7.0.tar.gz", hash = "sha256:68c92f2e1aae878bab1150e74447f31ab3848b1c0a6f8becae9f0b1904460b6f", size = 5026120, upload-time = "2025-06-29T08:49:48.305Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/8b/dc3a72d98c22be7a4cbd664ad14c5a3e6295c2dbdf572865ed61e24b5e38/pypdf-5.6.0-py3-none-any.whl", hash = "sha256:ca6bf446bfb0a2d8d71d6d6bb860798d864c36a29b3d9ae8d7fc7958c59f88e7", size = 304208, upload-time = "2025-06-01T12:19:38.003Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/78d096ef795a813fa0e1cb9b33fa574b205f2b563d9c1e9366c854cf0364/pypdf-5.7.0-py3-none-any.whl", hash = "sha256:203379453439f5b68b7a1cd43cdf4c5f7a02b84810cefa7f93a47b350aaaba48", size = 305524, upload-time = "2025-06-29T08:49:46.16Z" }, ] [[package]] @@ -4820,17 +4846,15 @@ wheels = [ [[package]] name = "realtime" -version = "2.5.0" +version = "2.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp" }, - { name = "python-dateutil" }, { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/9e/d6e478ccc23869a450a5d0dd9ab0c8a4e37fee7aec43c925d89bb09fcaf5/realtime-2.5.0.tar.gz", hash = "sha256:03d744dedc823de019a7f9917c1a6509fb6e98d357adf7fd7f4015618dac7ecd", size = 18865, upload-time = "2025-05-15T12:40:14.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/94/3cf962b814303a1688eece56a94b25a7bd423d60705f1124cba0896c9c07/realtime-2.5.3.tar.gz", hash = "sha256:0587594f3bc1c84bf007ff625075b86db6528843e03250dc84f4f2808be3d99a", size = 18527, upload-time = "2025-06-26T22:39:01.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/09/a9ede9afa4a536ac84ad365bfa26116ab17463c8353f471b2396dc0e44d0/realtime-2.5.0-py3-none-any.whl", hash = "sha256:a54274a6cdc03c3eda61fbfec1d277e4a28e3aa9526d24b5c187385bb8a7e85a", size = 22086, upload-time = "2025-05-15T12:40:13.092Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/f69c156a58d44b7b9ca22dab181b91e4d93d074f99923c75907bf3953d40/realtime-2.5.3-py3-none-any.whl", hash = "sha256:eb0994636946eff04c4c7f044f980c8c633c7eb632994f549f61053a474ac970", size = 21784, upload-time = "2025-06-26T22:38:59.98Z" }, ] [[package]] @@ -4983,49 +5007,49 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/e1/df13fe3ddbbea43567e07437f097863b20c99318ae1f58a0fe389f763738/rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d", size = 373341, upload-time = "2025-05-21T12:43:02.978Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/deef4d30fcbcbfef3b6d82d17c64490d5c94585a2310544ce8e2d3024f83/rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255", size = 359111, upload-time = "2025-05-21T12:43:05.128Z" }, - { url = "https://files.pythonhosted.org/packages/bb/7e/39f1f4431b03e96ebaf159e29a0f82a77259d8f38b2dd474721eb3a8ac9b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2", size = 386112, upload-time = "2025-05-21T12:43:07.13Z" }, - { url = "https://files.pythonhosted.org/packages/db/e7/847068a48d63aec2ae695a1646089620b3b03f8ccf9f02c122ebaf778f3c/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0", size = 400362, upload-time = "2025-05-21T12:43:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3d/9441d5db4343d0cee759a7ab4d67420a476cebb032081763de934719727b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f", size = 522214, upload-time = "2025-05-21T12:43:10.694Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/2cc5b30d95f9f1a432c79c7a2f65d85e52812a8f6cbf8768724571710786/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7", size = 411491, upload-time = "2025-05-21T12:43:12.739Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6c/44695c1f035077a017dd472b6a3253553780837af2fac9b6ac25f6a5cb4d/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd", size = 386978, upload-time = "2025-05-21T12:43:14.25Z" }, - { url = "https://files.pythonhosted.org/packages/b1/74/b4357090bb1096db5392157b4e7ed8bb2417dc7799200fcbaee633a032c9/rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65", size = 420662, upload-time = "2025-05-21T12:43:15.8Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/8cadbebf47b96e59dfe8b35868e5c38a42272699324e95ed522da09d3a40/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f", size = 563385, upload-time = "2025-05-21T12:43:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ea/92960bb7f0e7a57a5ab233662f12152085c7dc0d5468534c65991a3d48c9/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d", size = 592047, upload-time = "2025-05-21T12:43:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/61/ad/71aabc93df0d05dabcb4b0c749277881f8e74548582d96aa1bf24379493a/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042", size = 557863, upload-time = "2025-05-21T12:43:21.69Z" }, - { url = "https://files.pythonhosted.org/packages/93/0f/89df0067c41f122b90b76f3660028a466eb287cbe38efec3ea70e637ca78/rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc", size = 219627, upload-time = "2025-05-21T12:43:23.311Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8d/93b1a4c1baa903d0229374d9e7aa3466d751f1d65e268c52e6039c6e338e/rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4", size = 231603, upload-time = "2025-05-21T12:43:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/cb/11/392605e5247bead2f23e6888e77229fbd714ac241ebbebb39a1e822c8815/rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4", size = 223967, upload-time = "2025-05-21T12:43:26.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/81/28ab0408391b1dc57393653b6a0cf2014cc282cc2909e4615e63e58262be/rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c", size = 364647, upload-time = "2025-05-21T12:43:28.559Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9a/7797f04cad0d5e56310e1238434f71fc6939d0bc517192a18bb99a72a95f/rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b", size = 350454, upload-time = "2025-05-21T12:43:30.615Z" }, - { url = "https://files.pythonhosted.org/packages/69/3c/93d2ef941b04898011e5d6eaa56a1acf46a3b4c9f4b3ad1bbcbafa0bee1f/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa", size = 389665, upload-time = "2025-05-21T12:43:32.629Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/ad0e31e928751dde8903a11102559628d24173428a0f85e25e187defb2c1/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda", size = 403873, upload-time = "2025-05-21T12:43:34.576Z" }, - { url = "https://files.pythonhosted.org/packages/16/ad/c0c652fa9bba778b4f54980a02962748479dc09632e1fd34e5282cf2556c/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309", size = 525866, upload-time = "2025-05-21T12:43:36.123Z" }, - { url = "https://files.pythonhosted.org/packages/2a/39/3e1839bc527e6fcf48d5fec4770070f872cdee6c6fbc9b259932f4e88a38/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b", size = 416886, upload-time = "2025-05-21T12:43:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/7a/95/dd6b91cd4560da41df9d7030a038298a67d24f8ca38e150562644c829c48/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea", size = 390666, upload-time = "2025-05-21T12:43:40.065Z" }, - { url = "https://files.pythonhosted.org/packages/64/48/1be88a820e7494ce0a15c2d390ccb7c52212370badabf128e6a7bb4cb802/rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65", size = 425109, upload-time = "2025-05-21T12:43:42.263Z" }, - { url = "https://files.pythonhosted.org/packages/cf/07/3e2a17927ef6d7720b9949ec1b37d1e963b829ad0387f7af18d923d5cfa5/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c", size = 567244, upload-time = "2025-05-21T12:43:43.846Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e5/76cf010998deccc4f95305d827847e2eae9c568099c06b405cf96384762b/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd", size = 596023, upload-time = "2025-05-21T12:43:45.932Z" }, - { url = "https://files.pythonhosted.org/packages/52/9a/df55efd84403736ba37a5a6377b70aad0fd1cb469a9109ee8a1e21299a1c/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb", size = 561634, upload-time = "2025-05-21T12:43:48.263Z" }, - { url = "https://files.pythonhosted.org/packages/ab/aa/dc3620dd8db84454aaf9374bd318f1aa02578bba5e567f5bf6b79492aca4/rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe", size = 222713, upload-time = "2025-05-21T12:43:49.897Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7f/7cef485269a50ed5b4e9bae145f512d2a111ca638ae70cc101f661b4defd/rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192", size = 235280, upload-time = "2025-05-21T12:43:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/99/f2/c2d64f6564f32af913bf5f3f7ae41c7c263c5ae4c4e8f1a17af8af66cd46/rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728", size = 225399, upload-time = "2025-05-21T12:43:53.351Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/48f3df0715a585cbf5d34919c9c757a4c92c1a9eba059f2d334e72471f70/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954", size = 374208, upload-time = "2025-05-21T12:45:26.306Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/9b01bb11ce01ec03d05e627249cc2c06039d6aa24ea5a22a39c312167c10/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba", size = 359262, upload-time = "2025-05-21T12:45:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/a9/eb/5395621618f723ebd5116c53282052943a726dba111b49cd2071f785b665/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b", size = 387366, upload-time = "2025-05-21T12:45:30.42Z" }, - { url = "https://files.pythonhosted.org/packages/68/73/3d51442bdb246db619d75039a50ea1cf8b5b4ee250c3e5cd5c3af5981cd4/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038", size = 400759, upload-time = "2025-05-21T12:45:32.516Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4c/3a32d5955d7e6cb117314597bc0f2224efc798428318b13073efe306512a/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9", size = 523128, upload-time = "2025-05-21T12:45:34.396Z" }, - { url = "https://files.pythonhosted.org/packages/be/95/1ffccd3b0bb901ae60b1dd4b1be2ab98bb4eb834cd9b15199888f5702f7b/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1", size = 411597, upload-time = "2025-05-21T12:45:36.164Z" }, - { url = "https://files.pythonhosted.org/packages/ef/6d/6e6cd310180689db8b0d2de7f7d1eabf3fb013f239e156ae0d5a1a85c27f/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762", size = 388053, upload-time = "2025-05-21T12:45:38.45Z" }, - { url = "https://files.pythonhosted.org/packages/4a/87/ec4186b1fe6365ced6fa470960e68fc7804bafbe7c0cf5a36237aa240efa/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e", size = 421821, upload-time = "2025-05-21T12:45:40.732Z" }, - { url = "https://files.pythonhosted.org/packages/7a/60/84f821f6bf4e0e710acc5039d91f8f594fae0d93fc368704920d8971680d/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692", size = 564534, upload-time = "2025-05-21T12:45:42.672Z" }, - { url = "https://files.pythonhosted.org/packages/41/3a/bc654eb15d3b38f9330fe0f545016ba154d89cdabc6177b0295910cd0ebe/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf", size = 592674, upload-time = "2025-05-21T12:45:44.533Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781, upload-time = "2025-05-21T12:45:46.281Z" }, +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, + { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, + { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, + { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, + { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, + { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, + { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, + { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, + { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, + { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, + { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, + { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, + { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, ] [[package]] @@ -5101,14 +5125,14 @@ wheels = [ [[package]] name = "scipy-stubs" -version = "1.15.3.0" +version = "1.16.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/35c43bd7d412add4adcd68475702571b2489b50c40b6564f808b2355e452/scipy_stubs-1.15.3.0.tar.gz", hash = "sha256:e8f76c9887461cf9424c1e2ad78ea5dac71dd4cbb383dc85f91adfe8f74d1e17", size = 275699, upload-time = "2025-05-08T16:58:35.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/19/a8461383f7328300e83c34f58bf38ccc05f57c2289c0e54e2bea757de83c/scipy_stubs-1.16.0.2.tar.gz", hash = "sha256:f83aacaf2e899d044de6483e6112bf7a1942d683304077bc9e78cf6f21353acd", size = 306747, upload-time = "2025-07-01T23:19:04.513Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/42/cd8dc81f8060de1f14960885ad5b2d2651f41de8b93d09f3f919d6567a5a/scipy_stubs-1.15.3.0-py3-none-any.whl", hash = "sha256:a251254cf4fd6e7fb87c55c1feee92d32ddbc1f542ecdf6a0159cdb81c2fb62d", size = 459062, upload-time = "2025-05-08T16:58:33.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/b73418e6d3d8209fef684841d9a0e5b439d3528fa341a23b632fe47918dd/scipy_stubs-1.16.0.2-py3-none-any.whl", hash = "sha256:dc364d24a3accd1663e7576480bdb720533f94de8a05590354ff6d4a83d765c7", size = 491346, upload-time = "2025-07-01T23:19:03.222Z" }, ] [[package]] @@ -5145,38 +5169,6 @@ flask = [ { name = "markupsafe" }, ] -[[package]] -name = "setproctitle" -version = "1.3.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412, upload-time = "2025-04-29T13:32:58.135Z" }, - { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963, upload-time = "2025-04-29T13:32:59.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718, upload-time = "2025-04-29T13:33:00.36Z" }, - { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027, upload-time = "2025-04-29T13:33:01.499Z" }, - { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223, upload-time = "2025-04-29T13:33:03.259Z" }, - { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204, upload-time = "2025-04-29T13:33:04.455Z" }, - { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181, upload-time = "2025-04-29T13:33:05.697Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101, upload-time = "2025-04-29T13:33:07.223Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438, upload-time = "2025-04-29T13:33:08.538Z" }, - { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625, upload-time = "2025-04-29T13:33:09.707Z" }, - { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488, upload-time = "2025-04-29T13:33:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201, upload-time = "2025-04-29T13:33:12.389Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, - { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, - { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, - { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, - { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, - { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, - { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, - { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, -] - [[package]] name = "setuptools" version = "80.9.0" @@ -5375,12 +5367,11 @@ wheels = [ [[package]] name = "tablestore" -version = "6.1.0" +version = "6.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "crc32c" }, - { name = "enum34" }, { name = "flatbuffers" }, { name = "future" }, { name = "numpy" }, @@ -5388,7 +5379,10 @@ dependencies = [ { name = "six" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/ed/5bdd906ec9d2dbae3909525dbb7602558c377e0cbcdddb6405d2d0d3f1af/tablestore-6.1.0.tar.gz", hash = "sha256:bfe6a3e0fe88a230729723c357f4a46b8869a06a4b936db20692ed587a721c1c", size = 135690, upload-time = "2024-12-20T07:38:37.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/58/48d65d181a69f7db19f7cdee01d252168fbfbad2d1bb25abed03e6df3b05/tablestore-6.2.0.tar.gz", hash = "sha256:0773e77c00542be1bfebbc3c7a85f72a881c63e4e7df7c5a9793a54144590e68", size = 85942, upload-time = "2025-04-15T12:11:20.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/da/30451712a769bcf417add8e81163d478a4d668b0e8d489a9d667260d55df/tablestore-6.2.0-py3-none-any.whl", hash = "sha256:6af496d841ab1ff3f78b46abbd87b95a08d89605c51664d2b30933b1d1c5583a", size = 106297, upload-time = "2025-04-15T12:11:17.476Z" }, +] [[package]] name = "tabulate" @@ -5481,27 +5475,27 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.21.1" +version = "0.21.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256, upload-time = "2025-03-13T10:51:18.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767, upload-time = "2025-03-13T10:51:09.459Z" }, - { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555, upload-time = "2025-03-13T10:51:07.692Z" }, - { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541, upload-time = "2025-03-13T10:50:56.679Z" }, - { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058, upload-time = "2025-03-13T10:50:59.525Z" }, - { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278, upload-time = "2025-03-13T10:51:04.678Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253, upload-time = "2025-03-13T10:51:01.261Z" }, - { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225, upload-time = "2025-03-13T10:51:03.243Z" }, - { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874, upload-time = "2025-03-13T10:51:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448, upload-time = "2025-03-13T10:51:10.927Z" }, - { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877, upload-time = "2025-03-13T10:51:12.688Z" }, - { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645, upload-time = "2025-03-13T10:51:14.723Z" }, - { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380, upload-time = "2025-03-13T10:51:16.526Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506, upload-time = "2025-03-13T10:51:20.643Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481, upload-time = "2025-03-13T10:51:19.243Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1791eb329c07122a75b01035b1a3aa22ad139f3ce0ece1b059b506d9d9de/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12", size = 3019202, upload-time = "2025-06-24T10:24:31.791Z" }, + { url = "https://files.pythonhosted.org/packages/05/15/fd2d8104faa9f86ac68748e6f7ece0b5eb7983c7efc3a2c197cb98c99030/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91", size = 2934539, upload-time = "2025-06-24T10:24:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2e/53e8fd053e1f3ffbe579ca5f9546f35ac67cf0039ed357ad7ec57f5f5af0/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb", size = 3248665, upload-time = "2025-06-24T10:24:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/79713359f4037aa8f4d1f06ffca35312ac83629da062670e8830917e2153/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab", size = 3451305, upload-time = "2025-06-24T10:24:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/38/5f/959f3a8756fc9396aeb704292777b84f02a5c6f25c3fc3ba7530db5feb2c/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae", size = 3214757, upload-time = "2025-06-24T10:24:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/c5/74/f41a432a0733f61f3d21b288de6dfa78f7acff309c6f0f323b2833e9189f/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020", size = 3121887, upload-time = "2025-06-24T10:24:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6a/bc220a11a17e5d07b0dfb3b5c628621d4dcc084bccd27cfaead659963016/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19", size = 9091965, upload-time = "2025-06-24T10:24:44.431Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/ac386d79c4ef20dc6f39c4706640c24823dca7ebb6f703bfe6b5f0292d88/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d", size = 9053372, upload-time = "2025-06-24T10:24:46.455Z" }, + { url = "https://files.pythonhosted.org/packages/63/7b/5440bf203b2a5358f074408f7f9c42884849cd9972879e10ee6b7a8c3b3d/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365", size = 9298632, upload-time = "2025-06-24T10:24:48.446Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d2/faa1acac3f96a7427866e94ed4289949b2524f0c1878512516567d80563c/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958", size = 9470074, upload-time = "2025-06-24T10:24:50.378Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/896e1ef0707212745ae9f37e84c7d50269411aef2e9ccd0de63623feecdf/tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962", size = 2330115, upload-time = "2025-06-24T10:24:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload-time = "2025-06-24T10:24:53.71Z" }, ] [[package]] @@ -5614,11 +5608,11 @@ wheels = [ [[package]] name = "types-awscrt" -version = "0.27.2" +version = "0.27.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/6c/583522cfb3c330e92e726af517a91c13247e555e021791a60f1b03c6ff16/types_awscrt-0.27.2.tar.gz", hash = "sha256:acd04f57119eb15626ab0ba9157fc24672421de56e7bd7b9f61681fedee44e91", size = 16304, upload-time = "2025-05-16T03:10:08.712Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/95/02564024f8668feab6733a2c491005b5281b048b3d0573510622cbcd9fd4/types_awscrt-0.27.4.tar.gz", hash = "sha256:c019ba91a097e8a31d6948f6176ede1312963f41cdcacf82482ac877cbbcf390", size = 16941, upload-time = "2025-06-29T22:58:04.756Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/82/1ee2e5c9d28deac086ab3a6ff07c8bc393ef013a083f546c623699881715/types_awscrt-0.27.2-py3-none-any.whl", hash = "sha256:49a045f25bbd5ad2865f314512afced933aed35ddbafc252e2268efa8a787e4e", size = 37761, upload-time = "2025-05-16T03:10:07.466Z" }, + { url = "https://files.pythonhosted.org/packages/d4/40/cb4d04df4ac3520858f5b397a4ab89f34be2601000002a26edd8ddc0cac5/types_awscrt-0.27.4-py3-none-any.whl", hash = "sha256:a8c4b9d9ae66d616755c322aba75ab9bd793c6fef448917e6de2e8b8cdf66fb4", size = 39626, upload-time = "2025-06-29T22:58:03.157Z" }, ] [[package]] @@ -6178,7 +6172,7 @@ pptx = [ [[package]] name = "unstructured-client" -version = "0.36.0" +version = "0.37.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, @@ -6189,9 +6183,9 @@ dependencies = [ { name = "pypdf" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/4d/d829dbef1138251de771cd52b277d93fb1c4e79d56be3e44e6d2ce76bd62/unstructured_client-0.36.0.tar.gz", hash = "sha256:ab293498100275c0e1d74c926c82dae2b3ba3fbb88945c0ba03b4b7a29197e4a", size = 86010, upload-time = "2025-05-29T00:11:11.429Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/6f/8dd20dab879f25074d6abfbb98f77bb8efeea0ae1bdf9a414b3e73c152b6/unstructured_client-0.37.4.tar.gz", hash = "sha256:5a4029563c2f79de098374fd8a99090719df325b4bdcfa3a87820908f2c83e6c", size = 90481, upload-time = "2025-07-01T16:40:09.877Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/4a/ae162e583bbdd0996f92ad18871a737d710260d8c0cfd78f1be6aa0ac150/unstructured_client-0.36.0-py3-none-any.whl", hash = "sha256:d0ecf3ac4d481437d858147904ff6e41205032cf8353af5cdd3ebaa190481d6a", size = 195765, upload-time = "2025-05-29T00:11:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/93/09/4399b0c32564b1a19fef943b5acea5a16fa0c6aa7a320065ce726b8245c1/unstructured_client-0.37.4-py3-none-any.whl", hash = "sha256:31975c0ea4408e369e6aad11c9e746d1f3f14013ac5c89f9f8dbada3a21dcec0", size = 211242, upload-time = "2025-07-01T16:40:08.642Z" }, ] [[package]] @@ -6217,11 +6211,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] @@ -6235,15 +6229,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.34.3" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [package.optional-dependencies] @@ -6315,7 +6309,7 @@ wheels = [ [[package]] name = "wandb" -version = "0.20.1" +version = "0.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -6323,26 +6317,24 @@ dependencies = [ { name = "packaging" }, { name = "platformdirs" }, { name = "protobuf" }, - { name = "psutil" }, { name = "pydantic" }, { name = "pyyaml" }, { name = "requests" }, { name = "sentry-sdk" }, - { name = "setproctitle" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/1f/92be0ca87fb49eb48c16dcf0845a3579a57c4734fec2b95862cf5a0494a0/wandb-0.20.1.tar.gz", hash = "sha256:dbd3fc60dfe7bf83c4de24b206b99b44949fef323f817a783883db72fc5f3bfe", size = 40320062, upload-time = "2025-06-05T00:00:24.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/09/c84264a219e20efd615e4d5d150cc7d359d57d51328d3fa94ee02d70ed9c/wandb-0.21.0.tar.gz", hash = "sha256:473e01ef200b59d780416062991effa7349a34e51425d4be5ff482af2dc39e02", size = 40085784, upload-time = "2025-07-02T00:24:15.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/18/afcc37d0b93dd6f6d0f0c5683b9cfff9416ae1539931f58932a2938c0070/wandb-0.20.1-py3-none-any.whl", hash = "sha256:e6395cabf074247042be1cf0dc6ab0b06aa4c9538c2e1fdc5b507a690ce0cf17", size = 6458872, upload-time = "2025-06-04T23:59:55.441Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b5/70f9e2a3d1380b729ae5853763d938edc50072df357f79bbd19b9aae8e3f/wandb-0.20.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:2475a48c693adf677d40da9e1c8ceeaf86d745ffc3b7e3535731279d02f9e845", size = 22517483, upload-time = "2025-06-04T23:59:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/7e/4eb9aeb2fd974d410a8f2eb11b0219536503913a050d46a03206151705c8/wandb-0.20.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99cce804c31ec1e0d1e691650a7d51773ed7329c41745d56384fa3655a0e9b2c", size = 22034511, upload-time = "2025-06-05T00:00:01.301Z" }, - { url = "https://files.pythonhosted.org/packages/34/38/1df22c2273e6f7ab0aae4fd032085d6d92ab112f5b261646e7dc5e675cfe/wandb-0.20.1-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ce3ee412677a1679e04b21e03a91e1e02eb90faf658d682bee86c33cf5f32e09", size = 22720771, upload-time = "2025-06-05T00:00:04.122Z" }, - { url = "https://files.pythonhosted.org/packages/38/96/78fc7a7ea7158d136c84f481423f8736c9346a2387287ec8a6d92019975c/wandb-0.20.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e58ca32c7147161158f09b0fb5f5896876f8569d0d10ae7b64d0510c868ce33", size = 21537453, upload-time = "2025-06-05T00:00:09.474Z" }, - { url = "https://files.pythonhosted.org/packages/88/c9/41b8bdb493e5eda32b502bc1cc49d539335a92cacaf0ef304d7dae0240aa/wandb-0.20.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:591506ecbdd396648cc323ba270f3ab4aed3158e1dbfa7636c09f9f7f0253e1c", size = 23161349, upload-time = "2025-06-05T00:00:11.903Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/79e783cc50a47d373dfbda862eb5396de8139167e8c6443a16ef0166106f/wandb-0.20.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:382508532db09893f81cc926b1d333caa4c8a7db057878899fadf929bbdb3b56", size = 21550624, upload-time = "2025-06-05T00:00:14.28Z" }, - { url = "https://files.pythonhosted.org/packages/26/32/23890a726302e7be28bda9fff47ce9b491af64e339aba4d32b3b8d1a7aaf/wandb-0.20.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:29ea495e49393db860f17437fe37e48018da90436ce10949b471780f09293bd7", size = 23237996, upload-time = "2025-06-05T00:00:16.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/94/296e520b086b2a4f10e99bcea3cd5856421b9c004824663501e3789a713b/wandb-0.20.1-py3-none-win32.whl", hash = "sha256:455ee0a652e59ab1e4b546fa1dc833dd3063aa7e64eb8abf95d22f0e9f08c574", size = 22518456, upload-time = "2025-06-05T00:00:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/52/5f/c44ad7b2a062ca5f4da99ae475cea274c38f6ec37bdaca1b1c653ee87274/wandb-0.20.1-py3-none-win_amd64.whl", hash = "sha256:6d2431652f096b7e394c29a99135a6441c02ed3198b963f0b351a5b5e56aeca0", size = 22518459, upload-time = "2025-06-05T00:00:21.374Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/65eac086e1bc337bb5f0eed65ba1fe4a6dbc62c97f094e8e9df1ef83ffed/wandb-0.21.0-py3-none-any.whl", hash = "sha256:316e8cd4329738f7562f7369e6eabeeb28ef9d473203f7ead0d03e5dba01c90d", size = 6504284, upload-time = "2025-07-02T00:23:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/80556ce9097f59e10807aa68f4a9b29d736a90dca60852a9e2af1641baf8/wandb-0.21.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:701d9cbdfcc8550a330c1b54a26f1585519180e0f19247867446593d34ace46b", size = 21717388, upload-time = "2025-07-02T00:23:49.348Z" }, + { url = "https://files.pythonhosted.org/packages/23/ae/660bc75aa37bd23409822ea5ed616177d94873172d34271693c80405c820/wandb-0.21.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:01689faa6b691df23ba2367e0a1ecf6e4d0be44474905840098eedd1fbcb8bdf", size = 21141465, upload-time = "2025-07-02T00:23:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/23/ab/9861929530be56557c74002868c85d0d8ac57050cc21863afe909ae3d46f/wandb-0.21.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:55d3f42ddb7971d1699752dff2b85bcb5906ad098d18ab62846c82e9ce5a238d", size = 21793511, upload-time = "2025-07-02T00:23:55.447Z" }, + { url = "https://files.pythonhosted.org/packages/de/52/e5cad2eff6fbed1ac06f4a5b718457fa2fd437f84f5c8f0d31995a2ef046/wandb-0.21.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:893508f0c7da48917448daa5cd622c27ce7ce15119adaa861185034c2bd7b14c", size = 20704643, upload-time = "2025-07-02T00:23:58.255Z" }, + { url = "https://files.pythonhosted.org/packages/83/8f/6bed9358cc33767c877b221d4f565e1ddf00caf4bbbe54d2e3bbc932c6a7/wandb-0.21.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e8245a8912247ddf7654f7b5330f583a6c56ab88fee65589158490d583c57d", size = 22243012, upload-time = "2025-07-02T00:24:01.423Z" }, + { url = "https://files.pythonhosted.org/packages/be/61/9048015412ea5ca916844af55add4fed7c21fe1ad70bb137951e70b550c5/wandb-0.21.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c4f951e0d02755e315679bfdcb5bc38c1b02e2e5abc5432b91a91bb0cf246", size = 20716440, upload-time = "2025-07-02T00:24:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/fcd2273d8ec3f79323e40a031aba5d32d6fa9065702010eb428b5ffbab62/wandb-0.21.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:873749966eeac0069e0e742e6210641b6227d454fb1dae2cf5c437c6ed42d3ca", size = 22320652, upload-time = "2025-07-02T00:24:07.175Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/b8308db6b9c3c96dcd03be17c019aee105e1d7dc1e74d70756cdfb9241c6/wandb-0.21.0-py3-none-win32.whl", hash = "sha256:9d3cccfba658fa011d6cab9045fa4f070a444885e8902ae863802549106a5dab", size = 21484296, upload-time = "2025-07-02T00:24:10.147Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/71cc033e8abd00e54465e68764709ed945e2da2d66d764f72f4660262b22/wandb-0.21.0-py3-none-win_amd64.whl", hash = "sha256:28a0b2dad09d7c7344ac62b0276be18a2492a5578e4d7c84937a3e1991edaac7", size = 21484301, upload-time = "2025-07-02T00:24:12.658Z" }, ] [[package]] @@ -6558,11 +6550,11 @@ wheels = [ [[package]] name = "xlsxwriter" -version = "3.2.3" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/d1/e026d33dd5d552e5bf3a873dee54dad66b550230df8290d79394f09b2315/xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5", size = 209135, upload-time = "2025-04-17T10:11:23.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/47/7704bac42ac6fe1710ae099b70e6a1e68ed173ef14792b647808c357da43/xlsxwriter-3.2.5.tar.gz", hash = "sha256:7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe", size = 213306, upload-time = "2025-06-17T08:59:14.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/b1/a252d499f2760b314fcf264d2b36fcc4343a1ecdb25492b210cb0db70a68/XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d", size = 169433, upload-time = "2025-04-17T10:11:21.329Z" }, + { url = "https://files.pythonhosted.org/packages/fa/34/a22e6664211f0c8879521328000bdcae9bf6dbafa94a923e531f6d5b3f73/xlsxwriter-3.2.5-py3-none-any.whl", hash = "sha256:4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd", size = 172347, upload-time = "2025-06-17T08:59:13.453Z" }, ] [[package]] @@ -6631,14 +6623,14 @@ wheels = [ [[package]] name = "zope-event" -version = "5.0" +version = "5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350, upload-time = "2023-06-23T06:28:35.709Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/c7/31e6f40282a2c548602c177826df281177caf79efaa101dd14314fb4ee73/zope_event-5.1.tar.gz", hash = "sha256:a153660e0c228124655748e990396b9d8295d6e4f546fa1b34f3319e1c666e7f", size = 18632, upload-time = "2025-06-26T07:14:22.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824, upload-time = "2023-06-23T06:28:32.652Z" }, + { url = "https://files.pythonhosted.org/packages/00/ed/d8c3f56c1edb0ee9b51461dd08580382e9589850f769b69f0dedccff5215/zope_event-5.1-py3-none-any.whl", hash = "sha256:53de8f0e9f61dc0598141ac591f49b042b6d74784dab49971b9cc91d0f73a7df", size = 6905, upload-time = "2025-06-26T07:14:21.779Z" }, ] [[package]] diff --git a/dev/mypy-check b/dev/mypy-check index b1c2c969a8..8a2342730c 100755 --- a/dev/mypy-check +++ b/dev/mypy-check @@ -7,4 +7,4 @@ cd "$SCRIPT_DIR/.." # run mypy checks uv run --directory api --dev --with pip \ - python -m mypy --install-types --non-interactive ./ + python -m mypy --install-types --non-interactive --exclude venv ./ diff --git a/docker/.env.example b/docker/.env.example index 275da8e2e4..a024566c8f 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -285,6 +285,7 @@ BROKER_USE_SSL=false # If you are using Redis Sentinel for high availability, configure the following settings. CELERY_USE_SENTINEL=false CELERY_SENTINEL_MASTER_NAME= +CELERY_SENTINEL_PASSWORD= CELERY_SENTINEL_SOCKET_TIMEOUT=0.1 # ------------------------------ diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index a6a4ed959a..d45f8f8bfa 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:1.5.0 + image: langgenius/dify-api:1.5.1 restart: always environment: # Use the shared environment variables. @@ -31,7 +31,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.5.0 + image: langgenius/dify-api:1.5.1 restart: always environment: # Use the shared environment variables. @@ -57,7 +57,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.5.0 + image: langgenius/dify-web:1.5.1 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -142,7 +142,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.2-local + image: langgenius/dify-plugin-daemon:0.1.3-local restart: always environment: # Use the shared environment variables. @@ -168,7 +168,7 @@ services: PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} - S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index 5308a1f978..0b1885755b 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -71,7 +71,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.2-local + image: langgenius/dify-plugin-daemon:0.1.3-local restart: always env_file: - ./middleware.env diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 0019835357..7f91fd8796 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -79,6 +79,7 @@ x-shared-env: &shared-api-worker-env BROKER_USE_SSL: ${BROKER_USE_SSL:-false} CELERY_USE_SENTINEL: ${CELERY_USE_SENTINEL:-false} CELERY_SENTINEL_MASTER_NAME: ${CELERY_SENTINEL_MASTER_NAME:-} + CELERY_SENTINEL_PASSWORD: ${CELERY_SENTINEL_PASSWORD:-} CELERY_SENTINEL_SOCKET_TIMEOUT: ${CELERY_SENTINEL_SOCKET_TIMEOUT:-0.1} WEB_API_CORS_ALLOW_ORIGINS: ${WEB_API_CORS_ALLOW_ORIGINS:-*} CONSOLE_CORS_ALLOW_ORIGINS: ${CONSOLE_CORS_ALLOW_ORIGINS:-*} @@ -516,7 +517,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:1.5.0 + image: langgenius/dify-api:1.5.1 restart: always environment: # Use the shared environment variables. @@ -545,7 +546,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.5.0 + image: langgenius/dify-api:1.5.1 restart: always environment: # Use the shared environment variables. @@ -571,7 +572,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.5.0 + image: langgenius/dify-web:1.5.1 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -656,7 +657,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.2-local + image: langgenius/dify-plugin-daemon:0.1.3-local restart: always environment: # Use the shared environment variables. @@ -682,7 +683,7 @@ services: PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} - S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 0efc5082a4..92ba068b2b 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import TracingIcon from './tracing-icon' import ProviderPanel from './provider-panel' -import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' +import type { ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import ProviderConfigModal from './provider-config-modal' import Indicator from '@/app/components/header/indicator' @@ -23,11 +23,13 @@ export type PopupProps = { onStatusChange: (enabled: boolean) => void chosenProvider: TracingProvider | null onChooseProvider: (provider: TracingProvider) => void + arizeConfig: ArizeConfig | null + phoenixConfig: PhoenixConfig | null langSmithConfig: LangSmithConfig | null langFuseConfig: LangFuseConfig | null opikConfig: OpikConfig | null weaveConfig: WeaveConfig | null - onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void + onConfigUpdated: (provider: TracingProvider, payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void onConfigRemoved: (provider: TracingProvider) => void } @@ -38,6 +40,8 @@ const ConfigPopup: FC = ({ onStatusChange, chosenProvider, onChooseProvider, + arizeConfig, + phoenixConfig, langSmithConfig, langFuseConfig, opikConfig, @@ -65,7 +69,7 @@ const ConfigPopup: FC = ({ } }, [onChooseProvider]) - const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => { + const handleConfigUpdated = useCallback((payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => { onConfigUpdated(currentProvider!, payload) hideConfigModal() }, [currentProvider, hideConfigModal, onConfigUpdated]) @@ -75,8 +79,8 @@ const ConfigPopup: FC = ({ hideConfigModal() }, [currentProvider, hideConfigModal, onConfigRemoved]) - const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig - const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig + const providerAllConfigured = arizeConfig && phoenixConfig && langSmithConfig && langFuseConfig && opikConfig && weaveConfig + const providerAllNotConfigured = !arizeConfig && !phoenixConfig && !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig const switchContent = ( = ({ disabled={providerAllNotConfigured} /> ) + const arizePanel = ( + + ) + + const phoenixPanel = ( + + ) + const langSmithPanel = ( = ({ if (weaveConfig) configuredPanels.push(weavePanel) + if (arizeConfig) + configuredPanels.push(arizePanel) + + if (phoenixConfig) + configuredPanels.push(phoenixPanel) + return configuredPanels } const moreProviderPanel = () => { const notConfiguredPanels: JSX.Element[] = [] + if (!arizeConfig) + notConfiguredPanels.push(arizePanel) + + if (!phoenixConfig) + notConfiguredPanels.push(phoenixPanel) + if (!langFuseConfig) notConfiguredPanels.push(langfusePanel) @@ -174,6 +216,10 @@ const ConfigPopup: FC = ({ } const configuredProviderConfig = () => { + if (currentProvider === TracingProvider.arize) + return arizeConfig + if (currentProvider === TracingProvider.phoenix) + return phoenixConfig if (currentProvider === TracingProvider.langSmith) return langSmithConfig if (currentProvider === TracingProvider.langfuse) @@ -220,22 +266,24 @@ const ConfigPopup: FC = ({ ? ( <>
{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}
-
+
{langfusePanel} {langSmithPanel} {opikPanel} {weavePanel} + {arizePanel} + {phoenixPanel}
) : ( <>
{t(`${I18N_PREFIX}.configProviderTitle.configured`)}
-
+
{configuredProviderPanel()}
{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}
-
+
{moreProviderPanel()}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts index 5d3c4076bd..28433991e4 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts @@ -1,6 +1,8 @@ import { TracingProvider } from './type' export const docURL = { + [TracingProvider.arize]: 'https://docs.arize.com/arize', + [TracingProvider.phoenix]: 'https://docs.arize.com/phoenix', [TracingProvider.langSmith]: 'https://docs.smith.langchain.com/', [TracingProvider.langfuse]: 'https://docs.langfuse.com', [TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions', diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 76e90ecf19..c8286c9a51 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -7,12 +7,12 @@ import { import { useTranslation } from 'react-i18next' import { usePathname } from 'next/navigation' import { useBoolean } from 'ahooks' -import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' +import type { ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import TracingIcon from './tracing-icon' import ConfigButton from './config-button' import cn from '@/utils/classnames' -import { LangfuseIcon, LangsmithIcon, OpikIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' +import { ArizeIcon, LangfuseIcon, LangsmithIcon, OpikIcon, PhoenixIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' import Indicator from '@/app/components/header/indicator' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' import type { TracingStatus } from '@/models/app' @@ -62,24 +62,31 @@ const Panel: FC = () => { } const inUseTracingProvider: TracingProvider | null = tracingStatus?.tracing_provider || null - const InUseProviderIcon - = inUseTracingProvider === TracingProvider.langSmith - ? LangsmithIcon - : inUseTracingProvider === TracingProvider.langfuse - ? LangfuseIcon - : inUseTracingProvider === TracingProvider.opik - ? OpikIcon - : inUseTracingProvider === TracingProvider.weave - ? WeaveIcon - : LangsmithIcon + const providerIconMap: Record> = { + [TracingProvider.arize]: ArizeIcon, + [TracingProvider.phoenix]: PhoenixIcon, + [TracingProvider.langSmith]: LangsmithIcon, + [TracingProvider.langfuse]: LangfuseIcon, + [TracingProvider.opik]: OpikIcon, + [TracingProvider.weave]: WeaveIcon, + } + const InUseProviderIcon = inUseTracingProvider ? providerIconMap[inUseTracingProvider] : undefined + const [arizeConfig, setArizeConfig] = useState(null) + const [phoenixConfig, setPhoenixConfig] = useState(null) const [langSmithConfig, setLangSmithConfig] = useState(null) const [langFuseConfig, setLangFuseConfig] = useState(null) const [opikConfig, setOpikConfig] = useState(null) const [weaveConfig, setWeaveConfig] = useState(null) - const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig) + const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig) const fetchTracingConfig = async () => { + const { tracing_config: arizeConfig, has_not_configured: arizeHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.arize }) + if (!arizeHasNotConfig) + setArizeConfig(arizeConfig as ArizeConfig) + const { tracing_config: phoenixConfig, has_not_configured: phoenixHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.phoenix }) + if (!phoenixHasNotConfig) + setPhoenixConfig(phoenixConfig as PhoenixConfig) const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) if (!langSmithHasNotConfig) setLangSmithConfig(langSmithConfig as LangSmithConfig) @@ -97,7 +104,11 @@ const Panel: FC = () => { const handleTracingConfigUpdated = async (provider: TracingProvider) => { // call api to hide secret key value const { tracing_config } = await doFetchTracingConfig({ appId, provider }) - if (provider === TracingProvider.langSmith) + if (provider === TracingProvider.arize) + setArizeConfig(tracing_config as ArizeConfig) + else if (provider === TracingProvider.phoenix) + setPhoenixConfig(tracing_config as PhoenixConfig) + else if (provider === TracingProvider.langSmith) setLangSmithConfig(tracing_config as LangSmithConfig) else if (provider === TracingProvider.langfuse) setLangFuseConfig(tracing_config as LangFuseConfig) @@ -108,7 +119,11 @@ const Panel: FC = () => { } const handleTracingConfigRemoved = (provider: TracingProvider) => { - if (provider === TracingProvider.langSmith) + if (provider === TracingProvider.arize) + setArizeConfig(null) + else if (provider === TracingProvider.phoenix) + setPhoenixConfig(null) + else if (provider === TracingProvider.langSmith) setLangSmithConfig(null) else if (provider === TracingProvider.langfuse) setLangFuseConfig(null) @@ -170,6 +185,8 @@ const Panel: FC = () => { onStatusChange={handleTracingEnabledChange} chosenProvider={inUseTracingProvider} onChooseProvider={handleChooseProvider} + arizeConfig={arizeConfig} + phoenixConfig={phoenixConfig} langSmithConfig={langSmithConfig} langFuseConfig={langFuseConfig} opikConfig={opikConfig} @@ -205,6 +222,8 @@ const Panel: FC = () => { onStatusChange={handleTracingEnabledChange} chosenProvider={inUseTracingProvider} onChooseProvider={handleChooseProvider} + arizeConfig={arizeConfig} + phoenixConfig={phoenixConfig} langSmithConfig={langSmithConfig} langFuseConfig={langFuseConfig} opikConfig={opikConfig} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index b6c97add48..67cac95964 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import Field from './field' -import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' +import type { ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import { docURL } from './config' import { @@ -22,15 +22,28 @@ import Divider from '@/app/components/base/divider' type Props = { appId: string type: TracingProvider - payload?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null + payload?: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null onRemoved: () => void onCancel: () => void - onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void + onSaved: (payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void onChosen: (provider: TracingProvider) => void } const I18N_PREFIX = 'app.tracing.configProvider' +const arizeConfigTemplate = { + api_key: '', + space_id: '', + project: '', + endpoint: '', +} + +const phoenixConfigTemplate = { + api_key: '', + project: '', + endpoint: '', +} + const langSmithConfigTemplate = { api_key: '', project: '', @@ -71,11 +84,17 @@ const ProviderConfigModal: FC = ({ const isEdit = !!payload const isAdd = !isEdit const [isSaving, setIsSaving] = useState(false) - const [config, setConfig] = useState((() => { + const [config, setConfig] = useState((() => { if (isEdit) return payload - if (type === TracingProvider.langSmith) + if (type === TracingProvider.arize) + return arizeConfigTemplate + + else if (type === TracingProvider.phoenix) + return phoenixConfigTemplate + + else if (type === TracingProvider.langSmith) return langSmithConfigTemplate else if (type === TracingProvider.langfuse) @@ -115,6 +134,24 @@ const ProviderConfigModal: FC = ({ const checkValid = useCallback(() => { let errorMessage = '' + if (type === TracingProvider.arize) { + const postData = config as ArizeConfig + if (!postData.api_key) + errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' }) + if (!postData.space_id) + errorMessage = t('common.errorMsg.fieldRequired', { field: 'Space ID' }) + if (!errorMessage && !postData.project) + errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) }) + } + + if (type === TracingProvider.phoenix) { + const postData = config as PhoenixConfig + if (!postData.api_key) + errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' }) + if (!errorMessage && !postData.project) + errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) }) + } + if (type === TracingProvider.langSmith) { const postData = config as LangSmithConfig if (!postData.api_key) @@ -195,6 +232,68 @@ const ProviderConfigModal: FC = ({
+ {type === TracingProvider.arize && ( + <> + + + + + + )} + {type === TracingProvider.phoenix && ( + <> + + + + + )} {type === TracingProvider.weave && ( <> { return ({ + [TracingProvider.arize]: ArizeIconBig, + [TracingProvider.phoenix]: PhoenixIconBig, [TracingProvider.langSmith]: LangsmithIconBig, [TracingProvider.langfuse]: LangfuseIconBig, [TracingProvider.opik]: OpikIconBig, diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts index ed468caf65..7e934b74c6 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts @@ -1,10 +1,25 @@ export enum TracingProvider { + arize = 'arize', + phoenix = 'phoenix', langSmith = 'langsmith', langfuse = 'langfuse', opik = 'opik', weave = 'weave', } +export type ArizeConfig = { + api_key: string + space_id: string + project: string + endpoint: string +} + +export type PhoenixConfig = { + api_key: string + project: string + endpoint: string +} + export type LangSmithConfig = { api_key: string project: string diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index 31b9ed87c2..b7c9a2eee4 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -36,6 +36,7 @@ import AccessControl from '@/app/components/app/app-access-control' import { AccessMode } from '@/models/access-control' import { useGlobalPublicStore } from '@/context/global-public-context' import { formatTime } from '@/utils/time' +import { useGetUserCanAccessApp } from '@/service/access-control' export type AppCardProps = { app: App @@ -190,6 +191,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { }, [onRefresh, mutateApps, setShowAccessControl]) const Operations = (props: HtmlContentProps) => { + const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp } = useGetUserCanAccessApp({ appId: app?.id, enabled: (!!props?.open && systemFeatures.webapp_auth.enabled) }) const onMouseLeave = async () => { props.onClose?.() } @@ -267,10 +269,14 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { )} - - + { + (isGettingUserCanAccessApp || !userCanAccessApp?.result) ? null : <> + + + + } { systemFeatures.webapp_auth.enabled && isCurrentWorkspaceEditor && <> diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index 91293768b7..ebb2e6a806 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -1124,6 +1124,129 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
+ + + + Get a document's detail. + ### Path + - `dataset_id` (string) Dataset ID + - `document_id` (string) Document ID + + ### Query + - `metadata` (string) Metadata filter, can be `all`, `only`, or `without`. Default is `all`. + + ### Response + Returns the document's detail. + + + ### Request Example + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "f46ae30c-5c11-471b-96d0-464f5f32a7b2", + "position": 1, + "data_source_type": "upload_file", + "data_source_info": { + "upload_file": { + ... + } + }, + "dataset_process_rule_id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_process_rule": { + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "document_process_rule": { + "id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_id": "48a0db76-d1a9-46c1-ae35-2baaa919a8a9", + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "name": "xxxx", + "created_from": "web", + "created_by": "17f71940-a7b5-4c77-b60f-2bd645c1ffa0", + "created_at": 1750464191, + "tokens": null, + "indexing_status": "waiting", + "completed_at": null, + "updated_at": 1750464191, + "indexing_latency": null, + "error": null, + "enabled": true, + "disabled_at": null, + "disabled_by": null, + "archived": false, + "segment_count": 0, + "average_segment_length": 0, + "hit_count": null, + "display_status": "queuing", + "doc_form": "hierarchical_model", + "doc_language": "Chinese Simplified" + } + ``` + + + +___ +
+ + + + + ドキュメントの詳細を取得. + ### Path + - `dataset_id` (string) ナレッジベースID + - `document_id` (string) ドキュメントID + + ### Query + - `metadata` (string) metadataのフィルター条件 `all`、`only`、または`without`。デフォルトは `all`。 + + ### Response + ナレッジベースドキュメントの詳細を返す. + + + ### Request Example + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "f46ae30c-5c11-471b-96d0-464f5f32a7b2", + "position": 1, + "data_source_type": "upload_file", + "data_source_info": { + "upload_file": { + ... + } + }, + "dataset_process_rule_id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_process_rule": { + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "document_process_rule": { + "id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_id": "48a0db76-d1a9-46c1-ae35-2baaa919a8a9", + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "name": "xxxx", + "created_from": "web", + "created_by": "17f71940-a7b5-4c77-b60f-2bd645c1ffa0", + "created_at": 1750464191, + "tokens": null, + "indexing_status": "waiting", + "completed_at": null, + "updated_at": 1750464191, + "indexing_latency": null, + "error": null, + "enabled": true, + "disabled_at": null, + "disabled_by": null, + "archived": false, + "segment_count": 0, + "average_segment_length": 0, + "hit_count": null, + "display_status": "queuing", + "doc_form": "hierarchical_model", + "doc_language": "Chinese Simplified" + } + ``` + + + +___ +
+ + + + + + 获取文档详情. + ### Path + - `dataset_id` (string) 知识库 ID + - `document_id` (string) 文档 ID + + ### Query + - `metadata` (string) metadata 过滤条件 `all`, `only`, 或者 `without`. 默认是 `all`. + + ### Response + 返回知识库文档的详情. + + + ### Request Example + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "f46ae30c-5c11-471b-96d0-464f5f32a7b2", + "position": 1, + "data_source_type": "upload_file", + "data_source_info": { + "upload_file": { + ... + } + }, + "dataset_process_rule_id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_process_rule": { + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "document_process_rule": { + "id": "24b99906-845e-499f-9e3c-d5565dd6962c", + "dataset_id": "48a0db76-d1a9-46c1-ae35-2baaa919a8a9", + "mode": "hierarchical", + "rules": { + "pre_processing_rules": [ + { + "id": "remove_extra_spaces", + "enabled": true + }, + { + "id": "remove_urls_emails", + "enabled": false + } + ], + "segmentation": { + "separator": "**********page_ending**********", + "max_tokens": 1024, + "chunk_overlap": 0 + }, + "parent_mode": "paragraph", + "subchunk_segmentation": { + "separator": "\n", + "max_tokens": 512, + "chunk_overlap": 0 + } + } + }, + "name": "xxxx", + "created_from": "web", + "created_by": "17f71940-a7b5-4c77-b60f-2bd645c1ffa0", + "created_at": 1750464191, + "tokens": null, + "indexing_status": "waiting", + "completed_at": null, + "updated_at": 1750464191, + "indexing_latency": null, + "error": null, + "enabled": true, + "disabled_at": null, + "disabled_by": null, + "archived": false, + "segment_count": 0, + "average_segment_length": 0, + "hit_count": null, + "display_status": "queuing", + "doc_form": "hierarchical_model", + "doc_language": "Chinese Simplified" + } + ``` + + + +___ +
+ + { - const appCode = redirectUrl?.split('/').pop() + if (!redirectUrl) + return null + const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`) + const appCode = url.pathname.split('/').pop() if (!appCode) return null @@ -62,7 +65,7 @@ export default function CheckCode() { localStorage.setItem('webapp_access_token', ret.data.access_token) const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: ret.data.access_token }) await setAccessToken(appCode, tokenResp.access_token) - router.replace(redirectUrl) + router.replace(decodeURIComponent(redirectUrl)) } } catch (error) { console.error(error) } diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx index e9b15ae331..612a9677a6 100644 --- a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -23,7 +23,10 @@ const ExternalMemberSSOAuth = () => { } const getAppCodeFromRedirectUrl = useCallback(() => { - const appCode = redirectUrl?.split('/').pop() + if (!redirectUrl) + return null + const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`) + const appCode = url.pathname.split('/').pop() if (!appCode) return null diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx index d9e56af1b8..2201b28a2f 100644 --- a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx @@ -1,3 +1,4 @@ +'use client' import Link from 'next/link' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -33,7 +34,10 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut const redirectUrl = searchParams.get('redirect_url') const getAppCodeFromRedirectUrl = useCallback(() => { - const appCode = redirectUrl?.split('/').pop() + if (!redirectUrl) + return null + const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`) + const appCode = url.pathname.split('/').pop() if (!appCode) return null @@ -87,7 +91,7 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut localStorage.setItem('webapp_access_token', res.data.access_token) const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: res.data.access_token }) await setAccessToken(appCode, tokenResp.access_token) - router.replace(redirectUrl) + router.replace(decodeURIComponent(redirectUrl)) } else { Toast.notify({ diff --git a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx index 5d649322ba..bcba572644 100644 --- a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx @@ -23,7 +23,10 @@ const SSOAuth: FC = ({ const redirectUrl = searchParams.get('redirect_url') const getAppCodeFromRedirectUrl = useCallback(() => { - const appCode = redirectUrl?.split('/').pop() + if (!redirectUrl) + return null + const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`) + const appCode = url.pathname.split('/').pop() if (!appCode) return null diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index 07b7c88430..967516c416 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -46,7 +46,10 @@ const WebSSOForm: FC = () => { } const getAppCodeFromRedirectUrl = useCallback(() => { - const appCode = redirectUrl?.split('/').pop() + if (!redirectUrl) + return null + const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`) + const appCode = url.pathname.split('/').pop() if (!appCode) return null @@ -63,20 +66,20 @@ const WebSSOForm: FC = () => { localStorage.setItem('webapp_access_token', tokenFromUrl) const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: tokenFromUrl }) await setAccessToken(appCode, tokenResp.access_token) - router.replace(redirectUrl) + router.replace(decodeURIComponent(redirectUrl)) return } if (appCode && redirectUrl && localStorage.getItem('webapp_access_token')) { const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: localStorage.getItem('webapp_access_token') }) await setAccessToken(appCode, tokenResp.access_token) - router.replace(redirectUrl) + router.replace(decodeURIComponent(redirectUrl)) } })() }, [getAppCodeFromRedirectUrl, redirectUrl, router, tokenFromUrl, message]) useEffect(() => { if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC && redirectUrl) - router.replace(redirectUrl) + router.replace(decodeURIComponent(redirectUrl)) }, [webAppAccessMode, router, redirectUrl]) if (tokenFromUrl) { diff --git a/web/app/components/app/configuration/config-var/select-var-type.tsx b/web/app/components/app/configuration/config-var/select-var-type.tsx index f82e931882..485f9932f3 100644 --- a/web/app/components/app/configuration/config-var/select-var-type.tsx +++ b/web/app/components/app/configuration/config-var/select-var-type.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from '@/utils/classnames' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' import { PortalToFollowElem, @@ -28,11 +27,11 @@ type ItemProps = { const SelectItem: FC = ({ text, type, value, Icon, onClick }) => { return (
onClick(value)} > - {Icon ? : } -
{text}
+ {Icon ? : } +
{text}
) } @@ -57,17 +56,17 @@ const SelectVarType: FC = ({ }} > setOpen(v => !v)}> - + -
+
-
+
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 2b97a64f5b..947467bf83 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -80,6 +80,8 @@ import { import PluginDependency from '@/app/components/workflow/plugin-dependency' import { supportFunctionCall } from '@/utils/tool-call' import { MittProvider } from '@/context/mitt-context' +import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' +import Toast from '@/app/components/base/toast' type PublishConfig = { modelConfig: ModelConfig @@ -453,7 +455,21 @@ const Configuration: FC = () => { ...visionConfig, enabled: supportVision, }, true) - setCompletionParams({}) + + try { + const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams( + provider, + modelId, + completionParams, + ) + if (Object.keys(removedDetails).length) + Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` }) + setCompletionParams(filtered) + } + catch (e) { + Toast.notify({ type: 'error', message: t('common.error') }) + setCompletionParams({}) + } } const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index dd72c6c810..47f8c09e39 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -191,6 +191,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { const { userProfile: { timezone } } = useAppContext() const { formatTime } = useTimestamp() const { onClose, appDetail } = useContext(DrawerContext) + const { notify } = useContext(ToastContext) const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({ currentLogItem: state.currentLogItem, setCurrentLogItem: state.setCurrentLogItem, @@ -312,18 +313,34 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { return item })) }, [allChatItems]) - const handleAnnotationRemoved = useCallback((index: number) => { - setAllChatItems(allChatItems.map((item, i) => { - if (i === index) { - return { - ...item, - content: item.content, - annotation: undefined, - } + const handleAnnotationRemoved = useCallback(async (index: number): Promise => { + const annotation = allChatItems[index]?.annotation + + try { + if (annotation?.id) { + const { delAnnotation } = await import('@/service/annotation') + await delAnnotation(appDetail?.id || '', annotation.id) } - return item - })) - }, [allChatItems]) + + setAllChatItems(allChatItems.map((item, i) => { + if (i === index) { + return { + ...item, + content: item.content, + annotation: undefined, + } + } + return item + })) + + notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + return true + } + catch { + notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) + return false + } + }, [allChatItems, appDetail?.id, t]) const fetchInitiated = useRef(false) diff --git a/web/app/components/base/button/sync-button.tsx b/web/app/components/base/button/sync-button.tsx new file mode 100644 index 0000000000..013c86889a --- /dev/null +++ b/web/app/components/base/button/sync-button.tsx @@ -0,0 +1,27 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { RiRefreshLine } from '@remixicon/react' +import cn from '@/utils/classnames' +import TooltipPlus from '@/app/components/base/tooltip' + +type Props = { + className?: string, + popupContent?: string, + onClick: () => void +} + +const SyncButton: FC = ({ + className, + popupContent = '', + onClick, +}) => { + return ( + +
+ +
+
+ ) +} +export default React.memo(SyncButton) diff --git a/web/app/components/base/icons/assets/public/llm/openai-teal.svg b/web/app/components/base/icons/assets/public/llm/openai-teal.svg new file mode 100644 index 0000000000..359cb532b6 --- /dev/null +++ b/web/app/components/base/icons/assets/public/llm/openai-teal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/components/base/icons/assets/public/llm/openai-yellow.svg b/web/app/components/base/icons/assets/public/llm/openai-yellow.svg new file mode 100644 index 0000000000..015eb74adc --- /dev/null +++ b/web/app/components/base/icons/assets/public/llm/openai-yellow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/components/base/icons/assets/public/tracing/arize-icon-big.svg b/web/app/components/base/icons/assets/public/tracing/arize-icon-big.svg new file mode 100644 index 0000000000..80667847ab --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/arize-icon-big.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/app/components/base/icons/assets/public/tracing/arize-icon.svg b/web/app/components/base/icons/assets/public/tracing/arize-icon.svg new file mode 100644 index 0000000000..f43e28ff83 --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/arize-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/app/components/base/icons/assets/public/tracing/phoenix-icon-big.svg b/web/app/components/base/icons/assets/public/tracing/phoenix-icon-big.svg new file mode 100644 index 0000000000..d22e928449 --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/phoenix-icon-big.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/app/components/base/icons/assets/public/tracing/phoenix-icon.svg b/web/app/components/base/icons/assets/public/tracing/phoenix-icon.svg new file mode 100644 index 0000000000..b30edd6c97 --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/phoenix-icon.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/tracing/ArizeIcon.json b/web/app/components/base/icons/src/public/tracing/ArizeIcon.json new file mode 100644 index 0000000000..fc438819ee --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/ArizeIcon.json @@ -0,0 +1,122 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "id": "Layer_2", + "data-name": "Layer 2", + "xmlns": "http://www.w3.org/2000/svg", + "viewBox": "0 0 74 16", + "width": "74", + "height": "16" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Layer_1-2", + "data-name": "Layer 1" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "style": "fill: none;", + "y": "0", + "width": "74", + "height": "16" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Arize_-_standard", + "data-name": "Arize - standard" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M67.29,9.7h-7.52c0,.08,0,.15.01.21.35,1.03,1.03,1.73,2.13,1.92,1.13.19,2.14,0,2.86-1.01.06-.09.19-.18.29-.19.66-.02,1.33,0,1.99,0-.1,1.79-2.59,3.32-5.07,3.14-2.66-.2-4.61-2.53-4.39-5.25.25-3.08,2.44-4.88,5.58-4.56,2.7.27,4.45,2.69,4.12,5.76ZM59.81,7.77h5.3c-.28-1.25-1.36-2.01-2.78-1.98-1.25.03-2.32.87-2.52,1.98Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #ff008c;", + "d": "M13.87,1.69c.4-.02.74.17.99.54,2.03,2.92,4.06,5.85,6.08,8.77.42.61.28,1.33-.29,1.73-.56.39-1.31.24-1.74-.38-1.4-2.01-2.79-4.02-4.19-6.04-.03-.04-.05-.08-.08-.11-.55-.78-1.1-.78-1.64,0-1.41,2.03-2.82,4.06-4.23,6.09-.23.34-.52.59-.93.63-.51.06-.92-.13-1.19-.57-.28-.46-.25-.92.05-1.35.68-.98,1.36-1.96,2.04-2.93,1.34-1.93,2.68-3.85,4.02-5.78.26-.37.59-.6,1.11-.59Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M35.23,13.62h-2.01v-.77c-.07.01-.1,0-.13.02-1.64,1.16-3.39,1.25-5.12.34-1.74-.92-2.47-2.51-2.45-4.45.02-2.17,1.22-3.97,3.1-4.57,1.57-.5,3.09-.45,4.46.62.02.02.06.02.14.05v-.79h2.02v9.54ZM27.57,8.88c0,.11.01.31.03.5.14,1.39,1.18,2.39,2.61,2.51,1.4.12,2.53-.63,2.92-1.97.09-.32.14-.65.15-.98.09-2.15-1.5-3.51-3.56-3.07-1.28.27-2.13,1.43-2.15,3.02Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M53.61,5.93h-4.96v-1.85h7.76c0,.54.01,1.07,0,1.61,0,.12-.12.24-.21.34-1.52,1.79-3.05,3.57-4.57,5.36-.07.09-.14.18-.26.32h5.02v1.91h-7.83c0-.54-.01-1.08,0-1.61,0-.11.11-.22.19-.31,1.55-1.83,3.1-3.65,4.64-5.47.06-.07.11-.14.21-.28Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M39.6,13.69h-2.02V4.15h2.02v1.03c.54-.32,1.04-.68,1.58-.94.57-.28,1.19-.27,1.85-.23v1.96c-.1,0-.2.02-.3.02-1.58.02-2.68.9-3.01,2.46-.08.38-.11.77-.11,1.16-.01,1.24,0,2.47,0,3.71v.38Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M44.74,4.06h1.99v9.56h-1.99V4.06Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #ff008c;", + "d": "M13.82,13.02c.84,0,1.49.64,1.5,1.46,0,.83-.68,1.53-1.5,1.52-.82,0-1.47-.67-1.48-1.5,0-.83.64-1.48,1.47-1.48Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M47.13,1.41c0,.8-.61,1.43-1.39,1.43-.8,0-1.44-.63-1.44-1.43,0-.78.63-1.41,1.42-1.41.79,0,1.41.62,1.41,1.41Z" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + "name": "ArizeIcon" +} diff --git a/web/app/components/base/icons/src/public/tracing/ArizeIcon.tsx b/web/app/components/base/icons/src/public/tracing/ArizeIcon.tsx new file mode 100644 index 0000000000..dac1ec280e --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/ArizeIcon.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './ArizeIcon.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'ArizeIcon' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/ArizeIconBig.json b/web/app/components/base/icons/src/public/tracing/ArizeIconBig.json new file mode 100644 index 0000000000..57be25e5b3 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/ArizeIconBig.json @@ -0,0 +1,122 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "id": "Layer_2", + "data-name": "Layer 2", + "xmlns": "http://www.w3.org/2000/svg", + "viewBox": "0 0 111 24", + "width": "111", + "height": "24" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Layer_1-2", + "data-name": "Layer 1" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "style": "fill: none;", + "y": "0", + "width": "111", + "height": "24" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Arize_-_standard", + "data-name": "Arize - standard" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M100.94,14.55h-11.29c0,.13-.01.23.02.31.53,1.55,1.54,2.59,3.19,2.88,1.7.29,3.22,0,4.3-1.52.09-.13.28-.27.43-.28.99-.02,1.99-.01,2.99-.01-.16,2.69-3.89,4.98-7.6,4.7-3.99-.3-6.91-3.79-6.58-7.88.37-4.62,3.67-7.31,8.37-6.85,4.05.4,6.68,4.04,6.19,8.64ZM89.71,11.66h7.96c-.43-1.88-2.04-3.02-4.17-2.97-1.87.04-3.48,1.3-3.78,2.97Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #ff008c;", + "d": "M20.81,2.53c.59-.03,1.11.26,1.49.8,3.05,4.38,6.09,8.77,9.13,13.16.63.91.43,1.99-.43,2.59-.84.59-1.97.35-2.62-.57-2.1-3.01-4.19-6.04-6.28-9.05-.04-.06-.08-.11-.12-.17-.83-1.17-1.65-1.18-2.47,0-2.11,3.05-4.23,6.09-6.34,9.14-.35.5-.77.88-1.4.95-.77.08-1.39-.19-1.79-.86-.41-.69-.38-1.38.07-2.03,1.01-1.47,2.04-2.93,3.06-4.4,2.01-2.89,4.03-5.77,6.03-8.67.39-.56.88-.9,1.67-.89Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M52.84,20.43h-3.02v-1.16c-.1.02-.16.01-.19.03-2.46,1.73-5.09,1.88-7.68.51-2.61-1.38-3.71-3.77-3.68-6.67.03-3.26,1.82-5.96,4.64-6.86,2.35-.75,4.64-.67,6.69.93.04.03.09.03.2.07v-1.18h3.03v14.31ZM41.36,13.32c.01.17.02.46.05.75.22,2.09,1.76,3.58,3.91,3.76,2.1.18,3.8-.95,4.38-2.96.14-.47.2-.98.22-1.47.13-3.22-2.25-5.27-5.33-4.61-1.92.41-3.19,2.14-3.23,4.53Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M80.41,8.9h-7.44v-2.77h11.64c0,.81.02,1.61-.01,2.41,0,.17-.19.35-.32.51-2.28,2.68-4.57,5.36-6.85,8.04-.11.13-.21.26-.38.47h7.53v2.86h-11.74c0-.82-.02-1.62.01-2.42,0-.16.17-.33.28-.47,2.32-2.74,4.64-5.47,6.96-8.21.09-.1.16-.21.32-.42Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M59.39,20.54h-3.03V6.22h3.03v1.54c.8-.48,1.55-1.01,2.37-1.41.85-.41,1.79-.41,2.77-.35v2.94c-.16.01-.3.03-.45.03-2.37.03-4.01,1.36-4.51,3.69-.12.57-.16,1.16-.16,1.74-.02,1.85,0,3.71,0,5.56v.57Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M67.1,6.09h2.99v14.33h-2.99V6.09Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #ff008c;", + "d": "M20.73,19.53c1.25,0,2.24.96,2.25,2.19.01,1.24-1.02,2.29-2.24,2.28-1.23-.01-2.21-1.01-2.22-2.24,0-1.25.96-2.22,2.21-2.22Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M70.7,2.11c0,1.19-.92,2.14-2.09,2.15-1.19.01-2.16-.95-2.16-2.14C66.46.95,67.4,0,68.58,0c1.18,0,2.12.93,2.12,2.11Z" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + "name": "ArizeIconBig" +} diff --git a/web/app/components/base/icons/src/public/tracing/ArizeIconBig.tsx b/web/app/components/base/icons/src/public/tracing/ArizeIconBig.tsx new file mode 100644 index 0000000000..f817b481e3 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/ArizeIconBig.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './ArizeIconBig.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'ArizeIconBig' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/PhoenixIcon.json b/web/app/components/base/icons/src/public/tracing/PhoenixIcon.json new file mode 100644 index 0000000000..c02847bc03 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/PhoenixIcon.json @@ -0,0 +1,853 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "id": "Layer_2", + "data-name": "Layer 2", + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "viewBox": "0 0 74 16", + "width": "74", + "height": "16" + }, + "children": [ + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient", + "x1": "6.05", + "y1": ".98", + "x2": "13.18", + "y2": "13.34", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#11bab5" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".5", + "stop-color": "#00adee" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#0094c5" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-2", + "x1": "12.99", + "y1": "10.87", + "x2": "6.97", + "y2": ".45", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#fcfdff", + "stop-opacity": "0" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".12", + "stop-color": "#fcfdff", + "stop-opacity": ".17" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".3", + "stop-color": "#fcfdff", + "stop-opacity": ".42" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".47", + "stop-color": "#fcfdff", + "stop-opacity": ".63" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".64", + "stop-color": "#fcfdff", + "stop-opacity": ".79" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".78", + "stop-color": "#fcfdff", + "stop-opacity": ".9" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".91", + "stop-color": "#fcfdff", + "stop-opacity": ".97" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#fcfdff" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-3", + "x1": "11.21", + "y1": "10.8", + "x2": "6.88", + "y2": "3.29", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-4", + "x1": "10.62", + "y1": "11.35", + "x2": "8.2", + "y2": "7.16", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-5", + "x1": "10.72", + "y1": "12.2", + "x2": "9.21", + "y2": "9.59", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-6", + "x1": "8.17", + "y1": "11.96", + "x2": "8.17", + "y2": "14.71", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".03", + "stop-color": "#231f20", + "stop-opacity": ".9" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".22", + "stop-color": "#231f20", + "stop-opacity": ".4" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".42", + "stop-color": "#231f20", + "stop-opacity": ".1" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".65", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "Fade_to_Black_2", + "data-name": "Fade to Black 2", + "x1": "11.26", + "y1": "11.7", + "x2": "11.26", + "y2": "13.1", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-7", + "x1": "11.94", + "y1": "8.07", + "x2": "11.94", + "y2": "6.92", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".03", + "stop-color": "#231f20", + "stop-opacity": ".95" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".51", + "stop-color": "#231f20", + "stop-opacity": ".26" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".81", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-8", + "x1": "14.45", + "y1": "8.92", + "x2": "14.45", + "y2": "6.14", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".18", + "stop-color": "#231f20", + "stop-opacity": ".48" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".38", + "stop-color": "#231f20", + "stop-opacity": ".12" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".58", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "Fade_to_Black_2-2", + "data-name": "Fade to Black 2", + "x1": "17.03", + "y1": "11.1", + "x2": "16.07", + "y2": "9.44", + "xlink:href": "#Fade_to_Black_2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-9", + "x1": "17.8", + "y1": "10.64", + "x2": "16.36", + "y2": "8.16", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-10", + "x1": "14.07", + "y1": "10.22", + "x2": "16.16", + "y2": "9.02", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".31", + "stop-color": "#231f20", + "stop-opacity": ".5" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".67", + "stop-color": "#231f20", + "stop-opacity": ".13" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + } + ] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Layer_1-2", + "data-name": "Layer 1" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "style": "fill: none;", + "y": ".02", + "width": "74", + "height": "16" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Phoenix_horiz_-_gradient", + "data-name": "Phoenix horiz - gradient" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient);", + "d": "M17.91,10c-.04-.19-.14-.36-.28-.51-.06-.06-.12-.11-.19-.16-.03-.02-.07-.05-.1-.07-.04-.03-.09-.05-.14-.08-.19-.09-.34-.19-.48-.29-.06-.04-.11-.09-.16-.13-.12-.11-.21-.2-.27-.3-.02-.03-.04-.07-.05-.1,0-.01-.02-.03-.03-.04-.02-.02-.05-.02-.08-.02-.04,0-.08.04-.08.09,0,.03,0,.07,0,.11,0,.02,0,.04-.03.05,0,0,0,0-.02,0-.03,0-.06.02-.07.04-.02.02-.02.05-.02.08,0,.03.01.04.02.06h0s0,.03.01.04c.01.03.03.05.05.08.03.05.07.09.11.15.01.02.02.04,0,.06-.01.02-.03.03-.05.03-.25-.03-.46-.09-.67-.16-.02,0-.05-.02-.07-.03-.18-.08.15-.41.3-.64.66-1.02.8-2.19.81-3.38,0-1.15-.12-2.27-.35-3.39h0c-.15-.94-.27-1.29-.35-1.42-.02-.03-.04-.06-.06-.07-.02-.01-.03,0-.03,0-.04,0-.09.04-.15.1-.25.26-.23.57-.17.89.31,1.47.55,2.95.44,4.47-.07,1.04-.27,2.05-1,2.86-.04.05-.09.1-.14.14-.05.05-.14.16-.2.11-.08-.06,0-.18.02-.24.43-1,.55-2.05.48-3.12,0-.02,0-.05,0-.07h0s0-.04,0-.06c-.01-.14-.02-.28-.04-.43-.18-1.71-.4-1.82-.4-1.82h0s-.03-.02-.06-.03c-.12-.02-.18.08-.23.15-.23.35-.13.72-.05,1.08.26,1.28.27,2.55-.18,3.79-.04.11-.09.22-.14.33-.05.12-.09.26-.28.2-.18-.06-.13-.19-.1-.31.06-.31.11-.62.14-.93h0s0-.06,0-.09c0-.05,0-.11.01-.16,0-.05,0-.09,0-.13,0-.02,0-.04,0-.07.04-.99-.13-1.44-.19-1.57,0-.02-.01-.03-.02-.04h0s0,0,0,0c-.07-.1-.17-.09-.27,0-.17.17-.27.37-.22.62.13.74.05,1.46-.08,2.19-.02.1-.06.27-.27.23-.14-.03-.17-.09-.18-.21,0-.09,0-.19,0-.28,0,0,0,0,0,0h0s0-.03,0-.04h0s0-.01,0-.02c0-.13-.02-.25-.03-.38,0-.01,0-.02,0-.04-.08-.71-.18-.78-.18-.78-.07-.09-.17-.07-.26.02-.17.17-.26.37-.22.62.03.19.02.13.04.29.01.08.03.23.03.28,0,.04-.01.08-.04.11-.09.09-.23,0-.34-.05-2.08-.86-3.86-2.07-5.42-3.6-.61-.62-1.65-1.88-1.65-1.88h0s-.09-.07-.16-.04c-.14.06-.13.23-.14.38-.02.29.13.51.3.7,2.35,2.74,5.21,4.71,8.67,5.69l.62.16s.01,0,.02,0c0,0,0,0,0,0l.54.14h.01s-.04.01-.06.02c-.59.12-1.76-.1-2.69-.32-.46-.1-.92-.22-1.36-.36-.02,0-.03,0-.03,0h0c-1.73-.55-3.32-1.44-4.7-2.81-.13-.13-.25-.27-.37-.41-.11-.13-.34-.4-.41-.5,0-.01-.02-.02-.03-.03h0s0,0,0,0c-.02-.02-.05-.04-.09-.03-.1.02-.12.12-.14.21-.07.34.04.62.26.88,1.42,1.59,3.2,2.61,5.21,3.28,1.14.38,2.31.61,3.5.76-.71.23-1.44.3-2.18.28-1.43-.04-2.77-.38-4.03-.99-.46-.24-.9-.5-1.01-.58-.07-.04-.13-.07-.2-.02-.13.1-.04.27,0,.4.08.26.25.43.49.55,2.05,1.08,4.22,1.52,6.52,1.13.23-.04.45-.08.7-.16-.39.33-.83.55-1.3.72-1.44.53-2.88.51-4.31.15-.46-.12-.99-.32-.99-.32h0c-.08-.03-.15-.05-.21.04-.1.15-.01.3.08.45.21.33.63.36.95.48.11.04-.19.24-.29.31-.47.36-.94.69-1.41,1.04l-.31.23h0c-.05.04-.1.08-.08.15.03.09.12.1.2.12.34.07.62-.04.89-.24.57-.43,1.15-.84,1.71-1.28.21-.17.4-.15.7-.08-.73.55-1.4,1.06-2.08,1.57-.69.52-1.38,1.04-2.07,1.56l-.46.34s-.01,0-.02.01h0s0,0,0,0c-.04.03-.07.07-.06.11.02.1.14.13.24.15.41.07.66-.08.94-.29,1.39-1.05,2.79-2.09,4.18-3.14.31-.24.63-.37,1.08-.32-.38.29-.75.56-1.11.83l-.98.73s-.01.01-.02.02h0c-.05.04-.09.09-.07.16.04.13.19.15.33.17.24.04.45-.05.64-.19.11-.08.22-.16.33-.24h0s1.5-1.19,1.5-1.19c0,0,.35-.25.76-.49.04-.02.07-.05.11-.07.02,0,.05-.02.11-.05.04-.02.09-.05.13-.07.06-.03.14-.07.22-.12.24-.1.93-.42,1.51-1.03.27-.21.66-.48.95-.62h0s0,0,0,0c.02-.01.04-.02.07-.03h0s0,0,0,0c.01,0,.03-.01.04-.01h.04c.11-.05.28-.06.31.18,0,.06,0,.12,0,.18,0,.05-.01.1-.02.14,0,.04,0,.08.04.1,0,0,.01,0,.02,0,.04.02.09.01.12-.02.01-.01.02-.03.04-.04.15-.15.3-.26.45-.33.23-.1.47-.1.72,0,.12.05.23.11.34.19.05.03.1.07.14.12.03.03.06.05.08.08h0s.02.02.02.02c0,0,0,0,.01.01.02.02.05.02.08.02.03,0,.06-.03.07-.06,0,0,0-.01,0-.02.07-.21.09-.4.04-.59Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M13.48,7.75c-.02.1-.03.21-.05.31.02-.1.04-.21.05-.31h0Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M15.33,8.9s.02,0,.02,0c0,0-.02,0-.02,0" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "14.46 8.33 14.46 8.33 14.46 8.33" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "14.48 8.26 14.48 8.26 14.48 8.26" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "14.49 8.25 14.49 8.25 14.49 8.25" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "15.64 8.25 15.63 8.25 15.64 8.25" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M13.81,8.17s-.02.06-.04.08c.01-.03.02-.06.04-.08" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.57 8.11 12.57 8.11 12.57 8.11" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.39 8.03 12.39 8.03 12.39 8.03" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.39 8.03 12.39 8.03 12.39 8.03" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.39 8.03 12.39 8.03 12.39 8.03" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.38 8.02 12.38 8.03 12.38 8.02" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.38 8.02 12.38 8.02 12.38 8.02" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "12.35 7.89 12.35 7.89 12.35 7.89" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M12.81,7.81s0,.04-.01.06c0-.02,0-.04.01-.06" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M11.67,7.67s-.03.02-.05.03c.02,0,.03-.02.05-.03" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #e5b3a5;", + "d": "M12.39,8.03s.03.03.05.04h0s-.03-.03-.05-.04M12.39,8.03s0,0,0,0c0,0,0,0,0,0M12.39,8.03s0,0,0,0c0,0,0,0,0,0M12.38,8.03s0,0,0,0c0,0,0,0,0,0M12.38,8.02s0,0,0,0c0,0,0,0,0,0M12.38,8.02s0,0,0,0c0,0,0,0,0,0M12.35,7.89s0,0,0,0c0,0,0,0,0,0M12.35,7.89h0s0,0,0,0c0,0,0,0,0,0M11.45,7.68s0,0,0,0c.04.02.09.03.13.03.02,0,.03,0,.05,0-.02,0-.03,0-.05,0-.04,0-.09-.02-.13-.03M12.34,7.54s0,0,0,0c0,0,0,0,0,0" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #e5b3a5;", + "d": "M15.35,8.91s0,0,.01,0h0s0,0-.01,0M14.52,8.58s0,0,0,0c0,0,0,0,0,0M14.39,8.51s.01.06.04.08c.01,0,.02.01.03.01.02,0,.04,0,.05-.02-.02.01-.04.02-.05.02-.01,0-.02,0-.03-.01-.03-.02-.04-.05-.04-.08M14.46,8.33s0,0,0,.01c0,0,0,0,0-.01M14.48,8.26s-.02.05-.03.07c0-.02.02-.05.03-.07M14.49,8.25s0,0,0,0c0,0,0,0,0,0M15.63,8.25s0,0,0,0c0,0,0,0,0,0M15.64,8.24s0,0,0,0c0,0,0,0,0,0M14.49,8.24s0,0,0,0c0,0,0,0,0,0M13.4,8.21c0,.07.03.13.13.16.03.01.06.01.08.01.09,0,.13-.06.16-.13-.03.07-.07.13-.16.13-.02,0-.05,0-.08-.01-.1-.03-.13-.09-.13-.16M12.57,8.11h0,0M12.74,8.04s-.08.07-.14.07c0,0-.01,0-.02,0,0,0,.01,0,.02,0,.07,0,.11-.03.14-.07M14.05,7.54s0,0,0,0c-.03.1-.06.2-.1.3-.04.11-.09.22-.14.33h0c.05-.11.1-.22.14-.33.04-.1.07-.2.1-.3M12.87,7.49c-.02.11-.03.22-.05.33.02-.11.04-.22.06-.33h0M15.52,6.97s0,0,0,0c-.15.49-.38.95-.75,1.36-.04.05-.09.1-.14.14h0s.09-.09.14-.14c.37-.41.6-.87.75-1.36" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-2); opacity: .4;", + "d": "M4.17,2.09s-.04,0-.06.01c-.1.04-.12.14-.13.25.52.68,2.56,3.18,5.78,4.9,0,0,3.15,1.66,6.32,1.84-.25-.03-.46-.09-.67-.16-.01,0-.02,0-.04-.01h0c-1.15-.25-2.13-.56-2.83-.81.01,0,.03,0,.04,0-.01,0-.03,0-.04,0-.04,0-.07-.02-.1-.04-.55-.2-.9-.35-.98-.39-.04-.02-.08-.04-.12-.05-2.08-.86-3.86-2.07-5.42-3.6-.61-.62-1.65-1.88-1.65-1.88h0s-.06-.05-.1-.05" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-3); opacity: .4;", + "d": "M4.39,4.75s-.01,0-.02,0c-.1.02-.12.12-.14.21,0,.02,0,.03,0,.05,1.61,2.18,3.77,3.07,3.77,3.07,2.15,1.01,3.92,1.22,5,1.22.45,0,.77-.04.96-.06-.09,0-.18.01-.28.01-.63,0-1.53-.17-2.29-.35-.46-.1-.92-.22-1.36-.36-.02,0-.03,0-.03,0h0c-1.73-.55-3.32-1.44-4.7-2.81-.13-.13-.25-.27-.37-.41-.11-.13-.34-.4-.41-.5,0-.01-.02-.02-.03-.03h0s0,0,0,0c-.02-.02-.04-.03-.07-.03" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-4); opacity: .4", + "d": "M5.86,8.53s-.05,0-.08.03c-.11.08-.06.22-.02.35.81.49,2.66,1.44,4.84,1.44.83,0,1.7-.14,2.59-.48-.64.21-1.3.28-1.96.28-.07,0-.15,0-.22,0-1.43-.04-2.77-.38-4.03-.99-.46-.24-.9-.5-1.01-.58-.04-.03-.08-.05-.12-.05" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-5); opacity: .4;", + "d": "M13.34,10.6c-.35.27-.75.46-1.16.61-.77.28-1.55.41-2.32.41-.67,0-1.33-.09-1.99-.26-.46-.12-.99-.32-.99-.32h0s-.07-.03-.1-.03c-.04,0-.08.02-.11.06-.05.07-.05.14-.04.21.51.24,1.6.66,2.93.66,1.15,0,2.49-.32,3.78-1.34" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-6); opacity: .4;", + "d": "M9.91,12.25c-1.25,0-2.19-.25-2.31-.29.04.01.07.02.1.03.11.04-.19.24-.29.31-.35.27-.69.52-1.04.77h1.06c.33-.25.67-.5.99-.75.12-.1.24-.13.37-.13.1,0,.2.02.33.05-.73.55-1.4,1.06-2.08,1.57-.4.3-.79.6-1.19.9h1.07c.96-.72,1.92-1.44,2.87-2.16.22-.17.44-.28.71-.32-.2.01-.4.02-.58.02Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#Fade_to_Black_2); opacity: .4;", + "d": "M9.69,13.1h1.03l.76-.6s.35-.25.76-.49c.04-.02.07-.05.11-.07.02,0,.05-.02.11-.05.04-.02.09-.05.13-.07.07-.03.15-.08.23-.12-.73.31-1.49.46-2.18.52,0,0,.02,0,.03,0,.06,0,.12,0,.18.01-.38.29-.75.56-1.11.83l-.06.05Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-7); opacity: .4;", + "d": "M12.35,7.89c0-.09,0-.19,0-.28h0s0,0,0,0c0-.01,0-.03,0-.04h0s0-.02,0-.02c0-.08,0-.16-.02-.23-.25-.1-.48-.24-.69-.39,0,.02,0,.04,0,.07.03.19.02.13.04.29.01.08.03.23.03.28,0,.04-.01.08-.04.11-.03.03-.06.04-.09.04-.04,0-.09-.02-.13-.03.08.04.43.19.98.39-.06-.04-.08-.09-.08-.18Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-8); opacity: .4;", + "d": "M16.36,6.14c-.23.32-.51.6-.84.83-.15.49-.38.95-.75,1.36-.04.05-.09.1-.14.14-.05.04-.11.12-.17.12-.01,0-.02,0-.03-.01-.08-.06,0-.18.02-.24.14-.32.24-.65.32-.98-.23.09-.47.15-.72.18-.03.1-.06.2-.1.3-.04.11-.09.22-.14.33-.05.1-.08.21-.2.21-.02,0-.05,0-.08-.01-.18-.06-.13-.19-.1-.31.03-.16.06-.32.08-.49-.22,0-.43-.04-.64-.08-.02.13-.04.26-.07.39-.02.09-.05.24-.21.24-.02,0-.04,0-.06,0,.69.25,1.68.56,2.83.81h0s-.02,0-.03-.01c-.18-.08.15-.41.3-.64.43-.66.64-1.37.73-2.12Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#Fade_to_Black_2-2); opacity: .5;", + "d": "M16.19,9.7c-.14,0-.29.01-.43.04-.03,0-.05,0-.08,0-.04,0-.08,0-.11-.02,0,0,.01.01.02.02.06.09.1.18.11.28.02.02.03.04.04.07h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0t0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,.05,0,.07c0,.04,0,.07,0,.11,0,.05-.01.1-.02.14,0,0,0,.01,0,.02,0,.03.02.06.04.08h.02s.03.02.05.02c.03,0,.06-.01.08-.03.01-.01.02-.03.04-.04.15-.15.3-.26.45-.33.12-.05.23-.07.35-.07s.24.02.36.07c.12.05.23.11.34.19.05.03.1.07.14.12.03.03.06.05.08.08h0s.02.02.02.02h.01s.04.03.06.03c-.1-.21-.22-.41-.4-.56-.33-.29-.75-.41-1.18-.41" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-9); opacity: .4;", + "d": "M16.15,8.3h-.02s-.08.05-.08.09c0,.03,0,.07,0,.11,0,.01,0,.02,0,.03.18.26.56.73,1.15.91,0,0,.91.47.68,1.16h0v-.02c.08-.21.09-.4.05-.59-.04-.19-.14-.36-.28-.51-.06-.06-.12-.11-.19-.16-.03-.02-.07-.05-.1-.07-.04-.03-.09-.05-.14-.08-.19-.09-.34-.19-.48-.29-.06-.04-.11-.09-.16-.13-.12-.11-.21-.2-.27-.3-.02-.03-.04-.07-.05-.1,0-.01-.02-.03-.03-.04-.02-.01-.04-.02-.06-.02" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-10); opacity: .5;", + "d": "M15,10.21l.18-.11.77-1.03s-.42-.08-.63-.17c0,0-.21.96-.99,1.77l.29-.21c.11-.08.22-.15.38-.25Z" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M25.33,4.73h1.97c1.42,0,2.07.73,2.07,2.23v.74c0,1.5-.65,2.23-2.07,2.23h-1.14v4.09h-.82V4.73ZM27.3,9.19c.86,0,1.25-.39,1.25-1.44v-.84c0-1.04-.39-1.44-1.25-1.44h-1.14v3.71h1.14Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M32.4,4.73h.82v4.08h2.54v-4.08h.82v9.3h-.82v-4.48h-2.54v4.48h-.82V4.73Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M39.82,11.9v-5.02c0-1.48.74-2.27,2.09-2.27s2.09.8,2.09,2.27v5.02c0,1.48-.74,2.27-2.09,2.27s-2.09-.8-2.09-2.27ZM43.17,11.95v-5.13c0-1-.45-1.48-1.26-1.48s-1.26.48-1.26,1.48v5.13c0,1,.45,1.46,1.26,1.46s1.26-.47,1.26-1.46Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M47.23,4.73h3.72v.74h-2.9v3.34h2.38v.74h-2.38v3.72h2.9v.76h-3.72V4.73Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M53.93,4.73h1.09l2.42,7.19v-7.19h.77v9.3h-.88l-2.64-7.93v7.93h-.76V4.73Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M61.55,4.73h.82v9.3h-.82V4.73Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M67.19,9.29l-1.82-4.56h.88l1.46,3.69,1.48-3.69h.8l-1.82,4.56,1.9,4.74h-.88l-1.54-3.91-1.55,3.91h-.8l1.9-4.74Z" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + "name": "PhoenixIcon" +} diff --git a/web/app/components/base/icons/src/public/tracing/PhoenixIcon.tsx b/web/app/components/base/icons/src/public/tracing/PhoenixIcon.tsx new file mode 100644 index 0000000000..e0d36e065d --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/PhoenixIcon.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './PhoenixIcon.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'PhoenixIcon' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.json b/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.json new file mode 100644 index 0000000000..f48a2f53ae --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.json @@ -0,0 +1,853 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "id": "Layer_2", + "data-name": "Layer 2", + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "viewBox": "0 0 111 24", + "width": "111", + "height": "24" + }, + "children": [ + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient", + "x1": "9.07", + "y1": "1.47", + "x2": "19.77", + "y2": "20", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#11bab5" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".5", + "stop-color": "#00adee" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#0094c5" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-2", + "x1": "19.48", + "y1": "16.3", + "x2": "10.46", + "y2": ".67", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#fcfdff", + "stop-opacity": "0" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".12", + "stop-color": "#fcfdff", + "stop-opacity": ".17" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".3", + "stop-color": "#fcfdff", + "stop-opacity": ".42" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".47", + "stop-color": "#fcfdff", + "stop-opacity": ".63" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".64", + "stop-color": "#fcfdff", + "stop-opacity": ".79" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".78", + "stop-color": "#fcfdff", + "stop-opacity": ".9" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".91", + "stop-color": "#fcfdff", + "stop-opacity": ".97" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#fcfdff" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-3", + "x1": "16.82", + "y1": "16.21", + "x2": "10.32", + "y2": "4.94", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-4", + "x1": "15.92", + "y1": "17.03", + "x2": "12.29", + "y2": "10.74", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-5", + "x1": "16.08", + "y1": "18.3", + "x2": "13.82", + "y2": "14.38", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-6", + "x1": "12.26", + "y1": "17.94", + "x2": "12.26", + "y2": "22.07", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".03", + "stop-color": "#231f20", + "stop-opacity": ".9" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".22", + "stop-color": "#231f20", + "stop-opacity": ".4" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".42", + "stop-color": "#231f20", + "stop-opacity": ".1" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".65", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "Fade_to_Black_2", + "data-name": "Fade to Black 2", + "x1": "16.89", + "y1": "17.55", + "x2": "16.89", + "y2": "19.66", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-7", + "x1": "17.91", + "y1": "12.1", + "x2": "17.91", + "y2": "10.38", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".03", + "stop-color": "#231f20", + "stop-opacity": ".95" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".51", + "stop-color": "#231f20", + "stop-opacity": ".26" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".81", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-8", + "x1": "21.67", + "y1": "13.37", + "x2": "21.67", + "y2": "9.21", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".18", + "stop-color": "#231f20", + "stop-opacity": ".48" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".38", + "stop-color": "#231f20", + "stop-opacity": ".12" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".58", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "Fade_to_Black_2-2", + "data-name": "Fade to Black 2", + "x1": "25.54", + "y1": "16.65", + "x2": "24.1", + "y2": "14.16", + "xlink:href": "#Fade_to_Black_2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-9", + "x1": "26.7", + "y1": "15.97", + "x2": "24.55", + "y2": "12.24", + "xlink:href": "#linear-gradient-2" + }, + "children": [] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "linear-gradient-10", + "x1": "21.11", + "y1": "15.34", + "x2": "24.24", + "y2": "13.53", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0", + "stop-color": "#231f20" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".31", + "stop-color": "#231f20", + "stop-opacity": ".5" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": ".67", + "stop-color": "#231f20", + "stop-opacity": ".13" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#231f20", + "stop-opacity": "0" + }, + "children": [] + } + ] + } + ] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Layer_1-2", + "data-name": "Layer 1" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "style": "fill: none;", + "y": ".04", + "width": "111", + "height": "24" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Phoenix_horiz_-_gradient", + "data-name": "Phoenix horiz - gradient" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient);", + "d": "M26.87,15c-.06-.28-.2-.53-.42-.76-.08-.09-.18-.17-.28-.25-.05-.04-.1-.07-.15-.1-.07-.04-.14-.08-.21-.12-.28-.14-.51-.29-.72-.44-.08-.06-.17-.13-.24-.19-.19-.16-.31-.3-.4-.45-.03-.05-.06-.1-.08-.15-.01-.02-.03-.04-.05-.05-.03-.02-.08-.04-.12-.03-.07.01-.12.07-.13.13,0,.05,0,.11,0,.17,0,.03-.01.07-.04.08,0,0-.01,0-.02,0-.04,0-.08.03-.11.07-.03.04-.03.08-.02.13,0,.04.02.06.03.09v.02s.02.03.03.04c.02.04.04.08.07.12.05.07.1.14.17.22.02.02.02.06,0,.09-.02.03-.05.04-.08.04-.37-.05-.7-.13-1-.25-.04-.01-.07-.03-.11-.04-.28-.12.22-.61.45-.96,1-1.53,1.2-3.29,1.21-5.07,0-1.72-.18-3.41-.52-5.08h0c-.22-1.4-.41-1.93-.53-2.13-.03-.05-.05-.08-.08-.1-.03-.02-.05-.01-.05-.01-.06,0-.14.06-.23.15-.37.39-.35.86-.25,1.34.47,2.21.82,4.43.66,6.7-.11,1.56-.4,3.07-1.5,4.3-.07.07-.14.14-.21.21-.08.08-.21.24-.3.17-.13-.09-.01-.27.03-.36.64-1.5.82-3.07.73-4.69,0-.04,0-.07,0-.11h0s0-.06,0-.09c-.02-.21-.04-.43-.06-.64-.28-2.56-.61-2.73-.61-2.73h0s-.05-.03-.09-.04c-.17-.04-.27.12-.35.23-.35.52-.19,1.07-.08,1.62.39,1.92.4,3.82-.28,5.69-.06.17-.14.34-.21.5-.08.17-.14.39-.42.3-.26-.09-.19-.29-.16-.47.09-.47.16-.93.2-1.4h0s0-.09.01-.13c0-.08.01-.16.02-.24,0-.07,0-.13.01-.2,0-.03,0-.07,0-.1.05-1.48-.2-2.16-.29-2.36-.01-.02-.02-.05-.03-.07h0s0,0,0,0c-.1-.15-.26-.13-.4,0-.26.25-.4.56-.33.93.19,1.1.08,2.2-.13,3.28-.03.15-.09.41-.4.34-.21-.05-.26-.14-.27-.32,0-.14,0-.28,0-.42,0,0,0,0,0-.01h0s0-.04,0-.06h0s0-.02,0-.03c0-.19-.02-.38-.05-.57,0-.02,0-.04,0-.05-.12-1.07-.27-1.17-.27-1.17-.1-.13-.25-.11-.39.03-.26.25-.39.55-.33.93.04.28.03.19.06.43.02.12.05.35.05.42,0,.06-.02.12-.06.16-.13.14-.34,0-.5-.07-3.12-1.29-5.79-3.1-8.12-5.4-.92-.94-2.48-2.83-2.48-2.83h0c-.06-.07-.13-.11-.24-.06-.2.09-.2.35-.22.57-.04.44.2.76.45,1.06,3.52,4.11,7.82,7.06,13,8.54l.93.25s.02,0,.03,0c0,0,0,0,0,0l.8.21h.02s-.06.02-.09.03c-.88.17-2.63-.15-4.04-.47-.7-.15-1.38-.32-2.05-.53-.03,0-.05-.01-.05-.01h0c-2.6-.83-4.97-2.17-7.05-4.21-.2-.19-.38-.4-.56-.61-.16-.2-.5-.61-.62-.75-.01-.02-.03-.03-.04-.05h0s0,0,0,0c-.04-.04-.08-.06-.14-.05-.14.03-.19.18-.21.31-.11.51.05.93.39,1.31,2.13,2.39,4.8,3.91,7.81,4.91,1.71.57,3.46.91,5.25,1.13-1.07.35-2.16.45-3.27.42-2.14-.05-4.15-.57-6.04-1.49-.7-.36-1.34-.76-1.52-.86-.1-.07-.2-.11-.3-.03-.19.15-.06.4,0,.6.12.38.38.65.73.83,3.08,1.63,6.32,2.28,9.78,1.7.35-.06.68-.12,1.05-.25-.58.5-1.25.83-1.95,1.08-2.17.79-4.32.76-6.46.22-.69-.18-1.49-.48-1.49-.48h0c-.12-.05-.23-.07-.32.06-.16.22-.02.46.12.67.32.5.94.55,1.43.72.17.05-.29.36-.43.47-.71.54-1.4,1.04-2.11,1.56l-.46.34h0c-.08.05-.15.12-.11.23.04.13.18.15.31.18.51.1.94-.06,1.33-.36.86-.64,1.73-1.26,2.56-1.93.32-.25.61-.23,1.04-.12-1.09.82-2.11,1.59-3.13,2.36-1.03.78-2.07,1.56-3.1,2.33l-.68.51s-.02.01-.03.02h-.01s0,0,0,0c-.06.05-.1.1-.09.17.03.15.21.19.36.22.61.11.99-.12,1.41-.44,2.09-1.57,4.19-3.13,6.27-4.71.47-.36.95-.56,1.62-.48-.57.43-1.12.84-1.67,1.25l-1.47,1.09s-.02.02-.03.02h0c-.08.06-.13.13-.1.24.06.19.29.22.49.25.37.05.68-.08.96-.29.17-.12.33-.24.5-.37h0s2.25-1.79,2.25-1.79c0,0,.53-.38,1.15-.73.05-.03.11-.07.17-.1.02-.01.08-.04.17-.08.07-.03.13-.07.2-.1.1-.05.21-.11.33-.18.36-.15,1.4-.64,2.26-1.55.41-.32.98-.73,1.43-.92h0s0,0,0,0c.03-.02.07-.03.1-.04h0s0,0,0,0c.02,0,.04-.02.06-.02l.06-.02c.16-.05.43-.06.46.29,0,.09,0,.18,0,.27,0,.07-.02.15-.03.21-.01.06.01.12.06.15,0,0,.02.01.02.01.06.03.14.02.18-.03.02-.02.03-.04.05-.06.22-.23.44-.39.68-.49.35-.14.7-.14,1.07,0,.18.07.35.16.51.28.07.05.14.11.21.17.04.04.08.08.12.12h0s.03.04.03.04c0,0,.01.01.02.02.04.03.08.04.12.03.05-.01.09-.05.11-.1,0,0,0-.02.01-.03.11-.31.13-.6.07-.89Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M20.22,11.62c-.03.16-.05.31-.08.47.03-.16.06-.31.08-.47h0Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M22.99,13.35s.02,0,.03.01c-.01,0-.02,0-.03-.01" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "21.68 12.5 21.68 12.5 21.68 12.5" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "21.73 12.39 21.73 12.39 21.73 12.39" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "21.74 12.37 21.73 12.38 21.74 12.37" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "23.45 12.37 23.45 12.38 23.45 12.37" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M20.72,12.26s-.04.08-.06.12c.02-.04.04-.08.06-.12" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.86 12.17 18.86 12.17 18.86 12.17" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.58 12.04 18.58 12.04 18.58 12.04" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.58 12.04 18.58 12.04 18.58 12.04" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.58 12.04 18.58 12.04 18.58 12.04" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.58 12.04 18.58 12.04 18.58 12.04" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.58 12.03 18.58 12.04 18.58 12.03" + }, + "children": [] + }, + { + "type": "element", + "name": "polyline", + "attributes": { + "style": "fill: #fddab4;", + "points": "18.52 11.84 18.52 11.84 18.52 11.84" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M19.22,11.72s-.01.06-.02.09c0-.03.01-.06.02-.09" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #fddab4;", + "d": "M17.51,11.51s-.04.04-.07.05c.02,0,.05-.02.07-.05" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #e5b3a5;", + "d": "M18.58,12.04s.04.04.07.06h0s-.05-.04-.07-.06M18.58,12.04s0,0,0,0c0,0,0,0,0,0M18.58,12.04s0,0,0,0c0,0,0,0,0,0M18.58,12.04s0,0,0,0c0,0,0,0,0,0M18.58,12.04s0,0,0,0c0,0,0,0,0,0M18.57,12.03s0,0,0,0c0,0,0,0,0,0M18.52,11.84s0,0,0,0c0,0,0,0,0,0M18.52,11.84h0s0,0,0,0c0,0,0,0,0,0M17.17,11.51s0,0,0,0c.06.03.13.05.19.05.03,0,.05,0,.07-.01-.02,0-.05.01-.07.01-.06,0-.13-.02-.19-.05M18.52,11.31s0,0,0,0c0,0,0,0,0,0" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #e5b3a5;", + "d": "M23.02,13.37s.01,0,.02,0h0s-.01,0-.02,0M21.78,12.86s0,0,0,0c0,0,0,0,0,0M21.59,12.76s.02.08.06.11c.02.01.03.02.05.02.03,0,.05-.01.08-.03-.03.02-.05.03-.08.03-.02,0-.03,0-.05-.02-.04-.03-.06-.07-.06-.11M21.68,12.5s0,.01,0,.02c0,0,0-.01,0-.02M21.73,12.39s-.03.07-.04.11c.01-.03.03-.07.04-.11M21.73,12.38s0,0,0,.01c0,0,0,0,0-.01M23.45,12.38s0,0,0,0c0,0,0,0,0,0M23.45,12.37s0,0,0,0c0,0,0,0,0,0M21.74,12.37s0,0,0,0c0,0,0,0,0,0M20.1,12.32c0,.1.04.19.19.24.05.02.09.02.12.02.13,0,.19-.09.24-.2-.05.1-.11.2-.24.2-.04,0-.08,0-.12-.02-.15-.05-.19-.14-.19-.24M18.86,12.17h0,0M19.11,12.07c-.05.06-.11.1-.22.1-.01,0-.02,0-.03,0,.01,0,.02,0,.03,0,.1,0,.17-.04.22-.1M21.08,11.32s0,0,0,0c-.05.15-.09.3-.15.44-.06.17-.14.34-.21.5h0c.08-.16.15-.33.21-.5.05-.15.1-.3.15-.45M19.3,11.23c-.02.16-.05.33-.08.49.03-.16.06-.33.08-.49h0M23.28,10.45s0,0,0,0c-.22.73-.57,1.42-1.12,2.04-.07.07-.14.14-.21.21h0c.07-.07.14-.14.21-.21.55-.62.9-1.31,1.12-2.04" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-2); opacity: .4;", + "d": "M6.25,3.13s-.06,0-.09.02c-.14.06-.18.21-.2.37.78,1.02,3.84,4.77,8.67,7.35,0,0,4.72,2.49,9.48,2.76-.37-.05-.7-.13-1-.25-.02,0-.04-.01-.05-.02h0c-1.73-.37-3.2-.84-4.24-1.21.02,0,.04,0,.06,0-.02,0-.04,0-.06,0-.06-.01-.11-.03-.15-.05-.82-.3-1.35-.53-1.47-.59-.06-.03-.12-.06-.17-.08-3.12-1.29-5.79-3.1-8.12-5.4-.92-.94-2.48-2.83-2.48-2.83h0s-.09-.08-.15-.08" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-3); opacity: .4;", + "d": "M6.59,7.12s-.02,0-.03,0c-.14.03-.19.18-.21.31,0,.02,0,.05-.01.07,2.41,3.27,5.65,4.6,5.65,4.6,3.23,1.52,5.88,1.83,7.5,1.83.67,0,1.16-.05,1.44-.09-.13.01-.27.02-.42.02-.95,0-2.3-.26-3.43-.52-.7-.15-1.38-.32-2.05-.53-.03,0-.05-.01-.05-.01h0c-2.6-.83-4.97-2.17-7.05-4.21-.2-.19-.38-.4-.56-.61-.16-.2-.5-.61-.62-.75-.01-.02-.03-.03-.04-.05h0s0,0,0,0c-.03-.03-.06-.05-.1-.05" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-4); opacity: .4;", + "d": "M8.78,12.8s-.08.01-.12.04c-.16.13-.09.34-.02.52,1.21.73,3.98,2.16,7.27,2.16,1.24,0,2.55-.2,3.88-.72-.96.31-1.94.43-2.94.43-.11,0-.22,0-.33,0-2.14-.05-4.15-.57-6.04-1.49-.7-.36-1.34-.76-1.52-.86-.06-.04-.12-.07-.18-.07" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-5); opacity: .4;", + "d": "M20.02,15.9c-.53.4-1.12.68-1.74.91-1.16.42-2.32.61-3.47.61-1,0-1.99-.14-2.99-.39-.69-.18-1.49-.48-1.49-.48h0c-.05-.02-.1-.04-.15-.04-.06,0-.12.03-.17.1-.07.1-.08.21-.06.32.76.35,2.4.98,4.39.98,1.73,0,3.73-.47,5.67-2.01" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-6); opacity: .4;", + "d": "M14.87,18.37c-1.87,0-3.29-.38-3.47-.43.05.02.1.03.15.05.17.05-.29.36-.43.47-.52.4-1.04.78-1.56,1.16h1.59c.5-.37,1-.74,1.49-1.13.18-.14.35-.2.55-.2.15,0,.31.03.49.08-1.09.82-2.11,1.59-3.13,2.36-.6.45-1.19.9-1.79,1.34h1.6c1.44-1.08,2.88-2.15,4.31-3.24.33-.25.67-.42,1.07-.48-.3.02-.59.03-.88.03Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#Fade_to_Black_2); opacity: .4;", + "d": "M14.54,19.66h1.55l1.14-.91s.53-.38,1.15-.73c.05-.03.11-.07.17-.1.02-.01.08-.04.17-.08.07-.03.13-.07.2-.1.1-.05.22-.11.35-.19-1.1.46-2.23.69-3.28.77.01,0,.03,0,.04,0,.09,0,.18,0,.27.02-.57.43-1.12.84-1.67,1.25l-.09.07Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-7); opacity: .4;", + "d": "M18.52,11.84c0-.14,0-.28,0-.42h0s0-.01,0-.01c0-.02,0-.04,0-.06h0s0-.03,0-.03c0-.12-.01-.23-.03-.35-.37-.16-.72-.35-1.04-.59,0,.03,0,.07,0,.1.04.28.03.19.06.43.02.12.05.35.05.42,0,.06-.02.12-.06.16-.04.04-.09.06-.14.06-.06,0-.13-.02-.19-.05.12.06.65.29,1.48.59-.09-.05-.12-.14-.12-.27Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-8); opacity: .4;", + "d": "M24.54,9.21c-.34.48-.77.9-1.26,1.24-.22.73-.57,1.42-1.12,2.04-.07.07-.14.14-.21.21-.07.07-.17.19-.25.19-.02,0-.03,0-.05-.02-.13-.09-.01-.27.03-.36.21-.48.37-.98.48-1.47-.35.13-.71.22-1.08.27-.05.15-.09.3-.15.44-.06.17-.14.34-.21.5-.07.14-.12.32-.3.32-.04,0-.08,0-.12-.02-.26-.09-.19-.29-.16-.47.05-.24.09-.49.12-.73-.33-.01-.65-.05-.96-.12-.03.19-.06.39-.1.58-.02.13-.08.35-.31.35-.03,0-.06,0-.09-.01,1.04.37,2.51.84,4.24,1.21h0s-.03-.01-.05-.02c-.28-.12.22-.61.45-.96.64-.98.95-2.06,1.1-3.18Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#Fade_to_Black_2-2); opacity: .5;", + "d": "M24.28,14.56c-.22,0-.43.02-.65.06-.04,0-.08.01-.12.01-.06,0-.12,0-.17-.03,0,.01.02.02.03.03.09.13.14.27.16.42.02.03.04.06.06.1h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0t0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0c0,0,0,0,0,0,0,0,0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,0,0,0h0s0,.07,0,.1c0,.05,0,.11,0,.17,0,.07-.02.15-.03.21,0,0,0,.02,0,.03,0,.05.02.09.06.12h.02s.05.03.07.03c.04,0,.08-.02.11-.05.02-.02.03-.04.05-.06.22-.23.44-.39.68-.49.17-.07.35-.11.53-.11s.36.04.55.11c.18.07.35.16.51.28.07.05.14.11.21.17.04.04.08.08.12.12h0s.03.04.03.04l.02.02s.06.03.09.03c-.15-.32-.33-.61-.6-.84-.5-.43-1.13-.62-1.77-.62" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-9); opacity: .4;", + "d": "M24.22,12.45h-.03c-.07.01-.12.07-.13.14,0,.05,0,.11,0,.17,0,.02,0,.04-.01.05.27.4.84,1.1,1.72,1.36,0,0,1.36.71,1.01,1.74h0v-.03c.12-.31.14-.6.08-.89-.06-.28-.2-.53-.42-.76-.08-.09-.18-.17-.28-.25-.05-.04-.1-.07-.15-.1-.07-.04-.14-.08-.21-.12-.28-.14-.51-.29-.72-.44-.08-.06-.17-.13-.24-.19-.19-.16-.31-.3-.4-.45-.03-.05-.06-.1-.08-.15-.01-.02-.03-.04-.05-.05-.03-.02-.06-.03-.09-.03" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: url(#linear-gradient-10); opacity: .5;", + "d": "M22.5,15.32l.28-.16,1.16-1.55s-.63-.12-.94-.26c0,0-.32,1.45-1.49,2.66l.43-.32c.17-.12.33-.23.56-.37Z" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M38,7.1h2.95c2.13,0,3.11,1.1,3.11,3.35v1.12c0,2.25-.98,3.35-3.11,3.35h-1.71v6.14h-1.24V7.1ZM40.95,13.78c1.3,0,1.87-.58,1.87-2.15v-1.26c0-1.55-.58-2.15-1.87-2.15h-1.71v5.56h1.71Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M48.61,7.1h1.24v6.12h3.81v-6.12h1.24v13.95h-1.24v-6.72h-3.81v6.72h-1.24V7.1Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M59.73,17.84v-7.54c0-2.21,1.12-3.41,3.13-3.41s3.13,1.2,3.13,3.41v7.54c0,2.21-1.12,3.41-3.13,3.41s-3.13-1.2-3.13-3.41ZM64.75,17.92v-7.69c0-1.5-.68-2.21-1.89-2.21s-1.89.72-1.89,2.21v7.69c0,1.5.68,2.19,1.89,2.19s1.89-.7,1.89-2.19Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M70.85,7.1h5.58v1.12h-4.35v5h3.57v1.12h-3.57v5.58h4.35v1.14h-5.58V7.1Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M80.9,7.1h1.63l3.63,10.78V7.1h1.16v13.95h-1.32l-3.97-11.9v11.9h-1.14V7.1Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M92.32,7.1h1.24v13.95h-1.24V7.1Z" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": "fill: #404041;", + "d": "M100.79,13.94l-2.73-6.84h1.32l2.19,5.54,2.21-5.54h1.2l-2.73,6.84,2.85,7.12h-1.32l-2.31-5.86-2.33,5.86h-1.2l2.85-7.12Z" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + "name": "PhoenixIconBig" +} diff --git a/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.tsx b/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.tsx new file mode 100644 index 0000000000..9131e6bea6 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/PhoenixIconBig.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './PhoenixIconBig.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'PhoenixIconBig' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/index.ts b/web/app/components/base/icons/src/public/tracing/index.ts index 36b59e479b..61a117e863 100644 --- a/web/app/components/base/icons/src/public/tracing/index.ts +++ b/web/app/components/base/icons/src/public/tracing/index.ts @@ -1,9 +1,13 @@ +export { default as ArizeIconBig } from './ArizeIconBig' +export { default as ArizeIcon } from './ArizeIcon' export { default as LangfuseIconBig } from './LangfuseIconBig' export { default as LangfuseIcon } from './LangfuseIcon' export { default as LangsmithIconBig } from './LangsmithIconBig' export { default as LangsmithIcon } from './LangsmithIcon' export { default as OpikIconBig } from './OpikIconBig' export { default as OpikIcon } from './OpikIcon' +export { default as PhoenixIconBig } from './PhoenixIconBig' +export { default as PhoenixIcon } from './PhoenixIcon' export { default as TracingIcon } from './TracingIcon' export { default as WeaveIconBig } from './WeaveIconBig' export { default as WeaveIcon } from './WeaveIcon' diff --git a/web/app/components/base/input-number/index.spec.tsx b/web/app/components/base/input-number/index.spec.tsx index 8dfd1184b0..891cbd21e3 100644 --- a/web/app/components/base/input-number/index.spec.tsx +++ b/web/app/components/base/input-number/index.spec.tsx @@ -18,7 +18,7 @@ describe('InputNumber Component', () => { it('renders input with default values', () => { render() - const input = screen.getByRole('textbox') + const input = screen.getByRole('spinbutton') expect(input).toBeInTheDocument() }) @@ -56,7 +56,7 @@ describe('InputNumber Component', () => { it('handles direct input changes', () => { render() - const input = screen.getByRole('textbox') + const input = screen.getByRole('spinbutton') fireEvent.change(input, { target: { value: '42' } }) expect(defaultProps.onChange).toHaveBeenCalledWith(42) @@ -64,7 +64,7 @@ describe('InputNumber Component', () => { it('handles empty input', () => { render() - const input = screen.getByRole('textbox') + const input = screen.getByRole('spinbutton') fireEvent.change(input, { target: { value: '' } }) expect(defaultProps.onChange).toHaveBeenCalledWith(undefined) @@ -72,7 +72,7 @@ describe('InputNumber Component', () => { it('handles invalid input', () => { render() - const input = screen.getByRole('textbox') + const input = screen.getByRole('spinbutton') fireEvent.change(input, { target: { value: 'abc' } }) expect(defaultProps.onChange).not.toHaveBeenCalled() @@ -86,7 +86,7 @@ describe('InputNumber Component', () => { it('disables controls when disabled prop is true', () => { render() - const input = screen.getByRole('textbox') + const input = screen.getByRole('spinbutton') const incrementBtn = screen.getByRole('button', { name: /increment/i }) const decrementBtn = screen.getByRole('button', { name: /decrement/i }) diff --git a/web/app/components/base/input-number/index.tsx b/web/app/components/base/input-number/index.tsx index 98efc94462..5fd45944db 100644 --- a/web/app/components/base/input-number/index.tsx +++ b/web/app/components/base/input-number/index.tsx @@ -55,8 +55,8 @@ export const InputNumber: FC = (props) => { return
= (props) => { size={size} />
diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index acc891407f..ebd8552a99 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -507,13 +507,15 @@ const StepTwo = ({ const separator = rules.segmentation.separator const max = rules.segmentation.max_tokens const overlap = rules.segmentation.chunk_overlap + const isHierarchicalDocument = documentDetail.doc_form === ChunkingMode.parentChild + || (rules.parent_mode && rules.subchunk_segmentation) setSegmentIdentifier(separator) setMaxChunkLength(max) setOverlap(overlap!) setRules(rules.pre_processing_rules) setDefaultConfig(rules) - if (documentDetail.dataset_process_rule.mode === 'hierarchical') { + if (isHierarchicalDocument) { setParentChildConfig({ chunkForContext: rules.parent_mode || 'paragraph', parent: { @@ -575,6 +577,7 @@ const StepTwo = ({ onSuccess(data) { updateIndexingTypeCache && updateIndexingTypeCache(indexType as string) updateResultCache && updateResultCache(data) + updateRetrievalMethodCache && updateRetrievalMethodCache(retrievalConfig.search_method as string) }, }) } diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index acc5dd7606..2840e5fa4a 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -30,6 +30,7 @@ import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer' import StatusWithAction from '../common/document-status-with-action/status-with-action' import { useDocLink } from '@/context/i18n' +import { useFetchDefaultProcessRule } from '@/service/knowledge/use-create-dataset' const FolderPlusIcon = ({ className }: React.SVGProps) => { return @@ -178,6 +179,8 @@ const Documents: FC = ({ datasetId }) => { router.push(`/datasets/${datasetId}/documents/create`) } + const fetchDefaultProcessRuleMutation = useFetchDefaultProcessRule() + const handleSaveNotionPageSelected = async (selectedPages: NotionPage[]) => { const workspacesMap = groupBy(selectedPages, 'workspace_id') const workspaces = Object.keys(workspacesMap).map((workspaceId) => { @@ -186,6 +189,7 @@ const Documents: FC = ({ datasetId }) => { pages: workspacesMap[workspaceId], } }) + const { rules } = await fetchDefaultProcessRuleMutation.mutateAsync('/datasets/process-rule') const params = { data_source: { type: dataset?.data_source_type, @@ -209,7 +213,7 @@ const Documents: FC = ({ datasetId }) => { }, indexing_technique: dataset?.indexing_technique, process_rule: { - rules: {}, + rules, mode: ProcessMode.general, }, } as CreateDocumentReq diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx index 38efdcb1b7..065ef91eba 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx @@ -9,6 +9,8 @@ import { useAppContext } from '@/context/app-context' import { fetchNotionConnection } from '@/service/common' import NotionIcon from '@/app/components/base/notion-icon' import { noop } from 'lodash-es' +import { useTranslation } from 'react-i18next' +import Toast from '@/app/components/base/toast' const Icon: FC<{ src: string @@ -33,6 +35,7 @@ const DataSourceNotion: FC = ({ const { isCurrentWorkspaceManager } = useAppContext() const [canConnectNotion, setCanConnectNotion] = useState(false) const { data } = useSWR(canConnectNotion ? '/oauth/data-source/notion' : null, fetchNotionConnection) + const { t } = useTranslation() const connected = !!workspaces.length @@ -51,9 +54,19 @@ const DataSourceNotion: FC = ({ } useEffect(() => { - if (data?.data) - window.location.href = data.data - }, [data]) + if (data && 'data' in data) { + if (data.data && typeof data.data === 'string' && data.data.startsWith('http')) { + window.location.href = data.data + } + else if (data.data === 'internal') { + Toast.notify({ + type: 'info', + message: t('common.dataSource.notion.integratedAlert'), + }) + } + } + }, [data, t]) + return ( { const pathname = usePathname() const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname) - // // Check if the current path is a workflow canvas & fullscreen + // Check if the current path is a workflow canvas & fullscreen const inWorkflowCanvas = pathname.endsWith('/workflow') const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true' const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize) @@ -25,14 +25,12 @@ const HeaderWrapper = ({ setHideHeader(v.payload) }) - if (hideHeader && inWorkflowCanvas) - return null - return (
{children} diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx index b22f59fe2c..d3ac9d7d2e 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx @@ -62,7 +62,7 @@ const AppInputsPanel = ({ return [] let inputFormSchema = [] if (isBasicApp) { - inputFormSchema = currentApp.model_config.user_input_form.filter((item: any) => !item.external_data_tool).map((item: any) => { + inputFormSchema = currentApp.model_config?.user_input_form?.filter((item: any) => !item.external_data_tool).map((item: any) => { if (item.paragraph) { return { ...item.paragraph, @@ -108,10 +108,10 @@ const AppInputsPanel = ({ type: 'text-input', required: false, } - }) + }) || [] } else { - const startNode = currentWorkflow?.graph.nodes.find(node => node.data.type === BlockEnum.Start) as any + const startNode = currentWorkflow?.graph?.nodes.find(node => node.data.type === BlockEnum.Start) as any inputFormSchema = startNode?.data.variables.map((variable: any) => { if (variable.type === InputVarType.multiFiles) { return { @@ -132,7 +132,7 @@ const AppInputsPanel = ({ ...variable, required: false, } - }) + }) || [] } if ((currentApp.mode === 'completion' || currentApp.mode === 'workflow') && basicAppFileConfig.enabled) { inputFormSchema.push({ @@ -144,7 +144,7 @@ const AppInputsPanel = ({ fileUploadConfig, }) } - return inputFormSchema + return inputFormSchema || [] }, [basicAppFileConfig, currentApp, currentWorkflow, fileUploadConfig, isBasicApp]) const handleFormChange = (value: Record) => { diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index fd862720af..130773e0c2 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -18,6 +18,15 @@ type Props = { onSaved: (value: Record) => void } +const extractDefaultValues = (schemas: any[]) => { + const result: Record = {} + for (const field of schemas) { + if (field.default !== undefined) + result[field.name] = field.default + } + return result +} + const EndpointModal: FC = ({ formSchemas, defaultValues = {}, @@ -26,7 +35,10 @@ const EndpointModal: FC = ({ }) => { const getValueFromI18nObject = useRenderI18nObject() const { t } = useTranslation() - const [tempCredential, setTempCredential] = React.useState(defaultValues) + const initialValues = Object.keys(defaultValues).length > 0 + ? defaultValues + : extractDefaultValues(formSchemas) + const [tempCredential, setTempCredential] = React.useState(initialValues) const handleSave = () => { for (const field of formSchemas) { diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx index 60c63db5ea..51702a780a 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx @@ -25,6 +25,8 @@ import LLMParamsPanel from './llm-params-panel' import TTSParamsPanel from './tts-params-panel' import { useProviderContext } from '@/context/provider-context' import cn from '@/utils/classnames' +import Toast from '@/app/components/base/toast' +import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' export type ModelParameterModalProps = { popupClassName?: string @@ -121,17 +123,42 @@ const ModelParameterModal: FC = ({ return !isAPIKeySet || hasDeprecated || modelDisabled }, [hasDeprecated, isAPIKeySet, modelDisabled]) - const handleChangeModel = ({ provider, model }: DefaultModel) => { + const handleChangeModel = async ({ provider, model }: DefaultModel) => { const targetProvider = scopedModelList.find(modelItem => modelItem.provider === provider) const targetModelItem = targetProvider?.models.find((modelItem: { model: string }) => modelItem.model === model) const model_type = targetModelItem?.model_type as string + + let nextCompletionParams: FormValue = {} + + if (model_type === ModelTypeEnum.textGeneration) { + try { + const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams( + provider, + model, + value?.completion_params, + ) + nextCompletionParams = filtered + + const keys = Object.keys(removedDetails || {}) + if (keys.length) { + Toast.notify({ + type: 'warning', + message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}`, + }) + } + } + catch (e) { + Toast.notify({ type: 'error', message: t('common.error') }) + } + } + setModel({ provider, model, model_type, ...(model_type === ModelTypeEnum.textGeneration ? { mode: targetModelItem?.model_properties.mode as string, - completion_params: {}, + completion_params: nextCompletionParams, } : {}), }) } diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx index 7f5f22896a..fef79644cd 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx @@ -117,6 +117,7 @@ const MultipleToolSelector = ({ )} {!disabled && ( { + setCollapse(false) setOpen(!open) setPanelShowState(true) }}> @@ -126,23 +127,6 @@ const MultipleToolSelector = ({
{!collapse && ( <> -
- } - panelShowState={panelShowState} - onPanelShowStateChange={setPanelShowState} - isEdit={false} - /> {value.length === 0 && (
{t('plugin.detailPanel.toolSelector.empty')}
)} @@ -164,6 +148,23 @@ const MultipleToolSelector = ({ ))} )} +
+ } + panelShowState={panelShowState} + onPanelShowStateChange={setPanelShowState} + isEdit={false} + /> ) } diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index ca802414f3..350fe50933 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -275,7 +275,7 @@ const ToolSelector: FC = ({ /> )} - +
{!isShowSettingAuth && ( <> diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 9dc7ffcd79..4be6b18958 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -85,14 +85,6 @@ const TextGeneration: FC = ({ const router = useRouter() const pathname = usePathname() - useEffect(() => { - const params = new URLSearchParams(searchParams) - if (params.has('mode')) { - params.delete('mode') - router.replace(`${pathname}?${params.toString()}`) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) // Notice this situation isCallBatchAPI but not in batch tab const [isCallBatchAPI, setIsCallBatchAPI] = useState(false) diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx index f183dd9545..83f354d7d8 100644 --- a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx +++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx @@ -3,7 +3,7 @@ import { useCallback, useMemo, } from 'react' -import { useNodes } from 'reactflow' +import { useStore as useReactflowStore } from 'reactflow' import { RiApps2AddLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { @@ -22,7 +22,6 @@ import { BlockEnum, InputVarType, } from '@/app/components/workflow/types' -import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' import { useToastContext } from '@/app/components/base/toast' import { usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow' import type { PublishWorkflowParams } from '@/types/workflow' @@ -42,9 +41,9 @@ const FeaturesTrigger = () => { const publishedAt = useStore(s => s.publishedAt) const draftUpdatedAt = useStore(s => s.draftUpdatedAt) const toolPublished = useStore(s => s.toolPublished) - const nodes = useNodes() - const startNode = nodes.find(node => node.data.type === BlockEnum.Start) - const startVariables = startNode?.data.variables + const startVariables = useReactflowStore( + s => s.getNodes().find(node => node.data.type === BlockEnum.Start)?.data.variables, + ) const fileSettings = useFeatures(s => s.features.file) const variables = useMemo(() => { const data = startVariables || [] diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 6be190c897..f0b9bab2cf 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -7,7 +7,10 @@ import { WorkflowWithInnerContext } from '@/app/components/workflow' import type { WorkflowProps } from '@/app/components/workflow' import WorkflowChildren from './workflow-children' import { + useConfigsMap, + useInspectVarsCrud, useNodesSyncDraft, + useSetWorkflowVarsWithValue, useWorkflowRefreshDraft, useWorkflowRun, useWorkflowStartRun, @@ -61,6 +64,24 @@ const WorkflowMain = ({ handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, } = useWorkflowStartRun() + const { fetchInspectVars } = useSetWorkflowVarsWithValue() + const { + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, + } = useInspectVarsCrud() + const configsMap = useConfigsMap() const hooksStore = useMemo(() => { return { @@ -75,6 +96,22 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + fetchInspectVars, + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, + configsMap, } }, [ syncWorkflowDraftWhenPageClose, @@ -88,6 +125,22 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + fetchInspectVars, + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, + configsMap, ]) return ( diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index 6373a8591c..1ee7c030b9 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -5,3 +5,6 @@ export * from './use-workflow-run' export * from './use-workflow-start-run' export * from './use-is-chat-mode' export * from './use-workflow-refresh-draft' +export * from './use-fetch-workflow-inspect-vars' +export * from './use-inspect-vars-crud' +export * from './use-configs-map' diff --git a/web/app/components/workflow-app/hooks/use-configs-map.ts b/web/app/components/workflow-app/hooks/use-configs-map.ts new file mode 100644 index 0000000000..0db4f77856 --- /dev/null +++ b/web/app/components/workflow-app/hooks/use-configs-map.ts @@ -0,0 +1,12 @@ +import { useMemo } from 'react' +import { useStore } from '@/app/components/workflow/store' + +export const useConfigsMap = () => { + const appId = useStore(s => s.appId) + return useMemo(() => { + return { + conversationVarsUrl: `apps/${appId}/workflows/draft/conversation-variables`, + systemVarsUrl: `apps/${appId}/workflows/draft/system-variables`, + } + }, [appId]) +} diff --git a/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts b/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts index 9d3ff84929..07580c097e 100644 --- a/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts +++ b/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts @@ -1,19 +1,23 @@ +import { useCallback } from 'react' import type { NodeWithVar, VarInInspect } from '@/types/workflow' -import { useWorkflowStore } from '../../workflow/store' +import { useWorkflowStore } from '@/app/components/workflow/store' import { useStoreApi } from 'reactflow' import type { Node } from '@/app/components/workflow/types' import { fetchAllInspectVars } from '@/service/workflow' import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow' -import { useNodesInteractionsWithoutSync } from '../../workflow/hooks/use-nodes-interactions-without-sync' -const useSetWorkflowVarsWithValue = () => { +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { useConfigsMap } from './use-configs-map' + +export const useSetWorkflowVarsWithValue = () => { const workflowStore = useWorkflowStore() - const { setNodesWithInspectVars, appId } = workflowStore.getState() const store = useStoreApi() - const invalidateConversationVarValues = useInvalidateConversationVarValues(appId) - const invalidateSysVarValues = useInvalidateSysVarValues(appId) + const { conversationVarsUrl, systemVarsUrl } = useConfigsMap() + const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl) + const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl) const { handleCancelAllNodeSuccessStatus } = useNodesInteractionsWithoutSync() - const setInspectVarsToStore = (inspectVars: VarInInspect[]) => { + const setInspectVarsToStore = useCallback((inspectVars: VarInInspect[]) => { + const { setNodesWithInspectVars } = workflowStore.getState() const { getNodes } = store.getState() const nodeArr = getNodes() const nodesKeyValue: Record = {} @@ -51,18 +55,17 @@ const useSetWorkflowVarsWithValue = () => { return nodeWithVar }) setNodesWithInspectVars(res) - } + }, [workflowStore, store]) - const fetchInspectVars = async () => { + const fetchInspectVars = useCallback(async () => { + const { appId } = workflowStore.getState() invalidateConversationVarValues() invalidateSysVarValues() const data = await fetchAllInspectVars(appId) setInspectVarsToStore(data) handleCancelAllNodeSuccessStatus() // to make sure clear node output show the unset status - } + }, [workflowStore, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarsToStore, handleCancelAllNodeSuccessStatus]) return { fetchInspectVars, } } - -export default useSetWorkflowVarsWithValue diff --git a/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts b/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts new file mode 100644 index 0000000000..ce052b7ed4 --- /dev/null +++ b/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts @@ -0,0 +1,234 @@ +import { fetchNodeInspectVars } from '@/service/workflow' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' +import type { ValueSelector } from '@/app/components/workflow/types' +import type { VarInInspect } from '@/types/workflow' +import { VarInInspectType } from '@/types/workflow' +import { + useDeleteAllInspectorVars, + useDeleteInspectVar, + useDeleteNodeInspectorVars, + useEditInspectorVar, + useInvalidateConversationVarValues, + useInvalidateSysVarValues, + useResetConversationVar, + useResetToLastRunValue, +} from '@/service/use-workflow' +import { useCallback } from 'react' +import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import produce from 'immer' +import type { Node } from '@/app/components/workflow/types' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' +import { useConfigsMap } from './use-configs-map' + +export const useInspectVarsCrud = () => { + const workflowStore = useWorkflowStore() + const appId = useStore(s => s.appId) + const { conversationVarsUrl, systemVarsUrl } = useConfigsMap() + const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl) + const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId) + const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId) + const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl) + + const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId) + const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId) + const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId) + + const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId) + const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync() + const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() + const getNodeInspectVars = useCallback((nodeId: string) => { + const { nodesWithInspectVars } = workflowStore.getState() + const node = nodesWithInspectVars.find(node => node.nodeId === nodeId) + return node + }, [workflowStore]) + + const getVarId = useCallback((nodeId: string, varName: string) => { + const node = getNodeInspectVars(nodeId) + if (!node) + return undefined + const varId = node.vars.find((varItem) => { + return varItem.selector[1] === varName + })?.id + return varId + }, [getNodeInspectVars]) + + const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { + const node = getNodeInspectVars(nodeId) + if (!node) + return undefined + + const variable = node.vars.find((varItem) => { + return varItem.name === name + }) + return variable + }, [getNodeInspectVars]) + + const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { + const isEnv = isENV([nodeId]) + if (isEnv) // always have value + return true + const isSys = isSystemVar([nodeId]) + if (isSys) + return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) + const isChatVar = isConversationVar([nodeId]) + if (isChatVar) + return conversationVars.some(varItem => varItem.selector?.[1] === name) + return getInspectVar(nodeId, name) !== undefined + }, [getInspectVar]) + + const hasNodeInspectVars = useCallback((nodeId: string) => { + return !!getNodeInspectVars(nodeId) + }, [getNodeInspectVars]) + + const fetchInspectVarValue = useCallback(async (selector: ValueSelector) => { + const { + appId, + setNodeInspectVars, + } = workflowStore.getState() + const nodeId = selector[0] + const isSystemVar = nodeId === 'sys' + const isConversationVar = nodeId === 'conversation' + if (isSystemVar) { + invalidateSysVarValues() + return + } + if (isConversationVar) { + invalidateConversationVarValues() + return + } + const vars = await fetchNodeInspectVars(appId, nodeId) + setNodeInspectVars(nodeId, vars) + }, [workflowStore, invalidateSysVarValues, invalidateConversationVarValues]) + + // after last run would call this + const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => { + const { + nodesWithInspectVars, + setNodesWithInspectVars, + } = workflowStore.getState() + const nodes = produce(nodesWithInspectVars, (draft) => { + const nodeInfo = allNodes.find(node => node.id === nodeId) + if (nodeInfo) { + const index = draft.findIndex(node => node.nodeId === nodeId) + if (index === -1) { + draft.unshift({ + nodeId, + nodeType: nodeInfo.data.type, + title: nodeInfo.data.title, + vars: payload, + nodePayload: nodeInfo.data, + }) + } + else { + draft[index].vars = payload + // put the node to the topAdd commentMore actions + draft.unshift(draft.splice(index, 1)[0]) + } + } + }) + setNodesWithInspectVars(nodes) + handleCancelNodeSuccessStatus(nodeId) + }, [workflowStore, handleCancelNodeSuccessStatus]) + + const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => { + const { nodesWithInspectVars } = workflowStore.getState() + const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId) + if(!targetNode || !targetNode.vars) + return false + return targetNode.vars.some(item => item.id === varId) + }, [workflowStore]) + + const deleteInspectVar = useCallback(async (nodeId: string, varId: string) => { + const { deleteInspectVar } = workflowStore.getState() + if(hasNodeInspectVar(nodeId, varId)) { + await doDeleteInspectVar(varId) + deleteInspectVar(nodeId, varId) + } + }, [doDeleteInspectVar, workflowStore, hasNodeInspectVar]) + + const resetConversationVar = useCallback(async (varId: string) => { + await doResetConversationVar(varId) + invalidateConversationVarValues() + }, [doResetConversationVar, invalidateConversationVarValues]) + + const deleteNodeInspectorVars = useCallback(async (nodeId: string) => { + const { deleteNodeInspectVars } = workflowStore.getState() + if (hasNodeInspectVars(nodeId)) { + await doDeleteNodeInspectorVars(nodeId) + deleteNodeInspectVars(nodeId) + } + }, [doDeleteNodeInspectorVars, workflowStore, hasNodeInspectVars]) + + const deleteAllInspectorVars = useCallback(async () => { + const { deleteAllInspectVars } = workflowStore.getState() + await doDeleteAllInspectorVars() + await invalidateConversationVarValues() + await invalidateSysVarValues() + deleteAllInspectVars() + handleEdgeCancelRunningStatus() + }, [doDeleteAllInspectorVars, invalidateConversationVarValues, invalidateSysVarValues, workflowStore, handleEdgeCancelRunningStatus]) + + const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => { + const { setInspectVarValue } = workflowStore.getState() + await doEditInspectorVar({ + varId, + value, + }) + setInspectVarValue(nodeId, varId, value) + if (nodeId === VarInInspectType.conversation) + invalidateConversationVarValues() + if (nodeId === VarInInspectType.system) + invalidateSysVarValues() + }, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, workflowStore]) + + const renameInspectVarName = useCallback(async (nodeId: string, oldName: string, newName: string) => { + const { renameInspectVarName } = workflowStore.getState() + const varId = getVarId(nodeId, oldName) + if (!varId) + return + + const newSelector = [nodeId, newName] + await doEditInspectorVar({ + varId, + name: newName, + }) + renameInspectVarName(nodeId, varId, newSelector) + }, [doEditInspectorVar, getVarId, workflowStore]) + + const isInspectVarEdited = useCallback((nodeId: string, name: string) => { + const inspectVar = getInspectVar(nodeId, name) + if (!inspectVar) + return false + + return inspectVar.edited + }, [getInspectVar]) + + const resetToLastRunVar = useCallback(async (nodeId: string, varId: string) => { + const { resetToLastRunVar } = workflowStore.getState() + const isSysVar = nodeId === 'sys' + const data = await doResetToLastRunValue(varId) + + if(isSysVar) + invalidateSysVarValues() + else + resetToLastRunVar(nodeId, varId, data.value) + }, [doResetToLastRunValue, invalidateSysVarValues, workflowStore]) + + return { + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, + } +} diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index 99b88238f1..4c34d2ffb1 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -20,7 +20,7 @@ import type { VersionHistory } from '@/types/workflow' import { noop } from 'lodash-es' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useInvalidAllLastRun } from '@/service/use-workflow' -import useSetWorkflowVarsWithValue from './use-fetch-workflow-inspect-vars' +import { useSetWorkflowVarsWithValue } from './use-fetch-workflow-inspect-vars' export const useWorkflowRun = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index 019b32ae25..e2b4a7acc6 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -6,9 +6,9 @@ import Item from './item' import type { Plugin } from '@/app/components/plugins/types.ts' import cn from '@/utils/classnames' import Link from 'next/link' -import { MARKETPLACE_URL_PREFIX } from '@/config' import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react' import { noop } from 'lodash-es' +import { getMarketplaceUrl } from '@/utils/var' export type ListProps = { wrapElemRef: React.RefObject @@ -32,7 +32,7 @@ const List = forwardRef(({ const { t } = useTranslation() const hasFilter = !searchText const hasRes = list.length > 0 - const urlWithSearchText = `${MARKETPLACE_URL_PREFIX}/?q=${searchText}&tags=${tags.join(',')}` + const urlWithSearchText = getMarketplaceUrl('', { q: searchText, tags: tags.join(',') }) const nextToStickyELemRef = useRef(null) const { handleScroll, scrollPosition } = useStickyScroll({ @@ -71,7 +71,7 @@ const List = forwardRef(({ return ( {t('plugin.findMoreInMarketplace')} diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 0abf7b9031..f1bdbbfbd9 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -36,7 +36,7 @@ export type ToolValue = { provider_name: string tool_name: string tool_label: string - tool_description: string + tool_description?: string settings?: Record parameters?: Record enabled?: boolean diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx index ec016b1b65..5768e6bc06 100644 --- a/web/app/components/workflow/header/header-in-normal.tsx +++ b/web/app/components/workflow/header/header-in-normal.tsx @@ -33,6 +33,8 @@ const HeaderInNormal = ({ const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel) const setShowEnvPanel = useStore(s => s.setShowEnvPanel) const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel) + const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel) + const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel) const nodes = useNodes() const selectedNode = nodes.find(node => node.data.selected) const { handleBackupDraft } = useWorkflowRun() @@ -46,8 +48,10 @@ const HeaderInNormal = ({ setShowWorkflowVersionHistoryPanel(true) setShowEnvPanel(false) setShowDebugAndPreviewPanel(false) + setShowVariableInspectPanel(false) + setShowChatVariablePanel(false) }, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode, - setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel]) + setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel]) return ( <> diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 9f5e1a6650..4e8f74c774 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -7,6 +7,12 @@ import { } from 'zustand' import { createStore } from 'zustand/vanilla' import { HooksStoreContext } from './provider' +import type { IOtherOptions } from '@/service/base' +import type { VarInInspect } from '@/types/workflow' +import type { + Node, + ValueSelector, +} from '@/app/components/workflow/types' type CommonHooksFnMap = { doSyncWorkflowDraft: ( @@ -22,11 +28,30 @@ type CommonHooksFnMap = { handleBackupDraft: () => void handleLoadBackupDraft: () => void handleRestoreFromPublishedWorkflow: (...args: any[]) => void - handleRun: (...args: any[]) => void + handleRun: (params: any, callback?: IOtherOptions,) => void handleStopRun: (...args: any[]) => void handleStartWorkflowRun: () => void handleWorkflowStartRunInWorkflow: () => void handleWorkflowStartRunInChatflow: () => void + fetchInspectVars: () => Promise + hasNodeInspectVars: (nodeId: string) => boolean + hasSetInspectVar: (nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => boolean + fetchInspectVarValue: (selector: ValueSelector) => Promise + editInspectVarValue: (nodeId: string, varId: string, value: any) => Promise + renameInspectVarName: (nodeId: string, oldName: string, newName: string) => Promise + appendNodeInspectVars: (nodeId: string, payload: VarInInspect[], allNodes: Node[]) => void + deleteInspectVar: (nodeId: string, varId: string) => Promise + deleteNodeInspectorVars: (nodeId: string) => Promise + deleteAllInspectorVars: () => Promise + isInspectVarEdited: (nodeId: string, name: string) => boolean + resetToLastRunVar: (nodeId: string, varId: string) => Promise + invalidateSysVarValues: () => void + resetConversationVar: (varId: string) => Promise + invalidateConversationVarValues: () => void + configsMap?: { + conversationVarsUrl: string + systemVarsUrl: string + } } export type Shape = { @@ -45,6 +70,21 @@ export const createHooksStore = ({ handleStartWorkflowRun = noop, handleWorkflowStartRunInWorkflow = noop, handleWorkflowStartRunInChatflow = noop, + fetchInspectVars = async () => noop(), + hasNodeInspectVars = () => false, + hasSetInspectVar = () => false, + fetchInspectVarValue = async () => noop(), + editInspectVarValue = async () => noop(), + renameInspectVarName = async () => noop(), + appendNodeInspectVars = () => noop(), + deleteInspectVar = async () => noop(), + deleteNodeInspectorVars = async () => noop(), + deleteAllInspectorVars = async () => noop(), + isInspectVarEdited = () => false, + resetToLastRunVar = async () => noop(), + invalidateSysVarValues = noop, + resetConversationVar = async () => noop(), + invalidateConversationVarValues = noop, }: Partial) => { return createStore(set => ({ refreshAll: props => set(state => ({ ...state, ...props })), @@ -59,6 +99,21 @@ export const createHooksStore = ({ handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow, + fetchInspectVars, + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, })) } diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index fda0f50aa6..c2eebb0533 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -17,3 +17,5 @@ export * from './use-workflow-interactions' export * from './use-workflow-mode' export * from './use-format-time-from-now' export * from './use-workflow-refresh-draft' +export * from './use-inspect-vars-crud' +export * from './use-set-workflow-vars-with-value' diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts index 59cc98a17b..50188185c2 100644 --- a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts +++ b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts @@ -1,221 +1,29 @@ -import { fetchNodeInspectVars } from '@/service/workflow' -import { useStore, useWorkflowStore } from '../store' -import type { ValueSelector } from '../types' -import type { VarInInspect } from '@/types/workflow' -import { VarInInspectType } from '@/types/workflow' +import { useStore } from '../store' +import { useHooksStore } from '@/app/components/workflow/hooks-store' import { useConversationVarValues, - useDeleteAllInspectorVars, - useDeleteInspectVar, - useDeleteNodeInspectorVars, - useEditInspectorVar, - useInvalidateConversationVarValues, - useInvalidateSysVarValues, - useLastRun, - useResetConversationVar, - useResetToLastRunValue, useSysVarValues, } from '@/service/use-workflow' -import { useCallback, useEffect, useState } from 'react' -import { isConversationVar, isENV, isSystemVar } from '../nodes/_base/components/variable/utils' -import produce from 'immer' -import type { Node } from '@/app/components/workflow/types' -import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync' -import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync' const useInspectVarsCrud = () => { - const workflowStore = useWorkflowStore() const nodesWithInspectVars = useStore(s => s.nodesWithInspectVars) - const { - appId, - setNodeInspectVars, - setInspectVarValue, - renameInspectVarName: renameInspectVarNameInStore, - deleteAllInspectVars: deleteAllInspectVarsInStore, - deleteNodeInspectVars: deleteNodeInspectVarsInStore, - deleteInspectVar: deleteInspectVarInStore, - setNodesWithInspectVars, - resetToLastRunVar: resetToLastRunVarInStore, - } = workflowStore.getState() - - const { data: conversationVars } = useConversationVarValues(appId) - const invalidateConversationVarValues = useInvalidateConversationVarValues(appId) - const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId) - const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId) - const { data: systemVars } = useSysVarValues(appId) - const invalidateSysVarValues = useInvalidateSysVarValues(appId) - - const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId) - const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId) - const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId) - - const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId) - const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync() - const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() - const getNodeInspectVars = useCallback((nodeId: string) => { - const node = nodesWithInspectVars.find(node => node.nodeId === nodeId) - return node - }, [nodesWithInspectVars]) - - const getVarId = useCallback((nodeId: string, varName: string) => { - const node = getNodeInspectVars(nodeId) - if (!node) - return undefined - const varId = node.vars.find((varItem) => { - return varItem.selector[1] === varName - })?.id - return varId - }, [getNodeInspectVars]) - - const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { - const node = getNodeInspectVars(nodeId) - if (!node) - return undefined - - const variable = node.vars.find((varItem) => { - return varItem.name === name - }) - return variable - }, [getNodeInspectVars]) - - const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { - const isEnv = isENV([nodeId]) - if (isEnv) // always have value - return true - const isSys = isSystemVar([nodeId]) - if (isSys) - return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) - const isChatVar = isConversationVar([nodeId]) - if (isChatVar) - return conversationVars.some(varItem => varItem.selector?.[1] === name) - return getInspectVar(nodeId, name) !== undefined - }, [getInspectVar]) - - const hasNodeInspectVars = useCallback((nodeId: string) => { - return !!getNodeInspectVars(nodeId) - }, [getNodeInspectVars]) - - const fetchInspectVarValue = async (selector: ValueSelector) => { - const nodeId = selector[0] - const isSystemVar = nodeId === 'sys' - const isConversationVar = nodeId === 'conversation' - if (isSystemVar) { - invalidateSysVarValues() - return - } - if (isConversationVar) { - invalidateConversationVarValues() - return - } - const vars = await fetchNodeInspectVars(appId, nodeId) - setNodeInspectVars(nodeId, vars) - } - - // after last run would call this - const appendNodeInspectVars = (nodeId: string, payload: VarInInspect[], allNodes: Node[]) => { - const nodes = produce(nodesWithInspectVars, (draft) => { - const nodeInfo = allNodes.find(node => node.id === nodeId) - if (nodeInfo) { - const index = draft.findIndex(node => node.nodeId === nodeId) - if (index === -1) { - draft.push({ - nodeId, - nodeType: nodeInfo.data.type, - title: nodeInfo.data.title, - vars: payload, - }) - } - else { - draft[index].vars = payload - } - } - }) - setNodesWithInspectVars(nodes) - handleCancelNodeSuccessStatus(nodeId) - } - - const hasNodeInspectVar = (nodeId: string, varId: string) => { - const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId) - if(!targetNode || !targetNode.vars) - return false - return targetNode.vars.some(item => item.id === varId) - } - - const deleteInspectVar = async (nodeId: string, varId: string) => { - if(hasNodeInspectVar(nodeId, varId)) { - await doDeleteInspectVar(varId) - deleteInspectVarInStore(nodeId, varId) - } - } - - const resetConversationVar = async (varId: string) => { - await doResetConversationVar(varId) - invalidateConversationVarValues() - } - - const deleteNodeInspectorVars = async (nodeId: string) => { - if (hasNodeInspectVars(nodeId)) { - await doDeleteNodeInspectorVars(nodeId) - deleteNodeInspectVarsInStore(nodeId) - } - } - - const deleteAllInspectorVars = async () => { - await doDeleteAllInspectorVars() - await invalidateConversationVarValues() - await invalidateSysVarValues() - deleteAllInspectVarsInStore() - handleEdgeCancelRunningStatus() - } - - const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => { - await doEditInspectorVar({ - varId, - value, - }) - setInspectVarValue(nodeId, varId, value) - if (nodeId === VarInInspectType.conversation) - invalidateConversationVarValues() - if (nodeId === VarInInspectType.system) - invalidateSysVarValues() - }, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarValue]) - - const [currNodeId, setCurrNodeId] = useState(null) - const [currEditVarId, setCurrEditVarId] = useState(null) - const { data } = useLastRun(appId, currNodeId || '', !!currNodeId) - useEffect(() => { - if (data && currNodeId && currEditVarId) { - const inspectVar = getNodeInspectVars(currNodeId)?.vars?.find(item => item.id === currEditVarId) - resetToLastRunVarInStore(currNodeId, currEditVarId, data.outputs?.[inspectVar?.selector?.[1] || '']) - } - }, [data, currNodeId, currEditVarId, getNodeInspectVars, editInspectVarValue, resetToLastRunVarInStore]) - - const renameInspectVarName = async (nodeId: string, oldName: string, newName: string) => { - const varId = getVarId(nodeId, oldName) - if (!varId) - return - - const newSelector = [nodeId, newName] - await doEditInspectorVar({ - varId, - name: newName, - }) - renameInspectVarNameInStore(nodeId, varId, newSelector) - } - - const isInspectVarEdited = useCallback((nodeId: string, name: string) => { - const inspectVar = getInspectVar(nodeId, name) - if (!inspectVar) - return false - - return inspectVar.edited - }, [getInspectVar]) - - const resetToLastRunVar = async (nodeId: string, varId: string) => { - await doResetToLastRunValue(varId) - setCurrNodeId(nodeId) - setCurrEditVarId(varId) - } + const configsMap = useHooksStore(s => s.configsMap) + const { data: conversationVars } = useConversationVarValues(configsMap?.conversationVarsUrl) + const { data: systemVars } = useSysVarValues(configsMap?.systemVarsUrl) + const hasNodeInspectVars = useHooksStore(s => s.hasNodeInspectVars) + const hasSetInspectVar = useHooksStore(s => s.hasSetInspectVar) + const fetchInspectVarValue = useHooksStore(s => s.fetchInspectVarValue) + const editInspectVarValue = useHooksStore(s => s.editInspectVarValue) + const renameInspectVarName = useHooksStore(s => s.renameInspectVarName) + const appendNodeInspectVars = useHooksStore(s => s.appendNodeInspectVars) + const deleteInspectVar = useHooksStore(s => s.deleteInspectVar) + const deleteNodeInspectorVars = useHooksStore(s => s.deleteNodeInspectorVars) + const deleteAllInspectorVars = useHooksStore(s => s.deleteAllInspectorVars) + const isInspectVarEdited = useHooksStore(s => s.isInspectVarEdited) + const resetToLastRunVar = useHooksStore(s => s.resetToLastRunVar) + const invalidateSysVarValues = useHooksStore(s => s.invalidateSysVarValues) + const resetConversationVar = useHooksStore(s => s.resetConversationVar) + const invalidateConversationVarValues = useHooksStore(s => s.invalidateConversationVarValues) return { conversationVars: conversationVars || [], diff --git a/web/app/components/workflow/hooks/use-nodes-data.ts b/web/app/components/workflow/hooks/use-nodes-data.ts index aeb45ddb93..7df6b2ffd0 100644 --- a/web/app/components/workflow/hooks/use-nodes-data.ts +++ b/web/app/components/workflow/hooks/use-nodes-data.ts @@ -34,15 +34,14 @@ export const useNodesExtraData = () => { export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean, isInLoop?: boolean) => { const nodesExtraData = useNodesExtraData() const availablePrevBlocks = useMemo(() => { - if (!nodeType) + if (!nodeType || !nodesExtraData[nodeType]) return [] return nodesExtraData[nodeType].availablePrevNodes || [] }, [nodeType, nodesExtraData]) const availableNextBlocks = useMemo(() => { - if (!nodeType) + if (!nodeType || !nodesExtraData[nodeType]) return [] - return nodesExtraData[nodeType].availableNextNodes || [] }, [nodeType, nodesExtraData]) @@ -55,10 +54,7 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) return false - if (!isInLoop && nType === BlockEnum.LoopEnd) - return false - - return true + return !(!isInLoop && nType === BlockEnum.LoopEnd) }), availableNextBlocks: availableNextBlocks.filter((nType) => { if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) @@ -67,10 +63,7 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) return false - if (!isInLoop && nType === BlockEnum.LoopEnd) - return false - - return true + return !(!isInLoop && nType === BlockEnum.LoopEnd) }), } }, [isInIteration, availablePrevBlocks, availableNextBlocks, isInLoop]) diff --git a/web/app/components/workflow/hooks/use-set-workflow-vars-with-value.ts b/web/app/components/workflow/hooks/use-set-workflow-vars-with-value.ts new file mode 100644 index 0000000000..a04c2de305 --- /dev/null +++ b/web/app/components/workflow/hooks/use-set-workflow-vars-with-value.ts @@ -0,0 +1,9 @@ +import { useHooksStore } from '@/app/components/workflow/hooks-store' + +export const useSetWorkflowVarsWithValue = () => { + const fetchInspectVars = useHooksStore(s => s.fetchInspectVars) + + return { + fetchInspectVars, + } +} diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 429d07853d..8631eb58e3 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -42,6 +42,7 @@ import { useNodesSyncDraft, usePanelInteractions, useSelectionInteractions, + useSetWorkflowVarsWithValue, useShortcuts, useWorkflow, useWorkflowReadOnly, @@ -82,7 +83,6 @@ import Confirm from '@/app/components/base/confirm' import DatasetsDetailProvider from './datasets-detail-store/provider' import { HooksStoreContextProvider } from './hooks-store' import type { Shape as HooksStoreShape } from './hooks-store' -import useSetWorkflowVarsWithValue from '../workflow-app/hooks/use-fetch-workflow-inspect-vars' const nodeTypes = { [CUSTOM_NODE]: CustomNode, diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index 38968b2e0d..65afb36835 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -15,6 +15,7 @@ import { import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' import type { FileEntity } from '@/app/components/base/file-uploader/types' import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log' +import ActionButton from '@/app/components/base/action-button' type Props = { className?: string @@ -88,15 +89,16 @@ const Base: FC = ({
)} - {!isCopied - ? ( - - ) - : ( - - ) - } - + + {!isCopied + ? ( + + ) + : ( + + ) + } +
diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx index afb642955c..25d1a0aa63 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx @@ -1,10 +1,10 @@ import { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' +import { isEqual } from 'lodash-es' import { getConnectedEdges, getOutgoers, - useEdges, - useStoreApi, + useStore, } from 'reactflow' import { useToolIcon } from '../../../../hooks' import BlockIcon from '../../../../block-icon' @@ -26,12 +26,21 @@ const NextStep = ({ const { t } = useTranslation() const data = selectedNode.data const toolIcon = useToolIcon(data) - const store = useStoreApi() const branches = useMemo(() => { return data._targetBranches || [] }, [data]) - const edges = useEdges() - const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges) + const edges = useStore(s => s.edges.map(edge => ({ + id: edge.id, + source: edge.source, + sourceHandle: edge.sourceHandle, + target: edge.target, + targetHandle: edge.targetHandle, + })), isEqual) + const nodes = useStore(s => s.getNodes().map(node => ({ + id: node.id, + data: node.data, + })), isEqual) + const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges) const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id) const list = useMemo(() => { diff --git a/web/app/components/workflow/nodes/_base/components/node-position.tsx b/web/app/components/workflow/nodes/_base/components/node-position.tsx index 404648dfa6..e844726b4f 100644 --- a/web/app/components/workflow/nodes/_base/components/node-position.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-position.tsx @@ -1,30 +1,39 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' +import { useShallow } from 'zustand/react/shallow' import { RiCrosshairLine } from '@remixicon/react' -import type { XYPosition } from 'reactflow' -import { useReactFlow, useStoreApi } from 'reactflow' +import { useReactFlow, useStore } from 'reactflow' import TooltipPlus from '@/app/components/base/tooltip' import { useNodesSyncDraft } from '@/app/components/workflow-app/hooks' type NodePositionProps = { - nodePosition: XYPosition, - nodeWidth?: number | null, - nodeHeight?: number | null, + nodeId: string } const NodePosition = ({ - nodePosition, - nodeWidth, - nodeHeight, + nodeId, }: NodePositionProps) => { const { t } = useTranslation() const reactflow = useReactFlow() - const store = useStoreApi() const { doSyncWorkflowDraft } = useNodesSyncDraft() + const { + nodePosition, + nodeWidth, + nodeHeight, + } = useStore(useShallow((s) => { + const nodes = s.getNodes() + const currentNode = nodes.find(node => node.id === nodeId)! + + return { + nodePosition: currentNode.position, + nodeWidth: currentNode.width, + nodeHeight: currentNode.height, + } + })) + const transform = useStore(s => s.transform) if (!nodePosition || !nodeWidth || !nodeHeight) return null const workflowContainer = document.getElementById('workflow-container') - const { transform } = store.getState() const zoom = transform[2] const { clientWidth, clientHeight } = workflowContainer! diff --git a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx index cddd529aea..94b3ce7bfc 100644 --- a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx +++ b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx @@ -15,7 +15,7 @@ import { pluginManifestToCardPluginProps } from '@/app/components/plugins/instal import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index' import Link from 'next/link' import { useTranslation } from 'react-i18next' -import { MARKETPLACE_URL_PREFIX } from '@/config' +import { getMarketplaceUrl } from '@/utils/var' export type SwitchPluginVersionProps = { uniqueIdentifier: string @@ -82,7 +82,7 @@ export const SwitchPluginVersion: FC = (props) => { modalBottomLeft={ diff --git a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx index 0adfa3c9fb..0d965e2d22 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx @@ -13,6 +13,8 @@ type Props = { readonly: boolean value: string onChange: (value: string | number, varKindType: VarKindType, varInfo?: Var) => void + onOpenChange?: (open: boolean) => void + isLoading?: boolean } const DEFAULT_SCHEMA = {} as CredentialFormSchema @@ -22,6 +24,8 @@ const ConstantField: FC = ({ readonly, value, onChange, + onOpenChange, + isLoading, }) => { const language = useLanguage() const placeholder = (schema as CredentialFormSchemaSelect).placeholder @@ -36,7 +40,7 @@ const ConstantField: FC = ({ return ( <> - {schema.type === FormTypeEnum.select && ( + {(schema.type === FormTypeEnum.select || schema.type === FormTypeEnum.dynamicSelect) && ( = ({ items={(schema as CredentialFormSchemaSelect).options.map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))} onSelect={item => handleSelectChange(item.value)} placeholder={placeholder?.[language] || placeholder?.en_US} + onOpenChange={onOpenChange} + isLoading={isLoading} /> )} {schema.type === FormTypeEnum.textNumber && ( diff --git a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx index dba93aaf97..a7c9a9d172 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx @@ -8,7 +8,7 @@ import RemoveButton from '../remove-button' import VarTypePicker from './var-type-picker' import Input from '@/app/components/base/input' import type { VarType } from '@/app/components/workflow/types' -import { checkKeys } from '@/utils/var' +import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' import Toast from '@/app/components/base/toast' type Props = { @@ -37,6 +37,8 @@ const OutputVarList: FC = ({ const handleVarNameChange = useCallback((index: number) => { return (e: React.ChangeEvent) => { const oldKey = list[index].variable + + replaceSpaceWithUnderscreInVarNameInput(e.target) const newKey = e.target.value const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index c0f66e4998..b5cd76b8e5 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -1091,13 +1091,13 @@ export const getNodeUsedVarPassToServerKey = (node: Node, valueSelector: ValueSe break } case BlockEnum.Code: { - const targetVar = (data as CodeNodeType).variables?.find(v => v.value_selector.join('.') === valueSelector.join('.')) + const targetVar = (data as CodeNodeType).variables?.find(v => Array.isArray(v.value_selector) && v.value_selector && v.value_selector.join('.') === valueSelector.join('.')) if (targetVar) res = targetVar.variable break } case BlockEnum.TemplateTransform: { - const targetVar = (data as TemplateTransformNodeType).variables?.find(v => v.value_selector.join('.') === valueSelector.join('.')) + const targetVar = (data as TemplateTransformNodeType).variables?.find(v => Array.isArray(v.value_selector) && v.value_selector && v.value_selector.join('.') === valueSelector.join('.')) if (targetVar) res = targetVar.variable break diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index 181b278051..73ee8262a0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -8,7 +8,7 @@ import VarReferencePicker from './var-reference-picker' import Input from '@/app/components/base/input' import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { checkKeys } from '@/utils/var' +import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' import Toast from '@/app/components/base/toast' type Props = { @@ -38,6 +38,8 @@ const VarList: FC = ({ const handleVarNameChange = useCallback((index: number) => { return (e: React.ChangeEvent) => { + replaceSpaceWithUnderscreInVarNameInput(e.target) + const newKey = e.target.value const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) if (!isValid) { @@ -65,10 +67,11 @@ const VarList: FC = ({ }, [list, onVarNameChange, onChange]) const handleVarReferenceChange = useCallback((index: number) => { - return (value: ValueSelector | string, varKindType: VarKindType) => { + return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => { const newList = produce(list, (draft) => { if (!isSupportConstantValue || varKindType === VarKindType.variable) { draft[index].value_selector = value as ValueSelector + draft[index].value_type = varInfo?.type if (isSupportConstantValue) draft[index].variable_type = VarKindType.variable diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index e9825cd44a..23ccea2572 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -6,6 +6,7 @@ import { RiArrowDownSLine, RiCloseLine, RiErrorWarningFill, + RiLoader4Line, RiMoreLine, } from '@remixicon/react' import produce from 'immer' @@ -16,8 +17,9 @@ import VarReferencePopup from './var-reference-popup' import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils' import ConstantField from './constant-field' import cn from '@/utils/classnames' -import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' -import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' +import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { BlockEnum } from '@/app/components/workflow/types' import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' @@ -40,6 +42,8 @@ import Tooltip from '@/app/components/base/tooltip' import { isExceptionVariable } from '@/app/components/workflow/utils' import VarFullPathPanel from './var-full-path-panel' import { noop } from 'lodash-es' +import { useFetchDynamicOptions } from '@/service/use-plugins' +import type { Tool } from '@/app/components/tools/types' const TRIGGER_DEFAULT_WIDTH = 227 @@ -68,6 +72,8 @@ type Props = { minWidth?: number popupFor?: 'assigned' | 'toAssigned' zIndex?: number + currentTool?: Tool + currentProvider?: ToolWithProvider } const DEFAULT_VALUE_SELECTOR: Props['value'] = [] @@ -97,6 +103,8 @@ const VarReferencePicker: FC = ({ minWidth, popupFor, zIndex, + currentTool, + currentProvider, }) => { const { t } = useTranslation() const store = useStoreApi() @@ -176,9 +184,11 @@ const VarReferencePicker: FC = ({ return startNode?.data const node = getNodeInfoById(availableNodes, outputVarNodeId)?.data - return { - ...node, - id: outputVarNodeId, + if (node) { + return { + ...node, + id: outputVarNodeId, + } } }, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode, isLoopVar, loopNode]) @@ -316,6 +326,42 @@ const VarReferencePicker: FC = ({ return null }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) + + const [dynamicOptions, setDynamicOptions] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions( + currentProvider?.plugin_id || '', currentProvider?.name || '', currentTool?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '', + 'tool', + ) + const handleFetchDynamicOptions = async () => { + if (schema?.type !== FormTypeEnum.dynamicSelect || !currentTool || !currentProvider) + return + setIsLoading(true) + try { + const data = await fetchDynamicOptions() + setDynamicOptions(data?.options || []) + } + finally { + setIsLoading(false) + } + } + useEffect(() => { + handleFetchDynamicOptions() + }, [currentTool, currentProvider, schema]) + + const schemaWithDynamicSelect = useMemo(() => { + if (schema?.type !== FormTypeEnum.dynamicSelect) + return schema + // rewrite schema.options with dynamicOptions + if (dynamicOptions) { + return { + ...schema, + options: dynamicOptions, + } + } + return schema + }, [dynamicOptions]) + return (
= ({ void)} - schema={schema as CredentialFormSchema} + schema={schemaWithDynamicSelect as CredentialFormSchema} readonly={readonly} + isLoading={isLoading} /> ) : ( @@ -412,6 +459,7 @@ const VarReferencePicker: FC = ({ )}
{!hasValue && } + {isLoading && } {isEnv && } {isChatVar && }
= ({ {!isValidVar && } ) - :
{placeholder ?? t('workflow.common.setVarValuePlaceholder')}
} + :
+ {isLoading ? ( +
+ + {placeholder ?? t('workflow.common.setVarValuePlaceholder')} +
+ ) : ( + placeholder ?? t('workflow.common.setVarValuePlaceholder') + )} +
}
diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index a47bb226b2..164369e64c 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -62,15 +62,14 @@ import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevice type BasePanelProps = { children: ReactNode -} & Node + id: Node['id'] + data: Node['data'] +} const BasePanel: FC = ({ id, data, children, - position, - width, - height, }) => { const { t } = useTranslation() const { showMessageLogModal } = useAppStore(useShallow(state => ({ @@ -330,7 +329,7 @@ const BasePanel: FC = ({ ) } - +
diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 27d6adc62b..88771d098e 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -48,7 +48,9 @@ import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' type BaseNodeProps = { children: ReactElement -} & NodeProps + id: NodeProps['id'] + data: NodeProps['data'] +} const BaseNode: FC = ({ id, diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index 05d6cd7957..f928c13bd6 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -14,6 +14,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' import type { NodePanelProps } from '@/app/components/workflow/types' +import SyncButton from '@/app/components/base/button/sync-button' const i18nPrefix = 'workflow.nodes.code' const codeLanguages = [ @@ -40,6 +41,7 @@ const Panel: FC> = ({ handleVarListChange, handleAddVariable, handleRemoveVariable, + handleSyncFunctionSignature, handleCodeChange, handleCodeLanguageChange, handleVarsChange, @@ -68,7 +70,12 @@ const Panel: FC> = ({ : undefined + !readOnly ? ( +
+ + +
+ ) : undefined } > { setInputs(newInputs) }, [allLanguageDefault, inputs, setInputs]) + const handleSyncFunctionSignature = useCallback(() => { + const generateSyncSignatureCode = (code: string) => { + let mainDefRe + let newMainDef + if (inputs.code_language === CodeLanguage.javascript) { + mainDefRe = /function\s+main\b\s*\([\s\S]*?\)/g + newMainDef = 'function main({{var_list}})' + let param_list = inputs.variables?.map(item => item.variable).join(', ') || '' + param_list = param_list ? `{${param_list}}` : '' + newMainDef = newMainDef.replace('{{var_list}}', param_list) + } + + else if (inputs.code_language === CodeLanguage.python3) { + mainDefRe = /def\s+main\b\s*\([\s\S]*?\)/g + const param_list = [] + for (const item of inputs.variables) { + let param = item.variable + let param_type = '' + switch (item.value_type) { + case VarType.string: + param_type = ': str' + break + case VarType.number: + param_type = ': float' + break + case VarType.object: + param_type = ': dict' + break + case VarType.array: + param_type = ': list' + break + case VarType.arrayNumber: + param_type = ': list[float]' + break + case VarType.arrayString: + param_type = ': list[str]' + break + case VarType.arrayObject: + param_type = ': list[dict]' + break + } + param += param_type + param_list.push(`${param}`) + } + + newMainDef = `def main(${param_list.join(', ')})` + } + else { return code } + + const newCode = code.replace(mainDefRe, newMainDef) + return newCode + } + + const newInputs = produce(inputs, (draft) => { + draft.code = generateSyncSignatureCode(draft.code) + }) + setInputs(newInputs) + }, [inputs, setInputs]) + const { handleVarsChange, handleAddVariable: handleAddOutputVariable, @@ -119,6 +178,7 @@ const useConfig = (id: string, payload: CodeNodeType) => { handleVarListChange, handleAddVariable, handleRemoveVariable, + handleSyncFunctionSignature, handleCodeChange, handleCodeLanguageChange, handleVarsChange, diff --git a/web/app/components/workflow/nodes/http/node.tsx b/web/app/components/workflow/nodes/http/node.tsx index ad4b5f58ff..aa1912bd59 100644 --- a/web/app/components/workflow/nodes/http/node.tsx +++ b/web/app/components/workflow/nodes/http/node.tsx @@ -13,7 +13,7 @@ const Node: FC> = ({ return (
-
+
{method}
{ const nodeData = props.data - const NodeComponent = NodeComponentMap[nodeData.type] + const NodeComponent = useMemo(() => NodeComponentMap[nodeData.type], [nodeData.type]) return ( <> - + @@ -26,7 +29,12 @@ const CustomNode = (props: NodeProps) => { } CustomNode.displayName = 'CustomNode' -export const Panel = memo((props: Node) => { +export type PanelProps = { + type: Node['type'] + id: Node['id'] + data: Node['data'] +} +export const Panel = memo((props: PanelProps) => { const nodeClass = props.type const nodeData = props.data const PanelComponent = useMemo(() => { @@ -38,7 +46,11 @@ export const Panel = memo((props: Node) => { if (nodeClass === CUSTOM_NODE) { return ( - + ) diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 04acb61ef8..2a71dffa11 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -19,6 +19,8 @@ import Editor from '@/app/components/workflow/nodes/_base/components/prompt/edit import StructureOutput from './components/structure-output' import Switch from '@/app/components/base/switch' import { RiAlertFill, RiQuestionLine } from '@remixicon/react' +import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' +import Toast from '@/app/components/base/toast' const i18nPrefix = 'workflow.nodes.llm' @@ -68,10 +70,27 @@ const Panel: FC> = ({ modelId: string mode?: string }) => { - handleCompletionParamsChange({}) - handleModelChanged(model) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + (async () => { + try { + const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams( + model.provider, + model.modelId, + inputs.model.completion_params, + ) + const keys = Object.keys(removedDetails) + if (keys.length) + Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` }) + handleCompletionParamsChange(filtered) + } + catch (e) { + Toast.notify({ type: 'error', message: t('common.error') }) + handleCompletionParamsChange({}) + } + finally { + handleModelChanged(model) + } + })() + }, [inputs.model.completion_params]) return (
diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 1a609c58f5..244e54a5f2 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import type { ToolVarInputs } from '../types' import { VarType as VarKindType } from '../types' import cn from '@/utils/classnames' -import type { ValueSelector, Var } from '@/app/components/workflow/types' +import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -17,6 +17,7 @@ import { VarType } from '@/app/components/workflow/types' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import { noop } from 'lodash-es' +import type { Tool } from '@/app/components/tools/types' type Props = { readOnly: boolean @@ -27,6 +28,8 @@ type Props = { onOpen?: (index: number) => void isSupportConstantValue?: boolean filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean + currentTool?: Tool + currentProvider?: ToolWithProvider } const InputVarList: FC = ({ @@ -38,6 +41,8 @@ const InputVarList: FC = ({ onOpen = noop, isSupportConstantValue, filterVar, + currentTool, + currentProvider, }) => { const language = useLanguage() const { t } = useTranslation() @@ -58,6 +63,8 @@ const InputVarList: FC = ({ return 'ModelSelector' else if (type === FormTypeEnum.toolSelector) return 'ToolSelector' + else if (type === FormTypeEnum.dynamicSelect || type === FormTypeEnum.select) + return 'Select' else return 'String' } @@ -149,6 +156,7 @@ const InputVarList: FC = ({ const handleOpen = useCallback((index: number) => { return () => onOpen(index) }, [onOpen]) + return (
{ @@ -163,7 +171,8 @@ const InputVarList: FC = ({ } = schema const varInput = value[variable] const isNumber = type === FormTypeEnum.textNumber - const isSelect = type === FormTypeEnum.select + const isDynamicSelect = type === FormTypeEnum.dynamicSelect + const isSelect = type === FormTypeEnum.select || type === FormTypeEnum.dynamicSelect const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector @@ -198,11 +207,13 @@ const InputVarList: FC = ({ value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])} onChange={handleNotMixedTypeChange(variable)} onOpen={handleOpen(index)} - defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)} + defaultVarKindType={varInput?.type || ((isNumber || isDynamicSelect) ? VarKindType.constant : VarKindType.variable)} isSupportConstantValue={isSupportConstantValue} filterVar={isNumber ? filterVar : undefined} availableVars={isSelect ? availableVars : undefined} schema={schema} + currentTool={currentTool} + currentProvider={currentProvider} /> )} {isFile && ( diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index 5dd5242c71..038159870e 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -42,6 +42,7 @@ const Panel: FC> = ({ isLoading, outputSchema, hasObjectOutput, + currTool, } = useConfig(id, data) if (isLoading) { @@ -80,6 +81,8 @@ const Panel: FC> = ({ filterVar={filterVar} isSupportConstantValue onOpen={handleOnVarOpen} + currentProvider={currCollection} + currentTool={currTool} /> )} diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx index cf9d4152a4..60be8a0842 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx @@ -15,7 +15,7 @@ import { VarType } from '@/app/components/workflow/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { Folder } from '@/app/components/base/icons/src/vender/line/files' -import { checkKeys } from '@/utils/var' +import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' import Toast from '@/app/components/base/toast' const i18nPrefix = 'workflow.nodes.variableAssigner' @@ -89,6 +89,7 @@ const VarGroupItem: FC = ({ }] = useBoolean(false) const handleGroupNameChange = useCallback((e: ChangeEvent) => { + replaceSpaceWithUnderscreInVarNameInput(e.target) const value = e.target.value const { isValid, errorKey, errorMessageKey } = checkKeys([value], false) if (!isValid) { diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx index 0782ca141e..869317ca6a 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx @@ -16,7 +16,7 @@ import type { ConversationVariable } from '@/app/components/workflow/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import cn from '@/utils/classnames' -import { checkKeys } from '@/utils/var' +import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' export type ModalPropsType = { chatVar?: ConversationVariable @@ -143,6 +143,13 @@ const ChatVariableModal = ({ return true } + const handleVarNameChange = (e: React.ChangeEvent) => { + replaceSpaceWithUnderscreInVarNameInput(e.target) + if (!!e.target.value && !checkVariableName(e.target.value)) + return + setName(e.target.value || '') + } + const handleTypeChange = (v: ChatVarType) => { setValue(undefined) setEditorContent(undefined) @@ -275,7 +282,7 @@ const ChatVariableModal = ({ setName(e.target.value || '')} + onChange={handleVarNameChange} onBlur={e => checkVariableName(e.target.value)} type='text' /> diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 8464d04425..26d604ef11 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -8,7 +8,10 @@ import { import { useTranslation } from 'react-i18next' import { produce, setAutoFreeze } from 'immer' import { uniqBy } from 'lodash-es' -import { useWorkflowRun } from '../../hooks' +import { + useSetWorkflowVarsWithValue, + useWorkflowRun, +} from '../../hooks' import { NodeRunningStatus, WorkflowRunningStatus } from '../../types' import { useWorkflowStore } from '../../store' import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../constants' @@ -32,7 +35,6 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types' import { getThreadMessages } from '@/app/components/base/chat/utils' import { useInvalidAllLastRun } from '@/service/use-workflow' import { useParams } from 'next/navigation' -import useSetWorkflowVarsWithValue from '@/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars' type GetAbortController = (abortController: AbortController) => void type SendCallback = { @@ -499,7 +501,7 @@ export const useChat = ( }, }, ) - }, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, notify, t, config?.suggested_questions_after_answer?.enabled]) + }, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, notify, t, config?.suggested_questions_after_answer?.enabled, fetchInspectVars, invalidAllLastRun]) return { conversationId: conversationId.current, diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx index 43cbee625d..4877575d7e 100644 --- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx @@ -10,7 +10,7 @@ import { ToastContext } from '@/app/components/base/toast' import { useStore } from '@/app/components/workflow/store' import type { EnvironmentVariable } from '@/app/components/workflow/types' import cn from '@/utils/classnames' -import { checkKeys } from '@/utils/var' +import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' export type ModalPropsType = { env?: EnvironmentVariable @@ -43,6 +43,13 @@ const VariableModal = ({ return true } + const handleVarNameChange = (e: React.ChangeEvent) => { + replaceSpaceWithUnderscreInVarNameInput(e.target) + if (!!e.target.value && !checkVariableName(e.target.value)) + return + setName(e.target.value || '') + } + const handleSave = () => { if (!checkVariableName(name)) return @@ -130,7 +137,7 @@ const VariableModal = ({ setName(e.target.value || '')} + onChange={handleVarNameChange} onBlur={e => checkVariableName(e.target.value)} type='text' /> diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index 775191ecee..c728df97ee 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' -import { memo, useEffect, useRef } from 'react' -import { useNodes } from 'reactflow' -import type { CommonNodeType } from '../types' +import { useShallow } from 'zustand/react/shallow' +import { memo, useCallback, useEffect, useRef } from 'react' +import { useStore as useReactflow } from 'reactflow' import { Panel as NodePanel } from '../nodes' import { useStore } from '../store' import EnvPanel from './env-panel' @@ -13,51 +13,83 @@ export type PanelProps = { right?: React.ReactNode } } + +/** + * Reference MDN standard implementation:https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserverEntry/borderBoxSize + */ +const getEntryWidth = (entry: ResizeObserverEntry, element: HTMLElement): number => { + if (entry.borderBoxSize?.length > 0) + return entry.borderBoxSize[0].inlineSize + + if (entry.contentRect.width > 0) + return entry.contentRect.width + + return element.getBoundingClientRect().width +} + +const useResizeObserver = ( + callback: (width: number) => void, + dependencies: React.DependencyList = [], +) => { + const elementRef = useRef(null) + + const stableCallback = useCallback(callback, [callback]) + + useEffect(() => { + const element = elementRef.current + if (!element) return + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const width = getEntryWidth(entry, element) + stableCallback(width) + } + }) + + resizeObserver.observe(element) + + const initialWidth = element.getBoundingClientRect().width + stableCallback(initialWidth) + + return () => { + resizeObserver.disconnect() + } + }, [stableCallback, ...dependencies]) + return elementRef +} + const Panel: FC = ({ components, }) => { - const nodes = useNodes() - const selectedNode = nodes.find(node => node.data.selected) + const selectedNode = useReactflow(useShallow((s) => { + const nodes = s.getNodes() + const currentNode = nodes.find(node => node.data.selected) + + if (currentNode) { + return { + id: currentNode.id, + type: currentNode.type, + data: currentNode.data, + } + } + })) const showEnvPanel = useStore(s => s.showEnvPanel) const isRestoring = useStore(s => s.isRestoring) + const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel) - const rightPanelRef = useRef(null) const setRightPanelWidth = useStore(s => s.setRightPanelWidth) + const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth) - // get right panel width - useEffect(() => { - if (rightPanelRef.current) { - const resizeRightPanelObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const { inlineSize } = entry.borderBoxSize[0] - setRightPanelWidth(inlineSize) - } - }) - resizeRightPanelObserver.observe(rightPanelRef.current) - return () => { - resizeRightPanelObserver.disconnect() - } - } - }, [setRightPanelWidth]) + const rightPanelRef = useResizeObserver( + setRightPanelWidth, + [setRightPanelWidth, selectedNode, showEnvPanel, showWorkflowVersionHistoryPanel], + ) - const otherPanelRef = useRef(null) - const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth) + const otherPanelRef = useResizeObserver( + setOtherPanelWidth, + [setOtherPanelWidth, showEnvPanel, showWorkflowVersionHistoryPanel], + ) - // get other panel width - useEffect(() => { - if (otherPanelRef.current) { - const resizeOtherPanelObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const { inlineSize } = entry.borderBoxSize[0] - setOtherPanelWidth(inlineSize) - } - }) - resizeOtherPanelObserver.observe(otherPanelRef.current) - return () => { - resizeOtherPanelObserver.disconnect() - } - } - }, [setOtherPanelWidth]) return (
= ({ className={cn('absolute bottom-1 right-0 top-14 z-10 flex outline-none')} key={`${isRestoring}`} > - { - components?.left - } - { - !!selectedNode && ( - - ) - } + {components?.left} + {!!selectedNode && }
- { - components?.right - } - { - showEnvPanel && ( - - ) - } + {components?.right} + {showEnvPanel && }
) diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 9555cbd087..a4df5f4c74 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -29,6 +29,7 @@ import type { import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import { hasRetryNode } from '@/app/components/workflow/utils' import { useDocLink } from '@/context/i18n' +import Tooltip from '@/app/components/base/tooltip' type Props = { className?: string @@ -129,10 +130,16 @@ const NodePanel: FC = ({ /> )} -
{nodeInfo.title}
+ {nodeInfo.title}
+ } + > +
{nodeInfo.title}
+ {nodeInfo.status !== 'running' && !hideInfo && (
{nodeInfo.execution_metadata?.total_tokens ? `${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens · ` : ''}{`${getTime(nodeInfo.elapsed_time || 0)}`}
)} diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 79fb441206..3248ce798d 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -136,6 +136,7 @@ export type Variable = { variable: string } value_selector: ValueSelector + value_type?: VarType variable_type?: VarKindType value?: string options?: string[] diff --git a/web/app/components/workflow/variable-inspect/right.tsx b/web/app/components/workflow/variable-inspect/right.tsx index 851078d972..6ddd0d47d3 100644 --- a/web/app/components/workflow/variable-inspect/right.tsx +++ b/web/app/components/workflow/variable-inspect/right.tsx @@ -63,6 +63,17 @@ const Right = ({ resetConversationVar(currentNodeVar.var.id) } + const getCopyContent = () => { + const value = currentNodeVar?.var.value + if (value === null || value === undefined) + return '' + + if (typeof value === 'object') + return JSON.stringify(value) + + return String(value) + } + return (
{/* header */} @@ -124,7 +135,7 @@ const Right = ({ )} {currentNodeVar.var.value_type !== 'secret' && ( - + )} )} diff --git a/web/app/components/workflow/variable-inspect/trigger.tsx b/web/app/components/workflow/variable-inspect/trigger.tsx index 0107b0b3e3..a45c589ab9 100644 --- a/web/app/components/workflow/variable-inspect/trigger.tsx +++ b/web/app/components/workflow/variable-inspect/trigger.tsx @@ -12,6 +12,7 @@ import type { CommonNodeType } from '@/app/components/workflow/types' import { useEventEmitterContextContext } from '@/context/event-emitter' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import cn from '@/utils/classnames' +import { useNodesReadOnly } from '../hooks/use-workflow' const VariableInspectTrigger: FC = () => { const { t } = useTranslation() @@ -32,7 +33,10 @@ const VariableInspectTrigger: FC = () => { const allVars = [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars] return allVars }, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) - + const { + nodesReadOnly, + getNodesReadOnly, + } = useNodesReadOnly() const workflowRunningData = useStore(s => s.workflowRunningData) const nodes = useNodes() const isStepRunning = useMemo(() => nodes.some(node => node.data._singleRunningStatus === NodeRunningStatus.Running), [nodes]) @@ -61,8 +65,14 @@ const VariableInspectTrigger: FC = () => {
{!isRunning && !currentVars.length && (
setShowVariableInspectPanel(true)} + className={cn('system-2xs-semibold-uppercase flex h-5 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-tertiary shadow-lg backdrop-blur-sm hover:bg-background-default-hover', + nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', + )} + onClick={() => { + if (getNodesReadOnly()) + return + setShowVariableInspectPanel(true) + }} > {t('workflow.debug.variableInspect.trigger.normal')}
@@ -70,13 +80,21 @@ const VariableInspectTrigger: FC = () => { {!isRunning && currentVars.length > 0 && ( <>
setShowVariableInspectPanel(true)} + className={cn('system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent', + nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', + )} + onClick={() => { + if (getNodesReadOnly()) + return + setShowVariableInspectPanel(true) + }} > {t('workflow.debug.variableInspect.trigger.cached')}
{t('workflow.debug.variableInspect.trigger.clear')} diff --git a/web/app/styles/globals.css b/web/app/styles/globals.css index 52e36a2767..353cfa2fff 100644 --- a/web/app/styles/globals.css +++ b/web/app/styles/globals.css @@ -697,4 +697,15 @@ button:focus-within { -ms-overflow-style: none; scrollbar-width: none; } + + /* Hide arrows from number input */ + .no-spinner::-webkit-outer-spin-button, + .no-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + .no-spinner { + -moz-appearance: textfield; + } } diff --git a/web/hooks/use-tab-searchparams.ts b/web/hooks/use-tab-searchparams.ts index 0c0e3b7773..444944f812 100644 --- a/web/hooks/use-tab-searchparams.ts +++ b/web/hooks/use-tab-searchparams.ts @@ -29,9 +29,10 @@ export const useTabSearchParams = ({ const router = useRouter() const pathName = pathnameFromHook || window?.location?.pathname const searchParams = useSearchParams() + const searchParamValue = searchParams.has(searchParamName) ? decodeURIComponent(searchParams.get(searchParamName)!) : defaultTab const [activeTab, setTab] = useState( !disableSearchParams - ? (searchParams.get(searchParamName) || defaultTab) + ? searchParamValue : defaultTab, ) @@ -39,7 +40,7 @@ export const useTabSearchParams = ({ setTab(newActiveTab) if (disableSearchParams) return - router[`${routingBehavior}`](`${pathName}?${searchParamName}=${newActiveTab}`) + router[`${routingBehavior}`](`${pathName}?${searchParamName}=${encodeURIComponent(newActiveTab)}`) } return [activeTab, setActiveTab] as const diff --git a/web/i18n/de-DE/app.ts b/web/i18n/de-DE/app.ts index 4df2ce91c1..d29a475bcd 100644 --- a/web/i18n/de-DE/app.ts +++ b/web/i18n/de-DE/app.ts @@ -138,6 +138,14 @@ const translation = { notConfigured: 'Anbieter konfigurieren, um Nachverfolgung zu aktivieren', moreProvider: 'Weitere Anbieter', }, + arize: { + title: 'Arize', + description: 'Unternehmensgerechte LLM-Observierbarkeit, Online- und Offline-Bewertung, Überwachung und Experimentierung—unterstützt durch OpenTelemetry. Speziell für LLM- und agentenbasierte Anwendungen entwickelt.', + }, + phoenix: { + title: 'Phoenix', + description: 'Open-Source- und OpenTelemetry-basierte Plattform für Observierbarkeit, Bewertung, Prompt-Engineering und Experimentierung für Ihre LLM-Workflows und -Agenten.', + }, langsmith: { title: 'LangSmith', description: 'Eine All-in-One-Entwicklerplattform für jeden Schritt des LLM-gesteuerten Anwendungslebenszyklus.', diff --git a/web/i18n/de-DE/dataset-documents.ts b/web/i18n/de-DE/dataset-documents.ts index 22018f9da4..f52220a669 100644 --- a/web/i18n/de-DE/dataset-documents.ts +++ b/web/i18n/de-DE/dataset-documents.ts @@ -390,6 +390,8 @@ const translation = { addChildChunk: 'Untergeordneten Block hinzufügen', regenerationConfirmTitle: 'Möchten Sie untergeordnete Chunks regenerieren?', searchResults_one: 'ERGEBNIS', + keywordEmpty: 'Das Schlüsselwort darf nicht leer sein.', + keywordDuplicate: 'Das Schlüsselwort existiert bereits', }, } diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index e75a9d5358..b6496e9b9c 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -150,6 +150,14 @@ const translation = { notConfigured: 'Config provider to enable tracing', moreProvider: 'More Provider', }, + arize: { + title: 'Arize', + description: 'Enterprise-grade LLM observability, online & offline evaluation, monitoring, and experimentation—powered by OpenTelemetry. Purpose-built for LLM & agent-driven applications.', + }, + phoenix: { + title: 'Phoenix', + description: 'Open-source & OpenTelemetry-based observability, evaluation, prompt engineering and experimentation platform for your LLM workflows and agents.', + }, langsmith: { title: 'LangSmith', description: 'An all-in-one developer platform for every step of the LLM-powered application lifecycle.', @@ -199,9 +207,9 @@ const translation = { accessControl: 'Web App Access Control', accessItemsDescription: { anyone: 'Anyone can access the web app (no login required)', - specific: 'Only specific members within the platform can access the Web application', - organization: 'All members within the platform can access the Web application', - external: 'Only authenticated external users can access the Web application', + specific: 'Only specific members within the platform can access the web app', + organization: 'All members within the platform can access the web app', + external: 'Only authenticated external users can access the web app', }, accessControlDialog: { title: 'Web App Access Control', @@ -218,7 +226,7 @@ const translation = { members_one: '{{count}} MEMBER', members_other: '{{count}} MEMBERS', noGroupsOrMembers: 'No groups or members selected', - webAppSSONotEnabledTip: 'Please contact your organization administrator to configure external authentication for the Web application.', + webAppSSONotEnabledTip: 'Please contact your organization administrator to configure external authentication for the web app.', operateGroupAndMember: { searchPlaceholder: 'Search groups and members', allMembers: 'All members', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index f242ecd5a8..0823c6129c 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -456,6 +456,7 @@ const translation = { connected: 'Connected', disconnected: 'Disconnected', changeAuthorizedPages: 'Change authorized pages', + integratedAlert: 'Notion is integrated via internal credential, no need to re-authorize.', pagesAuthorized: 'Pages authorized', sync: 'Sync', remove: 'Remove', diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index 2a79324477..7d0a3541a0 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -355,7 +355,9 @@ const translation = { newChildChunk: 'New Child Chunk', keywords: 'KEYWORDS', addKeyWord: 'Add keyword', + keywordEmpty: 'The keyword cannot be empty', keywordError: 'The maximum length of keyword is 20', + keywordDuplicate: 'The keyword already exists', characters_one: 'character', characters_other: 'characters', hitCount: 'Retrieval count', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 74ec1e4ede..6154bccfd7 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -551,6 +551,7 @@ const translation = { advancedDependencies: 'Advanced Dependencies', advancedDependenciesTip: 'Add some preloaded dependencies that take more time to consume or are not default built-in here', searchDependencies: 'Search Dependencies', + syncFunctionSignature: 'Sync function signature to code', }, templateTransform: { inputVars: 'Input Variables', diff --git a/web/i18n/es-ES/app.ts b/web/i18n/es-ES/app.ts index 2f87b9aba0..18741226fa 100644 --- a/web/i18n/es-ES/app.ts +++ b/web/i18n/es-ES/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Configurar proveedor para habilitar el rastreo', moreProvider: 'Más proveedores', }, + arize: { + title: 'Arize', + description: 'Observabilidad de LLM de nivel empresarial, evaluación en línea y fuera de línea, monitoreo y experimentación—impulsada por OpenTelemetry. Diseñada específicamente para aplicaciones impulsadas por LLM y agentes.', + }, + phoenix: { + title: 'Phoenix', + description: 'Plataforma de observabilidad, evaluación, ingeniería de prompts y experimentación de código abierto basada en OpenTelemetry para sus flujos de trabajo y agentes de LLM.', + }, langsmith: { title: 'LangSmith', description: 'Una plataforma de desarrollo todo en uno para cada paso del ciclo de vida de la aplicación impulsada por LLM.', diff --git a/web/i18n/es-ES/dataset-documents.ts b/web/i18n/es-ES/dataset-documents.ts index cd5bb36197..53a6663847 100644 --- a/web/i18n/es-ES/dataset-documents.ts +++ b/web/i18n/es-ES/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { characters_one: 'carácter', regenerationSuccessMessage: 'Puede cerrar esta ventana.', regenerationConfirmTitle: '¿Desea regenerar fragmentos secundarios?', + keywordEmpty: 'La palabra clave no puede estar vacía', + keywordDuplicate: 'La palabra clave ya existe', }, } diff --git a/web/i18n/fa-IR/app.ts b/web/i18n/fa-IR/app.ts index 7c8f70958f..5e9bd938f2 100644 --- a/web/i18n/fa-IR/app.ts +++ b/web/i18n/fa-IR/app.ts @@ -140,6 +140,14 @@ const translation = { notConfigured: 'برای فعال‌سازی ردیابی ارائه‌دهنده را پیکربندی کنید', moreProvider: 'ارائه‌دهندگان بیشتر', }, + arize: { + title: 'Arize', + description: 'قابلیت مشاهده LLM در سطح سازمانی، ارزیابی آنلاین و آفلاین، نظارت و آزمایش — با پشتیبانی از OpenTelemetry. طراحی‌شده مخصوص برنامه‌های مبتنی بر LLM و عامل‌ها.', + }, + phoenix: { + title: 'Phoenix', + description: 'پلتفرم متن‌باز و مبتنی بر OpenTelemetry برای مشاهده‌پذیری، ارزیابی، مهندسی پرامپت و آزمایش برای جریان‌های کاری و عامل‌های LLM شما.', + }, langsmith: { title: 'LangSmith', description: 'یک پلتفرم همه‌کاره برای هر مرحله از چرخه عمر برنامه‌های مبتنی بر LLM.', diff --git a/web/i18n/fa-IR/dataset-documents.ts b/web/i18n/fa-IR/dataset-documents.ts index 85e1e0a4aa..048cb5163f 100644 --- a/web/i18n/fa-IR/dataset-documents.ts +++ b/web/i18n/fa-IR/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { regeneratingMessage: 'این ممکن است یک لحظه طول بکشد، لطفا صبر کنید...', regenerationConfirmTitle: 'آیا می خواهید تکه های کودک را بازسازی کنید؟', regenerationSuccessMessage: 'می توانید این پنجره را ببندید.', + keywordEmpty: 'کلمه کلیدی نمی‌تواند خالی باشد', + keywordDuplicate: 'این کلیدواژه قبلاً وجود دارد', }, } diff --git a/web/i18n/fr-FR/app.ts b/web/i18n/fr-FR/app.ts index 55de8c3867..29d7a9b3de 100644 --- a/web/i18n/fr-FR/app.ts +++ b/web/i18n/fr-FR/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Configurez le fournisseur pour activer le traçage', moreProvider: 'Plus de fournisseurs', }, + arize: { + title: 'Arize', + description: 'Observabilité de LLM de niveau entreprise, évaluation en ligne et hors ligne, surveillance et expérimentation—alimentée par OpenTelemetry. Conçue spécialement pour les applications basées sur LLM et agents.', + }, + phoenix: { + title: 'Phoenix', + description: 'Plateforme open-source basée sur OpenTelemetry pour l’observabilité, l’évaluation, l’ingénierie des prompts et l’expérimentation de vos flux de travail et agents LLM.', + }, langsmith: { title: 'LangSmith', description: 'Une plateforme de développement tout-en-un pour chaque étape du cycle de vie des applications basées sur LLM.', diff --git a/web/i18n/fr-FR/dataset-documents.ts b/web/i18n/fr-FR/dataset-documents.ts index 7a795202ed..d8c0fe4af7 100644 --- a/web/i18n/fr-FR/dataset-documents.ts +++ b/web/i18n/fr-FR/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { searchResults_zero: 'RÉSULTAT', empty: 'Aucun Chunk trouvé', editChildChunk: 'Modifier le morceau enfant', + keywordDuplicate: 'Le mot-clé existe déjà', + keywordEmpty: 'Le mot-clé ne peut pas être vide.', }, } diff --git a/web/i18n/hi-IN/app.ts b/web/i18n/hi-IN/app.ts index f70010fb55..9485d7359b 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'ट्रेसिंग सक्षम करने के लिए प्रदाता कॉन्फ़िगर करें', moreProvider: 'अधिक प्रदाता', }, + arize: { + title: 'Arize', + description: 'एंटरप्राइज-स्तरीय LLM ऑब्ज़र्वेबिलिटी, ऑनलाइन और ऑफ़लाइन मूल्यांकन, मॉनिटरिंग और प्रयोग — OpenTelemetry द्वारा समर्थित। LLM और एजेंट-आधारित अनुप्रयोगों के लिए विशेष रूप से तैयार किया गया।', + }, + phoenix: { + title: 'Phoenix', + description: 'आपके LLM वर्कफ़्लोज़ और एजेंट्स के लिए ओपन-सोर्स और OpenTelemetry-आधारित ऑब्ज़र्वेबिलिटी, मूल्यांकन, प्रॉम्प्ट इंजीनियरिंग और प्रयोग का प्लेटफ़ॉर्म।', + }, langsmith: { title: 'LangSmith', description: 'LLM-संचालित एप्लिकेशन जीवनचक्र के प्रत्येक चरण के लिए एक ऑल-इन-वन डेवलपर प्लेटफ़ॉर्म।', diff --git a/web/i18n/hi-IN/dataset-documents.ts b/web/i18n/hi-IN/dataset-documents.ts index 35bcb0aad2..1fcdd49449 100644 --- a/web/i18n/hi-IN/dataset-documents.ts +++ b/web/i18n/hi-IN/dataset-documents.ts @@ -390,6 +390,8 @@ const translation = { chunkAdded: '1 हिस्सा जोड़ा गया', chunkDetail: 'चंक विवरण', regenerationConfirmMessage: 'चाइल्ड चंक्स को रीजनरेट करने से वर्तमान चाइल्ड चंक्स ओवरराइट हो जाएंगे, जिसमें संपादित चंक्स और नए जोड़े गए चंक्स शामिल हैं। पुनरुत्थान को पूर्ववत नहीं किया जा सकता है।', + keywordDuplicate: 'कीवर्ड पहले से मौजूद है', + keywordEmpty: 'कीवर्ड ख़ाली नहीं हो सकता', }, } diff --git a/web/i18n/it-IT/app.ts b/web/i18n/it-IT/app.ts index e1d57f8270..feade00168 100644 --- a/web/i18n/it-IT/app.ts +++ b/web/i18n/it-IT/app.ts @@ -145,6 +145,14 @@ const translation = { notConfigured: 'Configura il provider per abilitare il tracciamento', moreProvider: 'Altri Provider', }, + arize: { + title: 'Arize', + description: 'Osservabilità LLM di livello aziendale, valutazione online e offline, monitoraggio e sperimentazione—alimentata da OpenTelemetry. Progettata appositamente per applicazioni basate su LLM e agenti.', + }, + phoenix: { + title: 'Phoenix', + description: 'Piattaforma open-source basata su OpenTelemetry per osservabilità, valutazione, ingegneria dei prompt e sperimentazione per i tuoi flussi di lavoro e agenti LLM.', + }, langsmith: { title: 'LangSmith', description: diff --git a/web/i18n/it-IT/dataset-documents.ts b/web/i18n/it-IT/dataset-documents.ts index b9afb1ea75..2881e1fcee 100644 --- a/web/i18n/it-IT/dataset-documents.ts +++ b/web/i18n/it-IT/dataset-documents.ts @@ -391,6 +391,8 @@ const translation = { regenerationSuccessMessage: 'È possibile chiudere questa finestra.', childChunkAdded: '1 blocco figlio aggiunto', childChunks_other: 'BLOCCHI FIGLIO', + keywordEmpty: 'La parola chiave non può essere vuota', + keywordDuplicate: 'La parola chiave esiste già', }, } diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index 43c61abfed..1d94d67eb0 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -145,6 +145,14 @@ const translation = { notConfigured: 'トレース機能を有効化するためには、サービスの設定が必要です。', moreProvider: 'その他のプロバイダー', }, + arize: { + title: 'Arize', + description: 'エンタープライズグレードのLLM可観測性、オンラインおよびオフライン評価、モニタリング、実験—OpenTelemetryによって支えられています。LLMおよびエージェント駆動型アプリケーション向けに特別に設計されています。', + }, + phoenix: { + title: 'Phoenix', + description: 'オープンソースおよびOpenTelemetryベースの可観測性、評価、プロンプトエンジニアリング、実験プラットフォームで、LLMワークフローおよびエージェントに対応します。', + }, langsmith: { title: 'LangSmith', description: 'LLM を利用したアプリケーションのライフサイクル全段階を支援する、オールインワンの開発者向けプラットフォームです。', diff --git a/web/i18n/ja-JP/dataset-documents.ts b/web/i18n/ja-JP/dataset-documents.ts index ecdbbf512c..e28425dc8f 100644 --- a/web/i18n/ja-JP/dataset-documents.ts +++ b/web/i18n/ja-JP/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { editedAt: '編集日時', expandChunks: 'チャンクを展開', collapseChunks: 'チャンクを折りたたむ', + keywordDuplicate: 'そのキーワードは既に存在しています', + keywordEmpty: 'キーワードは空であってはいけません', }, } diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 5a3cb64e17..ab429320eb 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -552,6 +552,7 @@ const translation = { advancedDependencies: '高度な依存関係', advancedDependenciesTip: '消費に時間がかかる、またはデフォルトで組み込まれていない事前ロードされた依存関係を追加します', searchDependencies: '依存関係を検索', + syncFunctionSignature: 'コードの関数署名を同期', }, templateTransform: { inputVars: '入力変数', diff --git a/web/i18n/ko-KR/app.ts b/web/i18n/ko-KR/app.ts index 19323d584a..fab6d8cba5 100644 --- a/web/i18n/ko-KR/app.ts +++ b/web/i18n/ko-KR/app.ts @@ -132,6 +132,14 @@ const translation = { notConfigured: '추적을 활성화하려면 제공업체를 구성하세요', moreProvider: '더 많은 제공업체', }, + arize: { + title: 'Arize', + description: '엔터프라이즈급 LLM 가시성, 온라인 및 오프라인 평가, 모니터링 및 실험—OpenTelemetry를 기반으로 합니다. LLM 및 에이전트 기반 애플리케이션을 위해 특별히 설계되었습니다.', + }, + phoenix: { + title: 'Phoenix', + description: '오픈소스 및 OpenTelemetry 기반의 가시성, 평가, 프롬프트 엔지니어링 및 실험 플랫폼으로, LLM 워크플로우 및 에이전트를 지원합니다.', + }, langsmith: { title: 'LangSmith', description: 'LLM 기반 애플리케이션 수명 주기의 모든 단계를 위한 올인원 개발자 플랫폼.', diff --git a/web/i18n/ko-KR/dataset-documents.ts b/web/i18n/ko-KR/dataset-documents.ts index a379318959..ee94a880a0 100644 --- a/web/i18n/ko-KR/dataset-documents.ts +++ b/web/i18n/ko-KR/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { addChunk: '청크 추가 (Add Chunk)', characters_other: '문자', regeneratingMessage: '시간이 걸릴 수 있으니 잠시만 기다려 주십시오...', + keywordDuplicate: '키워드가 이미 존재합니다.', + keywordEmpty: '키워드는 비워둘 수 없습니다.', }, } diff --git a/web/i18n/pl-PL/app.ts b/web/i18n/pl-PL/app.ts index a3011a0e71..ee422a8b56 100644 --- a/web/i18n/pl-PL/app.ts +++ b/web/i18n/pl-PL/app.ts @@ -143,6 +143,14 @@ const translation = { notConfigured: 'Skonfiguruj dostawcę, aby włączyć śledzenie', moreProvider: 'Więcej dostawców', }, + arize: { + title: 'Arize', + description: 'Obserwowalność LLM klasy korporacyjnej, ocena online i offline, monitorowanie i eksperymentowanie — oparta na OpenTelemetry. Zaprojektowana specjalnie dla aplikacji opartych na LLM i agentach.', + }, + phoenix: { + title: 'Phoenix', + description: 'Otwarta i oparta na OpenTelemetry platforma do obserwowalności, oceny, inżynierii promptów i eksperymentowania dla Twoich przepływów pracy i agentów LLM.', + }, langsmith: { title: 'LangSmith', description: 'Kompleksowa platforma deweloperska dla każdego etapu cyklu życia aplikacji opartej na LLM.', diff --git a/web/i18n/pl-PL/dataset-documents.ts b/web/i18n/pl-PL/dataset-documents.ts index 37f373ac93..78e427ba95 100644 --- a/web/i18n/pl-PL/dataset-documents.ts +++ b/web/i18n/pl-PL/dataset-documents.ts @@ -390,6 +390,8 @@ const translation = { newChildChunk: 'Nowy fragment podrzędny', clearFilter: 'Wyczyść filtr', childChunks_one: 'FRAGMENT POTOMNY', + keywordDuplicate: 'Słowo kluczowe już istnieje', + keywordEmpty: 'Słowo kluczowe nie może być puste', }, } diff --git a/web/i18n/pt-BR/app.ts b/web/i18n/pt-BR/app.ts index a844607872..742159692c 100644 --- a/web/i18n/pt-BR/app.ts +++ b/web/i18n/pt-BR/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Configure o provedor para habilitar o rastreamento', moreProvider: 'Mais provedores', }, + arize: { + title: 'Arize', + description: 'Observabilidade de LLM de nível empresarial, avaliação online e offline, monitoramento e experimentação—impulsionada pelo OpenTelemetry. Projetado especificamente para aplicações baseadas em LLM e agentes.', + }, + phoenix: { + title: 'Phoenix', + description: 'Plataforma de observabilidade, avaliação, engenharia de prompts e experimentação de código aberto baseada em OpenTelemetry para seus fluxos de trabalho e agentes de LLM.', + }, langsmith: { title: 'LangSmith', description: 'Uma plataforma de desenvolvedor completa para cada etapa do ciclo de vida do aplicativo impulsionado por LLM.', diff --git a/web/i18n/pt-BR/dataset-documents.ts b/web/i18n/pt-BR/dataset-documents.ts index 9a3d13bcab..b8c06c1769 100644 --- a/web/i18n/pt-BR/dataset-documents.ts +++ b/web/i18n/pt-BR/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { newChildChunk: 'Novo pedaço filho', characters_one: 'personagem', parentChunk: 'Pedaço pai', + keywordEmpty: 'A palavra-chave não pode estar vazia', + keywordDuplicate: 'A palavra-chave já existe', }, } diff --git a/web/i18n/ro-RO/app.ts b/web/i18n/ro-RO/app.ts index efb91cc52e..5fdb32945c 100644 --- a/web/i18n/ro-RO/app.ts +++ b/web/i18n/ro-RO/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Configurați furnizorul pentru a activa urmărirea', moreProvider: 'Mai mulți furnizori', }, + arize: { + title: 'Arize', + description: 'Observabilitate LLM de nivel enterprise, evaluare online și offline, monitorizare și experimentare—alimentată de OpenTelemetry. Proiectată special pentru aplicații bazate pe LLM și agenți.', + }, + phoenix: { + title: 'Phoenix', + description: 'Platformă open-source și bazată pe OpenTelemetry pentru observabilitate, evaluare, inginerie de prompturi și experimentare pentru fluxurile de lucru și agenții LLM.', + }, langsmith: { title: 'LangSmith', description: 'O platformă de dezvoltare all-in-one pentru fiecare etapă a ciclului de viață al aplicației bazate pe LLM.', diff --git a/web/i18n/ro-RO/dataset-documents.ts b/web/i18n/ro-RO/dataset-documents.ts index e42be87502..e0f3e8b476 100644 --- a/web/i18n/ro-RO/dataset-documents.ts +++ b/web/i18n/ro-RO/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { regeneratingTitle: 'Regenerarea bucăților secundare', addChildChunk: 'Adăugați o bucată copil', searchResults_other: 'REZULTATELE', + keywordDuplicate: 'Cuvântul cheie există deja', + keywordEmpty: 'Cuvântul cheie nu poate fi gol', }, } diff --git a/web/i18n/ru-RU/app.ts b/web/i18n/ru-RU/app.ts index 71f7e31201..03283180dc 100644 --- a/web/i18n/ru-RU/app.ts +++ b/web/i18n/ru-RU/app.ts @@ -141,6 +141,14 @@ const translation = { notConfigured: 'Настройте провайдера, чтобы включить трассировку', moreProvider: 'Больше провайдеров', }, + arize: { + title: 'Arize', + description: 'Корпоративный уровень наблюдаемости LLM, онлайн и оффлайн оценка, мониторинг и эксперименты—на основе OpenTelemetry. Специально разработан для приложений на базе LLM и агентов.', + }, + phoenix: { + title: 'Phoenix', + description: 'Открытая и основанная на OpenTelemetry платформа для наблюдаемости, оценки, инженерии подсказок и экспериментов для ваших рабочих процессов и агентов LLM.', + }, langsmith: { title: 'LangSmith', description: 'Универсальная платформа для разработчиков для каждого этапа жизненного цикла приложения на базе LLM.', diff --git a/web/i18n/ru-RU/dataset-documents.ts b/web/i18n/ru-RU/dataset-documents.ts index 735266c087..6fadee3b26 100644 --- a/web/i18n/ru-RU/dataset-documents.ts +++ b/web/i18n/ru-RU/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { characters_one: 'характер', addChildChunk: 'Добавить дочерний чанк', newChildChunk: 'Новый дочерний чанк', + keywordEmpty: 'Ключевое слово не может быть пустым', + keywordDuplicate: 'Ключевое слово уже существует', }, } diff --git a/web/i18n/sl-SI/app.ts b/web/i18n/sl-SI/app.ts index f452ffc39b..bba09351aa 100644 --- a/web/i18n/sl-SI/app.ts +++ b/web/i18n/sl-SI/app.ts @@ -146,6 +146,14 @@ const translation = { notConfigured: 'Konfigurirajte ponudnika za omogočanje sledenja', moreProvider: 'Več ponudnikov', }, + arize: { + title: 'Arize', + description: 'Podjetniško opazovanje LLM, spletno in nespletno vrednotenje, nadzorovanje in eksperimentiranje — s podporo OpenTelemetry. Namenjeno aplikacijam, ki temeljijo na LLM in agentih.', + }, + phoenix: { + title: 'Phoenix', + description: 'Odprtokodna in na OpenTelemetry osnovana platforma za opazovanje, vrednotenje, inženiring pozivov ter eksperimentiranje za vaše LLM poteke dela in agente.', + }, langsmith: { title: 'LangSmith', description: 'Vse-v-enem razvijalska platforma za vsak korak življenjskega cikla aplikacije, ki jo poganja LLM.', diff --git a/web/i18n/sl-SI/dataset-documents.ts b/web/i18n/sl-SI/dataset-documents.ts index 78d63c9e29..1a83335ad5 100644 --- a/web/i18n/sl-SI/dataset-documents.ts +++ b/web/i18n/sl-SI/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { chunk: 'Kos', addChunk: 'Dodajanje kosa', childChunkAdded: 'Dodan je 1 kos otroka', + keywordDuplicate: 'Ključna beseda že obstaja', + keywordEmpty: 'Ključna beseda ne more biti prazna', }, } diff --git a/web/i18n/th-TH/app.ts b/web/i18n/th-TH/app.ts index 71b44bc5a8..3d4809e874 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -142,6 +142,14 @@ const translation = { notConfigured: 'ผู้ให้บริการกําหนดค่าเพื่อเปิดใช้งานการติดตาม', moreProvider: 'ผู้ให้บริการเพิ่มเติม', }, + arize: { + title: 'Arize', + description: 'การสังเกตการณ์ LLM ระดับองค์กร การประเมินออนไลน์และออฟไลน์ การตรวจสอบ และการทดลอง—ขับเคลื่อนโดย OpenTelemetry ออกแบบมาโดยเฉพาะสำหรับแอปพลิเคชันที่ขับเคลื่อนด้วย LLM และตัวแทน', + }, + phoenix: { + title: 'Phoenix', + description: 'แพลตฟอร์มโอเพ่นซอร์สและ OpenTelemetry สำหรับการสังเกตการณ์ การประเมิน วิศวกรรมพรอมต์ และการทดลองสำหรับเวิร์กโฟลว์และตัวแทน LLM ของคุณ', + }, langsmith: { title: 'Langsmith', description: 'แพลตฟอร์มนักพัฒนาแบบครบวงจรสําหรับทุกขั้นตอนของ การพัฒนาโปรเจกต์ที่ขับเคลื่อนด้วย LLM', diff --git a/web/i18n/th-TH/dataset-documents.ts b/web/i18n/th-TH/dataset-documents.ts index 91d04d6bc1..87b43f31e2 100644 --- a/web/i18n/th-TH/dataset-documents.ts +++ b/web/i18n/th-TH/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { searchResults_other: 'ผลลัพธ์', regenerationSuccessMessage: 'คุณสามารถปิดหน้าต่างนี้ได้', childChunks_one: 'ก้อนเด็ก', + keywordDuplicate: 'คำสำคัญมีอยู่แล้ว', + keywordEmpty: 'คีย์เวิร์ดไม่สามารถว่างเปล่าได้', }, } diff --git a/web/i18n/tr-TR/app.ts b/web/i18n/tr-TR/app.ts index 41423882d8..639319fd81 100644 --- a/web/i18n/tr-TR/app.ts +++ b/web/i18n/tr-TR/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'İzlemeyi etkinleştirmek için sağlayıcıyı yapılandırın', moreProvider: 'Daha Fazla Sağlayıcı', }, + arize: { + title: 'Arize', + description: 'Kurumsal düzeyde LLM gözlemlenebilirliği, çevrimiçi ve çevrimdışı değerlendirme, izleme ve deneyler — OpenTelemetry ile desteklenmektedir. LLM ve ajan tabanlı uygulamalar için özel olarak tasarlanmıştır.', + }, + phoenix: { + title: 'Phoenix', + description: 'LLM iş akışlarınız ve ajanlarınız için açık kaynaklı ve OpenTelemetry tabanlı gözlemlenebilirlik, değerlendirme, istem mühendisliği ve deney platformu.', + }, langsmith: { title: 'LangSmith', description: 'LLM destekli uygulama yaşam döngüsünün her adımı için her şeyi kapsayan bir geliştirici platformu.', diff --git a/web/i18n/tr-TR/dataset-documents.ts b/web/i18n/tr-TR/dataset-documents.ts index f643375334..2e00975178 100644 --- a/web/i18n/tr-TR/dataset-documents.ts +++ b/web/i18n/tr-TR/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { chunks_other: 'Parçalar', editedAt: 'Şurada düzenlendi:', addChildChunk: 'Alt Parça Ekle', + keywordDuplicate: 'Anahtar kelime zaten var', + keywordEmpty: 'Anahtar kelime boş olamaz', }, } diff --git a/web/i18n/uk-UA/app.ts b/web/i18n/uk-UA/app.ts index 3dd3463f99..9e1d3c60c6 100644 --- a/web/i18n/uk-UA/app.ts +++ b/web/i18n/uk-UA/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Налаштуйте провайдера для увімкнення відстеження', moreProvider: 'Більше провайдерів', }, + arize: { + title: 'Arize', + description: 'Спостережуваність LLM корпоративного рівня, онлайн та офлайн оцінювання, моніторинг та експерименти—на основі OpenTelemetry. Спеціально розроблено для застосунків на базі LLM та агентів.', + }, + phoenix: { + title: 'Phoenix', + description: 'Відкрита та заснована на OpenTelemetry платформа для спостережуваності, оцінювання, інженерії підказок та експериментів для ваших робочих процесів та агентів LLM.', + }, langsmith: { title: 'LangSmith', description: 'Універсальна платформа розробника для кожного етапу життєвого циклу додатку на основі LLM.', diff --git a/web/i18n/uk-UA/dataset-documents.ts b/web/i18n/uk-UA/dataset-documents.ts index da012cbb57..e8464e5661 100644 --- a/web/i18n/uk-UA/dataset-documents.ts +++ b/web/i18n/uk-UA/dataset-documents.ts @@ -389,6 +389,8 @@ const translation = { regenerationSuccessMessage: 'Ви можете закрити це вікно.', expandChunks: 'Розгортання фрагментів', regenerationConfirmTitle: 'Хочете регенерувати дитячі шматки?', + keywordEmpty: 'Ключове слово не може бути порожнім', + keywordDuplicate: 'Ключове слово вже існує', }, } diff --git a/web/i18n/vi-VN/app.ts b/web/i18n/vi-VN/app.ts index ef8d72b222..0968fcb458 100644 --- a/web/i18n/vi-VN/app.ts +++ b/web/i18n/vi-VN/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: 'Cấu hình nhà cung cấp để bật theo dõi', moreProvider: 'Thêm nhà cung cấp', }, + arize: { + title: 'Arize', + description: 'Khả năng quan sát LLM cấp doanh nghiệp, đánh giá trực tuyến và ngoại tuyến, giám sát và thử nghiệm—được hỗ trợ bởi OpenTelemetry. Được thiết kế đặc biệt cho các ứng dụng dựa trên LLM và tác nhân.', + }, + phoenix: { + title: 'Phoenix', + description: 'Nền tảng mã nguồn mở và dựa trên OpenTelemetry cho khả năng quan sát, đánh giá, kỹ thuật prompt và thử nghiệm cho quy trình làm việc và tác nhân LLM của bạn.', + }, langsmith: { title: 'LangSmith', description: 'Nền tảng phát triển tất cả trong một cho mọi bước của vòng đời ứng dụng được hỗ trợ bởi LLM.', diff --git a/web/i18n/vi-VN/dataset-documents.ts b/web/i18n/vi-VN/dataset-documents.ts index 6e13c1185f..1cc050b804 100644 --- a/web/i18n/vi-VN/dataset-documents.ts +++ b/web/i18n/vi-VN/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { clearFilter: 'Bộ lọc rõ ràng', chunk: 'Khúc', edited: 'EDITED', + keywordDuplicate: 'Từ khóa đã tồn tại', + keywordEmpty: 'Từ khóa không được để trống', }, } diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index c5bfb39f4f..07ecc018fb 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -150,6 +150,14 @@ const translation = { notConfigured: '配置提供商以启用追踪', moreProvider: '更多提供商', }, + arize: { + title: 'Arize', + description: '企业级LLM可观测性、在线和离线评估、监控和实验平台,基于OpenTelemetry构建,专为LLM和代理驱动的应用程序设计。', + }, + phoenix: { + title: 'Phoenix', + description: '开源且基于OpenTelemetry的可观测性、评估、提示工程和实验平台,适用于您的LLM工作流程和代理。', + }, langsmith: { title: 'LangSmith', description: '一个全方位的开发者平台,适用于 LLM 驱动应用程序生命周期的每个步骤。', diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 5ff1b50f85..2eb883690b 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -387,6 +387,8 @@ const translation = { editedAt: '编辑于', expandChunks: '展开分段', collapseChunks: '折叠分段', + keywordEmpty: '关键词不能为空', + keywordDuplicate: '关键词已经存在', }, } diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 6ea7e536b0..e24926a0b8 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -552,6 +552,7 @@ const translation = { advancedDependencies: '高级依赖', advancedDependenciesTip: '在这里添加一些预加载需要消耗较多时间或非默认内置的依赖包', searchDependencies: '搜索依赖', + syncFunctionSignature: '同步函数签名至代码', }, templateTransform: { inputVars: '输入变量', diff --git a/web/i18n/zh-Hant/app.ts b/web/i18n/zh-Hant/app.ts index 5e5ffaaf96..41cd6324c9 100644 --- a/web/i18n/zh-Hant/app.ts +++ b/web/i18n/zh-Hant/app.ts @@ -136,6 +136,14 @@ const translation = { notConfigured: '配置提供商以啟用追蹤', moreProvider: '更多提供商', }, + arize: { + title: 'Arize', + description: '企業級LLM可觀測性、線上與離線評估、監控和實驗平台,基於OpenTelemetry構建,專為LLM和代理驅動的應用程式設計。', + }, + phoenix: { + title: 'Phoenix', + description: '開源且基於OpenTelemetry的可觀測性、評估、提示工程和實驗平台,適用於您的LLM工作流程和代理。', + }, langsmith: { title: 'LangSmith', description: '一個全方位的開發者平台,用於 LLM 驅動的應用程式生命週期的每個步驟。', diff --git a/web/i18n/zh-Hant/dataset-documents.ts b/web/i18n/zh-Hant/dataset-documents.ts index 60b1df80f3..a79f3993e0 100644 --- a/web/i18n/zh-Hant/dataset-documents.ts +++ b/web/i18n/zh-Hant/dataset-documents.ts @@ -388,6 +388,8 @@ const translation = { searchResults_zero: '結果', parentChunks_other: '父塊', newChildChunk: '新兒童塊', + keywordEmpty: '關鍵字不能為空', + keywordDuplicate: '關鍵字已經存在', }, } diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 460af03796..4e21ce1811 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -546,6 +546,7 @@ const translation = { advancedDependencies: '高級依賴', advancedDependenciesTip: '在這裡添加一些預加載需要消耗較多時間或非默認內置的依賴包', searchDependencies: '搜索依賴', + syncFunctionSignature: '同步函數簽名至代碼', }, templateTransform: { inputVars: '輸入變量', diff --git a/web/models/app.ts b/web/models/app.ts index b10ced703a..369cf32387 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -1,4 +1,4 @@ -import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' +import type { ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' import type { App, AppTemplate, SiteConfig } from '@/types/app' import type { Dependency } from '@/app/components/plugins/types' @@ -166,5 +166,5 @@ export type TracingStatus = { export type TracingConfig = { tracing_provider: TracingProvider - tracing_config: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig + tracing_config: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig } diff --git a/web/package.json b/web/package.json index 0ece7fac27..0862ddbb07 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "1.5.0", + "version": "1.5.1", "private": true, "engines": { "node": ">=v22.11.0" @@ -103,7 +103,7 @@ "mime": "^4.0.4", "mitt": "^3.0.1", "negotiator": "^0.6.3", - "next": "15.2.3", + "next": "15.2.4", "next-themes": "^0.4.3", "pinyin-pro": "^3.25.0", "qrcode.react": "^4.2.0", @@ -235,7 +235,11 @@ }, "pnpm": { "overrides": { - "esbuild@<0.25.0": "0.25.0" + "esbuild@<0.25.0": "0.25.0", + "pbkdf2@<3.1.3": "3.1.3", + "vite@<6.2.7": "6.2.7", + "prismjs@<1.30.0": "1.30.0", + "brace-expansion@<2.0.2": "2.0.2" } } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index fce3b6581b..ef945dfc54 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -9,6 +9,10 @@ overrides: '@types/react-dom': ~18.2.0 string-width: 4.2.3 esbuild@<0.25.0: 0.25.0 + pbkdf2@<3.1.3: 3.1.3 + vite@<6.2.7: 6.2.7 + prismjs@<1.30.0: 1.30.0 + brace-expansion@<2.0.2: 2.0.2 importers: @@ -207,8 +211,8 @@ importers: specifier: ^0.6.3 version: 0.6.4 next: - specifier: 15.2.3 - version: 15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3) + specifier: 15.2.4 + version: 15.2.4(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3) next-themes: specifier: ^0.4.3 version: 0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -392,7 +396,7 @@ importers: version: 8.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0) '@storybook/nextjs': specifier: 8.5.0 - version: 8.5.0(esbuild@0.25.0)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) + version: 8.5.0(esbuild@0.25.0)(next@15.2.4(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@storybook/react': specifier: 8.5.0 version: 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5) @@ -2086,8 +2090,8 @@ packages: '@napi-rs/wasm-runtime@0.2.8': resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} - '@next/env@15.2.3': - resolution: {integrity: sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw==} + '@next/env@15.2.4': + resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==} '@next/eslint-plugin-next@15.3.0': resolution: {integrity: sha512-511UUcpWw5GWTyKfzW58U2F/bYJyjLE9e3SlnGK/zSXq7RqLlqFO8B9bitJjumLpj317fycC96KZ2RZsjGNfBw==} @@ -2103,50 +2107,50 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@15.2.3': - resolution: {integrity: sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw==} + '@next/swc-darwin-arm64@15.2.4': + resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.2.3': - resolution: {integrity: sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw==} + '@next/swc-darwin-x64@15.2.4': + resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.2.3': - resolution: {integrity: sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ==} + '@next/swc-linux-arm64-gnu@15.2.4': + resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.2.3': - resolution: {integrity: sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q==} + '@next/swc-linux-arm64-musl@15.2.4': + resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.2.3': - resolution: {integrity: sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w==} + '@next/swc-linux-x64-gnu@15.2.4': + resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.2.3': - resolution: {integrity: sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg==} + '@next/swc-linux-x64-musl@15.2.4': + resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.2.3': - resolution: {integrity: sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q==} + '@next/swc-win32-arm64-msvc@15.2.4': + resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.2.3': - resolution: {integrity: sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w==} + '@next/swc-win32-x64-msvc@15.2.4': + resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3311,7 +3315,7 @@ packages: resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: 6.2.7 peerDependenciesMeta: msw: optional: true @@ -3726,11 +3730,8 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -4076,9 +4077,6 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -4134,6 +4132,9 @@ packages: create-ecdh@4.0.4: resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} + create-hash@1.1.3: + resolution: {integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==} + create-hash@1.2.0: resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} @@ -5383,6 +5384,9 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hash-base@2.0.2: + resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==} + hash-base@3.0.5: resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} engines: {node: '>= 0.10'} @@ -6565,8 +6569,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.2.3: - resolution: {integrity: sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w==} + next@15.2.4: + resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -6859,8 +6863,8 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} - pbkdf2@3.1.2: - resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} + pbkdf2@3.1.3: + resolution: {integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==} engines: {node: '>=0.12'} pdfjs-dist@4.4.168: @@ -7042,10 +7046,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prismjs@1.27.0: - resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} - engines: {node: '>=6'} - prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -7527,6 +7527,9 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + ripemd160@2.0.1: + resolution: {integrity: sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==} + ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} @@ -8042,6 +8045,10 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-buffer@1.2.1: + resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} + engines: {node: '>= 0.4'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -8367,8 +8374,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.2.6: - resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} + vite@6.2.7: + resolution: {integrity: sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -10565,7 +10572,7 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next/env@15.2.3': {} + '@next/env@15.2.4': {} '@next/eslint-plugin-next@15.3.0': dependencies: @@ -10578,28 +10585,28 @@ snapshots: '@mdx-js/loader': 3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@mdx-js/react': 3.1.0(@types/react@18.2.79)(react@19.0.0) - '@next/swc-darwin-arm64@15.2.3': + '@next/swc-darwin-arm64@15.2.4': optional: true - '@next/swc-darwin-x64@15.2.3': + '@next/swc-darwin-x64@15.2.4': optional: true - '@next/swc-linux-arm64-gnu@15.2.3': + '@next/swc-linux-arm64-gnu@15.2.4': optional: true - '@next/swc-linux-arm64-musl@15.2.3': + '@next/swc-linux-arm64-musl@15.2.4': optional: true - '@next/swc-linux-x64-gnu@15.2.3': + '@next/swc-linux-x64-gnu@15.2.4': optional: true - '@next/swc-linux-x64-musl@15.2.3': + '@next/swc-linux-x64-musl@15.2.4': optional: true - '@next/swc-win32-arm64-msvc@15.2.3': + '@next/swc-win32-arm64-msvc@15.2.4': optional: true - '@next/swc-win32-x64-msvc@15.2.3': + '@next/swc-win32-x64-msvc@15.2.4': optional: true '@nodelib/fs.scandir@2.1.5': @@ -11211,7 +11218,7 @@ snapshots: dependencies: storybook: 8.5.0 - '@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': + '@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.2.4(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) @@ -11237,7 +11244,7 @@ snapshots: find-up: 5.0.0 image-size: 1.2.1 loader-utils: 3.3.1 - next: 15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3) + next: 15.2.4(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3) node-polyfill-webpack-plugin: 2.0.1(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) pnp-webpack-plugin: 1.7.0(typescript@4.9.5) postcss: 8.5.3 @@ -11956,13 +11963,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.1(vite@6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1))': + '@vitest/mocker@3.1.1(vite@6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1))': dependencies: '@vitest/spy': 3.1.1 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) + vite: 6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) '@vitest/pretty-format@2.0.5': dependencies: @@ -12488,12 +12495,7 @@ snapshots: boolbase@1.0.0: {} - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -12846,8 +12848,6 @@ snapshots: compare-versions@6.1.1: {} - concat-map@0.0.1: {} - confbox@0.1.8: {} confbox@0.2.2: {} @@ -12905,6 +12905,13 @@ snapshots: bn.js: 4.12.1 elliptic: 6.6.1 + create-hash@1.1.3: + dependencies: + cipher-base: 1.0.6 + inherits: 2.0.4 + ripemd160: 2.0.2 + sha.js: 2.4.11 + create-hash@1.2.0: dependencies: cipher-base: 1.0.6 @@ -12959,7 +12966,7 @@ snapshots: diffie-hellman: 5.0.3 hash-base: 3.0.5 inherits: 2.0.4 - pbkdf2: 3.1.2 + pbkdf2: 3.1.3 public-encrypt: 4.0.3 randombytes: 2.1.0 randomfill: 1.0.4 @@ -14577,6 +14584,10 @@ snapshots: has-unicode@2.0.1: optional: true + hash-base@2.0.2: + dependencies: + inherits: 2.0.4 + hash-base@3.0.5: dependencies: inherits: 2.0.4 @@ -16239,15 +16250,15 @@ snapshots: minimatch@10.0.1: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 2.0.2 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} @@ -16307,9 +16318,9 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3): + next@15.2.4(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3): dependencies: - '@next/env': 15.2.3 + '@next/env': 15.2.4 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -16319,14 +16330,14 @@ snapshots: react-dom: 19.0.0(react@19.0.0) styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.2.3 - '@next/swc-darwin-x64': 15.2.3 - '@next/swc-linux-arm64-gnu': 15.2.3 - '@next/swc-linux-arm64-musl': 15.2.3 - '@next/swc-linux-x64-gnu': 15.2.3 - '@next/swc-linux-x64-musl': 15.2.3 - '@next/swc-win32-arm64-msvc': 15.2.3 - '@next/swc-win32-x64-msvc': 15.2.3 + '@next/swc-darwin-arm64': 15.2.4 + '@next/swc-darwin-x64': 15.2.4 + '@next/swc-linux-arm64-gnu': 15.2.4 + '@next/swc-linux-arm64-musl': 15.2.4 + '@next/swc-linux-x64-gnu': 15.2.4 + '@next/swc-linux-x64-musl': 15.2.4 + '@next/swc-win32-arm64-msvc': 15.2.4 + '@next/swc-win32-x64-msvc': 15.2.4 sass: 1.86.3 sharp: 0.33.5 transitivePeerDependencies: @@ -16563,7 +16574,7 @@ snapshots: browserify-aes: 1.2.0 evp_bytestokey: 1.0.3 hash-base: 3.0.5 - pbkdf2: 3.1.2 + pbkdf2: 3.1.3 safe-buffer: 5.2.1 parse-entities@2.0.0: @@ -16644,13 +16655,14 @@ snapshots: pathval@2.0.0: {} - pbkdf2@3.1.2: + pbkdf2@3.1.3: dependencies: - create-hash: 1.2.0 + create-hash: 1.1.3 create-hmac: 1.1.7 - ripemd160: 2.0.2 + ripemd160: 2.0.1 safe-buffer: 5.2.1 sha.js: 2.4.11 + to-buffer: 1.2.1 pdfjs-dist@4.4.168: optionalDependencies: @@ -16831,8 +16843,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prismjs@1.27.0: {} - prismjs@1.30.0: {} process-nextick-args@2.0.1: {} @@ -17247,7 +17257,7 @@ snapshots: dependencies: hastscript: 6.0.0 parse-entities: 2.0.0 - prismjs: 1.27.0 + prismjs: 1.30.0 regenerate-unicode-properties@10.2.0: dependencies: @@ -17441,6 +17451,11 @@ snapshots: dependencies: glob: 7.2.3 + ripemd160@2.0.1: + dependencies: + hash-base: 2.0.2 + inherits: 2.0.4 + ripemd160@2.0.2: dependencies: hash-base: 3.0.5 @@ -18041,6 +18056,12 @@ snapshots: tmpl@1.0.5: {} + to-buffer@1.2.1: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -18389,7 +18410,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) + vite: 6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) transitivePeerDependencies: - '@types/node' - jiti @@ -18404,7 +18425,7 @@ snapshots: - tsx - yaml - vite@6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1): + vite@6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1): dependencies: esbuild: 0.25.2 postcss: 8.5.3 @@ -18420,7 +18441,7 @@ snapshots: vitest@3.1.1(@types/debug@4.1.12)(@types/node@18.15.0)(happy-dom@17.4.4)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1): dependencies: '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(vite@6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)) + '@vitest/mocker': 3.1.1(vite@6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)) '@vitest/pretty-format': 3.1.1 '@vitest/runner': 3.1.1 '@vitest/snapshot': 3.1.1 @@ -18436,7 +18457,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.6(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) + vite: 6.2.7(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) vite-node: 3.1.1(@types/node@18.15.0)(jiti@1.21.7)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/web/service/access-control.ts b/web/service/access-control.ts index 865909d2f9..36999bf8f3 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -86,5 +86,8 @@ export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled } enabled: !!appId && enabled, staleTime: 0, gcTime: 0, + initialData: { + result: !enabled, + }, }) } diff --git a/web/service/base.ts b/web/service/base.ts index ba398c07a6..49377be912 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -110,7 +110,7 @@ function unicodeToChar(text: string) { function requiredWebSSOLogin(message?: string, code?: number) { const params = new URLSearchParams() - params.append('redirect_url', globalThis.location.pathname) + params.append('redirect_url', encodeURIComponent(`${globalThis.location.pathname}${globalThis.location.search}`)) if (message) params.append('message', message) if (code) diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index ecfdbcf993..ff092bb037 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect } from 'react' import type { + FormOption, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { fetchModelProviderModelList } from '@/service/common' @@ -477,7 +478,7 @@ export const usePluginTaskList = (category?: PluginType) => { refreshPluginList(category ? { category } as any : undefined, !category) } } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isRefetching]) const handleRefetch = useCallback(() => { @@ -571,3 +572,17 @@ export const usePluginInfo = (providerName?: string) => { enabled: !!providerName, }) } + +export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type: 'tool') => { + return useMutation({ + mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', { + params: { + plugin_id, + provider, + action, + parameter, + provider_type, + }, + }), + }) +} diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts index e070b46e65..eb07109857 100644 --- a/web/service/use-workflow.ts +++ b/web/service/use-workflow.ts @@ -113,18 +113,19 @@ export const useInvalidAllLastRun = (appId: string) => { const useConversationVarValuesKey = [NAME_SPACE, 'conversation-variable'] -export const useConversationVarValues = (appId: string) => { +export const useConversationVarValues = (url?: string) => { return useQuery({ - queryKey: [...useConversationVarValuesKey, appId], + enabled: !!url, + queryKey: [...useConversationVarValuesKey, url], queryFn: async () => { - const { items } = (await get(`apps/${appId}/workflows/draft/conversation-variables`)) as { items: VarInInspect[] } + const { items } = (await get(url || '')) as { items: VarInInspect[] } return items }, }) } -export const useInvalidateConversationVarValues = (appId: string) => { - return useInvalid([...useConversationVarValuesKey, appId]) +export const useInvalidateConversationVarValues = (url: string) => { + return useInvalid([...useConversationVarValuesKey, url]) } export const useResetConversationVar = (appId: string) => { @@ -139,25 +140,26 @@ export const useResetConversationVar = (appId: string) => { export const useResetToLastRunValue = (appId: string) => { return useMutation({ mutationKey: [NAME_SPACE, 'reset to last run value', appId], - mutationFn: async (varId: string) => { + mutationFn: async (varId: string): Promise<{ value: any }> => { return put(`apps/${appId}/workflows/draft/variables/${varId}/reset`) }, }) } export const useSysVarValuesKey = [NAME_SPACE, 'sys-variable'] -export const useSysVarValues = (appId: string) => { +export const useSysVarValues = (url?: string) => { return useQuery({ - queryKey: [...useSysVarValuesKey, appId], + enabled: !!url, + queryKey: [...useSysVarValuesKey, url], queryFn: async () => { - const { items } = (await get(`apps/${appId}/workflows/draft/system-variables`)) as { items: VarInInspect[] } + const { items } = (await get(url || '')) as { items: VarInInspect[] } return items }, }) } -export const useInvalidateSysVarValues = (appId: string) => { - return useInvalid([...useSysVarValuesKey, appId]) +export const useInvalidateSysVarValues = (url: string) => { + return useInvalid([...useSysVarValuesKey, url]) } export const useDeleteAllInspectorVars = (appId: string) => { diff --git a/web/themes/dark.css b/web/themes/dark.css index 114bf6d69b..b7adb61315 100644 --- a/web/themes/dark.css +++ b/web/themes/dark.css @@ -722,6 +722,8 @@ html[data-theme="dark"] { --color-util-colors-midnight-midnight-600: #a7aec5; --color-util-colors-midnight-midnight-700: #c6cbd9; + --color-third-party-Arize: #ffffff; + --color-third-party-Phoenix: #ffffff; --color-third-party-LangChain: #ffffff; --color-third-party-Langfuse: #ffffff; --color-third-party-Github: #ffffff; diff --git a/web/themes/light.css b/web/themes/light.css index 8a3f9d9d48..97b3b3b4ae 100644 --- a/web/themes/light.css +++ b/web/themes/light.css @@ -722,6 +722,8 @@ html[data-theme="light"] { --color-util-colors-midnight-midnight-600: #5d698d; --color-util-colors-midnight-midnight-700: #3e465e; + --color-third-party-Arize: #000000; + --color-third-party-Phoenix: #000000; --color-third-party-LangChain: #1c3c3c; --color-third-party-Langfuse: #000000; --color-third-party-Github: #1b1f24; diff --git a/web/themes/tailwind-theme-var-define.ts b/web/themes/tailwind-theme-var-define.ts index 11189441ee..935228199d 100644 --- a/web/themes/tailwind-theme-var-define.ts +++ b/web/themes/tailwind-theme-var-define.ts @@ -722,6 +722,8 @@ const vars = { 'util-colors-midnight-midnight-600': 'var(--color-util-colors-midnight-midnight-600)', 'util-colors-midnight-midnight-700': 'var(--color-util-colors-midnight-midnight-700)', + 'third-party-Arize': 'var(--color-third-party-Arize)', + 'third-party-Phoenix': 'var(--color-third-party-Phoenix)', 'third-party-LangChain': 'var(--color-third-party-LangChain)', 'third-party-Langfuse': 'var(--color-third-party-Langfuse)', 'third-party-Github': 'var(--color-third-party-Github)', diff --git a/web/utils/completion-params.ts b/web/utils/completion-params.ts new file mode 100644 index 0000000000..b46c3ab720 --- /dev/null +++ b/web/utils/completion-params.ts @@ -0,0 +1,88 @@ +import type { FormValue, ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' + +export const mergeValidCompletionParams = ( + oldParams: FormValue | undefined, + rules: ModelParameterRule[], +): { params: FormValue; removedDetails: Record } => { + if (!oldParams || Object.keys(oldParams).length === 0) + return { params: {}, removedDetails: {} } + + const acceptedKeys = new Set(rules.map(r => r.name)) + const ruleMap: Record = {} + rules.forEach((r) => { + ruleMap[r.name] = r + }) + + const nextParams: FormValue = {} + const removedDetails: Record = {} + + Object.entries(oldParams).forEach(([key, value]) => { + if (!acceptedKeys.has(key)) { + removedDetails[key] = 'unsupported' + return + } + + const rule = ruleMap[key] + if (!rule) { + removedDetails[key] = 'unsupported' + return + } + + switch (rule.type) { + case 'int': + case 'float': { + if (typeof value !== 'number') { + removedDetails[key] = 'invalid type' + return + } + const min = rule.min ?? Number.NEGATIVE_INFINITY + const max = rule.max ?? Number.POSITIVE_INFINITY + if (value < min || value > max) { + removedDetails[key] = `out of range (${min}-${max})` + return + } + nextParams[key] = value + return + } + case 'boolean': { + if (typeof value !== 'boolean') { + removedDetails[key] = 'invalid type' + return + } + nextParams[key] = value + return + } + case 'string': + case 'text': { + if (typeof value !== 'string') { + removedDetails[key] = 'invalid type' + return + } + if (Array.isArray(rule.options) && rule.options.length) { + if (!(rule.options as string[]).includes(value)) { + removedDetails[key] = 'unsupported option' + return + } + } + nextParams[key] = value + return + } + default: { + removedDetails[key] = `unsupported rule type: ${(rule as any)?.type ?? 'unknown'}` + } + } + }) + + return { params: nextParams, removedDetails } +} + +export const fetchAndMergeValidCompletionParams = async ( + provider: string, + modelId: string, + oldParams: FormValue | undefined, +): Promise<{ params: FormValue; removedDetails: Record }> => { + const { fetchModelParameterRules } = await import('@/service/common') + const url = `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` + const { data: parameterRules } = await fetchModelParameterRules(url) + return mergeValidCompletionParams(oldParams, parameterRules ?? []) +} diff --git a/web/utils/var.ts b/web/utils/var.ts index 8ce7bdb858..ce0ca030e1 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -120,3 +120,13 @@ export function getMarketplaceUrl(path: string, params?: Record { + const start = input.selectionStart + const end = input.selectionEnd + + input.value = input.value.replaceAll(' ', '_') + + if (start !== null && end !== null) + input.setSelectionRange(start, end) +}