fix: merge main

This commit is contained in:
CodingOnStar 2025-10-09 18:26:10 +08:00
commit 4df606f439
191 changed files with 3623 additions and 1316 deletions

View File

@ -40,18 +40,18 @@
<p align="center">
<a href="./README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README/README_TW.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="./README/README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README/README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README/README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README/README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README/README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README/README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README/README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README/README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README/README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README/README_DE.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="./README/README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="./docs/zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="./docs/zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./docs/ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./docs/es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./docs/fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./docs/tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./docs/ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./docs/ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./docs/tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./docs/vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./docs/de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="./docs/bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify is an open-source platform for developing LLM applications. Its intuitive interface combines agentic AI workflows, RAG pipelines, agent capabilities, model management, observability features, and more—allowing you to quickly move from prototype to production.

View File

@ -427,8 +427,8 @@ CODE_EXECUTION_POOL_MAX_KEEPALIVE_CONNECTIONS=20
CODE_EXECUTION_POOL_KEEPALIVE_EXPIRY=5.0
CODE_MAX_NUMBER=9223372036854775807
CODE_MIN_NUMBER=-9223372036854775808
CODE_MAX_STRING_LENGTH=80000
TEMPLATE_TRANSFORM_MAX_LENGTH=80000
CODE_MAX_STRING_LENGTH=400000
TEMPLATE_TRANSFORM_MAX_LENGTH=400000
CODE_MAX_STRING_ARRAY_LENGTH=30
CODE_MAX_OBJECT_ARRAY_LENGTH=30
CODE_MAX_NUMBER_ARRAY_LENGTH=1000

View File

@ -150,7 +150,7 @@ class CodeExecutionSandboxConfig(BaseSettings):
CODE_MAX_STRING_LENGTH: PositiveInt = Field(
description="Maximum allowed length for strings in code execution",
default=80000,
default=400_000,
)
CODE_MAX_STRING_ARRAY_LENGTH: PositiveInt = Field(
@ -582,6 +582,11 @@ class WorkflowConfig(BaseSettings):
default=200 * 1024,
)
TEMPLATE_TRANSFORM_MAX_LENGTH: PositiveInt = Field(
description="Maximum number of characters allowed in Template Transform node output",
default=400_000,
)
# GraphEngine Worker Pool Configuration
GRAPH_ENGINE_MIN_WORKERS: PositiveInt = Field(
description="Minimum number of workers per GraphEngine instance",

View File

@ -1,4 +1,5 @@
from configs import dify_config
from libs.collection_utils import convert_to_lower_and_upper_set
HIDDEN_VALUE = "[__HIDDEN__]"
UNKNOWN_VALUE = "[__UNKNOWN__]"
@ -6,24 +7,39 @@ UUID_NIL = "00000000-0000-0000-0000-000000000000"
DEFAULT_FILE_NUMBER_LIMITS = 3
IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "webp", "gif", "svg"]
IMAGE_EXTENSIONS.extend([ext.upper() for ext in IMAGE_EXTENSIONS])
IMAGE_EXTENSIONS = convert_to_lower_and_upper_set({"jpg", "jpeg", "png", "webp", "gif", "svg"})
VIDEO_EXTENSIONS = ["mp4", "mov", "mpeg", "webm"]
VIDEO_EXTENSIONS.extend([ext.upper() for ext in VIDEO_EXTENSIONS])
VIDEO_EXTENSIONS = convert_to_lower_and_upper_set({"mp4", "mov", "mpeg", "webm"})
AUDIO_EXTENSIONS = ["mp3", "m4a", "wav", "amr", "mpga"]
AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS])
AUDIO_EXTENSIONS = convert_to_lower_and_upper_set({"mp3", "m4a", "wav", "amr", "mpga"})
_doc_extensions: list[str]
_doc_extensions: set[str]
if dify_config.ETL_TYPE == "Unstructured":
_doc_extensions = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "vtt", "properties"]
_doc_extensions.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub"))
_doc_extensions = {
"txt",
"markdown",
"md",
"mdx",
"pdf",
"html",
"htm",
"xlsx",
"xls",
"vtt",
"properties",
"doc",
"docx",
"csv",
"eml",
"msg",
"pptx",
"xml",
"epub",
}
if dify_config.UNSTRUCTURED_API_URL:
_doc_extensions.append("ppt")
_doc_extensions.add("ppt")
else:
_doc_extensions = [
_doc_extensions = {
"txt",
"markdown",
"md",
@ -37,5 +53,5 @@ else:
"csv",
"vtt",
"properties",
]
DOCUMENT_EXTENSIONS = _doc_extensions + [ext.upper() for ext in _doc_extensions]
}
DOCUMENT_EXTENSIONS: set[str] = convert_to_lower_and_upper_set(_doc_extensions)

View File

@ -1,4 +1,3 @@
from fastapi.encoders import jsonable_encoder
from flask import make_response, redirect, request
from flask_login import current_user
from flask_restx import Resource, reqparse
@ -11,6 +10,7 @@ from controllers.console.wraps import (
setup_required,
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.oauth import OAuthHandler
from libs.helper import StrLen
from libs.login import login_required

View File

@ -1,4 +1,5 @@
import uuid
from typing import Literal, cast
from core.app.app_config.entities import (
DatasetEntity,
@ -74,6 +75,9 @@ class DatasetConfigManager:
return None
query_variable = config.get("dataset_query_variable")
metadata_model_config_dict = dataset_configs.get("metadata_model_config")
metadata_filtering_conditions_dict = dataset_configs.get("metadata_filtering_conditions")
if dataset_configs["retrieval_model"] == "single":
return DatasetEntity(
dataset_ids=dataset_ids,
@ -82,18 +86,23 @@ class DatasetConfigManager:
retrieve_strategy=DatasetRetrieveConfigEntity.RetrieveStrategy.value_of(
dataset_configs["retrieval_model"]
),
metadata_filtering_mode=dataset_configs.get("metadata_filtering_mode", "disabled"),
metadata_model_config=ModelConfig(**dataset_configs.get("metadata_model_config"))
if dataset_configs.get("metadata_model_config")
metadata_filtering_mode=cast(
Literal["disabled", "automatic", "manual"],
dataset_configs.get("metadata_filtering_mode", "disabled"),
),
metadata_model_config=ModelConfig(**metadata_model_config_dict)
if isinstance(metadata_model_config_dict, dict)
else None,
metadata_filtering_conditions=MetadataFilteringCondition(
**dataset_configs.get("metadata_filtering_conditions", {})
)
if dataset_configs.get("metadata_filtering_conditions")
metadata_filtering_conditions=MetadataFilteringCondition(**metadata_filtering_conditions_dict)
if isinstance(metadata_filtering_conditions_dict, dict)
else None,
),
)
else:
score_threshold_val = dataset_configs.get("score_threshold")
reranking_model_val = dataset_configs.get("reranking_model")
weights_val = dataset_configs.get("weights")
return DatasetEntity(
dataset_ids=dataset_ids,
retrieve_config=DatasetRetrieveConfigEntity(
@ -101,22 +110,23 @@ class DatasetConfigManager:
retrieve_strategy=DatasetRetrieveConfigEntity.RetrieveStrategy.value_of(
dataset_configs["retrieval_model"]
),
top_k=dataset_configs.get("top_k", 4),
score_threshold=dataset_configs.get("score_threshold")
if dataset_configs.get("score_threshold_enabled", False)
top_k=int(dataset_configs.get("top_k", 4)),
score_threshold=float(score_threshold_val)
if dataset_configs.get("score_threshold_enabled", False) and score_threshold_val is not None
else None,
reranking_model=dataset_configs.get("reranking_model"),
weights=dataset_configs.get("weights"),
reranking_enabled=dataset_configs.get("reranking_enabled", True),
reranking_model=reranking_model_val if isinstance(reranking_model_val, dict) else None,
weights=weights_val if isinstance(weights_val, dict) else None,
reranking_enabled=bool(dataset_configs.get("reranking_enabled", True)),
rerank_mode=dataset_configs.get("reranking_mode", "reranking_model"),
metadata_filtering_mode=dataset_configs.get("metadata_filtering_mode", "disabled"),
metadata_model_config=ModelConfig(**dataset_configs.get("metadata_model_config"))
if dataset_configs.get("metadata_model_config")
metadata_filtering_mode=cast(
Literal["disabled", "automatic", "manual"],
dataset_configs.get("metadata_filtering_mode", "disabled"),
),
metadata_model_config=ModelConfig(**metadata_model_config_dict)
if isinstance(metadata_model_config_dict, dict)
else None,
metadata_filtering_conditions=MetadataFilteringCondition(
**dataset_configs.get("metadata_filtering_conditions", {})
)
if dataset_configs.get("metadata_filtering_conditions")
metadata_filtering_conditions=MetadataFilteringCondition(**metadata_filtering_conditions_dict)
if isinstance(metadata_filtering_conditions_dict, dict)
else None,
),
)
@ -134,18 +144,17 @@ class DatasetConfigManager:
config = cls.extract_dataset_config_for_legacy_compatibility(tenant_id, app_mode, config)
# dataset_configs
if not config.get("dataset_configs"):
config["dataset_configs"] = {"retrieval_model": "single"}
if "dataset_configs" not in config or not config.get("dataset_configs"):
config["dataset_configs"] = {}
config["dataset_configs"]["retrieval_model"] = config["dataset_configs"].get("retrieval_model", "single")
if not isinstance(config["dataset_configs"], dict):
raise ValueError("dataset_configs must be of object type")
if not config["dataset_configs"].get("datasets"):
if "datasets" not in config["dataset_configs"] or not config["dataset_configs"].get("datasets"):
config["dataset_configs"]["datasets"] = {"strategy": "router", "datasets": []}
need_manual_query_datasets = config.get("dataset_configs") and config["dataset_configs"].get(
"datasets", {}
).get("datasets")
need_manual_query_datasets = config.get("dataset_configs", {}).get("datasets", {}).get("datasets")
if need_manual_query_datasets and app_mode == AppMode.COMPLETION:
# Only check when mode is completion
@ -166,8 +175,8 @@ class DatasetConfigManager:
:param config: app model config args
"""
# Extract dataset config for legacy compatibility
if not config.get("agent_mode"):
config["agent_mode"] = {"enabled": False, "tools": []}
if "agent_mode" not in config or not config.get("agent_mode"):
config["agent_mode"] = {}
if not isinstance(config["agent_mode"], dict):
raise ValueError("agent_mode must be of object type")
@ -180,19 +189,22 @@ class DatasetConfigManager:
raise ValueError("enabled in agent_mode must be of boolean type")
# tools
if not config["agent_mode"].get("tools"):
if "tools" not in config["agent_mode"] or not config["agent_mode"].get("tools"):
config["agent_mode"]["tools"] = []
if not isinstance(config["agent_mode"]["tools"], list):
raise ValueError("tools in agent_mode must be a list of objects")
# strategy
if not config["agent_mode"].get("strategy"):
if "strategy" not in config["agent_mode"] or not config["agent_mode"].get("strategy"):
config["agent_mode"]["strategy"] = PlanningStrategy.ROUTER.value
has_datasets = False
if config["agent_mode"]["strategy"] in {PlanningStrategy.ROUTER.value, PlanningStrategy.REACT_ROUTER.value}:
for tool in config["agent_mode"]["tools"]:
if config.get("agent_mode", {}).get("strategy") in {
PlanningStrategy.ROUTER.value,
PlanningStrategy.REACT_ROUTER.value,
}:
for tool in config.get("agent_mode", {}).get("tools", []):
key = list(tool.keys())[0]
if key == "dataset":
# old style, use tool name as key
@ -217,7 +229,7 @@ class DatasetConfigManager:
has_datasets = True
need_manual_query_datasets = has_datasets and config["agent_mode"]["enabled"]
need_manual_query_datasets = has_datasets and config.get("agent_mode", {}).get("enabled")
if need_manual_query_datasets and app_mode == AppMode.COMPLETION:
# Only check when mode is completion

View File

@ -107,7 +107,6 @@ class MessageCycleManager:
if dify_config.DEBUG:
logger.exception("generate conversation name failed, conversation_id: %s", conversation_id)
db.session.merge(conversation)
db.session.commit()
db.session.close()

View File

@ -1,7 +1,6 @@
from typing import TYPE_CHECKING, Any, Optional
from openai import BaseModel
from pydantic import Field
from pydantic import BaseModel, Field
# Import InvokeFrom locally to avoid circular import
from core.app.entities.app_invoke_entities import InvokeFrom

View File

@ -1,7 +1,6 @@
from typing import Any
from openai import BaseModel
from pydantic import Field
from pydantic import BaseModel, Field
from core.app.entities.app_invoke_entities import InvokeFrom
from core.tools.entities.tool_entities import CredentialType, ToolInvokeFrom

View File

@ -1,7 +1,7 @@
import os
from collections.abc import Mapping, Sequence
from typing import Any
from configs import dify_config
from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage
from core.workflow.enums import ErrorStrategy, NodeType, WorkflowNodeExecutionStatus
from core.workflow.node_events import NodeRunResult
@ -9,7 +9,7 @@ from core.workflow.nodes.base.entities import BaseNodeData, RetryConfig
from core.workflow.nodes.base.node import Node
from core.workflow.nodes.template_transform.entities import TemplateTransformNodeData
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = int(os.environ.get("TEMPLATE_TRANSFORM_MAX_LENGTH", "80000"))
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
class TemplateTransformNode(Node):

View File

@ -10,14 +10,14 @@ from dify_app import DifyApp
def init_app(app: DifyApp):
@app.after_request
def after_request(response):
def after_request(response): # pyright: ignore[reportUnusedFunction]
"""Add Version headers to the response."""
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():
def health(): # pyright: ignore[reportUnusedFunction]
return Response(
json.dumps({"pid": os.getpid(), "status": "ok", "version": dify_config.project.version}),
status=200,
@ -25,7 +25,7 @@ def init_app(app: DifyApp):
)
@app.route("/threads")
def threads():
def threads(): # pyright: ignore[reportUnusedFunction]
num_threads = threading.active_count()
threads = threading.enumerate()
@ -50,7 +50,7 @@ def init_app(app: DifyApp):
}
@app.route("/db-pool-stat")
def pool_stat():
def pool_stat(): # pyright: ignore[reportUnusedFunction]
from extensions.ext_database import db
engine = db.engine

View File

@ -10,7 +10,7 @@ from models.engine import db
logger = logging.getLogger(__name__)
# Global flag to avoid duplicate registration of event listener
_GEVENT_COMPATIBILITY_SETUP: bool = False
_gevent_compatibility_setup: bool = False
def _safe_rollback(connection):
@ -26,14 +26,14 @@ def _safe_rollback(connection):
def _setup_gevent_compatibility():
global _GEVENT_COMPATIBILITY_SETUP # pylint: disable=global-statement
global _gevent_compatibility_setup # pylint: disable=global-statement
# Avoid duplicate registration
if _GEVENT_COMPATIBILITY_SETUP:
if _gevent_compatibility_setup:
return
@event.listens_for(Pool, "reset")
def _safe_reset(dbapi_connection, connection_record, reset_state): # pylint: disable=unused-argument
def _safe_reset(dbapi_connection, connection_record, reset_state): # pyright: ignore[reportUnusedFunction]
if reset_state.terminate_only:
return
@ -47,7 +47,7 @@ def _setup_gevent_compatibility():
except (AttributeError, ImportError):
_safe_rollback(dbapi_connection)
_GEVENT_COMPATIBILITY_SETUP = True
_gevent_compatibility_setup = True
def init_app(app: DifyApp):

View File

@ -2,4 +2,4 @@ from dify_app import DifyApp
def init_app(app: DifyApp):
from events import event_handlers # noqa: F401
from events import event_handlers # noqa: F401 # pyright: ignore[reportUnusedImport]

View File

@ -136,6 +136,7 @@ def init_app(app: DifyApp):
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPSpanExporter
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
@ -238,6 +239,7 @@ def init_app(app: DifyApp):
init_sqlalchemy_instrumentor(app)
RedisInstrumentor().instrument()
RequestsInstrumentor().instrument()
HTTPXClientInstrumentor().instrument()
atexit.register(shutdown_tracer)

View File

@ -4,7 +4,6 @@ from dify_app import DifyApp
def init_app(app: DifyApp):
if dify_config.SENTRY_DSN:
import openai
import sentry_sdk
from langfuse import parse_error # type: ignore
from sentry_sdk.integrations.celery import CeleryIntegration
@ -28,7 +27,6 @@ def init_app(app: DifyApp):
HTTPException,
ValueError,
FileNotFoundError,
openai.APIStatusError,
InvokeRateLimitError,
parse_error.defaultErrorResponse,
],

View File

@ -33,7 +33,9 @@ class AliyunOssStorage(BaseStorage):
def load_once(self, filename: str) -> bytes:
obj = self.client.get_object(self.__wrapper_folder_filename(filename))
data: bytes = obj.read()
data = obj.read()
if not isinstance(data, bytes):
return b""
return data
def load_stream(self, filename: str) -> Generator:

View File

@ -39,10 +39,10 @@ class AwsS3Storage(BaseStorage):
self.client.head_bucket(Bucket=self.bucket_name)
except ClientError as e:
# if bucket not exists, create it
if e.response["Error"]["Code"] == "404":
if e.response.get("Error", {}).get("Code") == "404":
self.client.create_bucket(Bucket=self.bucket_name)
# if bucket is not accessible, pass, maybe the bucket is existing but not accessible
elif e.response["Error"]["Code"] == "403":
elif e.response.get("Error", {}).get("Code") == "403":
pass
else:
# other error, raise exception
@ -55,7 +55,7 @@ class AwsS3Storage(BaseStorage):
try:
data: bytes = self.client.get_object(Bucket=self.bucket_name, Key=filename)["Body"].read()
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
if ex.response.get("Error", {}).get("Code") == "NoSuchKey":
raise FileNotFoundError("File not found")
else:
raise
@ -66,7 +66,7 @@ class AwsS3Storage(BaseStorage):
response = self.client.get_object(Bucket=self.bucket_name, Key=filename)
yield from response["Body"].iter_chunks()
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
if ex.response.get("Error", {}).get("Code") == "NoSuchKey":
raise FileNotFoundError("file not found")
elif "reached max retries" in str(ex):
raise ValueError("please do not request the same file too frequently")

View File

@ -27,24 +27,38 @@ class AzureBlobStorage(BaseStorage):
self.credential = None
def save(self, filename, data):
if not self.bucket_name:
return
client = self._sync_client()
blob_container = client.get_container_client(container=self.bucket_name)
blob_container.upload_blob(filename, data)
def load_once(self, filename: str) -> bytes:
if not self.bucket_name:
raise FileNotFoundError("Azure bucket name is not configured.")
client = self._sync_client()
blob = client.get_container_client(container=self.bucket_name)
blob = blob.get_blob_client(blob=filename)
data: bytes = blob.download_blob().readall()
data = blob.download_blob().readall()
if not isinstance(data, bytes):
raise TypeError(f"Expected bytes from blob.readall(), got {type(data).__name__}")
return data
def load_stream(self, filename: str) -> Generator:
if not self.bucket_name:
raise FileNotFoundError("Azure bucket name is not configured.")
client = self._sync_client()
blob = client.get_blob_client(container=self.bucket_name, blob=filename)
blob_data = blob.download_blob()
yield from blob_data.chunks()
def download(self, filename, target_filepath):
if not self.bucket_name:
return
client = self._sync_client()
blob = client.get_blob_client(container=self.bucket_name, blob=filename)
@ -53,12 +67,18 @@ class AzureBlobStorage(BaseStorage):
blob_data.readinto(my_blob)
def exists(self, filename):
if not self.bucket_name:
return False
client = self._sync_client()
blob = client.get_blob_client(container=self.bucket_name, blob=filename)
return blob.exists()
def delete(self, filename):
if not self.bucket_name:
return
client = self._sync_client()
blob_container = client.get_container_client(container=self.bucket_name)

View File

@ -430,7 +430,7 @@ class ClickZettaVolumeStorage(BaseStorage):
rows = self._execute_sql(sql, fetch=True)
exists = len(rows) > 0
exists = len(rows) > 0 if rows else False
logger.debug("File %s exists check: %s", filename, exists)
return exists
except Exception as e:
@ -509,16 +509,17 @@ class ClickZettaVolumeStorage(BaseStorage):
rows = self._execute_sql(sql, fetch=True)
result = []
for row in rows:
file_path = row[0] # relative_path column
if rows:
for row in rows:
file_path = row[0] # relative_path column
# For User Volume, remove dify prefix from results
dify_prefix_with_slash = f"{self._config.dify_prefix}/"
if volume_prefix == "USER VOLUME" and file_path.startswith(dify_prefix_with_slash):
file_path = file_path[len(dify_prefix_with_slash) :] # Remove prefix
# For User Volume, remove dify prefix from results
dify_prefix_with_slash = f"{self._config.dify_prefix}/"
if volume_prefix == "USER VOLUME" and file_path.startswith(dify_prefix_with_slash):
file_path = file_path[len(dify_prefix_with_slash) :] # Remove prefix
if files and not file_path.endswith("/") or directories and file_path.endswith("/"):
result.append(file_path)
if files and not file_path.endswith("/") or directories and file_path.endswith("/"):
result.append(file_path)
logger.debug("Scanned %d items in path %s", len(result), path)
return result

View File

@ -439,6 +439,11 @@ class VolumePermissionManager:
self._permission_cache.clear()
logger.debug("Permission cache cleared")
@property
def volume_type(self) -> str | None:
"""Get the volume type."""
return self._volume_type
def get_permission_summary(self, dataset_id: str | None = None) -> dict[str, bool]:
"""Get permission summary
@ -632,13 +637,13 @@ def check_volume_permission(permission_manager: VolumePermissionManager, operati
VolumePermissionError: If no permission
"""
if not permission_manager.validate_operation(operation, dataset_id):
error_message = f"Permission denied for operation '{operation}' on {permission_manager._volume_type} volume"
error_message = f"Permission denied for operation '{operation}' on {permission_manager.volume_type} volume"
if dataset_id:
error_message += f" (dataset: {dataset_id})"
raise VolumePermissionError(
error_message,
operation=operation,
volume_type=permission_manager._volume_type or "unknown",
volume_type=permission_manager.volume_type or "unknown",
dataset_id=dataset_id,
)

View File

@ -35,12 +35,16 @@ class GoogleCloudStorage(BaseStorage):
def load_once(self, filename: str) -> bytes:
bucket = self.client.get_bucket(self.bucket_name)
blob = bucket.get_blob(filename)
if blob is None:
raise FileNotFoundError("File not found")
data: bytes = blob.download_as_bytes()
return data
def load_stream(self, filename: str) -> Generator:
bucket = self.client.get_bucket(self.bucket_name)
blob = bucket.get_blob(filename)
if blob is None:
raise FileNotFoundError("File not found")
with blob.open(mode="rb") as blob_stream:
while chunk := blob_stream.read(4096):
yield chunk
@ -48,6 +52,8 @@ class GoogleCloudStorage(BaseStorage):
def download(self, filename, target_filepath):
bucket = self.client.get_bucket(self.bucket_name)
blob = bucket.get_blob(filename)
if blob is None:
raise FileNotFoundError("File not found")
blob.download_to_filename(target_filepath)
def exists(self, filename):

View File

@ -45,7 +45,7 @@ class HuaweiObsStorage(BaseStorage):
def _get_meta(self, filename):
res = self.client.getObjectMetadata(bucketName=self.bucket_name, objectKey=filename)
if res.status < 300:
if res and res.status and res.status < 300:
return res
else:
return None

View File

@ -3,9 +3,9 @@ import os
from collections.abc import Generator
from pathlib import Path
import opendal
from dotenv import dotenv_values
from opendal import Operator
from opendal.layers import RetryLayer
from extensions.storage.base_storage import BaseStorage
@ -35,7 +35,7 @@ class OpenDALStorage(BaseStorage):
root = kwargs.get("root", "storage")
Path(root).mkdir(parents=True, exist_ok=True)
retry_layer = RetryLayer(max_times=3, factor=2.0, jitter=True)
retry_layer = opendal.layers.RetryLayer(max_times=3, factor=2.0, jitter=True)
self.op = Operator(scheme=scheme, **kwargs).layer(retry_layer)
logger.debug("opendal operator created with scheme %s", scheme)
logger.debug("added retry layer to opendal operator")

View File

@ -29,7 +29,7 @@ class OracleOCIStorage(BaseStorage):
try:
data: bytes = self.client.get_object(Bucket=self.bucket_name, Key=filename)["Body"].read()
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
if ex.response.get("Error", {}).get("Code") == "NoSuchKey":
raise FileNotFoundError("File not found")
else:
raise
@ -40,7 +40,7 @@ class OracleOCIStorage(BaseStorage):
response = self.client.get_object(Bucket=self.bucket_name, Key=filename)
yield from response["Body"].iter_chunks()
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
if ex.response.get("Error", {}).get("Code") == "NoSuchKey":
raise FileNotFoundError("File not found")
else:
raise

View File

@ -46,13 +46,13 @@ class SupabaseStorage(BaseStorage):
Path(target_filepath).write_bytes(result)
def exists(self, filename):
result = self.client.storage.from_(self.bucket_name).list(filename)
if result.count() > 0:
result = self.client.storage.from_(self.bucket_name).list(path=filename)
if len(result) > 0:
return True
return False
def delete(self, filename):
self.client.storage.from_(self.bucket_name).remove(filename)
self.client.storage.from_(self.bucket_name).remove([filename])
def bucket_exists(self):
buckets = self.client.storage.list_buckets()

View File

@ -11,6 +11,14 @@ class VolcengineTosStorage(BaseStorage):
def __init__(self):
super().__init__()
if not dify_config.VOLCENGINE_TOS_ACCESS_KEY:
raise ValueError("VOLCENGINE_TOS_ACCESS_KEY is not set")
if not dify_config.VOLCENGINE_TOS_SECRET_KEY:
raise ValueError("VOLCENGINE_TOS_SECRET_KEY is not set")
if not dify_config.VOLCENGINE_TOS_ENDPOINT:
raise ValueError("VOLCENGINE_TOS_ENDPOINT is not set")
if not dify_config.VOLCENGINE_TOS_REGION:
raise ValueError("VOLCENGINE_TOS_REGION is not set")
self.bucket_name = dify_config.VOLCENGINE_TOS_BUCKET_NAME
self.client = tos.TosClientV2(
ak=dify_config.VOLCENGINE_TOS_ACCESS_KEY,
@ -20,27 +28,39 @@ class VolcengineTosStorage(BaseStorage):
)
def save(self, filename, data):
if not self.bucket_name:
raise ValueError("VOLCENGINE_TOS_BUCKET_NAME is not set")
self.client.put_object(bucket=self.bucket_name, key=filename, content=data)
def load_once(self, filename: str) -> bytes:
if not self.bucket_name:
raise FileNotFoundError("VOLCENGINE_TOS_BUCKET_NAME is not set")
data = self.client.get_object(bucket=self.bucket_name, key=filename).read()
if not isinstance(data, bytes):
raise TypeError(f"Expected bytes, got {type(data).__name__}")
return data
def load_stream(self, filename: str) -> Generator:
if not self.bucket_name:
raise FileNotFoundError("VOLCENGINE_TOS_BUCKET_NAME is not set")
response = self.client.get_object(bucket=self.bucket_name, key=filename)
while chunk := response.read(4096):
yield chunk
def download(self, filename, target_filepath):
if not self.bucket_name:
raise ValueError("VOLCENGINE_TOS_BUCKET_NAME is not set")
self.client.get_object_to_file(bucket=self.bucket_name, key=filename, file_path=target_filepath)
def exists(self, filename):
if not self.bucket_name:
return False
res = self.client.head_object(bucket=self.bucket_name, key=filename)
if res.status_code != 200:
return False
return True
def delete(self, filename):
if not self.bucket_name:
return
self.client.delete_object(bucket=self.bucket_name, key=filename)

View File

@ -0,0 +1,14 @@
def convert_to_lower_and_upper_set(inputs: list[str] | set[str]) -> set[str]:
"""
Convert a list or set of strings to a set containing both lower and upper case versions of each string.
Args:
inputs (list[str] | set[str]): A list or set of strings to be converted.
Returns:
set[str]: A set containing both lower and upper case versions of each string.
"""
if not inputs:
return set()
else:
return {case for s in inputs if s for case in (s.lower(), s.upper())}

View File

@ -5,7 +5,6 @@ requires-python = ">=3.11,<3.13"
dependencies = [
"arize-phoenix-otel~=0.9.2",
"authlib==1.6.4",
"azure-identity==1.16.1",
"beautifulsoup4==4.12.2",
"boto3==1.35.99",
@ -35,10 +34,8 @@ dependencies = [
"json-repair>=0.41.1",
"langfuse~=2.51.3",
"langsmith~=0.1.77",
"mailchimp-transactional~=1.0.50",
"markdown~=3.5.1",
"numpy~=1.26.4",
"openai~=1.61.0",
"openpyxl~=3.1.5",
"opik~=1.7.25",
"opentelemetry-api==1.27.0",
@ -50,6 +47,7 @@ dependencies = [
"opentelemetry-instrumentation==0.48b0",
"opentelemetry-instrumentation-celery==0.48b0",
"opentelemetry-instrumentation-flask==0.48b0",
"opentelemetry-instrumentation-httpx==0.48b0",
"opentelemetry-instrumentation-redis==0.48b0",
"opentelemetry-instrumentation-requests==0.48b0",
"opentelemetry-instrumentation-sqlalchemy==0.48b0",
@ -61,7 +59,6 @@ dependencies = [
"opentelemetry-semantic-conventions==0.48b0",
"opentelemetry-util-http==0.48b0",
"pandas[excel,output-formatting,performance]~=2.2.2",
"pandoc~=2.4",
"psycogreen~=1.0.2",
"psycopg2-binary~=2.9.6",
"pycryptodome==3.19.1",
@ -181,10 +178,10 @@ dev = [
# Required for storage clients
############################################################
storage = [
"azure-storage-blob==12.13.0",
"azure-storage-blob==12.26.0",
"bce-python-sdk~=0.9.23",
"cos-python-sdk-v5==1.9.38",
"esdk-obs-python==3.24.6.1",
"esdk-obs-python==3.25.8",
"google-cloud-storage==2.16.0",
"opendal~=0.46.0",
"oss2==2.18.5",

View File

@ -4,9 +4,7 @@
"tests/",
".venv",
"migrations/",
"core/rag",
"extensions",
"core/app/app_config/easy_ui_based_app/dataset"
"core/rag"
],
"typeCheckingMode": "strict",
"allowedUntypedLibraries": [
@ -14,6 +12,7 @@
"flask_login",
"opentelemetry.instrumentation.celery",
"opentelemetry.instrumentation.flask",
"opentelemetry.instrumentation.httpx",
"opentelemetry.instrumentation.requests",
"opentelemetry.instrumentation.sqlalchemy",
"opentelemetry.instrumentation.redis"
@ -25,7 +24,6 @@
"reportUnknownLambdaType": "hint",
"reportMissingParameterType": "hint",
"reportMissingTypeArgument": "hint",
"reportUnnecessaryContains": "hint",
"reportUnnecessaryComparison": "hint",
"reportUnnecessaryCast": "hint",
"reportUnnecessaryIsInstance": "hint",

View File

@ -7,7 +7,7 @@ env =
CHATGLM_API_BASE = http://a.abc.com:11451
CODE_EXECUTION_API_KEY = dify-sandbox
CODE_EXECUTION_ENDPOINT = http://127.0.0.1:8194
CODE_MAX_STRING_LENGTH = 80000
CODE_MAX_STRING_LENGTH = 400000
PLUGIN_DAEMON_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi
PLUGIN_DAEMON_URL=http://127.0.0.1:5002
PLUGIN_MAX_PACKAGE_SIZE=15728640

View File

@ -2,8 +2,6 @@ import uuid
from collections.abc import Generator, Mapping
from typing import Any, Union
from openai._exceptions import RateLimitError
from configs import dify_config
from core.app.apps.advanced_chat.app_generator import AdvancedChatAppGenerator
from core.app.apps.agent_chat.app_generator import AgentChatAppGenerator
@ -122,8 +120,6 @@ class AppGenerateService:
)
else:
raise ValueError(f"Invalid app mode {app_model.mode}")
except RateLimitError as e:
raise InvokeRateLimitError(str(e))
except Exception:
rate_limit.exit(request_id)
raise

View File

@ -149,8 +149,7 @@ class RagPipelineTransformService:
file_extensions = node.get("data", {}).get("fileExtensions", [])
if not file_extensions:
return node
file_extensions = [file_extension.lower() for file_extension in file_extensions]
node["data"]["fileExtensions"] = DOCUMENT_EXTENSIONS
node["data"]["fileExtensions"] = [ext.lower() for ext in file_extensions if ext in DOCUMENT_EXTENSIONS]
return node
def _deal_knowledge_index(

View File

@ -349,14 +349,10 @@ class BuiltinToolManageService:
provider_controller = ToolManager.get_builtin_provider(default_provider.provider, tenant_id)
credentials: list[ToolProviderCredentialApiEntity] = []
encrypters = {}
for provider in providers:
credential_type = provider.credential_type
if credential_type not in encrypters:
encrypters[credential_type] = BuiltinToolManageService.create_tool_encrypter(
tenant_id, provider, provider.provider, provider_controller
)[0]
encrypter = encrypters[credential_type]
encrypter, _ = BuiltinToolManageService.create_tool_encrypter(
tenant_id, provider, provider.provider, provider_controller
)
decrypt_credential = encrypter.mask_tool_credentials(encrypter.decrypt(provider.credentials))
credential_entity = ToolTransformService.convert_builtin_provider_to_credential_entity(
provider=provider,

View File

@ -79,7 +79,6 @@ class WorkflowConverter:
new_app.updated_by = account.id
db.session.add(new_app)
db.session.flush()
db.session.commit()
workflow.app_id = new_app.id
db.session.commit()

View File

@ -29,23 +29,10 @@ def priority_rag_pipeline_run_task(
tenant_id: str,
):
"""
Async Run rag pipeline
:param rag_pipeline_invoke_entities: Rag pipeline invoke entities
rag_pipeline_invoke_entities include:
:param pipeline_id: Pipeline ID
:param user_id: User ID
:param tenant_id: Tenant ID
:param workflow_id: Workflow ID
:param invoke_from: Invoke source (debugger, published, etc.)
:param streaming: Whether to stream results
:param datasource_type: Type of datasource
:param datasource_info: Datasource information dict
:param batch: Batch identifier
:param document_id: Document ID (optional)
:param start_node_id: Starting node ID
:param inputs: Input parameters dict
:param workflow_execution_id: Workflow execution ID
:param workflow_thread_pool_id: Thread pool ID for workflow execution
Async Run rag pipeline task using high priority queue.
:param rag_pipeline_invoke_entities_file_id: File ID containing serialized RAG pipeline invoke entities
:param tenant_id: Tenant ID for the pipeline execution
"""
# run with threading, thread pool size is 10

View File

@ -30,23 +30,10 @@ def rag_pipeline_run_task(
tenant_id: str,
):
"""
Async Run rag pipeline
:param rag_pipeline_invoke_entities: Rag pipeline invoke entities
rag_pipeline_invoke_entities include:
:param pipeline_id: Pipeline ID
:param user_id: User ID
:param tenant_id: Tenant ID
:param workflow_id: Workflow ID
:param invoke_from: Invoke source (debugger, published, etc.)
:param streaming: Whether to stream results
:param datasource_type: Type of datasource
:param datasource_info: Datasource information dict
:param batch: Batch identifier
:param document_id: Document ID (optional)
:param start_node_id: Starting node ID
:param inputs: Input parameters dict
:param workflow_execution_id: Workflow execution ID
:param workflow_thread_pool_id: Thread pool ID for workflow execution
Async Run rag pipeline task using regular priority queue.
:param rag_pipeline_invoke_entities_file_id: File ID containing serialized RAG pipeline invoke entities
:param tenant_id: Tenant ID for the pipeline execution
"""
# run with threading, thread pool size is 10

View File

@ -5,15 +5,10 @@ These tasks provide asynchronous storage capabilities for workflow execution dat
improving performance by offloading storage operations to background workers.
"""
import logging
from celery import shared_task # type: ignore[import-untyped]
from sqlalchemy.orm import Session
from extensions.ext_database import db
_logger = logging.getLogger(__name__)
from services.workflow_draft_variable_service import DraftVarFileDeletion, WorkflowDraftVariableService

View File

@ -1,9 +1,9 @@
import time
import uuid
from os import getenv
import pytest
from configs import dify_config
from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities import GraphInitParams, GraphRuntimeState, VariablePool
from core.workflow.enums import WorkflowNodeExecutionStatus
@ -15,7 +15,7 @@ from core.workflow.system_variable import SystemVariable
from models.enums import UserFrom
from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock
CODE_MAX_STRING_LENGTH = int(getenv("CODE_MAX_STRING_LENGTH", "10000"))
CODE_MAX_STRING_LENGTH = dify_config.CODE_MAX_STRING_LENGTH
def init_code_node(code_config: dict):

View File

@ -18,6 +18,7 @@ from flask.testing import FlaskClient
from sqlalchemy import Engine, text
from sqlalchemy.orm import Session
from testcontainers.core.container import DockerContainer
from testcontainers.core.network import Network
from testcontainers.core.waiting_utils import wait_for_logs
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer
@ -41,6 +42,7 @@ class DifyTestContainers:
def __init__(self):
"""Initialize container management with default configurations."""
self.network: Network | None = None
self.postgres: PostgresContainer | None = None
self.redis: RedisContainer | None = None
self.dify_sandbox: DockerContainer | None = None
@ -62,12 +64,18 @@ class DifyTestContainers:
logger.info("Starting test containers for Dify integration tests...")
# Create Docker network for container communication
logger.info("Creating Docker network for container communication...")
self.network = Network()
self.network.create()
logger.info("Docker network created successfully with name: %s", self.network.name)
# Start PostgreSQL container for main application database
# PostgreSQL is used for storing user data, workflows, and application state
logger.info("Initializing PostgreSQL container...")
self.postgres = PostgresContainer(
image="postgres:14-alpine",
)
).with_network(self.network)
self.postgres.start()
db_host = self.postgres.get_container_host_ip()
db_port = self.postgres.get_exposed_port(5432)
@ -137,7 +145,7 @@ class DifyTestContainers:
# Start Redis container for caching and session management
# Redis is used for storing session data, cache entries, and temporary data
logger.info("Initializing Redis container...")
self.redis = RedisContainer(image="redis:6-alpine", port=6379)
self.redis = RedisContainer(image="redis:6-alpine", port=6379).with_network(self.network)
self.redis.start()
redis_host = self.redis.get_container_host_ip()
redis_port = self.redis.get_exposed_port(6379)
@ -153,7 +161,7 @@ class DifyTestContainers:
# Start Dify Sandbox container for code execution environment
# Dify Sandbox provides a secure environment for executing user code
logger.info("Initializing Dify Sandbox container...")
self.dify_sandbox = DockerContainer(image="langgenius/dify-sandbox:latest")
self.dify_sandbox = DockerContainer(image="langgenius/dify-sandbox:latest").with_network(self.network)
self.dify_sandbox.with_exposed_ports(8194)
self.dify_sandbox.env = {
"API_KEY": "test_api_key",
@ -173,22 +181,28 @@ class DifyTestContainers:
# Start Dify Plugin Daemon container for plugin management
# Dify Plugin Daemon provides plugin lifecycle management and execution
logger.info("Initializing Dify Plugin Daemon container...")
self.dify_plugin_daemon = DockerContainer(image="langgenius/dify-plugin-daemon:0.3.0-local")
self.dify_plugin_daemon = DockerContainer(image="langgenius/dify-plugin-daemon:0.3.0-local").with_network(
self.network
)
self.dify_plugin_daemon.with_exposed_ports(5002)
# Get container internal network addresses
postgres_container_name = self.postgres.get_wrapped_container().name
redis_container_name = self.redis.get_wrapped_container().name
self.dify_plugin_daemon.env = {
"DB_HOST": db_host,
"DB_PORT": str(db_port),
"DB_HOST": postgres_container_name, # Use container name for internal network communication
"DB_PORT": "5432", # Use internal port
"DB_USERNAME": self.postgres.username,
"DB_PASSWORD": self.postgres.password,
"DB_DATABASE": "dify_plugin",
"REDIS_HOST": redis_host,
"REDIS_PORT": str(redis_port),
"REDIS_HOST": redis_container_name, # Use container name for internal network communication
"REDIS_PORT": "6379", # Use internal port
"REDIS_PASSWORD": "",
"SERVER_PORT": "5002",
"SERVER_KEY": "test_plugin_daemon_key",
"MAX_PLUGIN_PACKAGE_SIZE": "52428800",
"PPROF_ENABLED": "false",
"DIFY_INNER_API_URL": f"http://{db_host}:5001",
"DIFY_INNER_API_URL": f"http://{postgres_container_name}:5001",
"DIFY_INNER_API_KEY": "test_inner_api_key",
"PLUGIN_REMOTE_INSTALLING_HOST": "0.0.0.0",
"PLUGIN_REMOTE_INSTALLING_PORT": "5003",
@ -253,6 +267,15 @@ class DifyTestContainers:
# Log error but don't fail the test cleanup
logger.warning("Failed to stop container %s: %s", container, e)
# Stop and remove the network
if self.network:
try:
logger.info("Removing Docker network...")
self.network.remove()
logger.info("Successfully removed Docker network")
except Exception as e:
logger.warning("Failed to remove Docker network: %s", e)
self._containers_started = False
logger.info("All test containers stopped and cleaned up successfully")

View File

@ -3,7 +3,6 @@ from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from openai._exceptions import RateLimitError
from core.app.entities.app_invoke_entities import InvokeFrom
from models.model import EndUser
@ -484,36 +483,6 @@ class TestAppGenerateService:
# Verify error message
assert "Rate limit exceeded" in str(exc_info.value)
def test_generate_with_rate_limit_error_from_openai(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test generation when OpenAI rate limit error occurs.
"""
fake = Faker()
app, account = self._create_test_app_and_account(
db_session_with_containers, mock_external_service_dependencies, mode="completion"
)
# Setup completion generator to raise RateLimitError
mock_response = MagicMock()
mock_response.request = MagicMock()
mock_external_service_dependencies["completion_generator"].return_value.generate.side_effect = RateLimitError(
"Rate limit exceeded", response=mock_response, body=None
)
# Setup test arguments
args = {"inputs": {"query": fake.text(max_nb_chars=50)}, "response_mode": "streaming"}
# Execute the method under test and expect rate limit error
with pytest.raises(InvokeRateLimitError) as exc_info:
AppGenerateService.generate(
app_model=app, user=account, args=args, invoke_from=InvokeFrom.SERVICE_API, streaming=True
)
# Verify error message
assert "Rate limit exceeded" in str(exc_info.value)
def test_generate_with_invalid_app_mode(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test generation with invalid app mode.

View File

@ -784,133 +784,6 @@ class TestCleanDatasetTask:
print(f"Total cleanup time: {cleanup_duration:.3f} seconds")
print(f"Average time per document: {cleanup_duration / len(documents):.3f} seconds")
def test_clean_dataset_task_concurrent_cleanup_scenarios(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test dataset cleanup with concurrent cleanup scenarios and race conditions.
This test verifies that the task can properly:
1. Handle multiple cleanup operations on the same dataset
2. Prevent data corruption during concurrent access
3. Maintain data consistency across multiple cleanup attempts
4. Handle race conditions gracefully
5. Ensure idempotent cleanup operations
"""
# Create test data
account, tenant = self._create_test_account_and_tenant(db_session_with_containers)
dataset = self._create_test_dataset(db_session_with_containers, account, tenant)
document = self._create_test_document(db_session_with_containers, account, tenant, dataset)
segment = self._create_test_segment(db_session_with_containers, account, tenant, dataset, document)
upload_file = self._create_test_upload_file(db_session_with_containers, account, tenant)
# Update document with file reference
import json
document.data_source_info = json.dumps({"upload_file_id": upload_file.id})
from extensions.ext_database import db
db.session.commit()
# Save IDs for verification
dataset_id = dataset.id
tenant_id = tenant.id
upload_file_id = upload_file.id
# Mock storage to simulate slow operations
mock_storage = mock_external_service_dependencies["storage"]
original_delete = mock_storage.delete
def slow_delete(key):
import time
time.sleep(0.1) # Simulate slow storage operation
return original_delete(key)
mock_storage.delete.side_effect = slow_delete
# Execute multiple cleanup operations concurrently
import threading
cleanup_results = []
cleanup_errors = []
def run_cleanup():
try:
clean_dataset_task(
dataset_id=dataset_id,
tenant_id=tenant_id,
indexing_technique="high_quality",
index_struct='{"type": "paragraph"}',
collection_binding_id=str(uuid.uuid4()),
doc_form="paragraph_index",
)
cleanup_results.append("success")
except Exception as e:
cleanup_errors.append(str(e))
# Start multiple cleanup threads
threads = []
for i in range(3):
thread = threading.Thread(target=run_cleanup)
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
# Verify results
# Check that all documents were deleted (only once)
remaining_documents = db.session.query(Document).filter_by(dataset_id=dataset_id).all()
assert len(remaining_documents) == 0
# Check that all segments were deleted (only once)
remaining_segments = db.session.query(DocumentSegment).filter_by(dataset_id=dataset_id).all()
assert len(remaining_segments) == 0
# Check that upload file was deleted (only once)
# Note: In concurrent scenarios, the first thread deletes documents and segments,
# subsequent threads may not find the related data to clean up upload files
# This demonstrates the idempotent nature of the cleanup process
remaining_files = db.session.query(UploadFile).filter_by(id=upload_file_id).all()
# The upload file should be deleted by the first successful cleanup operation
# However, in concurrent scenarios, this may not always happen due to race conditions
# This test demonstrates the idempotent nature of the cleanup process
if len(remaining_files) > 0:
print(f"Warning: Upload file {upload_file_id} was not deleted in concurrent scenario")
print("This is expected behavior demonstrating the idempotent nature of cleanup")
# We don't assert here as the behavior depends on timing and race conditions
# Verify that storage.delete was called (may be called multiple times in concurrent scenarios)
# In concurrent scenarios, storage operations may be called multiple times due to race conditions
assert mock_storage.delete.call_count > 0
# Verify that index processor was called (may be called multiple times in concurrent scenarios)
mock_index_processor = mock_external_service_dependencies["index_processor"]
assert mock_index_processor.clean.call_count > 0
# Check cleanup results
assert len(cleanup_results) == 3, "All cleanup operations should complete"
assert len(cleanup_errors) == 0, "No cleanup errors should occur"
# Verify idempotency by running cleanup again on the same dataset
# This should not perform any additional operations since data is already cleaned
clean_dataset_task(
dataset_id=dataset_id,
tenant_id=tenant_id,
indexing_technique="high_quality",
index_struct='{"type": "paragraph"}',
collection_binding_id=str(uuid.uuid4()),
doc_form="paragraph_index",
)
# Verify that no additional storage operations were performed
# Note: In concurrent scenarios, the exact count may vary due to race conditions
print(f"Final storage delete calls: {mock_storage.delete.call_count}")
print(f"Final index processor calls: {mock_index_processor.clean.call_count}")
print("Note: Multiple calls in concurrent scenarios are expected due to race conditions")
def test_clean_dataset_task_storage_exception_handling(
self, db_session_with_containers, mock_external_service_dependencies
):

View File

@ -148,61 +148,6 @@ class TestEnableSegmentsToIndexTask:
db.session.commit()
return segments
def test_enable_segments_to_index_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful segments indexing with paragraph index type.
This test verifies:
- Proper dataset and document retrieval from database
- Correct segment processing and document creation
- Index processor integration
- Database state updates
- Redis cache key deletion
"""
# Arrange: Create test data
dataset, document = self._create_test_dataset_and_document(
db_session_with_containers, mock_external_service_dependencies
)
segments = self._create_test_segments(db_session_with_containers, document, dataset)
# Set up Redis cache keys to simulate indexing in progress
segment_ids = [segment.id for segment in segments]
for segment in segments:
indexing_cache_key = f"segment_{segment.id}_indexing"
redis_client.set(indexing_cache_key, "processing", ex=300) # 5 minutes expiry
# Verify cache keys exist
for segment in segments:
indexing_cache_key = f"segment_{segment.id}_indexing"
assert redis_client.exists(indexing_cache_key) == 1
# Act: Execute the task
enable_segments_to_index_task(segment_ids, dataset.id, document.id)
# Assert: Verify the expected outcomes
# Verify index processor was called correctly
mock_external_service_dependencies["index_processor_factory"].assert_called_once_with(IndexType.PARAGRAPH_INDEX)
mock_external_service_dependencies["index_processor"].load.assert_called_once()
# Verify the load method was called with correct parameters
call_args = mock_external_service_dependencies["index_processor"].load.call_args
assert call_args is not None
documents = call_args[0][1] # Second argument should be documents list
assert len(documents) == 3
# Verify document structure
for i, doc in enumerate(documents):
assert doc.page_content == segments[i].content
assert doc.metadata["doc_id"] == segments[i].index_node_id
assert doc.metadata["doc_hash"] == segments[i].index_node_hash
assert doc.metadata["document_id"] == document.id
assert doc.metadata["dataset_id"] == dataset.id
# Verify Redis cache keys were deleted
for segment in segments:
indexing_cache_key = f"segment_{segment.id}_indexing"
assert redis_client.exists(indexing_cache_key) == 0
def test_enable_segments_to_index_with_different_index_type(
self, db_session_with_containers, mock_external_service_dependencies
):

View File

@ -0,0 +1,242 @@
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from extensions.ext_database import db
from libs.email_i18n import EmailType
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
from tasks.mail_account_deletion_task import send_account_deletion_verification_code, send_deletion_success_task
class TestMailAccountDeletionTask:
"""Integration tests for mail account deletion tasks using testcontainers."""
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
with (
patch("tasks.mail_account_deletion_task.mail") as mock_mail,
patch("tasks.mail_account_deletion_task.get_email_i18n_service") as mock_get_email_service,
):
# Setup mock mail service
mock_mail.is_inited.return_value = True
# Setup mock email service
mock_email_service = MagicMock()
mock_get_email_service.return_value = mock_email_service
yield {
"mail": mock_mail,
"get_email_service": mock_get_email_service,
"email_service": mock_email_service,
}
def _create_test_account(self, db_session_with_containers):
"""
Helper method to create a test account for testing.
Args:
db_session_with_containers: Database session from testcontainers infrastructure
Returns:
Account: Created account instance
"""
fake = Faker()
# Create account
account = Account(
email=fake.email(),
name=fake.name(),
interface_language="en-US",
status="active",
)
db.session.add(account)
db.session.commit()
# Create tenant
tenant = Tenant(
name=fake.company(),
status="normal",
)
db.session.add(tenant)
db.session.commit()
# Create tenant-account join
join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER.value,
current=True,
)
db.session.add(join)
db.session.commit()
return account
def test_send_deletion_success_task_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful account deletion success email sending.
This test verifies:
- Proper email service initialization check
- Correct email service method calls
- Template context is properly formatted
- Email type is correctly specified
"""
# Arrange: Create test data
account = self._create_test_account(db_session_with_containers)
test_email = account.email
test_language = "en-US"
# Act: Execute the task
send_deletion_success_task(test_email, test_language)
# Assert: Verify the expected outcomes
# Verify mail service was checked
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
# Verify email service was retrieved
mock_external_service_dependencies["get_email_service"].assert_called_once()
# Verify email was sent with correct parameters
mock_external_service_dependencies["email_service"].send_email.assert_called_once_with(
email_type=EmailType.ACCOUNT_DELETION_SUCCESS,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"email": test_email,
},
)
def test_send_deletion_success_task_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test account deletion success email when mail service is not initialized.
This test verifies:
- Early return when mail service is not initialized
- No email service calls are made
- No exceptions are raised
"""
# Arrange: Setup mail service to return not initialized
mock_external_service_dependencies["mail"].is_inited.return_value = False
account = self._create_test_account(db_session_with_containers)
test_email = account.email
# Act: Execute the task
send_deletion_success_task(test_email)
# Assert: Verify no email service calls were made
mock_external_service_dependencies["get_email_service"].assert_not_called()
mock_external_service_dependencies["email_service"].send_email.assert_not_called()
def test_send_deletion_success_task_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test account deletion success email when email service raises exception.
This test verifies:
- Exception is properly caught and logged
- Task completes without raising exception
- Error logging is recorded
"""
# Arrange: Setup email service to raise exception
mock_external_service_dependencies["email_service"].send_email.side_effect = Exception("Email service failed")
account = self._create_test_account(db_session_with_containers)
test_email = account.email
# Act: Execute the task (should not raise exception)
send_deletion_success_task(test_email)
# Assert: Verify email service was called but exception was handled
mock_external_service_dependencies["email_service"].send_email.assert_called_once()
def test_send_account_deletion_verification_code_success(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful account deletion verification code email sending.
This test verifies:
- Proper email service initialization check
- Correct email service method calls
- Template context includes verification code
- Email type is correctly specified
"""
# Arrange: Create test data
account = self._create_test_account(db_session_with_containers)
test_email = account.email
test_code = "123456"
test_language = "en-US"
# Act: Execute the task
send_account_deletion_verification_code(test_email, test_code, test_language)
# Assert: Verify the expected outcomes
# Verify mail service was checked
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
# Verify email service was retrieved
mock_external_service_dependencies["get_email_service"].assert_called_once()
# Verify email was sent with correct parameters
mock_external_service_dependencies["email_service"].send_email.assert_called_once_with(
email_type=EmailType.ACCOUNT_DELETION_VERIFICATION,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"code": test_code,
},
)
def test_send_account_deletion_verification_code_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test account deletion verification code email when mail service is not initialized.
This test verifies:
- Early return when mail service is not initialized
- No email service calls are made
- No exceptions are raised
"""
# Arrange: Setup mail service to return not initialized
mock_external_service_dependencies["mail"].is_inited.return_value = False
account = self._create_test_account(db_session_with_containers)
test_email = account.email
test_code = "123456"
# Act: Execute the task
send_account_deletion_verification_code(test_email, test_code)
# Assert: Verify no email service calls were made
mock_external_service_dependencies["get_email_service"].assert_not_called()
mock_external_service_dependencies["email_service"].send_email.assert_not_called()
def test_send_account_deletion_verification_code_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test account deletion verification code email when email service raises exception.
This test verifies:
- Exception is properly caught and logged
- Task completes without raising exception
- Error logging is recorded
"""
# Arrange: Setup email service to raise exception
mock_external_service_dependencies["email_service"].send_email.side_effect = Exception("Email service failed")
account = self._create_test_account(db_session_with_containers)
test_email = account.email
test_code = "123456"
# Act: Execute the task (should not raise exception)
send_account_deletion_verification_code(test_email, test_code)
# Assert: Verify email service was called but exception was handled
mock_external_service_dependencies["email_service"].send_email.assert_called_once()

View File

@ -0,0 +1,282 @@
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from libs.email_i18n import EmailType
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
from tasks.mail_change_mail_task import send_change_mail_completed_notification_task, send_change_mail_task
class TestMailChangeMailTask:
"""Integration tests for mail_change_mail_task using testcontainers."""
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
with (
patch("tasks.mail_change_mail_task.mail") as mock_mail,
patch("tasks.mail_change_mail_task.get_email_i18n_service") as mock_get_email_i18n_service,
):
# Setup mock mail service
mock_mail.is_inited.return_value = True
# Setup mock email i18n service
mock_email_service = MagicMock()
mock_get_email_i18n_service.return_value = mock_email_service
yield {
"mail": mock_mail,
"email_i18n_service": mock_email_service,
"get_email_i18n_service": mock_get_email_i18n_service,
}
def _create_test_account(self, db_session_with_containers):
"""
Helper method to create a test account for testing.
Args:
db_session_with_containers: Database session from testcontainers infrastructure
Returns:
Account: Created account instance
"""
fake = Faker()
# Create account
account = Account(
email=fake.email(),
name=fake.name(),
interface_language="en-US",
status="active",
)
db_session_with_containers.add(account)
db_session_with_containers.commit()
# Create tenant
tenant = Tenant(
name=fake.company(),
status="normal",
)
db_session_with_containers.add(tenant)
db_session_with_containers.commit()
# Create tenant-account join
join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER.value,
current=True,
)
db_session_with_containers.add(join)
db_session_with_containers.commit()
return account
def test_send_change_mail_task_success_old_email_phase(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful change email task execution for old_email phase.
This test verifies:
- Proper mail service initialization check
- Correct email service method call with old_email phase
- Successful task completion
"""
# Arrange: Create test data
account = self._create_test_account(db_session_with_containers)
test_language = "en-US"
test_email = account.email
test_code = "123456"
test_phase = "old_email"
# Act: Execute the task
send_change_mail_task(test_language, test_email, test_code, test_phase)
# Assert: Verify the expected outcomes
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_called_once()
mock_external_service_dependencies["email_i18n_service"].send_change_email.assert_called_once_with(
language_code=test_language,
to=test_email,
code=test_code,
phase=test_phase,
)
def test_send_change_mail_task_success_new_email_phase(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful change email task execution for new_email phase.
This test verifies:
- Proper mail service initialization check
- Correct email service method call with new_email phase
- Successful task completion
"""
# Arrange: Create test data
account = self._create_test_account(db_session_with_containers)
test_language = "zh-Hans"
test_email = "new@example.com"
test_code = "789012"
test_phase = "new_email"
# Act: Execute the task
send_change_mail_task(test_language, test_email, test_code, test_phase)
# Assert: Verify the expected outcomes
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_called_once()
mock_external_service_dependencies["email_i18n_service"].send_change_email.assert_called_once_with(
language_code=test_language,
to=test_email,
code=test_code,
phase=test_phase,
)
def test_send_change_mail_task_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test change email task when mail service is not initialized.
This test verifies:
- Early return when mail service is not initialized
- No email service calls when mail is not available
"""
# Arrange: Setup mail service as not initialized
mock_external_service_dependencies["mail"].is_inited.return_value = False
test_language = "en-US"
test_email = "test@example.com"
test_code = "123456"
test_phase = "old_email"
# Act: Execute the task
send_change_mail_task(test_language, test_email, test_code, test_phase)
# Assert: Verify no email service calls
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_not_called()
mock_external_service_dependencies["email_i18n_service"].send_change_email.assert_not_called()
def test_send_change_mail_task_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test change email task when email service raises an exception.
This test verifies:
- Exception is properly caught and logged
- Task completes without raising exception
"""
# Arrange: Setup email service to raise exception
mock_external_service_dependencies["email_i18n_service"].send_change_email.side_effect = Exception(
"Email service failed"
)
test_language = "en-US"
test_email = "test@example.com"
test_code = "123456"
test_phase = "old_email"
# Act: Execute the task (should not raise exception)
send_change_mail_task(test_language, test_email, test_code, test_phase)
# Assert: Verify email service was called despite exception
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_called_once()
mock_external_service_dependencies["email_i18n_service"].send_change_email.assert_called_once_with(
language_code=test_language,
to=test_email,
code=test_code,
phase=test_phase,
)
def test_send_change_mail_completed_notification_task_success(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful change email completed notification task execution.
This test verifies:
- Proper mail service initialization check
- Correct email service method call with CHANGE_EMAIL_COMPLETED type
- Template context is properly constructed
- Successful task completion
"""
# Arrange: Create test data
account = self._create_test_account(db_session_with_containers)
test_language = "en-US"
test_email = account.email
# Act: Execute the task
send_change_mail_completed_notification_task(test_language, test_email)
# Assert: Verify the expected outcomes
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_called_once()
mock_external_service_dependencies["email_i18n_service"].send_email.assert_called_once_with(
email_type=EmailType.CHANGE_EMAIL_COMPLETED,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"email": test_email,
},
)
def test_send_change_mail_completed_notification_task_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test change email completed notification task when mail service is not initialized.
This test verifies:
- Early return when mail service is not initialized
- No email service calls when mail is not available
"""
# Arrange: Setup mail service as not initialized
mock_external_service_dependencies["mail"].is_inited.return_value = False
test_language = "en-US"
test_email = "test@example.com"
# Act: Execute the task
send_change_mail_completed_notification_task(test_language, test_email)
# Assert: Verify no email service calls
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_not_called()
mock_external_service_dependencies["email_i18n_service"].send_email.assert_not_called()
def test_send_change_mail_completed_notification_task_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test change email completed notification task when email service raises an exception.
This test verifies:
- Exception is properly caught and logged
- Task completes without raising exception
"""
# Arrange: Setup email service to raise exception
mock_external_service_dependencies["email_i18n_service"].send_email.side_effect = Exception(
"Email service failed"
)
test_language = "en-US"
test_email = "test@example.com"
# Act: Execute the task (should not raise exception)
send_change_mail_completed_notification_task(test_language, test_email)
# Assert: Verify email service was called despite exception
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
mock_external_service_dependencies["get_email_i18n_service"].assert_called_once()
mock_external_service_dependencies["email_i18n_service"].send_email.assert_called_once_with(
email_type=EmailType.CHANGE_EMAIL_COMPLETED,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"email": test_email,
},
)

View File

@ -0,0 +1,598 @@
"""
TestContainers-based integration tests for send_email_code_login_mail_task.
This module provides comprehensive integration tests for the email code login mail task
using TestContainers infrastructure. The tests ensure that the task properly sends
email verification codes for login with internationalization support and handles
various error scenarios in a real database environment.
All tests use the testcontainers infrastructure to ensure proper database isolation
and realistic testing scenarios with actual PostgreSQL and Redis instances.
"""
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from libs.email_i18n import EmailType
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
from tasks.mail_email_code_login import send_email_code_login_mail_task
class TestSendEmailCodeLoginMailTask:
"""
Comprehensive integration tests for send_email_code_login_mail_task using testcontainers.
This test class covers all major functionality of the email code login mail task:
- Successful email sending with different languages
- Email service integration and template rendering
- Error handling for various failure scenarios
- Performance metrics and logging verification
- Edge cases and boundary conditions
All tests use the testcontainers infrastructure to ensure proper database isolation
and realistic testing environment with actual database interactions.
"""
@pytest.fixture(autouse=True)
def cleanup_database(self, db_session_with_containers):
"""Clean up database before each test to ensure isolation."""
from extensions.ext_redis import redis_client
# Clear all test data
db_session_with_containers.query(TenantAccountJoin).delete()
db_session_with_containers.query(Tenant).delete()
db_session_with_containers.query(Account).delete()
db_session_with_containers.commit()
# Clear Redis cache
redis_client.flushdb()
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
with (
patch("tasks.mail_email_code_login.mail") as mock_mail,
patch("tasks.mail_email_code_login.get_email_i18n_service") as mock_email_service,
):
# Setup default mock returns
mock_mail.is_inited.return_value = True
# Mock email service
mock_email_service_instance = MagicMock()
mock_email_service_instance.send_email.return_value = None
mock_email_service.return_value = mock_email_service_instance
yield {
"mail": mock_mail,
"email_service": mock_email_service,
"email_service_instance": mock_email_service_instance,
}
def _create_test_account(self, db_session_with_containers, fake=None):
"""
Helper method to create a test account for testing.
Args:
db_session_with_containers: Database session from testcontainers infrastructure
fake: Faker instance for generating test data
Returns:
Account: Created account instance
"""
if fake is None:
fake = Faker()
# Create account
account = Account(
email=fake.email(),
name=fake.name(),
interface_language="en-US",
status="active",
)
db_session_with_containers.add(account)
db_session_with_containers.commit()
return account
def _create_test_tenant_and_account(self, db_session_with_containers, fake=None):
"""
Helper method to create a test tenant and account for testing.
Args:
db_session_with_containers: Database session from testcontainers infrastructure
fake: Faker instance for generating test data
Returns:
tuple: (Account, Tenant) created instances
"""
if fake is None:
fake = Faker()
# Create account using the existing helper method
account = self._create_test_account(db_session_with_containers, fake)
# Create tenant
tenant = Tenant(
name=fake.company(),
plan="basic",
status="active",
)
db_session_with_containers.add(tenant)
db_session_with_containers.commit()
# Create tenant-account relationship
tenant_account_join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER,
)
db_session_with_containers.add(tenant_account_join)
db_session_with_containers.commit()
return account, tenant
def test_send_email_code_login_mail_task_success_english(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful email code login mail sending in English.
This test verifies that the task can successfully:
1. Send email code login mail with English language
2. Use proper email service integration
3. Pass correct template context to email service
4. Log performance metrics correctly
5. Complete task execution without errors
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "123456"
test_language = "en-US"
# Act: Execute the task
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_mail = mock_external_service_dependencies["mail"]
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify mail service was checked for initialization
mock_mail.is_inited.assert_called_once()
# Verify email service was called with correct parameters
mock_email_service_instance.send_email.assert_called_once_with(
email_type=EmailType.EMAIL_CODE_LOGIN,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"code": test_code,
},
)
def test_send_email_code_login_mail_task_success_chinese(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful email code login mail sending in Chinese.
This test verifies that the task can successfully:
1. Send email code login mail with Chinese language
2. Handle different language codes properly
3. Use correct template context for Chinese emails
4. Complete task execution without errors
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "789012"
test_language = "zh-Hans"
# Act: Execute the task
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify email service was called with Chinese language
mock_email_service_instance.send_email.assert_called_once_with(
email_type=EmailType.EMAIL_CODE_LOGIN,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"code": test_code,
},
)
def test_send_email_code_login_mail_task_success_multiple_languages(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful email code login mail sending with multiple languages.
This test verifies that the task can successfully:
1. Handle various language codes correctly
2. Send emails with different language configurations
3. Maintain proper template context for each language
4. Complete multiple task executions without conflicts
"""
# Arrange: Setup test data
fake = Faker()
test_languages = ["en-US", "zh-Hans", "zh-CN", "ja-JP", "ko-KR"]
test_emails = [fake.email() for _ in test_languages]
test_codes = [fake.numerify("######") for _ in test_languages]
# Act: Execute the task for each language
for i, language in enumerate(test_languages):
send_email_code_login_mail_task(
language=language,
to=test_emails[i],
code=test_codes[i],
)
# Assert: Verify expected outcomes
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify email service was called for each language
assert mock_email_service_instance.send_email.call_count == len(test_languages)
# Verify each call had correct parameters
for i, language in enumerate(test_languages):
call_args = mock_email_service_instance.send_email.call_args_list[i]
assert call_args[1]["email_type"] == EmailType.EMAIL_CODE_LOGIN
assert call_args[1]["language_code"] == language
assert call_args[1]["to"] == test_emails[i]
assert call_args[1]["template_context"]["code"] == test_codes[i]
def test_send_email_code_login_mail_task_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task when mail service is not initialized.
This test verifies that the task can properly:
1. Check mail service initialization status
2. Return early when mail is not initialized
3. Not attempt to send email when service is unavailable
4. Handle gracefully without errors
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "123456"
test_language = "en-US"
# Mock mail service as not initialized
mock_mail = mock_external_service_dependencies["mail"]
mock_mail.is_inited.return_value = False
# Act: Execute the task
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify mail service was checked for initialization
mock_mail.is_inited.assert_called_once()
# Verify email service was not called
mock_email_service_instance.send_email.assert_not_called()
def test_send_email_code_login_mail_task_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task when email service raises an exception.
This test verifies that the task can properly:
1. Handle email service exceptions gracefully
2. Log appropriate error messages
3. Continue execution without crashing
4. Maintain proper error handling
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "123456"
test_language = "en-US"
# Mock email service to raise an exception
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
mock_email_service_instance.send_email.side_effect = Exception("Email service unavailable")
# Act: Execute the task - it should handle the exception gracefully
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_mail = mock_external_service_dependencies["mail"]
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify mail service was checked for initialization
mock_mail.is_inited.assert_called_once()
# Verify email service was called (and failed)
mock_email_service_instance.send_email.assert_called_once_with(
email_type=EmailType.EMAIL_CODE_LOGIN,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"code": test_code,
},
)
def test_send_email_code_login_mail_task_invalid_parameters(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task with invalid parameters.
This test verifies that the task can properly:
1. Handle empty or None email addresses
2. Process empty or None verification codes
3. Handle invalid language codes
4. Maintain proper error handling for invalid inputs
"""
# Arrange: Setup test data
fake = Faker()
test_language = "en-US"
# Test cases for invalid parameters
invalid_test_cases = [
{"email": "", "code": "123456", "description": "empty email"},
{"email": None, "code": "123456", "description": "None email"},
{"email": fake.email(), "code": "", "description": "empty code"},
{"email": fake.email(), "code": None, "description": "None code"},
{"email": "invalid-email", "code": "123456", "description": "invalid email format"},
]
for test_case in invalid_test_cases:
# Reset mocks for each test case
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
mock_email_service_instance.reset_mock()
# Act: Execute the task with invalid parameters
send_email_code_login_mail_task(
language=test_language,
to=test_case["email"],
code=test_case["code"],
)
# Assert: Verify that email service was still called
# The task should pass parameters to email service as-is
# and let the email service handle validation
mock_email_service_instance.send_email.assert_called_once()
def test_send_email_code_login_mail_task_edge_cases(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task with edge cases and boundary conditions.
This test verifies that the task can properly:
1. Handle very long email addresses
2. Process very long verification codes
3. Handle special characters in parameters
4. Process extreme language codes
"""
# Arrange: Setup test data
fake = Faker()
test_language = "en-US"
# Edge case test data
edge_cases = [
{
"email": "a" * 100 + "@example.com", # Very long email
"code": "1" * 20, # Very long code
"description": "very long email and code",
},
{
"email": "test+tag@example.com", # Email with special characters
"code": "123-456", # Code with special characters
"description": "special characters",
},
{
"email": "test@sub.domain.example.com", # Complex domain
"code": "000000", # All zeros
"description": "complex domain and all zeros code",
},
{
"email": "test@example.co.uk", # International domain
"code": "999999", # All nines
"description": "international domain and all nines code",
},
]
for test_case in edge_cases:
# Reset mocks for each test case
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
mock_email_service_instance.reset_mock()
# Act: Execute the task with edge case data
send_email_code_login_mail_task(
language=test_language,
to=test_case["email"],
code=test_case["code"],
)
# Assert: Verify that email service was called with edge case data
mock_email_service_instance.send_email.assert_called_once_with(
email_type=EmailType.EMAIL_CODE_LOGIN,
language_code=test_language,
to=test_case["email"],
template_context={
"to": test_case["email"],
"code": test_case["code"],
},
)
def test_send_email_code_login_mail_task_database_integration(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task with database integration.
This test verifies that the task can properly:
1. Work with real database connections
2. Handle database session management
3. Maintain proper database state
4. Complete without database-related errors
"""
# Arrange: Setup test data with database
fake = Faker()
account, tenant = self._create_test_tenant_and_account(db_session_with_containers, fake)
test_email = account.email
test_code = "123456"
test_language = "en-US"
# Act: Execute the task
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify email service was called with database account email
mock_email_service_instance.send_email.assert_called_once_with(
email_type=EmailType.EMAIL_CODE_LOGIN,
language_code=test_language,
to=test_email,
template_context={
"to": test_email,
"code": test_code,
},
)
# Verify database state is maintained
db_session_with_containers.refresh(account)
assert account.email == test_email
assert account.status == "active"
def test_send_email_code_login_mail_task_redis_integration(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email code login mail task with Redis integration.
This test verifies that the task can properly:
1. Work with Redis cache connections
2. Handle Redis operations without errors
3. Maintain proper cache state
4. Complete without Redis-related errors
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "123456"
test_language = "en-US"
# Setup Redis cache data
from extensions.ext_redis import redis_client
cache_key = f"email_code_login_test_{test_email}"
redis_client.set(cache_key, "test_value", ex=300)
# Act: Execute the task
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify expected outcomes
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
# Verify email service was called
mock_email_service_instance.send_email.assert_called_once()
# Verify Redis cache is still accessible
assert redis_client.exists(cache_key) == 1
assert redis_client.get(cache_key) == b"test_value"
# Clean up Redis cache
redis_client.delete(cache_key)
def test_send_email_code_login_mail_task_error_handling_comprehensive(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test comprehensive error handling for email code login mail task.
This test verifies that the task can properly:
1. Handle various types of exceptions
2. Log appropriate error messages
3. Continue execution despite errors
4. Maintain proper error reporting
"""
# Arrange: Setup test data
fake = Faker()
test_email = fake.email()
test_code = "123456"
test_language = "en-US"
# Test different exception types
exception_types = [
("ValueError", ValueError("Invalid email format")),
("RuntimeError", RuntimeError("Service unavailable")),
("ConnectionError", ConnectionError("Network error")),
("TimeoutError", TimeoutError("Request timeout")),
("Exception", Exception("Generic error")),
]
for error_name, exception in exception_types:
# Reset mocks for each test case
mock_email_service_instance = mock_external_service_dependencies["email_service_instance"]
mock_email_service_instance.reset_mock()
mock_email_service_instance.send_email.side_effect = exception
# Mock logging to capture error messages
with patch("tasks.mail_email_code_login.logger") as mock_logger:
# Act: Execute the task - it should handle the exception gracefully
send_email_code_login_mail_task(
language=test_language,
to=test_email,
code=test_code,
)
# Assert: Verify error handling
# Verify email service was called (and failed)
mock_email_service_instance.send_email.assert_called_once()
# Verify error was logged
error_calls = [
call
for call in mock_logger.exception.call_args_list
if f"Send email code login mail to {test_email} failed" in str(call)
]
# Check if any exception call was made (the exact message format may vary)
assert mock_logger.exception.call_count >= 1, f"Error should be logged for {error_name}"
# Reset side effect for next iteration
mock_email_service_instance.send_email.side_effect = None

View File

@ -0,0 +1,261 @@
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from tasks.mail_inner_task import send_inner_email_task
class TestMailInnerTask:
"""Integration tests for send_inner_email_task using testcontainers."""
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
with (
patch("tasks.mail_inner_task.mail") as mock_mail,
patch("tasks.mail_inner_task.get_email_i18n_service") as mock_get_email_i18n_service,
patch("tasks.mail_inner_task._render_template_with_strategy") as mock_render_template,
):
# Setup mock mail service
mock_mail.is_inited.return_value = True
# Setup mock email i18n service
mock_email_service = MagicMock()
mock_get_email_i18n_service.return_value = mock_email_service
# Setup mock template rendering
mock_render_template.return_value = "<html>Test email content</html>"
yield {
"mail": mock_mail,
"email_service": mock_email_service,
"render_template": mock_render_template,
}
def _create_test_email_data(self, fake: Faker) -> dict:
"""
Helper method to create test email data for testing.
Args:
fake: Faker instance for generating test data
Returns:
dict: Test email data including recipients, subject, body, and substitutions
"""
return {
"to": [fake.email() for _ in range(3)],
"subject": fake.sentence(nb_words=4),
"body": "Hello {{name}}, this is a test email from {{company}}.",
"substitutions": {
"name": fake.name(),
"company": fake.company(),
"date": fake.date(),
},
}
def test_send_inner_email_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful email sending with valid data.
This test verifies:
- Proper email service initialization check
- Template rendering with substitutions
- Email service integration
- Multiple recipient handling
"""
# Arrange: Create test data
fake = Faker()
email_data = self._create_test_email_data(fake)
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify the expected outcomes
# Verify mail service was checked for initialization
mock_external_service_dependencies["mail"].is_inited.assert_called_once()
# Verify template rendering was called with correct parameters
mock_external_service_dependencies["render_template"].assert_called_once_with(
email_data["body"], email_data["substitutions"]
)
# Verify email service was called once with the full recipient list
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_raw_email.assert_called_once_with(
to=email_data["to"],
subject=email_data["subject"],
html_content="<html>Test email content</html>",
)
def test_send_inner_email_single_recipient(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test email sending with single recipient.
This test verifies:
- Single recipient handling
- Template rendering
- Email service integration
"""
# Arrange: Create test data with single recipient
fake = Faker()
email_data = {
"to": [fake.email()],
"subject": fake.sentence(nb_words=3),
"body": "Welcome {{user_name}}!",
"substitutions": {
"user_name": fake.name(),
},
}
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify the expected outcomes
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_raw_email.assert_called_once_with(
to=email_data["to"],
subject=email_data["subject"],
html_content="<html>Test email content</html>",
)
def test_send_inner_email_empty_substitutions(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test email sending with empty substitutions.
This test verifies:
- Template rendering with empty substitutions
- Email service integration
- Handling of minimal template context
"""
# Arrange: Create test data with empty substitutions
fake = Faker()
email_data = {
"to": [fake.email()],
"subject": fake.sentence(nb_words=3),
"body": "This is a simple email without variables.",
"substitutions": {},
}
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify the expected outcomes
mock_external_service_dependencies["render_template"].assert_called_once_with(email_data["body"], {})
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_raw_email.assert_called_once_with(
to=email_data["to"],
subject=email_data["subject"],
html_content="<html>Test email content</html>",
)
def test_send_inner_email_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email sending when mail service is not initialized.
This test verifies:
- Early return when mail service is not initialized
- No template rendering occurs
- No email service calls
- No exceptions raised
"""
# Arrange: Setup mail service as not initialized
mock_external_service_dependencies["mail"].is_inited.return_value = False
fake = Faker()
email_data = self._create_test_email_data(fake)
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify no processing occurred
mock_external_service_dependencies["render_template"].assert_not_called()
mock_external_service_dependencies["email_service"].send_raw_email.assert_not_called()
def test_send_inner_email_template_rendering_error(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email sending when template rendering fails.
This test verifies:
- Exception handling during template rendering
- No email service calls when template fails
"""
# Arrange: Setup template rendering to raise an exception
mock_external_service_dependencies["render_template"].side_effect = Exception("Template rendering failed")
fake = Faker()
email_data = self._create_test_email_data(fake)
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify template rendering was attempted
mock_external_service_dependencies["render_template"].assert_called_once()
# Verify no email service calls due to exception
mock_external_service_dependencies["email_service"].send_raw_email.assert_not_called()
def test_send_inner_email_service_error(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test email sending when email service fails.
This test verifies:
- Exception handling during email sending
- Graceful error handling
"""
# Arrange: Setup email service to raise an exception
mock_external_service_dependencies["email_service"].send_raw_email.side_effect = Exception(
"Email service failed"
)
fake = Faker()
email_data = self._create_test_email_data(fake)
# Act: Execute the task
send_inner_email_task(
to=email_data["to"],
subject=email_data["subject"],
body=email_data["body"],
substitutions=email_data["substitutions"],
)
# Assert: Verify template rendering occurred
mock_external_service_dependencies["render_template"].assert_called_once()
# Verify email service was called (and failed)
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_raw_email.assert_called_once_with(
to=email_data["to"],
subject=email_data["subject"],
html_content="<html>Test email content</html>",
)

View File

@ -0,0 +1,543 @@
"""
Integration tests for mail_invite_member_task using testcontainers.
This module provides integration tests for the invite member email task
using TestContainers infrastructure. The tests ensure that the task properly sends
invitation emails with internationalization support, handles error scenarios,
and integrates correctly with the database and Redis for token management.
All tests use the testcontainers infrastructure to ensure proper database isolation
and realistic testing scenarios with actual PostgreSQL and Redis instances.
"""
import json
import uuid
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from extensions.ext_redis import redis_client
from libs.email_i18n import EmailType
from models.account import Account, AccountStatus, Tenant, TenantAccountJoin, TenantAccountRole
from tasks.mail_invite_member_task import send_invite_member_mail_task
class TestMailInviteMemberTask:
"""
Integration tests for send_invite_member_mail_task using testcontainers.
This test class covers the core functionality of the invite member email task:
- Email sending with proper internationalization
- Template context generation and URL construction
- Error handling for failure scenarios
- Integration with Redis for token validation
- Mail service initialization checks
- Real database integration with actual invitation flow
All tests use the testcontainers infrastructure to ensure proper database isolation
and realistic testing environment with actual database and Redis interactions.
"""
@pytest.fixture(autouse=True)
def cleanup_database(self, db_session_with_containers):
"""Clean up database before each test to ensure isolation."""
# Clear all test data
db_session_with_containers.query(TenantAccountJoin).delete()
db_session_with_containers.query(Tenant).delete()
db_session_with_containers.query(Account).delete()
db_session_with_containers.commit()
# Clear Redis cache
redis_client.flushdb()
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
with (
patch("tasks.mail_invite_member_task.mail") as mock_mail,
patch("tasks.mail_invite_member_task.get_email_i18n_service") as mock_email_service,
patch("tasks.mail_invite_member_task.dify_config") as mock_config,
):
# Setup mail service mock
mock_mail.is_inited.return_value = True
# Setup email service mock
mock_email_service_instance = MagicMock()
mock_email_service_instance.send_email.return_value = None
mock_email_service.return_value = mock_email_service_instance
# Setup config mock
mock_config.CONSOLE_WEB_URL = "https://console.dify.ai"
yield {
"mail": mock_mail,
"email_service": mock_email_service_instance,
"config": mock_config,
}
def _create_test_account_and_tenant(self, db_session_with_containers):
"""
Helper method to create a test account and tenant for testing.
Args:
db_session_with_containers: Database session from testcontainers infrastructure
Returns:
tuple: (Account, Tenant) created instances
"""
fake = Faker()
# Create account
account = Account(
email=fake.email(),
name=fake.name(),
password=fake.password(),
interface_language="en-US",
status=AccountStatus.ACTIVE.value,
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db_session_with_containers.add(account)
db_session_with_containers.commit()
db_session_with_containers.refresh(account)
# Create tenant
tenant = Tenant(
name=fake.company(),
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db_session_with_containers.add(tenant)
db_session_with_containers.commit()
db_session_with_containers.refresh(tenant)
# Create tenant member relationship
tenant_join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER.value,
created_at=datetime.now(UTC),
)
db_session_with_containers.add(tenant_join)
db_session_with_containers.commit()
return account, tenant
def _create_invitation_token(self, tenant, account):
"""
Helper method to create a valid invitation token in Redis.
Args:
tenant: Tenant instance
account: Account instance
Returns:
str: Generated invitation token
"""
token = str(uuid.uuid4())
invitation_data = {
"account_id": account.id,
"email": account.email,
"workspace_id": tenant.id,
}
cache_key = f"member_invite:token:{token}"
redis_client.setex(cache_key, 24 * 60 * 60, json.dumps(invitation_data)) # 24 hours
return token
def _create_pending_account_for_invitation(self, db_session_with_containers, email, tenant):
"""
Helper method to create a pending account for invitation testing.
Args:
db_session_with_containers: Database session
email: Email address for the account
tenant: Tenant instance
Returns:
Account: Created pending account
"""
account = Account(
email=email,
name=email.split("@")[0],
password="",
interface_language="en-US",
status=AccountStatus.PENDING.value,
created_at=datetime.now(UTC),
updated_at=datetime.now(UTC),
)
db_session_with_containers.add(account)
db_session_with_containers.commit()
db_session_with_containers.refresh(account)
# Create tenant member relationship
tenant_join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.NORMAL.value,
created_at=datetime.now(UTC),
)
db_session_with_containers.add(tenant_join)
db_session_with_containers.commit()
return account
def test_send_invite_member_mail_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful invitation email sending with all parameters.
This test verifies:
- Email service is called with correct parameters
- Template context includes all required fields
- URL is constructed correctly with token
- Performance logging is recorded
- No exceptions are raised
"""
# Arrange: Create test data
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
invitee_email = "test@example.com"
language = "en-US"
token = self._create_invitation_token(tenant, inviter)
inviter_name = inviter.name
workspace_name = tenant.name
# Act: Execute the task
send_invite_member_mail_task(
language=language,
to=invitee_email,
token=token,
inviter_name=inviter_name,
workspace_name=workspace_name,
)
# Assert: Verify email service was called correctly
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_email.assert_called_once()
# Verify call arguments
call_args = mock_email_service.send_email.call_args
assert call_args[1]["email_type"] == EmailType.INVITE_MEMBER
assert call_args[1]["language_code"] == language
assert call_args[1]["to"] == invitee_email
# Verify template context
template_context = call_args[1]["template_context"]
assert template_context["to"] == invitee_email
assert template_context["inviter_name"] == inviter_name
assert template_context["workspace_name"] == workspace_name
assert template_context["url"] == f"https://console.dify.ai/activate?token={token}"
def test_send_invite_member_mail_different_languages(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test invitation email sending with different language codes.
This test verifies:
- Email service handles different language codes correctly
- Template context is passed correctly for each language
- No language-specific errors occur
"""
# Arrange: Create test data
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
token = self._create_invitation_token(tenant, inviter)
test_languages = ["en-US", "zh-CN", "ja-JP", "fr-FR", "de-DE", "es-ES"]
for language in test_languages:
# Act: Execute the task with different language
send_invite_member_mail_task(
language=language,
to="test@example.com",
token=token,
inviter_name=inviter.name,
workspace_name=tenant.name,
)
# Assert: Verify language code was passed correctly
mock_email_service = mock_external_service_dependencies["email_service"]
call_args = mock_email_service.send_email.call_args
assert call_args[1]["language_code"] == language
def test_send_invite_member_mail_mail_not_initialized(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test behavior when mail service is not initialized.
This test verifies:
- Task returns early when mail is not initialized
- Email service is not called
- No exceptions are raised
"""
# Arrange: Setup mail service as not initialized
mock_mail = mock_external_service_dependencies["mail"]
mock_mail.is_inited.return_value = False
# Act: Execute the task
result = send_invite_member_mail_task(
language="en-US",
to="test@example.com",
token="test-token",
inviter_name="Test User",
workspace_name="Test Workspace",
)
# Assert: Verify early return
assert result is None
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_email.assert_not_called()
def test_send_invite_member_mail_email_service_exception(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test error handling when email service raises an exception.
This test verifies:
- Exception is caught and logged
- Task completes without raising exception
- Error logging is performed
"""
# Arrange: Setup email service to raise exception
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_email.side_effect = Exception("Email service failed")
# Act & Assert: Execute task and verify exception is handled
with patch("tasks.mail_invite_member_task.logger") as mock_logger:
send_invite_member_mail_task(
language="en-US",
to="test@example.com",
token="test-token",
inviter_name="Test User",
workspace_name="Test Workspace",
)
# Verify error was logged
mock_logger.exception.assert_called_once()
error_call = mock_logger.exception.call_args[0][0]
assert "Send invite member mail to %s failed" in error_call
def test_send_invite_member_mail_template_context_validation(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test template context contains all required fields for email rendering.
This test verifies:
- All required template context fields are present
- Field values match expected data
- URL construction is correct
- No missing or None values in context
"""
# Arrange: Create test data with specific values
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
token = "test-token-123"
invitee_email = "invitee@example.com"
inviter_name = "John Doe"
workspace_name = "Acme Corp"
# Act: Execute the task
send_invite_member_mail_task(
language="en-US",
to=invitee_email,
token=token,
inviter_name=inviter_name,
workspace_name=workspace_name,
)
# Assert: Verify template context
mock_email_service = mock_external_service_dependencies["email_service"]
call_args = mock_email_service.send_email.call_args
template_context = call_args[1]["template_context"]
# Verify all required fields are present
required_fields = ["to", "inviter_name", "workspace_name", "url"]
for field in required_fields:
assert field in template_context
assert template_context[field] is not None
assert template_context[field] != ""
# Verify specific values
assert template_context["to"] == invitee_email
assert template_context["inviter_name"] == inviter_name
assert template_context["workspace_name"] == workspace_name
assert template_context["url"] == f"https://console.dify.ai/activate?token={token}"
def test_send_invite_member_mail_integration_with_redis_token(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test integration with Redis token validation.
This test verifies:
- Task works with real Redis token data
- Token validation can be performed after email sending
- Redis data integrity is maintained
"""
# Arrange: Create test data and store token in Redis
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
token = self._create_invitation_token(tenant, inviter)
# Verify token exists in Redis before sending email
cache_key = f"member_invite:token:{token}"
assert redis_client.exists(cache_key) == 1
# Act: Execute the task
send_invite_member_mail_task(
language="en-US",
to=inviter.email,
token=token,
inviter_name=inviter.name,
workspace_name=tenant.name,
)
# Assert: Verify token still exists after email sending
assert redis_client.exists(cache_key) == 1
# Verify token data integrity
token_data = redis_client.get(cache_key)
assert token_data is not None
invitation_data = json.loads(token_data)
assert invitation_data["account_id"] == inviter.id
assert invitation_data["email"] == inviter.email
assert invitation_data["workspace_id"] == tenant.id
def test_send_invite_member_mail_with_special_characters(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test email sending with special characters in names and workspace names.
This test verifies:
- Special characters are handled correctly in template context
- Email service receives properly formatted data
- No encoding issues occur
"""
# Arrange: Create test data with special characters
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
token = self._create_invitation_token(tenant, inviter)
special_cases = [
("John O'Connor", "Acme & Co."),
("José María", "Café & Restaurant"),
("李小明", "北京科技有限公司"),
("François & Marie", "L'École Internationale"),
("Александр", "ООО Технологии"),
("محمد أحمد", "شركة التقنية المتقدمة"),
]
for inviter_name, workspace_name in special_cases:
# Act: Execute the task
send_invite_member_mail_task(
language="en-US",
to="test@example.com",
token=token,
inviter_name=inviter_name,
workspace_name=workspace_name,
)
# Assert: Verify special characters are preserved
mock_email_service = mock_external_service_dependencies["email_service"]
call_args = mock_email_service.send_email.call_args
template_context = call_args[1]["template_context"]
assert template_context["inviter_name"] == inviter_name
assert template_context["workspace_name"] == workspace_name
def test_send_invite_member_mail_real_database_integration(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test real database integration with actual invitation flow.
This test verifies:
- Task works with real database entities
- Account and tenant relationships are properly maintained
- Database state is consistent after email sending
- Real invitation data flow is tested
"""
# Arrange: Create real database entities
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
invitee_email = "newmember@example.com"
# Create a pending account for invitation (simulating real invitation flow)
pending_account = self._create_pending_account_for_invitation(db_session_with_containers, invitee_email, tenant)
# Create invitation token with real account data
token = self._create_invitation_token(tenant, pending_account)
# Act: Execute the task with real data
send_invite_member_mail_task(
language="en-US",
to=invitee_email,
token=token,
inviter_name=inviter.name,
workspace_name=tenant.name,
)
# Assert: Verify email service was called with real data
mock_email_service = mock_external_service_dependencies["email_service"]
mock_email_service.send_email.assert_called_once()
# Verify database state is maintained
db_session_with_containers.refresh(pending_account)
db_session_with_containers.refresh(tenant)
assert pending_account.status == AccountStatus.PENDING.value
assert pending_account.email == invitee_email
assert tenant.name is not None
# Verify tenant relationship exists
tenant_join = (
db_session_with_containers.query(TenantAccountJoin)
.filter_by(tenant_id=tenant.id, account_id=pending_account.id)
.first()
)
assert tenant_join is not None
assert tenant_join.role == TenantAccountRole.NORMAL.value
def test_send_invite_member_mail_token_lifecycle_management(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test token lifecycle management and validation.
This test verifies:
- Token is properly stored in Redis with correct TTL
- Token data structure is correct
- Token can be retrieved and validated after email sending
- Token expiration is handled correctly
"""
# Arrange: Create test data
inviter, tenant = self._create_test_account_and_tenant(db_session_with_containers)
token = self._create_invitation_token(tenant, inviter)
# Act: Execute the task
send_invite_member_mail_task(
language="en-US",
to=inviter.email,
token=token,
inviter_name=inviter.name,
workspace_name=tenant.name,
)
# Assert: Verify token lifecycle
cache_key = f"member_invite:token:{token}"
# Token should still exist
assert redis_client.exists(cache_key) == 1
# Token should have correct TTL (approximately 24 hours)
ttl = redis_client.ttl(cache_key)
assert 23 * 60 * 60 <= ttl <= 24 * 60 * 60 # Allow some tolerance
# Token data should be valid
token_data = redis_client.get(cache_key)
assert token_data is not None
invitation_data = json.loads(token_data)
assert invitation_data["account_id"] == inviter.id
assert invitation_data["email"] == inviter.email
assert invitation_data["workspace_id"] == tenant.id

View File

@ -33,6 +33,7 @@ def test_dify_config(monkeypatch: pytest.MonkeyPatch):
assert config.EDITION == "SELF_HOSTED"
assert config.API_COMPRESSION_ENABLED is False
assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0
assert config.TEMPLATE_TRANSFORM_MAX_LENGTH == 400_000
# annotated field with default value
assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600

View File

@ -172,73 +172,31 @@ class TestSupabaseStorage:
assert "test-bucket" in [call[0][0] for call in mock_client.storage.from_.call_args_list if call[0]]
mock_client.storage.from_().download.assert_called_with("test.txt")
def test_exists_with_list_containing_items(self, storage_with_mock_client):
"""Test exists returns True when list() returns items (using len() > 0)."""
def test_exists_returns_true_when_file_found(self, storage_with_mock_client):
"""Test exists returns True when list() returns items."""
storage, mock_client = storage_with_mock_client
# Mock list return with special object that has count() method
mock_list_result = Mock()
mock_list_result.count.return_value = 1
mock_client.storage.from_().list.return_value = mock_list_result
mock_client.storage.from_().list.return_value = [{"name": "test.txt"}]
result = storage.exists("test.txt")
assert result is True
# from_ gets called during init too, so just check it was called with the right bucket
assert "test-bucket" in [call[0][0] for call in mock_client.storage.from_.call_args_list if call[0]]
mock_client.storage.from_().list.assert_called_with("test.txt")
mock_client.storage.from_().list.assert_called_with(path="test.txt")
def test_exists_with_count_method_greater_than_zero(self, storage_with_mock_client):
"""Test exists returns True when list result has count() > 0."""
def test_exists_returns_false_when_file_not_found(self, storage_with_mock_client):
"""Test exists returns False when list() returns an empty list."""
storage, mock_client = storage_with_mock_client
# Mock list return with count() method
mock_list_result = Mock()
mock_list_result.count.return_value = 1
mock_client.storage.from_().list.return_value = mock_list_result
result = storage.exists("test.txt")
assert result is True
# Verify the correct calls were made
assert "test-bucket" in [call[0][0] for call in mock_client.storage.from_.call_args_list if call[0]]
mock_client.storage.from_().list.assert_called_with("test.txt")
mock_list_result.count.assert_called()
def test_exists_with_count_method_zero(self, storage_with_mock_client):
"""Test exists returns False when list result has count() == 0."""
storage, mock_client = storage_with_mock_client
# Mock list return with count() method returning 0
mock_list_result = Mock()
mock_list_result.count.return_value = 0
mock_client.storage.from_().list.return_value = mock_list_result
mock_client.storage.from_().list.return_value = []
result = storage.exists("test.txt")
assert result is False
# Verify the correct calls were made
assert "test-bucket" in [call[0][0] for call in mock_client.storage.from_.call_args_list if call[0]]
mock_client.storage.from_().list.assert_called_with("test.txt")
mock_list_result.count.assert_called()
mock_client.storage.from_().list.assert_called_with(path="test.txt")
def test_exists_with_empty_list(self, storage_with_mock_client):
"""Test exists returns False when list() returns empty list."""
storage, mock_client = storage_with_mock_client
# Mock list return with special object that has count() method returning 0
mock_list_result = Mock()
mock_list_result.count.return_value = 0
mock_client.storage.from_().list.return_value = mock_list_result
result = storage.exists("test.txt")
assert result is False
# Verify the correct calls were made
assert "test-bucket" in [call[0][0] for call in mock_client.storage.from_.call_args_list if call[0]]
mock_client.storage.from_().list.assert_called_with("test.txt")
def test_delete_calls_remove_with_filename(self, storage_with_mock_client):
def test_delete_calls_remove_with_filename_in_list(self, storage_with_mock_client):
"""Test delete calls remove([...]) (some client versions require a list)."""
storage, mock_client = storage_with_mock_client
@ -247,7 +205,7 @@ class TestSupabaseStorage:
storage.delete(filename)
mock_client.storage.from_.assert_called_once_with("test-bucket")
mock_client.storage.from_().remove.assert_called_once_with(filename)
mock_client.storage.from_().remove.assert_called_once_with([filename])
def test_bucket_exists_returns_true_when_bucket_found(self):
"""Test bucket_exists returns True when bucket is found in list."""

View File

@ -1,3 +1,5 @@
from unittest.mock import patch
import pytest
from tos import TosClientV2 # type: ignore
@ -13,7 +15,13 @@ class TestVolcengineTos(BaseStorageTest):
@pytest.fixture(autouse=True)
def setup_method(self, setup_volcengine_tos_mock):
"""Executed before each test method."""
self.storage = VolcengineTosStorage()
with patch("extensions.storage.volcengine_tos_storage.dify_config") as mock_config:
mock_config.VOLCENGINE_TOS_ACCESS_KEY = "test_access_key"
mock_config.VOLCENGINE_TOS_SECRET_KEY = "test_secret_key"
mock_config.VOLCENGINE_TOS_ENDPOINT = "test_endpoint"
mock_config.VOLCENGINE_TOS_REGION = "test_region"
self.storage = VolcengineTosStorage()
self.storage.bucket_name = get_example_bucket()
self.storage.client = TosClientV2(
ak="dify",

View File

@ -445,16 +445,17 @@ wheels = [
[[package]]
name = "azure-storage-blob"
version = "12.13.0"
version = "12.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-core" },
{ name = "cryptography" },
{ name = "msrest" },
{ name = "isodate" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/93/b13bf390e940a79a399981f75ac8d2e05a70112a95ebb7b41e9b752d2921/azure-storage-blob-12.13.0.zip", hash = "sha256:53f0d4cd32970ac9ff9b9753f83dd2fb3f9ac30e1d01e71638c436c509bfd884", size = 684838 }
sdist = { url = "https://files.pythonhosted.org/packages/96/95/3e3414491ce45025a1cde107b6ae72bf72049e6021597c201cd6a3029b9a/azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f", size = 583332, upload-time = "2025-07-16T21:34:07.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/2a/b8246df35af68d64fb7292c93dbbde63cd25036f2f669a9d9ae59e518c76/azure_storage_blob-12.13.0-py3-none-any.whl", hash = "sha256:280a6ab032845bab9627582bee78a50497ca2f14772929b5c5ee8b4605af0cb3", size = 377309 },
{ url = "https://files.pythonhosted.org/packages/5b/64/63dbfdd83b31200ac58820a7951ddfdeed1fbee9285b0f3eae12d1357155/azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe", size = 412907, upload-time = "2025-07-16T21:34:09.367Z" },
]
[[package]]
@ -1297,7 +1298,6 @@ version = "1.9.1"
source = { virtual = "." }
dependencies = [
{ name = "arize-phoenix-otel" },
{ name = "authlib" },
{ name = "azure-identity" },
{ name = "beautifulsoup4" },
{ name = "boto3" },
@ -1329,10 +1329,8 @@ dependencies = [
{ name = "json-repair" },
{ name = "langfuse" },
{ name = "langsmith" },
{ name = "mailchimp-transactional" },
{ name = "markdown" },
{ name = "numpy" },
{ name = "openai" },
{ name = "openpyxl" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-distro" },
@ -1343,6 +1341,7 @@ dependencies = [
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-instrumentation-celery" },
{ name = "opentelemetry-instrumentation-flask" },
{ name = "opentelemetry-instrumentation-httpx" },
{ name = "opentelemetry-instrumentation-redis" },
{ name = "opentelemetry-instrumentation-requests" },
{ name = "opentelemetry-instrumentation-sqlalchemy" },
@ -1354,7 +1353,6 @@ dependencies = [
{ name = "opik" },
{ name = "packaging" },
{ name = "pandas", extra = ["excel", "output-formatting", "performance"] },
{ name = "pandoc" },
{ name = "psycogreen" },
{ name = "psycopg2-binary" },
{ name = "pycryptodome" },
@ -1493,7 +1491,6 @@ vdb = [
[package.metadata]
requires-dist = [
{ name = "arize-phoenix-otel", specifier = "~=0.9.2" },
{ name = "authlib", specifier = "==1.6.4" },
{ name = "azure-identity", specifier = "==1.16.1" },
{ name = "beautifulsoup4", specifier = "==4.12.2" },
{ name = "boto3", specifier = "==1.35.99" },
@ -1526,10 +1523,8 @@ requires-dist = [
{ name = "json-repair", specifier = ">=0.41.1" },
{ name = "langfuse", specifier = "~=2.51.3" },
{ name = "langsmith", specifier = "~=0.1.77" },
{ name = "mailchimp-transactional", specifier = "~=1.0.50" },
{ name = "markdown", specifier = "~=3.5.1" },
{ name = "numpy", specifier = "~=1.26.4" },
{ name = "openai", specifier = "~=1.61.0" },
{ name = "openpyxl", specifier = "~=3.1.5" },
{ name = "opentelemetry-api", specifier = "==1.27.0" },
{ name = "opentelemetry-distro", specifier = "==0.48b0" },
@ -1540,6 +1535,7 @@ requires-dist = [
{ name = "opentelemetry-instrumentation", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-celery", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-flask", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-httpx", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-redis", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-requests", specifier = "==0.48b0" },
{ name = "opentelemetry-instrumentation-sqlalchemy", specifier = "==0.48b0" },
@ -1551,7 +1547,6 @@ requires-dist = [
{ name = "opik", specifier = "~=1.7.25" },
{ name = "packaging", specifier = "~=23.2" },
{ name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" },
{ name = "pandoc", specifier = "~=2.4" },
{ name = "psycogreen", specifier = "~=1.0.2" },
{ name = "psycopg2-binary", specifier = "~=2.9.6" },
{ name = "pycryptodome", specifier = "==3.19.1" },
@ -1647,10 +1642,10 @@ dev = [
{ name = "types-ujson", specifier = ">=5.10.0" },
]
storage = [
{ name = "azure-storage-blob", specifier = "==12.13.0" },
{ name = "azure-storage-blob", specifier = "==12.26.0" },
{ name = "bce-python-sdk", specifier = "~=0.9.23" },
{ name = "cos-python-sdk-v5", specifier = "==1.9.38" },
{ name = "esdk-obs-python", specifier = "==3.24.6.1" },
{ name = "esdk-obs-python", specifier = "==3.25.8" },
{ name = "google-cloud-storage", specifier = "==2.16.0" },
{ name = "opendal", specifier = "~=0.46.0" },
{ name = "oss2", specifier = "==2.18.5" },
@ -1789,12 +1784,14 @@ wheels = [
[[package]]
name = "esdk-obs-python"
version = "3.24.6.1"
version = "3.25.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "crcmod" },
{ name = "pycryptodome" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f7/af/d83276f9e288bd6a62f44d67ae1eafd401028ba1b2b643ae4014b51da5bd/esdk-obs-python-3.24.6.1.tar.gz", hash = "sha256:c45fed143e99d9256c8560c1d78f651eae0d2e809d16e962f8b286b773c33bf0", size = 85798 }
sdist = { url = "https://files.pythonhosted.org/packages/40/99/52362d6e081a642d6de78f6ab53baa5e3f82f2386c48954e18ee7b4ab22b/esdk-obs-python-3.25.8.tar.gz", hash = "sha256:aeded00b27ecd5a25ffaec38a2cc9416b51923d48db96c663f1a735f859b5273", size = 96302, upload-time = "2025-09-01T11:35:20.432Z" }
[[package]]
name = "et-xmlfile"
@ -3945,6 +3942,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588 },
]
[[package]]
name = "opentelemetry-instrumentation-httpx"
version = "0.48b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/d9/c65d818607c16d1b7ea8d2de6111c6cecadf8d2fd38c1885a72733a7c6d3/opentelemetry_instrumentation_httpx-0.48b0.tar.gz", hash = "sha256:ee977479e10398931921fb995ac27ccdeea2e14e392cb27ef012fc549089b60a", size = 16931, upload-time = "2024-08-28T21:28:03.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/fe/f2daa9d6d988c093b8c7b1d35df675761a8ece0b600b035dc04982746c9d/opentelemetry_instrumentation_httpx-0.48b0-py3-none-any.whl", hash = "sha256:d94f9d612c82d09fe22944d1904a30a464c19bea2ba76be656c99a28ad8be8e5", size = 13900, upload-time = "2024-08-28T21:27:01.566Z" },
]
[[package]]
name = "opentelemetry-instrumentation-redis"
version = "0.48b0"

View File

@ -867,14 +867,14 @@ CODE_MAX_NUMBER=9223372036854775807
CODE_MIN_NUMBER=-9223372036854775808
CODE_MAX_DEPTH=5
CODE_MAX_PRECISION=20
CODE_MAX_STRING_LENGTH=80000
CODE_MAX_STRING_LENGTH=400000
CODE_MAX_STRING_ARRAY_LENGTH=30
CODE_MAX_OBJECT_ARRAY_LENGTH=30
CODE_MAX_NUMBER_ARRAY_LENGTH=1000
CODE_EXECUTION_CONNECT_TIMEOUT=10
CODE_EXECUTION_READ_TIMEOUT=60
CODE_EXECUTION_WRITE_TIMEOUT=10
TEMPLATE_TRANSFORM_MAX_LENGTH=80000
TEMPLATE_TRANSFORM_MAX_LENGTH=400000
# Workflow runtime configuration
WORKFLOW_MAX_EXECUTION_STEPS=500

View File

@ -390,14 +390,14 @@ x-shared-env: &shared-api-worker-env
CODE_MIN_NUMBER: ${CODE_MIN_NUMBER:--9223372036854775808}
CODE_MAX_DEPTH: ${CODE_MAX_DEPTH:-5}
CODE_MAX_PRECISION: ${CODE_MAX_PRECISION:-20}
CODE_MAX_STRING_LENGTH: ${CODE_MAX_STRING_LENGTH:-80000}
CODE_MAX_STRING_LENGTH: ${CODE_MAX_STRING_LENGTH:-400000}
CODE_MAX_STRING_ARRAY_LENGTH: ${CODE_MAX_STRING_ARRAY_LENGTH:-30}
CODE_MAX_OBJECT_ARRAY_LENGTH: ${CODE_MAX_OBJECT_ARRAY_LENGTH:-30}
CODE_MAX_NUMBER_ARRAY_LENGTH: ${CODE_MAX_NUMBER_ARRAY_LENGTH:-1000}
CODE_EXECUTION_CONNECT_TIMEOUT: ${CODE_EXECUTION_CONNECT_TIMEOUT:-10}
CODE_EXECUTION_READ_TIMEOUT: ${CODE_EXECUTION_READ_TIMEOUT:-60}
CODE_EXECUTION_WRITE_TIMEOUT: ${CODE_EXECUTION_WRITE_TIMEOUT:-10}
TEMPLATE_TRANSFORM_MAX_LENGTH: ${TEMPLATE_TRANSFORM_MAX_LENGTH:-80000}
TEMPLATE_TRANSFORM_MAX_LENGTH: ${TEMPLATE_TRANSFORM_MAX_LENGTH:-400000}
WORKFLOW_MAX_EXECUTION_STEPS: ${WORKFLOW_MAX_EXECUTION_STEPS:-500}
WORKFLOW_MAX_EXECUTION_TIME: ${WORKFLOW_MAX_EXECUTION_TIME:-1200}
WORKFLOW_CALL_MAX_DEPTH: ${WORKFLOW_CALL_MAX_DEPTH:-5}

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
<div style="text-align: right;">
@ -97,7 +99,7 @@
</br>
أسهل طريقة لبدء تشغيل خادم Dify هي تشغيل ملف [docker-compose.yml](docker/docker-compose.yaml) الخاص بنا. قبل تشغيل أمر التثبيت، تأكد من تثبيت [Docker](https://docs.docker.com/get-docker/) و [Docker Compose](https://docs.docker.com/compose/install/) على جهازك:
أسهل طريقة لبدء تشغيل خادم Dify هي تشغيل ملف [docker-compose.yml](../../docker/docker-compose.yaml) الخاص بنا. قبل تشغيل أمر التثبيت، تأكد من تثبيت [Docker](https://docs.docker.com/get-docker/) و [Docker Compose](https://docs.docker.com/compose/install/) على جهازك:
```bash
cd docker
@ -111,7 +113,7 @@ docker compose up -d
## الخطوات التالية
إذا كنت بحاجة إلى تخصيص الإعدادات، فيرجى الرجوع إلى التعليقات في ملف [.env.example](docker/.env.example) وتحديث القيم المقابلة في ملف `.env`. بالإضافة إلى ذلك، قد تحتاج إلى إجراء تعديلات على ملف `docker-compose.yaml` نفسه، مثل تغيير إصدارات الصور أو تعيينات المنافذ أو نقاط تحميل وحدات التخزين، بناءً على بيئة النشر ومتطلباتك الخاصة. بعد إجراء أي تغييرات، يرجى إعادة تشغيل `docker-compose up -d`. يمكنك العثور على قائمة كاملة بمتغيرات البيئة المتاحة [هنا](https://docs.dify.ai/getting-started/install-self-hosted/environments).
إذا كنت بحاجة إلى تخصيص الإعدادات، فيرجى الرجوع إلى التعليقات في ملف [.env.example](../../docker/.env.example) وتحديث القيم المقابلة في ملف `.env`. بالإضافة إلى ذلك، قد تحتاج إلى إجراء تعديلات على ملف `docker-compose.yaml` نفسه، مثل تغيير إصدارات الصور أو تعيينات المنافذ أو نقاط تحميل وحدات التخزين، بناءً على بيئة النشر ومتطلباتك الخاصة. بعد إجراء أي تغييرات، يرجى إعادة تشغيل `docker-compose up -d`. يمكنك العثور على قائمة كاملة بمتغيرات البيئة المتاحة [هنا](https://docs.dify.ai/getting-started/install-self-hosted/environments).
يوجد مجتمع خاص بـ [Helm Charts](https://helm.sh/) وملفات YAML التي تسمح بتنفيذ Dify على Kubernetes للنظام من الإيجابيات العلوية.
@ -185,12 +187,4 @@ docker compose up -d
## الرخصة
هذا المستودع متاح تحت [رخصة البرنامج الحر Dify](../LICENSE)، والتي تعتبر بشكل أساسي Apache 2.0 مع بعض القيود الإضافية.
## الكشف عن الأمان
لحماية خصوصيتك، يرجى تجنب نشر مشكلات الأمان على GitHub. بدلاً من ذلك، أرسل أسئلتك إلى <security@dify.ai> وسنقدم لك إجابة أكثر تفصيلاً.
## الرخصة
هذا المستودع متاح تحت [رخصة البرنامج الحر Dify](../LICENSE)، والتي تعتبر بشكل أساسي Apache 2.0 مع بعض القيود الإضافية.
هذا المستودع متاح تحت [رخصة البرنامج الحر Dify](../../LICENSE)، والتي تعتبر بشكل أساسي Apache 2.0 مع بعض القيود الإضافية.

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">ডিফাই ওয়ার্কফ্লো ফাইল আপলোড পরিচিতি: গুগল নোটবুক-এলএম পডকাস্ট পুনর্নির্মাণ</a>
@ -39,18 +39,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_DE.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
ডিফাই একটি ওপেন-সোর্স LLM অ্যাপ ডেভেলপমেন্ট প্ল্যাটফর্ম। এটি ইন্টুইটিভ ইন্টারফেস, এজেন্টিক AI ওয়ার্কফ্লো, RAG পাইপলাইন, এজেন্ট ক্যাপাবিলিটি, মডেল ম্যানেজমেন্ট, মনিটরিং সুবিধা এবং আরও অনেক কিছু একত্রিত করে, যা দ্রুত প্রোটোটাইপ থেকে প্রোডাকশন পর্যন্ত নিয়ে যেতে সহায়তা করে।
@ -64,7 +65,7 @@
</br>
ডিফাই সার্ভার চালু করার সবচেয়ে সহজ উপায় [docker compose](docker/docker-compose.yaml) মাধ্যমে। নিম্নলিখিত কমান্ডগুলো ব্যবহার করে ডিফাই চালানোর আগে, নিশ্চিত করুন যে আপনার মেশিনে [Docker](https://docs.docker.com/get-docker/) এবং [Docker Compose](https://docs.docker.com/compose/install/) ইনস্টল করা আছে :
ডিফাই সার্ভার চালু করার সবচেয়ে সহজ উপায় [docker compose](../../docker/docker-compose.yaml) মাধ্যমে। নিম্নলিখিত কমান্ডগুলো ব্যবহার করে ডিফাই চালানোর আগে, নিশ্চিত করুন যে আপনার মেশিনে [Docker](https://docs.docker.com/get-docker/) এবং [Docker Compose](https://docs.docker.com/compose/install/) ইনস্টল করা আছে :
```bash
cd dify
@ -128,7 +129,7 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন
## Advanced Setup
যদি আপনার কনফিগারেশনটি কাস্টমাইজ করার প্রয়োজন হয়, তাহলে অনুগ্রহ করে আমাদের [.env.example](docker/.env.example) ফাইল দেখুন এবং আপনার `.env` ফাইলে সংশ্লিষ্ট মানগুলি আপডেট করুন। এছাড়াও, আপনার নির্দিষ্ট এনভায়রনমেন্ট এবং প্রয়োজনীয়তার উপর ভিত্তি করে আপনাকে `docker-compose.yaml` ফাইলে সমন্বয় করতে হতে পারে, যেমন ইমেজ ভার্সন পরিবর্তন করা, পোর্ট ম্যাপিং করা, অথবা ভলিউম মাউন্ট করা।
যদি আপনার কনফিগারেশনটি কাস্টমাইজ করার প্রয়োজন হয়, তাহলে অনুগ্রহ করে আমাদের [.env.example](../../docker/.env.example) ফাইল দেখুন এবং আপনার `.env` ফাইলে সংশ্লিষ্ট মানগুলি আপডেট করুন। এছাড়াও, আপনার নির্দিষ্ট এনভায়রনমেন্ট এবং প্রয়োজনীয়তার উপর ভিত্তি করে আপনাকে `docker-compose.yaml` ফাইলে সমন্বয় করতে হতে পারে, যেমন ইমেজ ভার্সন পরিবর্তন করা, পোর্ট ম্যাপিং করা, অথবা ভলিউম মাউন্ট করা।
যেকোনো পরিবর্তন করার পর, অনুগ্রহ করে `docker-compose up -d` পুনরায় চালান। ভেরিয়েবলের সম্পূর্ণ তালিকা [এখানে] (https://docs.dify.ai/getting-started/install-self-hosted/environments) খুঁজে পেতে পারেন।
যদি আপনি একটি হাইলি এভেইলেবল সেটআপ কনফিগার করতে চান, তাহলে কমিউনিটি [Helm Charts](https://helm.sh/) এবং YAML ফাইল রয়েছে যা Dify কে Kubernetes-এ ডিপ্লয় করার প্রক্রিয়া বর্ণনা করে।
@ -175,7 +176,7 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন
## Contributing
যারা কোড অবদান রাখতে চান, তাদের জন্য আমাদের [অবদান নির্দেশিকা] দেখুন (https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)।
যারা কোড অবদান রাখতে চান, তাদের জন্য আমাদের [অবদান নির্দেশিকা](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) দেখুন
একই সাথে, সোশ্যাল মিডিয়া এবং ইভেন্ট এবং কনফারেন্সে এটি শেয়ার করে Dify কে সমর্থন করুন।
> আমরা ম্যান্ডারিন বা ইংরেজি ছাড়া অন্য ভাষায় Dify অনুবাদ করতে সাহায্য করার জন্য অবদানকারীদের খুঁজছি। আপনি যদি সাহায্য করতে আগ্রহী হন, তাহলে আরও তথ্যের জন্য [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) দেখুন এবং আমাদের [ডিসকর্ড কমিউনিটি সার্ভার](https://discord.gg/8Tpq4AcN9c) এর `গ্লোবাল-ইউজারস` চ্যানেলে আমাদের একটি মন্তব্য করুন।
@ -203,4 +204,4 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন
## লাইসেন্স
এই রিপোজিটরিটি [ডিফাই ওপেন সোর্স লাইসেন্স](../LICENSE) এর অধিনে , যা মূলত অ্যাপাচি ২., তবে কিছু অতিরিক্ত বিধিনিষেধ রয়েছে।
এই রিপোজিটরিটি [ডিফাই ওপেন সোর্স লাইসেন্স](../../LICENSE) এর অধিনে , যা মূলত অ্যাপাচি ২., তবে কিছু অতিরিক্ত বিধিনিষেধ রয়েছে।

View File

@ -6,7 +6,7 @@ Wir müssen wendig sein und schnell liefern, aber wir möchten auch sicherstelle
Dieser Leitfaden ist, wie Dify selbst, in ständiger Entwicklung. Wir sind dankbar für Ihr Verständnis, falls er manchmal hinter dem eigentlichen Projekt zurückbleibt, und begrüßen jedes Feedback zur Verbesserung.
Bitte nehmen Sie sich einen Moment Zeit, um unsere [Lizenz- und Mitwirkungsvereinbarung](../LICENSE) zu lesen. Die Community hält sich außerdem an den [Verhaltenskodex](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
Bitte nehmen Sie sich einen Moment Zeit, um unsere [Lizenz- und Mitwirkungsvereinbarung](../../LICENSE) zu lesen. Die Community hält sich außerdem an den [Verhaltenskodex](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Bevor Sie loslegen

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Einführung in Dify Workflow File Upload: Google NotebookLM Podcast nachbilden</a>
@ -39,18 +39,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_DE.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify ist eine Open-Source-Plattform zur Entwicklung von LLM-Anwendungen. Ihre intuitive Benutzeroberfläche vereint agentenbasierte KI-Workflows, RAG-Pipelines, Agentenfunktionen, Modellverwaltung, Überwachungsfunktionen und mehr, sodass Sie schnell von einem Prototyp in die Produktion übergehen können.
@ -64,7 +65,7 @@ Dify ist eine Open-Source-Plattform zur Entwicklung von LLM-Anwendungen. Ihre in
</br>
Der einfachste Weg, den Dify-Server zu starten, ist über [docker compose](docker/docker-compose.yaml). Stellen Sie vor dem Ausführen von Dify mit den folgenden Befehlen sicher, dass [Docker](https://docs.docker.com/get-docker/) und [Docker Compose](https://docs.docker.com/compose/install/) auf Ihrem System installiert sind:
Der einfachste Weg, den Dify-Server zu starten, ist über [docker compose](../../docker/docker-compose.yaml). Stellen Sie vor dem Ausführen von Dify mit den folgenden Befehlen sicher, dass [Docker](https://docs.docker.com/get-docker/) und [Docker Compose](https://docs.docker.com/compose/install/) auf Ihrem System installiert sind:
```bash
cd dify
@ -127,7 +128,7 @@ Star Dify auf GitHub und lassen Sie sich sofort über neue Releases benachrichti
## Erweiterte Einstellungen
Falls Sie die Konfiguration anpassen müssen, lesen Sie bitte die Kommentare in unserer [.env.example](docker/.env.example)-Datei und aktualisieren Sie die entsprechenden Werte in Ihrer `.env`-Datei. Zusätzlich müssen Sie eventuell Anpassungen an der `docker-compose.yaml`-Datei vornehmen, wie zum Beispiel das Ändern von Image-Versionen, Portzuordnungen oder Volumen-Mounts, je nach Ihrer spezifischen Einsatzumgebung und Ihren Anforderungen. Nachdem Sie Änderungen vorgenommen haben, starten Sie `docker-compose up -d` erneut. Eine vollständige Liste der verfügbaren Umgebungsvariablen finden Sie [hier](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Falls Sie die Konfiguration anpassen müssen, lesen Sie bitte die Kommentare in unserer [.env.example](../../docker/.env.example)-Datei und aktualisieren Sie die entsprechenden Werte in Ihrer `.env`-Datei. Zusätzlich müssen Sie eventuell Anpassungen an der `docker-compose.yaml`-Datei vornehmen, wie zum Beispiel das Ändern von Image-Versionen, Portzuordnungen oder Volumen-Mounts, je nach Ihrer spezifischen Einsatzumgebung und Ihren Anforderungen. Nachdem Sie Änderungen vorgenommen haben, starten Sie `docker-compose up -d` erneut. Eine vollständige Liste der verfügbaren Umgebungsvariablen finden Sie [hier](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Falls Sie eine hochverfügbare Konfiguration einrichten möchten, gibt es von der Community bereitgestellte [Helm Charts](https://helm.sh/) und YAML-Dateien, die es ermöglichen, Dify auf Kubernetes bereitzustellen.
@ -173,14 +174,14 @@ Stellen Sie Dify mit einem Klick in AKS bereit, indem Sie [Azure Devops Pipeline
## Contributing
Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_DE.md). Gleichzeitig bitten wir Sie, Dify zu unterstützen, indem Sie es in den sozialen Medien teilen und auf Veranstaltungen und Konferenzen präsentieren.
Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide](./CONTRIBUTING.md). Gleichzeitig bitten wir Sie, Dify zu unterstützen, indem Sie es in den sozialen Medien teilen und auf Veranstaltungen und Konferenzen präsentieren.
> Wir suchen Mitwirkende, die dabei helfen, Dify in weitere Sprachen zu übersetzen außer Mandarin oder Englisch. Wenn Sie Interesse an einer Mitarbeit haben, lesen Sie bitte die [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) für weitere Informationen und hinterlassen Sie einen Kommentar im `global-users`-Kanal unseres [Discord Community Servers](https://discord.gg/8Tpq4AcN9c).
## Gemeinschaft & Kontakt
- [GitHub Discussion](https://github.com/langgenius/dify/discussions). Am besten geeignet für: den Austausch von Feedback und das Stellen von Fragen.
- [GitHub Issues](https://github.com/langgenius/dify/issues). Am besten für: Fehler, auf die Sie bei der Verwendung von Dify.AI stoßen, und Funktionsvorschläge. Siehe unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
- [GitHub Issues](https://github.com/langgenius/dify/issues). Am besten für: Fehler, auf die Sie bei der Verwendung von Dify.AI stoßen, und Funktionsvorschläge. Siehe unseren [Contribution Guide](./CONTRIBUTING.md).
- [Discord](https://discord.gg/FngNHpbcY7). Am besten geeignet für: den Austausch von Bewerbungen und den Austausch mit der Community.
- [X(Twitter)](https://twitter.com/dify_ai). Am besten geeignet für: den Austausch von Bewerbungen und den Austausch mit der Community.
@ -200,4 +201,4 @@ Um Ihre Privatsphäre zu schützen, vermeiden Sie es bitte, Sicherheitsprobleme
## Lizenz
Dieses Repository steht unter der [Dify Open Source License](../LICENSE), die im Wesentlichen Apache 2.0 mit einigen zusätzlichen Einschränkungen ist.
Dieses Repository steht unter der [Dify Open Source License](../../LICENSE), die im Wesentlichen Apache 2.0 mit einigen zusätzlichen Einschränkungen ist.

View File

@ -6,7 +6,7 @@ Necesitamos ser ágiles y enviar rápidamente dado donde estamos, pero también
Esta guía, como Dify mismo, es un trabajo en constante progreso. Agradecemos mucho tu comprensión si a veces se queda atrás del proyecto real, y damos la bienvenida a cualquier comentario para que podamos mejorar.
En términos de licencia, por favor tómate un minuto para leer nuestro breve [Acuerdo de Licencia y Colaborador](../LICENSE). La comunidad también se adhiere al [código de conducta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
En términos de licencia, por favor tómate un minuto para leer nuestro breve [Acuerdo de Licencia y Colaborador](../../LICENSE). La comunidad también se adhiere al [código de conducta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Antes de empezar

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
#
@ -108,7 +110,7 @@ Dale estrella a Dify en GitHub y serás notificado instantáneamente de las nuev
</br>
La forma más fácil de iniciar el servidor de Dify es ejecutar nuestro archivo [docker-compose.yml](docker/docker-compose.yaml). Antes de ejecutar el comando de instalación, asegúrate de que [Docker](https://docs.docker.com/get-docker/) y [Docker Compose](https://docs.docker.com/compose/install/) estén instalados en tu máquina:
La forma más fácil de iniciar el servidor de Dify es ejecutar nuestro archivo [docker-compose.yml](../../docker/docker-compose.yaml). Antes de ejecutar el comando de instalación, asegúrate de que [Docker](https://docs.docker.com/get-docker/) y [Docker Compose](https://docs.docker.com/compose/install/) estén instalados en tu máquina:
```bash
cd docker
@ -122,7 +124,7 @@ Después de ejecutarlo, puedes acceder al panel de control de Dify en tu navegad
## Próximos pasos
Si necesita personalizar la configuración, consulte los comentarios en nuestro archivo [.env.example](docker/.env.example) y actualice los valores correspondientes en su archivo `.env`. Además, es posible que deba realizar ajustes en el propio archivo `docker-compose.yaml`, como cambiar las versiones de las imágenes, las asignaciones de puertos o los montajes de volúmenes, según su entorno de implementación y requisitos específicos. Después de realizar cualquier cambio, vuelva a ejecutar `docker-compose up -d`. Puede encontrar la lista completa de variables de entorno disponibles [aquí](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Si necesita personalizar la configuración, consulte los comentarios en nuestro archivo [.env.example](../../docker/.env.example) y actualice los valores correspondientes en su archivo `.env`. Además, es posible que deba realizar ajustes en el propio archivo `docker-compose.yaml`, como cambiar las versiones de las imágenes, las asignaciones de puertos o los montajes de volúmenes, según su entorno de implementación y requisitos específicos. Después de realizar cualquier cambio, vuelva a ejecutar `docker-compose up -d`. Puede encontrar la lista completa de variables de entorno disponibles [aquí](https://docs.dify.ai/getting-started/install-self-hosted/environments).
. Después de realizar los cambios, ejecuta `docker-compose up -d` nuevamente. Puedes ver la lista completa de variables de entorno [aquí](https://docs.dify.ai/getting-started/install-self-hosted/environments).
@ -170,7 +172,7 @@ Implementa Dify en AKS con un clic usando [Azure Devops Pipeline Helm Chart by @
## Contribuir
Para aquellos que deseen contribuir con código, consulten nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_ES.md).
Para aquellos que deseen contribuir con código, consulten nuestra [Guía de contribución](./CONTRIBUTING.md).
Al mismo tiempo, considera apoyar a Dify compartiéndolo en redes sociales y en eventos y conferencias.
> Estamos buscando colaboradores para ayudar con la traducción de Dify a idiomas que no sean el mandarín o el inglés. Si estás interesado en ayudar, consulta el [README de i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para obtener más información y déjanos un comentario en el canal `global-users` de nuestro [Servidor de Comunidad en Discord](https://discord.gg/8Tpq4AcN9c).
@ -184,7 +186,7 @@ Al mismo tiempo, considera apoyar a Dify compartiéndolo en redes sociales y en
## Comunidad y Contacto
- [Discusión en GitHub](https://github.com/langgenius/dify/discussions). Lo mejor para: compartir comentarios y hacer preguntas.
- [Reporte de problemas en GitHub](https://github.com/langgenius/dify/issues). Lo mejor para: errores que encuentres usando Dify.AI y propuestas de características. Consulta nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
- [Reporte de problemas en GitHub](https://github.com/langgenius/dify/issues). Lo mejor para: errores que encuentres usando Dify.AI y propuestas de características. Consulta nuestra [Guía de contribución](./CONTRIBUTING.md).
- [Discord](https://discord.gg/FngNHpbcY7). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
- [X(Twitter)](https://twitter.com/dify_ai). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
@ -198,12 +200,4 @@ Para proteger tu privacidad, evita publicar problemas de seguridad en GitHub. En
## Licencia
Este repositorio está disponible bajo la [Licencia de Código Abierto de Dify](../LICENSE), que es esencialmente Apache 2.0 con algunas restricciones adicionales.
## Divulgación de Seguridad
Para proteger tu privacidad, evita publicar problemas de seguridad en GitHub. En su lugar, envía tus preguntas a security@dify.ai y te proporcionaremos una respuesta más detallada.
## Licencia
Este repositorio está disponible bajo la [Licencia de Código Abierto de Dify](../LICENSE), que es esencialmente Apache 2.0 con algunas restricciones adicionales.
Este repositorio está disponible bajo la [Licencia de Código Abierto de Dify](../../LICENSE), que es esencialmente Apache 2.0 con algunas restricciones adicionales.

View File

@ -6,7 +6,7 @@ Nous devons être agiles et livrer rapidement compte tenu de notre position, mai
Ce guide, comme Dify lui-même, est un travail en constante évolution. Nous apprécions grandement votre compréhension si parfois il est en retard par rapport au projet réel, et nous accueillons tout commentaire pour nous aider à nous améliorer.
En termes de licence, veuillez prendre une minute pour lire notre bref [Accord de Licence et de Contributeur](../LICENSE). La communauté adhère également au [code de conduite](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
En termes de licence, veuillez prendre une minute pour lire notre bref [Accord de Licence et de Contributeur](../../LICENSE). La communauté adhère également au [code de conduite](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Avant de vous lancer

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
#
@ -108,7 +110,7 @@ Mettez une étoile à Dify sur GitHub et soyez instantanément informé des nouv
</br>
La manière la plus simple de démarrer le serveur Dify est d'exécuter notre fichier [docker-compose.yml](docker/docker-compose.yaml). Avant d'exécuter la commande d'installation, assurez-vous que [Docker](https://docs.docker.com/get-docker/) et [Docker Compose](https://docs.docker.com/compose/install/) sont installés sur votre machine:
La manière la plus simple de démarrer le serveur Dify est d'exécuter notre fichier [docker-compose.yml](../../docker/docker-compose.yaml). Avant d'exécuter la commande d'installation, assurez-vous que [Docker](https://docs.docker.com/get-docker/) et [Docker Compose](https://docs.docker.com/compose/install/) sont installés sur votre machine:
```bash
cd docker
@ -122,7 +124,7 @@ Après l'exécution, vous pouvez accéder au tableau de bord Dify dans votre nav
## Prochaines étapes
Si vous devez personnaliser la configuration, veuillez vous référer aux commentaires dans notre fichier [.env.example](docker/.env.example) et mettre à jour les valeurs correspondantes dans votre fichier `.env`. De plus, vous devrez peut-être apporter des modifications au fichier `docker-compose.yaml` lui-même, comme changer les versions d'image, les mappages de ports ou les montages de volumes, en fonction de votre environnement de déploiement et de vos exigences spécifiques. Après avoir effectué des modifications, veuillez réexécuter `docker-compose up -d`. Vous pouvez trouver la liste complète des variables d'environnement disponibles [ici](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Si vous devez personnaliser la configuration, veuillez vous référer aux commentaires dans notre fichier [.env.example](../../docker/.env.example) et mettre à jour les valeurs correspondantes dans votre fichier `.env`. De plus, vous devrez peut-être apporter des modifications au fichier `docker-compose.yaml` lui-même, comme changer les versions d'image, les mappages de ports ou les montages de volumes, en fonction de votre environnement de déploiement et de vos exigences spécifiques. Après avoir effectué des modifications, veuillez réexécuter `docker-compose up -d`. Vous pouvez trouver la liste complète des variables d'environnement disponibles [ici](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Si vous souhaitez configurer une configuration haute disponibilité, la communauté fournit des [Helm Charts](https://helm.sh/) et des fichiers YAML, à travers lesquels vous pouvez déployer Dify sur Kubernetes.
@ -168,7 +170,7 @@ Déployez Dify sur AKS en un clic en utilisant [Azure Devops Pipeline Helm Chart
## Contribuer
Pour ceux qui souhaitent contribuer du code, consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_FR.md).
Pour ceux qui souhaitent contribuer du code, consultez notre [Guide de contribution](./CONTRIBUTING.md).
Dans le même temps, veuillez envisager de soutenir Dify en le partageant sur les réseaux sociaux et lors d'événements et de conférences.
> Nous recherchons des contributeurs pour aider à traduire Dify dans des langues autres que le mandarin ou l'anglais. Si vous êtes intéressé à aider, veuillez consulter le [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) pour plus d'informations, et laissez-nous un commentaire dans le canal `global-users` de notre [Serveur communautaire Discord](https://discord.gg/8Tpq4AcN9c).
@ -182,7 +184,7 @@ Dans le même temps, veuillez envisager de soutenir Dify en le partageant sur le
## Communauté & Contact
- [Discussion GitHub](https://github.com/langgenius/dify/discussions). Meilleur pour: partager des commentaires et poser des questions.
- [Problèmes GitHub](https://github.com/langgenius/dify/issues). Meilleur pour: les bogues que vous rencontrez en utilisant Dify.AI et les propositions de fonctionnalités. Consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
- [Problèmes GitHub](https://github.com/langgenius/dify/issues). Meilleur pour: les bogues que vous rencontrez en utilisant Dify.AI et les propositions de fonctionnalités. Consultez notre [Guide de contribution](./CONTRIBUTING.md).
- [Discord](https://discord.gg/FngNHpbcY7). Meilleur pour: partager vos applications et passer du temps avec la communauté.
- [X(Twitter)](https://twitter.com/dify_ai). Meilleur pour: partager vos applications et passer du temps avec la communauté.
@ -196,12 +198,4 @@ Pour protéger votre vie privée, veuillez éviter de publier des problèmes de
## Licence
Ce référentiel est disponible sous la [Licence open source Dify](../LICENSE), qui est essentiellement l'Apache 2.0 avec quelques restrictions supplémentaires.
## Divulgation de sécurité
Pour protéger votre vie privée, veuillez éviter de publier des problèmes de sécurité sur GitHub. Au lieu de cela, envoyez vos questions à security@dify.ai et nous vous fournirons une réponse plus détaillée.
## Licence
Ce référentiel est disponible sous la [Licence open source Dify](../LICENSE), qui est essentiellement l'Apache 2.0 avec quelques restrictions supplémentaires.
Ce référentiel est disponible sous la [Licence open source Dify](../../LICENSE), qui est essentiellement l'Apache 2.0 avec quelques restrictions supplémentaires.

View File

@ -6,7 +6,7 @@ Difyに貢献しようとお考えですか素晴らしいですね。私た
このガイドは、Dify自体と同様に、常に進化し続けています。実際のプロジェクトの進行状況と多少のずれが生じる場合もございますが、ご理解いただけますと幸いです。改善のためのフィードバックも歓迎いたします。
ライセンスについては、[ライセンスと貢献者同意書](../LICENSE)をご一読ください。また、コミュニティは[行動規範](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)に従っています。
ライセンスについては、[ライセンスと貢献者同意書](../../LICENSE)をご一読ください。また、コミュニティは[行動規範](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)に従っています。
## 始める前に

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
#
@ -109,7 +111,7 @@ GitHub上でDifyにスターを付けることで、Difyに関する新しいニ
</br>
Difyサーバーを起動する最も簡単な方法は、[docker-compose.yml](docker/docker-compose.yaml)ファイルを実行することです。インストールコマンドを実行する前に、マシンに[Docker](https://docs.docker.com/get-docker/)と[Docker Compose](https://docs.docker.com/compose/install/)がインストールされていることを確認してください。
Difyサーバーを起動する最も簡単な方法は、[docker-compose.yml](../../docker/docker-compose.yaml)ファイルを実行することです。インストールコマンドを実行する前に、マシンに[Docker](https://docs.docker.com/get-docker/)と[Docker Compose](https://docs.docker.com/compose/install/)がインストールされていることを確認してください。
```bash
cd docker
@ -123,7 +125,7 @@ docker compose up -d
## 次のステップ
設定をカスタマイズする必要がある場合は、[.env.example](docker/.env.example) ファイルのコメントを参照し、`.env` ファイルの対応する値を更新してください。さらに、デプロイ環境や要件に応じて、`docker-compose.yaml` ファイル自体を調整する必要がある場合があります。たとえば、イメージのバージョン、ポートのマッピング、ボリュームのマウントなどを変更します。変更を加えた後は、`docker-compose up -d` を再実行してください。利用可能な環境変数の全一覧は、[こちら](https://docs.dify.ai/getting-started/install-self-hosted/environments)で確認できます。
設定をカスタマイズする必要がある場合は、[.env.example](../../docker/.env.example) ファイルのコメントを参照し、`.env` ファイルの対応する値を更新してください。さらに、デプロイ環境や要件に応じて、`docker-compose.yaml` ファイル自体を調整する必要がある場合があります。たとえば、イメージのバージョン、ポートのマッピング、ボリュームのマウントなどを変更します。変更を加えた後は、`docker-compose up -d` を再実行してください。利用可能な環境変数の全一覧は、[こちら](https://docs.dify.ai/getting-started/install-self-hosted/environments)で確認できます。
高可用性設定を設定する必要がある場合、コミュニティは[Helm Charts](https://helm.sh/)とYAMLファイルにより、DifyをKubernetesにデプロイすることができます。
@ -169,7 +171,7 @@ docker compose up -d
## 貢献
コードに貢献したい方は、[Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_JA.md)を参照してください。
コードに貢献したい方は、[Contribution Guide](./CONTRIBUTING.md)を参照してください。
同時に、DifyをSNSやイベント、カンファレンスで共有してサポートしていただけると幸いです。
> Difyを英語または中国語以外の言語に翻訳してくれる貢献者を募集しています。興味がある場合は、詳細については[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)を参照してください。また、[Discordコミュニティサーバー](https://discord.gg/8Tpq4AcN9c)の`global-users`チャンネルにコメントを残してください。
@ -183,10 +185,10 @@ docker compose up -d
## コミュニティ & お問い合わせ
- [GitHub Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。
- [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](../CONTRIBUTING/CONTRIBUTING_JA.md)を参照してください
- [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](./CONTRIBUTING.md)を参照してください
- [Discord](https://discord.gg/FngNHpbcY7). 主に: アプリケーションの共有やコミュニティとの交流。
- [X(Twitter)](https://twitter.com/dify_ai). 主に: アプリケーションの共有やコミュニティとの交流。
## ライセンス
このリポジトリは、Dify Open Source License にいくつかの追加制限を加えた[Difyオープンソースライセンス](../LICENSE)の下で利用可能です。
このリポジトリは、Dify Open Source License にいくつかの追加制限を加えた[Difyオープンソースライセンス](../../LICENSE)の下で利用可能です。

View File

@ -6,7 +6,7 @@ Dify에 기여하려고 하시는군요 - 정말 멋집니다, 당신이 무엇
이 가이드는 Dify 자체와 마찬가지로 끊임없이 진행 중인 작업입니다. 때로는 실제 프로젝트보다 뒤처질 수 있다는 점을 이해해 주시면 감사하겠으며, 개선을 위한 피드백은 언제든지 환영합니다.
라이센스 측면에서, 간략한 [라이센스 및 기여자 동의서](../LICENSE)를 읽어보는 시간을 가져주세요. 커뮤니티는 또한 [행동 강령](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)을 준수합니다.
라이센스 측면에서, 간략한 [라이센스 및 기여자 동의서](../../LICENSE)를 읽어보는 시간을 가져주세요. 커뮤니티는 또한 [행동 강령](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)을 준수합니다.
## 시작하기 전에

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify 클라우드</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify는 오픈 소스 LLM 앱 개발 플랫폼입니다. 직관적인 인터페이스를 통해 AI 워크플로우, RAG 파이프라인, 에이전트 기능, 모델 관리, 관찰 기능 등을 결합하여 프로토타입에서 프로덕션까지 빠르게 전환할 수 있습니다. 주요 기능 목록은 다음과 같습니다:</br> </br>
@ -102,7 +104,7 @@ GitHub에서 Dify에 별표를 찍어 새로운 릴리스를 즉시 알림 받
</br>
Dify 서버를 시작하는 가장 쉬운 방법은 [docker-compose.yml](docker/docker-compose.yaml) 파일을 실행하는 것입니다. 설치 명령을 실행하기 전에 [Docker](https://docs.docker.com/get-docker/) 및 [Docker Compose](https://docs.docker.com/compose/install/)가 머신에 설치되어 있는지 확인하세요.
Dify 서버를 시작하는 가장 쉬운 방법은 [docker-compose.yml](../../docker/docker-compose.yaml) 파일을 실행하는 것입니다. 설치 명령을 실행하기 전에 [Docker](https://docs.docker.com/get-docker/) 및 [Docker Compose](https://docs.docker.com/compose/install/)가 머신에 설치되어 있는지 확인하세요.
```bash
cd docker
@ -116,7 +118,7 @@ docker compose up -d
## 다음 단계
구성을 사용자 정의해야 하는 경우 [.env.example](docker/.env.example) 파일의 주석을 참조하고 `.env` 파일에서 해당 값을 업데이트하십시오. 또한 특정 배포 환경 및 요구 사항에 따라 `docker-compose.yaml` 파일 자체를 조정해야 할 수도 있습니다. 예를 들어 이미지 버전, 포트 매핑 또는 볼륨 마운트를 변경합니다. 변경 한 후 `docker-compose up -d`를 다시 실행하십시오. 사용 가능한 환경 변수의 전체 목록은 [여기](https://docs.dify.ai/getting-started/install-self-hosted/environments)에서 찾을 수 있습니다.
구성을 사용자 정의해야 하는 경우 [.env.example](../../docker/.env.example) 파일의 주석을 참조하고 `.env` 파일에서 해당 값을 업데이트하십시오. 또한 특정 배포 환경 및 요구 사항에 따라 `docker-compose.yaml` 파일 자체를 조정해야 할 수도 있습니다. 예를 들어 이미지 버전, 포트 매핑 또는 볼륨 마운트를 변경합니다. 변경 한 후 `docker-compose up -d`를 다시 실행하십시오. 사용 가능한 환경 변수의 전체 목록은 [여기](https://docs.dify.ai/getting-started/install-self-hosted/environments)에서 찾을 수 있습니다.
Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했다는 커뮤니티가 제공하는 [Helm Charts](https://helm.sh/)와 YAML 파일이 존재합니다.
@ -162,7 +164,7 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
## 기여
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_KR.md)를 참조하세요.
코드에 기여하고 싶은 분들은 [기여 가이드](./CONTRIBUTING.md)를 참조하세요.
동시에 Dify를 소셜 미디어와 행사 및 컨퍼런스에 공유하여 지원하는 것을 고려해 주시기 바랍니다.
> 우리는 Dify를 중국어나 영어 이외의 언어로 번역하는 데 도움을 줄 수 있는 기여자를 찾고 있습니다. 도움을 주고 싶으시다면 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)에서 더 많은 정보를 확인하시고 [Discord 커뮤니티 서버](https://discord.gg/8Tpq4AcN9c)의 `global-users` 채널에 댓글을 남겨주세요.
@ -176,7 +178,7 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
## 커뮤니티 & 연락처
- [GitHub 토론](https://github.com/langgenius/dify/discussions). 피드백 공유 및 질문하기에 적합합니다.
- [GitHub 이슈](https://github.com/langgenius/dify/issues). Dify.AI 사용 중 발견한 버그와 기능 제안에 적합합니다. [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
- [GitHub 이슈](https://github.com/langgenius/dify/issues). Dify.AI 사용 중 발견한 버그와 기능 제안에 적합합니다. [기여 가이드](./CONTRIBUTING.md)를 참조하세요.
- [디스코드](https://discord.gg/FngNHpbcY7). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다.
- [트위터](https://twitter.com/dify_ai). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다.
@ -190,4 +192,4 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
## 라이선스
이 저장소는 기본적으로 몇 가지 추가 제한 사항이 있는 Apache 2.0인 [Dify 오픈 소스 라이선스](../LICENSE)에 따라 사용할 수 있습니다.
이 저장소는 기본적으로 몇 가지 추가 제한 사항이 있는 Apache 2.0인 [Dify 오픈 소스 라이선스](../../LICENSE)에 따라 사용할 수 있습니다.

View File

@ -6,7 +6,7 @@ Precisamos ser ágeis e entregar rapidamente considerando onde estamos, mas tamb
Este guia, como o próprio Dify, é um trabalho em constante evolução. Agradecemos muito a sua compreensão se às vezes ele ficar atrasado em relação ao projeto real, e damos as boas-vindas a qualquer feedback para que possamos melhorar.
Em termos de licenciamento, por favor, dedique um minuto para ler nosso breve [Acordo de Licença e Contribuidor](../LICENSE). A comunidade também adere ao [código de conduta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
Em termos de licenciamento, por favor, dedique um minuto para ler nosso breve [Acordo de Licença e Contribuidor](../../LICENSE). A comunidade também adere ao [código de conduta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Antes de começar

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Introduzindo o Dify Workflow com Upload de Arquivo: Recrie o Podcast Google NotebookLM</a>
@ -39,18 +39,20 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README em Inglês" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README em Espanhol" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README em Francês" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README em Coreano" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README em Árabe" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="README em Turco" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README em Vietnamita" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_PT.md"><img alt="README em Português - BR" src="https://img.shields.io/badge/Portugu%C3%AAs-BR?style=flat&label=BR&color=d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README em Inglês" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README em Espanhol" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README em Francês" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README em Coreano" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README em Árabe" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="README em Turco" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README em Vietnamita" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../pt-BR/README.md"><img alt="README em Português - BR" src="https://img.shields.io/badge/Portugu%C3%AAs-BR?style=flat&label=BR&color=d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify é uma plataforma de desenvolvimento de aplicativos LLM de código aberto. Sua interface intuitiva combina workflow de IA, pipeline RAG, capacidades de agente, gerenciamento de modelos, recursos de observabilidade e muito mais, permitindo que você vá rapidamente do protótipo à produção. Aqui está uma lista das principais funcionalidades:
@ -108,7 +110,7 @@ Dê uma estrela no Dify no GitHub e seja notificado imediatamente sobre novos la
</br>
A maneira mais fácil de iniciar o servidor Dify é executar nosso arquivo [docker-compose.yml](docker/docker-compose.yaml). Antes de rodar o comando de instalação, certifique-se de que o [Docker](https://docs.docker.com/get-docker/) e o [Docker Compose](https://docs.docker.com/compose/install/) estão instalados na sua máquina:
A maneira mais fácil de iniciar o servidor Dify é executar nosso arquivo [docker-compose.yml](../../docker/docker-compose.yaml). Antes de rodar o comando de instalação, certifique-se de que o [Docker](https://docs.docker.com/get-docker/) e o [Docker Compose](https://docs.docker.com/compose/install/) estão instalados na sua máquina:
```bash
cd docker
@ -122,7 +124,7 @@ Após a execução, você pode acessar o painel do Dify no navegador em [http://
## Próximos passos
Se precisar personalizar a configuração, consulte os comentários no nosso arquivo [.env.example](docker/.env.example) e atualize os valores correspondentes no seu arquivo `.env`. Além disso, talvez seja necessário fazer ajustes no próprio arquivo `docker-compose.yaml`, como alterar versões de imagem, mapeamentos de portas ou montagens de volumes, com base no seu ambiente de implantação específico e nas suas necessidades. Após fazer quaisquer alterações, execute novamente `docker-compose up -d`. Você pode encontrar a lista completa de variáveis de ambiente disponíveis [aqui](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Se precisar personalizar a configuração, consulte os comentários no nosso arquivo [.env.example](../../docker/.env.example) e atualize os valores correspondentes no seu arquivo `.env`. Além disso, talvez seja necessário fazer ajustes no próprio arquivo `docker-compose.yaml`, como alterar versões de imagem, mapeamentos de portas ou montagens de volumes, com base no seu ambiente de implantação específico e nas suas necessidades. Após fazer quaisquer alterações, execute novamente `docker-compose up -d`. Você pode encontrar a lista completa de variáveis de ambiente disponíveis [aqui](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Se deseja configurar uma instalação de alta disponibilidade, há [Helm Charts](https://helm.sh/) e arquivos YAML contribuídos pela comunidade que permitem a implantação do Dify no Kubernetes.
@ -168,7 +170,7 @@ Implante o Dify no AKS com um clique usando [Azure Devops Pipeline Helm Chart by
## Contribuindo
Para aqueles que desejam contribuir com código, veja nosso [Guia de Contribuição](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_PT.md).
Para aqueles que desejam contribuir com código, veja nosso [Guia de Contribuição](./CONTRIBUTING.md).
Ao mesmo tempo, considere apoiar o Dify compartilhando-o nas redes sociais e em eventos e conferências.
> Estamos buscando contribuidores para ajudar na tradução do Dify para idiomas além de Mandarim e Inglês. Se você tiver interesse em ajudar, consulte o [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para mais informações e deixe-nos um comentário no canal `global-users` em nosso [Servidor da Comunidade no Discord](https://discord.gg/8Tpq4AcN9c).
@ -182,7 +184,7 @@ Ao mesmo tempo, considere apoiar o Dify compartilhando-o nas redes sociais e em
## Comunidade e contato
- [Discussões no GitHub](https://github.com/langgenius/dify/discussions). Melhor para: compartilhar feedback e fazer perguntas.
- [Problemas no GitHub](https://github.com/langgenius/dify/issues). Melhor para: relatar bugs encontrados no Dify.AI e propor novos recursos. Veja nosso [Guia de Contribuição](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
- [Problemas no GitHub](https://github.com/langgenius/dify/issues). Melhor para: relatar bugs encontrados no Dify.AI e propor novos recursos. Veja nosso [Guia de Contribuição](./CONTRIBUTING.md).
- [Discord](https://discord.gg/FngNHpbcY7). Melhor para: compartilhar suas aplicações e interagir com a comunidade.
- [X(Twitter)](https://twitter.com/dify_ai). Melhor para: compartilhar suas aplicações e interagir com a comunidade.
@ -196,4 +198,4 @@ Para proteger sua privacidade, evite postar problemas de segurança no GitHub. E
## Licença
Este repositório está disponível sob a [Licença de Código Aberto Dify](../LICENSE), que é essencialmente Apache 2.0 com algumas restrições adicionais.
Este repositório está disponível sob a [Licença de Código Aberto Dify](../../LICENSE), que é essencialmente Apache 2.0 com algumas restrições adicionais.

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast</a>
@ -36,18 +36,20 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_SI.md"><img alt="README Slovenščina" src="https://img.shields.io/badge/Sloven%C5%A1%C4%8Dina-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../sl-SI/README.md"><img alt="README Slovenščina" src="https://img.shields.io/badge/Sloven%C5%A1%C4%8Dina-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje.
@ -169,7 +171,7 @@ Z enim klikom namestite Dify v AKS z uporabo [Azure Devops Pipeline Helm Chart b
## Prispevam
Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
Za tiste, ki bi radi prispevali kodo, si oglejte naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord .
@ -196,4 +198,4 @@ Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj
## Licenca
To skladišče je na voljo pod [odprtokodno licenco Dify](../LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami.
To skladišče je na voljo pod [odprtokodno licenco Dify](../../LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami.

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
#
@ -108,7 +110,7 @@ Star Dify on GitHub and be instantly notified of new releases.
</br>
The easiest way to start the Dify server is to run our [docker-compose.yml](docker/docker-compose.yaml) file. Before running the installation command, make sure that [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) are installed on your machine:
The easiest way to start the Dify server is to run our [docker-compose.yml](../../docker/docker-compose.yaml) file. Before running the installation command, make sure that [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) are installed on your machine:
```bash
cd docker
@ -122,7 +124,7 @@ After running, you can access the Dify dashboard in your browser at [http://loca
## Next steps
If you need to customize the configuration, please refer to the comments in our [.env.example](docker/.env.example) file and update the corresponding values in your `.env` file. Additionally, you might need to make adjustments to the `docker-compose.yaml` file itself, such as changing image versions, port mappings, or volume mounts, based on your specific deployment environment and requirements. After making any changes, please re-run `docker-compose up -d`. You can find the full list of available environment variables [here](https://docs.dify.ai/getting-started/install-self-hosted/environments).
If you need to customize the configuration, please refer to the comments in our [.env.example](../../docker/.env.example) file and update the corresponding values in your `.env` file. Additionally, you might need to make adjustments to the `docker-compose.yaml` file itself, such as changing image versions, port mappings, or volume mounts, based on your specific deployment environment and requirements. After making any changes, please re-run `docker-compose up -d`. You can find the full list of available environment variables [here](https://docs.dify.ai/getting-started/install-self-hosted/environments).
If you'd like to configure a highly-available setup, there are community-contributed [Helm Charts](https://helm.sh/) and YAML files which allow Dify to be deployed on Kubernetes.
@ -181,10 +183,7 @@ At the same time, please consider supporting Dify by sharing it on social media
## Community & Contact
- \[GitHub Discussion\](https://github.com/langgenius/dify/discussions
). Best for: sharing feedback and asking questions.
- [GitHub Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
- [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
- [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
- [X(Twitter)](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
@ -199,4 +198,4 @@ To protect your privacy, please avoid posting security issues on GitHub. Instead
## License
This repository is available under the [Dify Open Source License](../LICENSE), which is essentially Apache 2.0 with a few additional restrictions.
This repository is available under the [Dify Open Source License](../../LICENSE), which is essentially Apache 2.0 with a few additional restrictions.

View File

@ -6,7 +6,7 @@ Bulunduğumuz noktada çevik olmamız ve hızlı hareket etmemiz gerekiyor, anca
Bu rehber, Dify'ın kendisi gibi, sürekli gelişen bir çalışmadır. Bazen gerçek projenin gerisinde kalırsa anlayışınız için çok minnettarız ve gelişmemize yardımcı olacak her türlü geri bildirimi memnuniyetle karşılıyoruz.
Lisanslama konusunda, lütfen kısa [Lisans ve Katkıda Bulunan Anlaşmamızı](../LICENSE) okumak için bir dakikanızı ayırın. Topluluk ayrıca [davranış kurallarına](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md) da uyar.
Lisanslama konusunda, lütfen kısa [Lisans ve Katkıda Bulunan Anlaşmamızı](../../LICENSE) okumak için bir dakikanızı ayırın. Topluluk ayrıca [davranış kurallarına](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md) da uyar.
## Başlamadan Önce

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Bulut</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify, açık kaynaklı bir LLM uygulama geliştirme platformudur. Sezgisel arayüzü, AI iş akışı, RAG pipeline'ı, ajan yetenekleri, model yönetimi, gözlemlenebilirlik özellikleri ve daha fazlasını birleştirerek, prototipten üretime hızlıca geçmenizi sağlar. İşte temel özelliklerin bir listesi:
@ -102,7 +104,7 @@ GitHub'da Dify'a yıldız verin ve yeni sürümlerden anında haberdar olun.
> - RAM >= 4GB
</br>
Dify sunucusunu başlatmanın en kolay yolu, [docker-compose.yml](docker/docker-compose.yaml) dosyamızı çalıştırmaktır. Kurulum komutunu çalıştırmadan önce, makinenizde [Docker](https://docs.docker.com/get-docker/) ve [Docker Compose](https://docs.docker.com/compose/install/)'un kurulu olduğundan emin olun:
Dify sunucusunu başlatmanın en kolay yolu, [docker-compose.yml](../../docker/docker-compose.yaml) dosyamızı çalıştırmaktır. Kurulum komutunu çalıştırmadan önce, makinenizde [Docker](https://docs.docker.com/get-docker/) ve [Docker Compose](https://docs.docker.com/compose/install/)'un kurulu olduğundan emin olun:
```bash
cd docker
@ -116,7 +118,7 @@ docker compose up -d
## Sonraki adımlar
Yapılandırmayı özelleştirmeniz gerekiyorsa, lütfen [.env.example](docker/.env.example) dosyamızdaki yorumlara bakın ve `.env` dosyanızdaki ilgili değerleri güncelleyin. Ayrıca, spesifik dağıtım ortamınıza ve gereksinimlerinize bağlı olarak `docker-compose.yaml` dosyasının kendisinde de, imaj sürümlerini, port eşlemelerini veya hacim bağlantılarını değiştirmek gibi ayarlamalar yapmanız gerekebilir. Herhangi bir değişiklik yaptıktan sonra, lütfen `docker-compose up -d` komutunu tekrar çalıştırın. Kullanılabilir tüm ortam değişkenlerinin tam listesini [burada](https://docs.dify.ai/getting-started/install-self-hosted/environments) bulabilirsiniz.
Yapılandırmayı özelleştirmeniz gerekiyorsa, lütfen [.env.example](../../docker/.env.example) dosyamızdaki yorumlara bakın ve `.env` dosyanızdaki ilgili değerleri güncelleyin. Ayrıca, spesifik dağıtım ortamınıza ve gereksinimlerinize bağlı olarak `docker-compose.yaml` dosyasının kendisinde de, imaj sürümlerini, port eşlemelerini veya hacim bağlantılarını değiştirmek gibi ayarlamalar yapmanız gerekebilir. Herhangi bir değişiklik yaptıktan sonra, lütfen `docker-compose up -d` komutunu tekrar çalıştırın. Kullanılabilir tüm ortam değişkenlerinin tam listesini [burada](https://docs.dify.ai/getting-started/install-self-hosted/environments) bulabilirsiniz.
Yüksek kullanılabilirliğe sahip bir kurulum yapılandırmak isterseniz, Dify'ın Kubernetes üzerine dağıtılmasına olanak tanıyan topluluk katkılı [Helm Charts](https://helm.sh/) ve YAML dosyaları mevcuttur.
@ -161,7 +163,7 @@ Dify'ı bulut platformuna tek tıklamayla dağıtın [terraform](https://www.ter
## Katkıda Bulunma
Kod katkısında bulunmak isteyenler için [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_TR.md) bakabilirsiniz.
Kod katkısında bulunmak isteyenler için [Katkı Kılavuzumuza](./CONTRIBUTING.md) bakabilirsiniz.
Aynı zamanda, lütfen Dify'ı sosyal medyada, etkinliklerde ve konferanslarda paylaşarak desteklemeyi düşünün.
> Dify'ı Mandarin veya İngilizce dışındaki dillere çevirmemize yardımcı olacak katkıda bulunanlara ihtiyacımız var. Yardımcı olmakla ilgileniyorsanız, lütfen daha fazla bilgi için [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) dosyasına bakın ve [Discord Topluluk Sunucumuzdaki](https://discord.gg/8Tpq4AcN9c) `global-users` kanalında bize bir yorum bırakın.
@ -175,7 +177,7 @@ Aynı zamanda, lütfen Dify'ı sosyal medyada, etkinliklerde ve konferanslarda p
## Topluluk & iletişim
- [GitHub Tartışmaları](https://github.com/langgenius/dify/discussions). En uygun: geri bildirim paylaşmak ve soru sormak için.
- [GitHub Sorunları](https://github.com/langgenius/dify/issues). En uygun: Dify.AI kullanırken karşılaştığınız hatalar ve özellik önerileri için. [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakın.
- [GitHub Sorunları](https://github.com/langgenius/dify/issues). En uygun: Dify.AI kullanırken karşılaştığınız hatalar ve özellik önerileri için. [Katkı Kılavuzumuza](./CONTRIBUTING.md) bakın.
- [Discord](https://discord.gg/FngNHpbcY7). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
- [X(Twitter)](https://twitter.com/dify_ai). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
@ -189,4 +191,4 @@ Gizliliğinizi korumak için, lütfen güvenlik sorunlarını GitHub'da paylaşm
## Lisans
Bu depo, temel olarak Apache 2.0 lisansı ve birkaç ek kısıtlama içeren [Dify Açık Kaynak Lisansı](../LICENSE) altında kullanıma sunulmuştur.
Bu depo, temel olarak Apache 2.0 lisansı ve birkaç ek kısıtlama içeren [Dify Açık Kaynak Lisansı](../../LICENSE) altında kullanıma sunulmuştur.

View File

@ -6,7 +6,7 @@ Chúng tôi cần phải nhanh nhẹn và triển khai nhanh chóng, nhưng cũn
Hướng dẫn này, giống như Dify, đang được phát triển liên tục. Chúng tôi rất cảm kích sự thông cảm của bạn nếu đôi khi nó chưa theo kịp dự án thực tế, và hoan nghênh mọi phản hồi để cải thiện.
Về giấy phép, vui lòng dành chút thời gian đọc [Thỏa thuận Cấp phép và Người đóng góp](../LICENSE) ngắn gọn của chúng tôi. Cộng đồng cũng tuân theo [quy tắc ứng xử](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
Về giấy phép, vui lòng dành chút thời gian đọc [Thỏa thuận Cấp phép và Người đóng góp](../../LICENSE) ngắn gọn của chúng tôi. Cộng đồng cũng tuân theo [quy tắc ứng xử](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md).
## Trước khi bắt đầu

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
@ -35,17 +35,19 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</p>
Dify là một nền tảng phát triển ứng dụng LLM mã nguồn mở. Giao diện trực quan kết hợp quy trình làm việc AI, mô hình RAG, khả năng tác nhân, quản lý mô hình, tính năng quan sát và hơn thế nữa, cho phép bạn nhanh chóng chuyển từ nguyên mẫu sang sản phẩm. Đây là danh sách các tính năng cốt lõi:
@ -103,7 +105,7 @@ Yêu thích Dify trên GitHub và được thông báo ngay lập tức về cá
</br>
Cách dễ nhất để khởi động máy chủ Dify là chạy tệp [docker-compose.yml](docker/docker-compose.yaml) của chúng tôi. Trước khi chạy lệnh cài đặt, hãy đảm bảo rằng [Docker](https://docs.docker.com/get-docker/) và [Docker Compose](https://docs.docker.com/compose/install/) đã được cài đặt trên máy của bạn:
Cách dễ nhất để khởi động máy chủ Dify là chạy tệp [docker-compose.yml](../../docker/docker-compose.yaml) của chúng tôi. Trước khi chạy lệnh cài đặt, hãy đảm bảo rằng [Docker](https://docs.docker.com/get-docker/) và [Docker Compose](https://docs.docker.com/compose/install/) đã được cài đặt trên máy của bạn:
```bash
cd docker
@ -117,7 +119,7 @@ Sau khi chạy, bạn có thể truy cập bảng điều khiển Dify trong tr
## Các bước tiếp theo
Nếu bạn cần tùy chỉnh cấu hình, vui lòng tham khảo các nhận xét trong tệp [.env.example](docker/.env.example) của chúng tôi và cập nhật các giá trị tương ứng trong tệp `.env` của bạn. Ngoài ra, bạn có thể cần điều chỉnh tệp `docker-compose.yaml`, chẳng hạn như thay đổi phiên bản hình ảnh, ánh xạ cổng hoặc gắn kết khối lượng, dựa trên môi trường triển khai cụ thể và yêu cầu của bạn. Sau khi thực hiện bất kỳ thay đổi nào, vui lòng chạy lại `docker-compose up -d`. Bạn có thể tìm thấy danh sách đầy đủ các biến môi trường có sẵn [tại đây](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Nếu bạn cần tùy chỉnh cấu hình, vui lòng tham khảo các nhận xét trong tệp [.env.example](../../docker/.env.example) của chúng tôi và cập nhật các giá trị tương ứng trong tệp `.env` của bạn. Ngoài ra, bạn có thể cần điều chỉnh tệp `docker-compose.yaml`, chẳng hạn như thay đổi phiên bản hình ảnh, ánh xạ cổng hoặc gắn kết khối lượng, dựa trên môi trường triển khai cụ thể và yêu cầu của bạn. Sau khi thực hiện bất kỳ thay đổi nào, vui lòng chạy lại `docker-compose up -d`. Bạn có thể tìm thấy danh sách đầy đủ các biến môi trường có sẵn [tại đây](https://docs.dify.ai/getting-started/install-self-hosted/environments).
Nếu bạn muốn cấu hình một cài đặt có độ sẵn sàng cao, có các [Helm Charts](https://helm.sh/) và tệp YAML do cộng đồng đóng góp cho phép Dify được triển khai trên Kubernetes.
@ -162,7 +164,7 @@ Triển khai Dify lên AKS chỉ với một cú nhấp chuột bằng [Azure De
## Đóng góp
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_VI.md) của chúng tôi.
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](./CONTRIBUTING.md) của chúng tôi.
Đồng thời, vui lòng xem xét hỗ trợ Dify bằng cách chia sẻ nó trên mạng xã hội và tại các sự kiện và hội nghị.
> Chúng tôi đang tìm kiếm người đóng góp để giúp dịch Dify sang các ngôn ngữ khác ngoài tiếng Trung hoặc tiếng Anh. Nếu bạn quan tâm đến việc giúp đỡ, vui lòng xem [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) để biết thêm thông tin và để lại bình luận cho chúng tôi trong kênh `global-users` của [Máy chủ Cộng đồng Discord](https://discord.gg/8Tpq4AcN9c) của chúng tôi.
@ -176,7 +178,7 @@ Triển khai Dify lên AKS chỉ với một cú nhấp chuột bằng [Azure De
## Cộng đồng & liên hệ
- [Thảo luận GitHub](https://github.com/langgenius/dify/discussions). Tốt nhất cho: chia sẻ phản hồi và đặt câu hỏi.
- [Vấn đề GitHub](https://github.com/langgenius/dify/issues). Tốt nhất cho: lỗi bạn gặp phải khi sử dụng Dify.AI và đề xuất tính năng. Xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
- [Vấn đề GitHub](https://github.com/langgenius/dify/issues). Tốt nhất cho: lỗi bạn gặp phải khi sử dụng Dify.AI và đề xuất tính năng. Xem [Hướng dẫn Đóng góp](./CONTRIBUTING.md) của chúng tôi.
- [Discord](https://discord.gg/FngNHpbcY7). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
- [X(Twitter)](https://twitter.com/dify_ai). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
@ -190,4 +192,4 @@ Triển khai Dify lên AKS chỉ với một cú nhấp chuột bằng [Azure De
## Giấy phép
Kho lưu trữ này có sẵn theo [Giấy phép Mã nguồn Mở Dify](../LICENSE), về cơ bản là Apache 2.0 với một vài hạn chế bổ sung.
Kho lưu trữ này có sẵn theo [Giấy phép Mã nguồn Mở Dify](../../LICENSE), về cơ bản là Apache 2.0 với một vài hạn chế bổ sung.

View File

@ -6,7 +6,7 @@
本指南和 Dify 一样在不断完善中。如果有任何滞后于项目实际情况的地方,恳请谅解,我们也欢迎任何改进建议。
关于许可证,请花一分钟阅读我们简短的[许可和贡献者协议](../LICENSE)。同时也请遵循社区[行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
关于许可证,请花一分钟阅读我们简短的[许可和贡献者协议](../../LICENSE)。同时也请遵循社区[行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
## 开始之前

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<div align="center">
<a href="https://cloud.dify.ai">Dify 云服务</a> ·
@ -35,17 +35,19 @@
</p>
<div align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../bn-BD/README.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
</div>
#
@ -111,7 +113,7 @@ Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI
### 快速启动
启动 Dify 服务器的最简单方法是运行我们的 [docker-compose.yml](docker/docker-compose.yaml) 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)
启动 Dify 服务器的最简单方法是运行我们的 [docker-compose.yml](../../docker/docker-compose.yaml) 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)
```bash
cd docker
@ -123,7 +125,7 @@ docker compose up -d
### 自定义配置
如果您需要自定义配置,请参考 [.env.example](docker/.env.example) 文件中的注释,并更新 `.env` 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 `docker-compose.yaml` 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 `docker-compose up -d`。您可以在[此处](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用环境变量的完整列表。
如果您需要自定义配置,请参考 [.env.example](../../docker/.env.example) 文件中的注释,并更新 `.env` 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 `docker-compose.yaml` 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 `docker-compose up -d`。您可以在[此处](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用环境变量的完整列表。
#### 使用 Helm Chart 或 Kubernetes 资源清单YAML部署
@ -180,7 +182,7 @@ docker compose up -d
## Contributing
对于那些想要贡献代码的人,请参阅我们的[贡献指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_CN.md)。
对于那些想要贡献代码的人,请参阅我们的[贡献指南](./CONTRIBUTING.md)。
同时,请考虑通过社交媒体、活动和会议来支持 Dify 的分享。
> 我们正在寻找贡献者来帮助将 Dify 翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)获取更多信息,并在我们的[Discord 社区服务器](https://discord.gg/8Tpq4AcN9c)的`global-users`频道中留言。
@ -196,7 +198,7 @@ docker compose up -d
我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括提交代码、问题、新想法或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。
- [GitHub Discussion](https://github.com/langgenius/dify/discussions). 👉:分享您的应用程序并与社区交流。
- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](../CONTRIBUTING.md)。
- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](./CONTRIBUTING.md)。
- [电子邮件支持](mailto:hello@dify.ai?subject=%5BGitHub%5DQuestions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。
- [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。
- [X(Twitter)](https://twitter.com/dify_ai)。👉:分享您的应用程序并与社区交流。
@ -208,4 +210,4 @@ docker compose up -d
## License
本仓库遵循 [Dify Open Source License](../LICENSE) 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。
本仓库遵循 [Dify Open Source License](../../LICENSE) 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。

View File

@ -6,7 +6,7 @@
這份指南與 Dify 一樣,都在持續完善中。如果指南內容有落後於實際專案的情況,還請見諒,也歡迎提供改進建議。
關於授權部分,請花點時間閱讀我們簡短的[授權和貢獻者協議](../LICENSE)。社群也需遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
關於授權部分,請花點時間閱讀我們簡短的[授權和貢獻者協議](../../LICENSE)。社群也需遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
## 開始之前

View File

@ -1,4 +1,4 @@
![cover-v5-optimized](../images/GitHub_README_if.png)
![cover-v5-optimized](../../images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">介紹 Dify 工作流程檔案上傳功能:重現 Google NotebookLM Podcast</a>
@ -39,18 +39,18 @@
</p>
<p align="center">
<a href="../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="./README_TW.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="./README_DE.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
<a href="../../README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
<a href="../zh-TW/README.md"><img alt="繁體中文文件" src="https://img.shields.io/badge/繁體中文-d9d9d9"></a>
<a href="../zh-CN/README.md"><img alt="简体中文文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
<a href="../ja-JP/README.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
<a href="../es-ES/README.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
<a href="../fr-FR/README.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
<a href="../tlh/README.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
<a href="../ko-KR/README.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
<a href="../ar-SA/README.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
<a href="../tr-TR/README.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
<a href="../vi-VN/README.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
<a href="../de-DE/README.md"><img alt="README in Deutsch" src="https://img.shields.io/badge/German-d9d9d9"></a>
</p>
Dify 是一個開源的 LLM 應用程式開發平台。其直觀的界面結合了智能代理工作流程、RAG 管道、代理功能、模型管理、可觀察性功能等,讓您能夠快速從原型進展到生產環境。
@ -64,7 +64,7 @@ Dify 是一個開源的 LLM 應用程式開發平台。其直觀的界面結合
</br>
啟動 Dify 伺服器最簡單的方式是透過 [docker compose](docker/docker-compose.yaml)。在使用以下命令運行 Dify 之前,請確保您的機器已安裝 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)
啟動 Dify 伺服器最簡單的方式是透過 [docker compose](../../docker/docker-compose.yaml)。在使用以下命令運行 Dify 之前,請確保您的機器已安裝 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)
```bash
cd dify
@ -128,7 +128,7 @@ Dify 的所有功能都提供相應的 API因此您可以輕鬆地將 Dify
## 進階設定
如果您需要自定義配置,請參考我們的 [.env.example](docker/.env.example) 文件中的註釋,並在您的 `.env` 文件中更新相應的值。此外,根據您特定的部署環境和需求,您可能需要調整 `docker-compose.yaml` 文件本身,例如更改映像版本、端口映射或卷掛載。進行任何更改後,請重新運行 `docker-compose up -d`。您可以在[這裡](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用環境變數的完整列表。
如果您需要自定義配置,請參考我們的 [.env.example](../../docker/.env.example) 文件中的註釋,並在您的 `.env` 文件中更新相應的值。此外,根據您特定的部署環境和需求,您可能需要調整 `docker-compose.yaml` 文件本身,例如更改映像版本、端口映射或卷掛載。進行任何更改後,請重新運行 `docker-compose up -d`。您可以在[這裡](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用環境變數的完整列表。
如果您想配置高可用性設置,社區貢獻的 [Helm Charts](https://helm.sh/) 和 Kubernetes 資源清單YAML允許在 Kubernetes 上部署 Dify。
@ -173,7 +173,7 @@ Dify 的所有功能都提供相應的 API因此您可以輕鬆地將 Dify
## 貢獻
對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_TW.md)。
對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](./CONTRIBUTING.md)。
同時,也請考慮透過在社群媒體和各種活動與會議上分享 Dify 來支持我們。
> 我們正在尋找貢獻者協助將 Dify 翻譯成中文和英文以外的語言。如果您有興趣幫忙,請查看 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) 獲取更多資訊,並在我們的 [Discord 社群伺服器](https://discord.gg/8Tpq4AcN9c) 的 `global-users` 頻道留言給我們。
@ -181,7 +181,7 @@ Dify 的所有功能都提供相應的 API因此您可以輕鬆地將 Dify
## 社群與聯絡方式
- [GitHub Discussion](https://github.com/langgenius/dify/discussions):最適合分享反饋和提問。
- [GitHub Issues](https://github.com/langgenius/dify/issues):最適合報告使用 Dify.AI 時遇到的問題和提出功能建議。請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。
- [GitHub Issues](https://github.com/langgenius/dify/issues):最適合報告使用 Dify.AI 時遇到的問題和提出功能建議。請參閱我們的[貢獻指南](./CONTRIBUTING.md)。
- [Discord](https://discord.gg/FngNHpbcY7):最適合分享您的應用程式並與社群互動。
- [X(Twitter)](https://twitter.com/dify_ai):最適合分享您的應用程式並與社群互動。
@ -201,4 +201,4 @@ Dify 的所有功能都提供相應的 API因此您可以輕鬆地將 Dify
## 授權條款
本代碼庫採用 [Dify 開源授權](../LICENSE),這基本上是 Apache 2.0 授權加上一些額外限制條款。
本代碼庫採用 [Dify 開源授權](../../LICENSE),這基本上是 Apache 2.0 授權加上一些額外限制條款。

View File

@ -16,7 +16,7 @@ jest.mock('cmdk', () => ({
Item: ({ children, onSelect, value, className }: any) => (
<div
className={className}
onClick={() => onSelect && onSelect()}
onClick={() => onSelect?.()}
data-value={value}
data-testid={`command-item-${value}`}
>

View File

@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from 'react'
import type { PopupProps } from './config-popup'
import ConfigPopup from './config-popup'
import cn from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@ -45,7 +46,7 @@ const ConfigBtn: FC<Props> = ({
offset={12}
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<div className="select-none">
<div className={cn('select-none', className)}>
{children}
</div>
</PortalToFollowElemTrigger>

View File

@ -28,7 +28,8 @@ const CSVUploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -37,7 +38,8 @@ const CSVUploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -348,7 +348,8 @@ const AppPublisher = ({
<SuggestedAction
className='flex-1'
onClick={() => {
publishedAt && handleOpenInExplore()
if (publishedAt)
handleOpenInExplore()
}}
disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)}
icon={<RiPlanetLine className='h-4 w-4' />}

View File

@ -40,7 +40,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
return
}
else {
titleError && setTitleError(false)
if (titleError)
setTitleError(false)
}
if (releaseNotes.length > RELEASE_NOTES_MAX_LENGTH) {
@ -52,7 +53,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
return
}
else {
releaseNotesError && setReleaseNotesError(false)
if (releaseNotesError)
setReleaseNotesError(false)
}
onPublish({ title, releaseNotes, id: versionInfo?.id })

View File

@ -0,0 +1,29 @@
import type { SVGProps } from 'react'
const CitationIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...props}
>
<path
d="M7 6h10M7 12h6M7 18h10"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 6c0-1.105.895-2 2-2h10c1.105 0 2 .895 2 2v12c0 1.105-.895 2-2 2H9l-4 3v-3H7"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</svg>
)
export default CitationIcon

View File

@ -32,6 +32,19 @@ import { TransferMethod } from '@/types/app'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
const TEXT_MAX_LENGTH = 256
const CHECKBOX_DEFAULT_TRUE_VALUE = 'true'
const CHECKBOX_DEFAULT_FALSE_VALUE = 'false'
const getCheckboxDefaultSelectValue = (value: InputVar['default']) => {
if (typeof value === 'boolean')
return value ? CHECKBOX_DEFAULT_TRUE_VALUE : CHECKBOX_DEFAULT_FALSE_VALUE
if (typeof value === 'string')
return value.toLowerCase() === CHECKBOX_DEFAULT_TRUE_VALUE ? CHECKBOX_DEFAULT_TRUE_VALUE : CHECKBOX_DEFAULT_FALSE_VALUE
return CHECKBOX_DEFAULT_FALSE_VALUE
}
const parseCheckboxSelectValue = (value: string) =>
value === CHECKBOX_DEFAULT_TRUE_VALUE
export type IConfigModalProps = {
isCreate?: boolean
@ -66,7 +79,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
try {
return JSON.stringify(JSON.parse(tempPayload.json_schema).properties, null, 2)
}
catch (_e) {
catch {
return ''
}
}, [tempPayload.json_schema])
@ -110,7 +123,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
}
handlePayloadChange('json_schema')(JSON.stringify(res, null, 2))
}
catch (_e) {
catch {
return null
}
}, [handlePayloadChange])
@ -198,6 +211,8 @@ const ConfigModal: FC<IConfigModalProps> = ({
handlePayloadChange('variable')(e.target.value)
}, [handlePayloadChange, t])
const checkboxDefaultSelectValue = useMemo(() => getCheckboxDefaultSelectValue(tempPayload.default), [tempPayload.default])
const handleConfirm = () => {
const moreInfo = tempPayload.variable === payload?.variable
? undefined
@ -324,6 +339,23 @@ const ConfigModal: FC<IConfigModalProps> = ({
</Field>
)}
{type === InputVarType.checkbox && (
<Field title={t('appDebug.variableConfig.defaultValue')}>
<SimpleSelect
className="w-full"
optionWrapClassName="max-h-[140px] overflow-y-auto"
items={[
{ value: CHECKBOX_DEFAULT_TRUE_VALUE, name: t('appDebug.variableConfig.startChecked') },
{ value: CHECKBOX_DEFAULT_FALSE_VALUE, name: t('appDebug.variableConfig.noDefaultSelected') },
]}
defaultValue={checkboxDefaultSelectValue}
onSelect={item => handlePayloadChange('default')(parseCheckboxSelectValue(String(item.value)))}
placeholder={t('appDebug.variableConfig.selectDefaultValue')}
allowSearch={false}
/>
</Field>
)}
{type === InputVarType.select && (
<>
<Field title={t('appDebug.variableConfig.options')}>

View File

@ -480,7 +480,7 @@ const Configuration: FC = () => {
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` })
setCompletionParams(filtered)
}
catch (e) {
catch {
Toast.notify({ type: 'error', message: t('common.error') })
setCompletionParams({})
}

View File

@ -192,7 +192,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<Button
variant="primary"
disabled={canNotRun}
onClick={() => onSend && onSend()}
onClick={() => onSend?.()}
className="w-[96px]">
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('appDebug.inputs.run')}
@ -203,7 +203,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<Button
variant="primary"
disabled={canNotRun}
onClick={() => onSend && onSend()}
onClick={() => onSend?.()}
className="w-[96px]">
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('appDebug.inputs.run')}

View File

@ -38,7 +38,8 @@ const Uploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -47,7 +48,8 @@ const Uploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -107,7 +107,8 @@ const Chart: React.FC<IChartProps> = ({
const { t } = useTranslation()
const statistics = chartData.data
const statisticsLen = statistics.length
const extraDataForMarkLine = new Array(statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen).fill('1')
const markLineLength = statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen
const extraDataForMarkLine = Array.from({ length: markLineLength }, () => '1')
extraDataForMarkLine.push('')
extraDataForMarkLine.unshift('')

View File

@ -127,7 +127,7 @@ export default class AudioPlayer {
}
catch {
this.isLoadData = false
this.callback && this.callback('error')
this.callback?.('error')
}
}
@ -137,15 +137,14 @@ export default class AudioPlayer {
if (this.audioContext.state === 'suspended') {
this.audioContext.resume().then((_) => {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
if (this.callback)
this.callback('play')
this.callback?.('play')
}
else {
this.isLoadData = true
@ -189,24 +188,24 @@ export default class AudioPlayer {
if (this.audio.paused) {
this.audioContext.resume().then((_) => {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
else if (this.audio.played) { /* empty */ }
else {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
}
}
public pauseAudio() {
this.callback && this.callback('paused')
this.callback?.('paused')
this.audio.pause()
this.audioContext.suspend()
}

View File

@ -128,7 +128,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const localState = localStorage.getItem('webappSidebarCollapse')
return localState === 'collapsed'
}
catch (e) {
catch {
// localStorage may be disabled in private browsing mode or by security settings
// fallback to default value
return false
@ -142,7 +142,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
try {
localStorage.setItem('webappSidebarCollapse', state ? 'collapsed' : 'expanded')
}
catch (e) {
catch {
// localStorage may be disabled, continue without persisting state
}
}
@ -235,13 +235,15 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}
}
if(item.checkbox) {
if (item.checkbox) {
const preset = initInputs[item.checkbox.variable] === true
return {
...item.checkbox,
default: false,
default: preset || item.default || item.checkbox.default,
type: 'checkbox',
}
}
if (item.select) {
const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
return {

View File

@ -101,10 +101,14 @@ const Answer: FC<AnswerProps> = ({
}, [])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
if (direction === 'prev')
item.prevSibling && switchSibling?.(item.prevSibling)
else
item.nextSibling && switchSibling?.(item.nextSibling)
if (direction === 'prev') {
if (item.prevSibling)
switchSibling?.(item.prevSibling)
}
else {
if (item.nextSibling)
switchSibling?.(item.nextSibling)
}
}, [switchSibling, item.prevSibling, item.nextSibling])
return (

View File

@ -73,10 +73,14 @@ const Question: FC<QuestionProps> = ({
}, [content])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
if (direction === 'prev')
item.prevSibling && switchSibling?.(item.prevSibling)
else
item.nextSibling && switchSibling?.(item.nextSibling)
if (direction === 'prev') {
if (item.prevSibling)
switchSibling?.(item.prevSibling)
}
else {
if (item.nextSibling)
switchSibling?.(item.nextSibling)
}
}, [switchSibling, item.prevSibling, item.nextSibling])
const getContentWidth = () => {

View File

@ -195,13 +195,16 @@ export const useEmbeddedChatbot = () => {
type: 'number',
}
}
if (item.checkbox) {
const preset = initInputs[item.checkbox.variable] === true
return {
...item.checkbox,
default: false,
default: preset || item.default || item.checkbox.default,
type: 'checkbox',
}
}
if (item.select) {
const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
return {

View File

@ -124,7 +124,7 @@ export const parseDateWithFormat = (dateString: string, format?: string): Dayjs
}
// Format date output with localization support
export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, locale: string = 'en-US'): string => {
export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, _locale: string = 'en-US'): string => {
if (!date || !date.isValid()) return ''
if (includeTime) {

View File

@ -47,7 +47,10 @@ export default function Drawer({
<Dialog
unmount={unmount}
open={isOpen}
onClose={() => !clickOutsideNotOpen && onClose()}
onClose={() => {
if (!clickOutsideNotOpen)
onClose()
}}
className={cn('fixed inset-0 z-[30] overflow-y-auto', dialogClassName)}
>
<div className={cn('flex h-screen w-screen justify-end', positionCenter && '!justify-center')}>
@ -55,7 +58,8 @@ export default function Drawer({
<DialogBackdrop
className={cn('fixed inset-0 z-[40]', mask && 'bg-black/30', dialogBackdropClassName)}
onClick={() => {
!clickOutsideNotOpen && onClose()
if (!clickOutsideNotOpen)
onClose()
}}
/>
<div className={cn('relative z-[50] flex w-full max-w-sm flex-col justify-between overflow-hidden bg-components-panel-bg p-6 text-left align-middle shadow-xl', panelClassName)}>
@ -80,11 +84,11 @@ export default function Drawer({
<Button
className='mr-2'
onClick={() => {
onCancel && onCancel()
onCancel?.()
}}>{t('common.operation.cancel')}</Button>
<Button
onClick={() => {
onOk && onOk()
onOk?.()
}}>{t('common.operation.save')}</Button>
</div>)}
</div>

View File

@ -45,7 +45,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
<Divider className='mb-0 mt-3' />
<div className='flex w-full items-center justify-center gap-2 p-3'>
<Button className='w-full' onClick={() => {
onClose && onClose()
onClose?.()
}}>
{t('app.iconPicker.cancel')}
</Button>
@ -54,7 +54,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
variant="primary"
className='w-full'
onClick={() => {
onSelect && onSelect(selectedEmoji, selectedBackground!)
onSelect?.(selectedEmoji, selectedBackground!)
}}>
{t('app.iconPicker.ok')}
</Button>

View File

@ -33,7 +33,10 @@ const SelectField = ({
<PureSelect
value={field.state.value}
options={options}
onChange={value => field.handleChange(value)}
onChange={(value) => {
field.handleChange(value)
onChange?.(value)
}}
{...selectProps}
/>
</div>

View File

@ -62,7 +62,7 @@ const ImageList: FC<ImageListProps> = ({
{item.progress === -1 && (
<RefreshCcw01
className="h-5 w-5 text-white"
onClick={() => onReUpload && onReUpload(item._id)}
onClick={() => onReUpload?.(item._id)}
/>
)}
</div>
@ -122,7 +122,7 @@ const ImageList: FC<ImageListProps> = ({
'rounded-2xl shadow-lg hover:bg-state-base-hover',
item.progress === -1 ? 'flex' : 'hidden group-hover:flex',
)}
onClick={() => onRemove && onRemove(item._id)}
onClick={() => onRemove?.(item._id)}
>
<RiCloseLine className="h-3 w-3 text-text-tertiary" />
</button>

View File

@ -20,7 +20,7 @@ const isBase64 = (str: string): boolean => {
try {
return btoa(atob(str)) === str
}
catch (err) {
catch {
return false
}
}

View File

@ -8,12 +8,14 @@ import {
import ActionButton from '@/app/components/base/action-button'
import CopyIcon from '@/app/components/base/copy-icon'
import SVGBtn from '@/app/components/base/svg'
import Flowchart from '@/app/components/base/mermaid'
import { Theme } from '@/types/app'
import useTheme from '@/hooks/use-theme'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
import ErrorBoundary from '@/app/components/base/markdown/error-boundary'
import dynamic from 'next/dynamic'
const Flowchart = dynamic(() => import('@/app/components/base/mermaid'), { ssr: false })
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
@ -125,7 +127,7 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Store event handlers in useMemo to avoid recreating them
const echartsEvents = useMemo(() => ({
finished: (params: EChartsEventParams) => {
finished: (_params: EChartsEventParams) => {
// Limit finished event frequency to avoid infinite loops
finishedEventCountRef.current++
if (finishedEventCountRef.current > 3) {

Some files were not shown because too many files have changed in this diff Show More