Merge branch 'main' into feat/r2

feat/datasource
jyong 10 months ago
commit b277acc298

@ -0,0 +1,28 @@
name: Deploy RAG Dev
permissions:
contents: read
on:
workflow_run:
workflows: ["Build and Push API & Web"]
branches:
- "deploy/rag-dev"
types:
- completed
jobs:
deploy:
runs-on: ubuntu-latest
if: |
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'deploy/rag-dev'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{ secrets.RAG_SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
${{ vars.SSH_SCRIPT || secrets.SSH_SCRIPT }}

@ -10,6 +10,7 @@ yq eval '.services["elasticsearch"].ports += ["9200:9200"]' -i docker/docker-com
yq eval '.services.couchbase-server.ports += ["8091-8096:8091-8096"]' -i docker/docker-compose.yaml yq eval '.services.couchbase-server.ports += ["8091-8096:8091-8096"]' -i docker/docker-compose.yaml
yq eval '.services.couchbase-server.ports += ["11210:11210"]' -i docker/docker-compose.yaml yq eval '.services.couchbase-server.ports += ["11210:11210"]' -i docker/docker-compose.yaml
yq eval '.services.tidb.ports += ["4000:4000"]' -i docker/tidb/docker-compose.yaml yq eval '.services.tidb.ports += ["4000:4000"]' -i docker/tidb/docker-compose.yaml
yq eval '.services.oceanbase.ports += ["2881:2881"]' -i docker/docker-compose.yaml
yq eval '.services.opengauss.ports += ["6600:6600"]' -i docker/docker-compose.yaml yq eval '.services.opengauss.ports += ["6600:6600"]' -i docker/docker-compose.yaml
echo "Ports exposed for sandbox, weaviate, tidb, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase, opengauss" echo "Ports exposed for sandbox, weaviate, tidb, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase, opengauss"

@ -31,6 +31,13 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Free Disk Space
uses: endersonmenezes/free-disk-space@v2
with:
remove_dotnet: true
remove_haskell: true
remove_tool_cache: true
- name: Setup UV and Python - name: Setup UV and Python
uses: ./.github/actions/setup-uv uses: ./.github/actions/setup-uv
with: with:
@ -59,7 +66,7 @@ jobs:
tidb tidb
tiflash tiflash
- name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma, MyScale, ElasticSearch, Couchbase) - name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma, MyScale, ElasticSearch, Couchbase, OceanBase)
uses: hoverkraft-tech/compose-action@v2.0.2 uses: hoverkraft-tech/compose-action@v2.0.2
with: with:
compose-file: | compose-file: |
@ -75,9 +82,12 @@ jobs:
pgvector pgvector
chroma chroma
elasticsearch elasticsearch
oceanbase
- name: Check TiDB Ready - name: Check VDB Ready (TiDB, Oceanbase)
run: uv run --project api python api/tests/integration_tests/vdb/tidb_vector/check_tiflash_ready.py run: |
uv run --project api python api/tests/integration_tests/vdb/tidb_vector/check_tiflash_ready.py
uv run --project api python api/tests/integration_tests/vdb/oceanbase/check_oceanbase_ready.py
- name: Test Vector Stores - name: Test Vector Stores
run: uv run --project api bash dev/pytest/pytest_vdb.sh run: uv run --project api bash dev/pytest/pytest_vdb.sh

@ -27,7 +27,7 @@ from models.dataset import Dataset, DatasetCollectionBinding, DatasetMetadata, D
from models.dataset import Document as DatasetDocument from models.dataset import Document as DatasetDocument
from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation
from models.provider import Provider, ProviderModel from models.provider import Provider, ProviderModel
from services.account_service import RegisterService, TenantService from services.account_service import AccountService, RegisterService, TenantService
from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs
from services.plugin.data_migration import PluginDataMigration from services.plugin.data_migration import PluginDataMigration
from services.plugin.plugin_migration import PluginMigration from services.plugin.plugin_migration import PluginMigration
@ -68,6 +68,7 @@ def reset_password(email, new_password, password_confirm):
account.password = base64_password_hashed account.password = base64_password_hashed
account.password_salt = base64_salt account.password_salt = base64_salt
db.session.commit() db.session.commit()
AccountService.reset_login_error_rate_limit(email)
click.echo(click.style("Password reset successfully.", fg="green")) click.echo(click.style("Password reset successfully.", fg="green"))

@ -47,7 +47,13 @@ class AppInfoApi(Resource):
def get(self, app_model: App): def get(self, app_model: App):
"""Get app information""" """Get app information"""
tags = [tag.name for tag in app_model.tags] tags = [tag.name for tag in app_model.tags]
return {"name": app_model.name, "description": app_model.description, "tags": tags, "mode": app_model.mode} return {
"name": app_model.name,
"description": app_model.description,
"tags": tags,
"mode": app_model.mode,
"author_name": app_model.author_name,
}
api.add_resource(AppParameterApi, "/parameters") api.add_resource(AppParameterApi, "/parameters")

@ -1,3 +1,4 @@
import logging
import time import time
from collections.abc import Generator, Mapping, Sequence from collections.abc import Generator, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Optional, Union from typing import TYPE_CHECKING, Any, Optional, Union
@ -33,6 +34,8 @@ from models.model import App, AppMode, Message, MessageAnnotation
if TYPE_CHECKING: if TYPE_CHECKING:
from core.file.models import File from core.file.models import File
_logger = logging.getLogger(__name__)
class AppRunner: class AppRunner:
def get_pre_calculate_rest_tokens( def get_pre_calculate_rest_tokens(
@ -298,7 +301,7 @@ class AppRunner:
) )
def _handle_invoke_result_stream( def _handle_invoke_result_stream(
self, invoke_result: Generator, queue_manager: AppQueueManager, agent: bool self, invoke_result: Generator[LLMResultChunk, None, None], queue_manager: AppQueueManager, agent: bool
) -> None: ) -> None:
""" """
Handle invoke result Handle invoke result
@ -317,18 +320,28 @@ class AppRunner:
else: else:
queue_manager.publish(QueueAgentMessageEvent(chunk=result), PublishFrom.APPLICATION_MANAGER) queue_manager.publish(QueueAgentMessageEvent(chunk=result), PublishFrom.APPLICATION_MANAGER)
text += result.delta.message.content message = result.delta.message
if isinstance(message.content, str):
text += message.content
elif isinstance(message.content, list):
for content in message.content:
if not isinstance(content, str):
# TODO(QuantumGhost): Add multimodal output support for easy ui.
_logger.warning("received multimodal output, type=%s", type(content))
text += content.data
else:
text += content # failback to str
if not model: if not model:
model = result.model model = result.model
if not prompt_messages: if not prompt_messages:
prompt_messages = result.prompt_messages prompt_messages = list(result.prompt_messages)
if result.delta.usage: if result.delta.usage:
usage = result.delta.usage usage = result.delta.usage
if not usage: if usage is None:
usage = LLMUsage.empty_usage() usage = LLMUsage.empty_usage()
llm_result = LLMResult( llm_result = LLMResult(

@ -48,6 +48,7 @@ from core.model_manager import ModelInstance
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.model_runtime.entities.message_entities import ( from core.model_runtime.entities.message_entities import (
AssistantPromptMessage, AssistantPromptMessage,
TextPromptMessageContent,
) )
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.ops.entities.trace_entity import TraceTaskName from core.ops.entities.trace_entity import TraceTaskName
@ -309,6 +310,23 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
delta_text = chunk.delta.message.content delta_text = chunk.delta.message.content
if delta_text is None: if delta_text is None:
continue continue
if isinstance(chunk.delta.message.content, list):
delta_text = ""
for content in chunk.delta.message.content:
logger.debug(
"The content type %s in LLM chunk delta message content.: %r", type(content), content
)
if isinstance(content, TextPromptMessageContent):
delta_text += content.data
elif isinstance(content, str):
delta_text += content # failback to str
else:
logger.warning(
"Unsupported content type %s in LLM chunk delta message content.: %r",
type(content),
content,
)
continue
if not self._task_state.llm_result.prompt_messages: if not self._task_state.llm_result.prompt_messages:
self._task_state.llm_result.prompt_messages = chunk.prompt_messages self._task_state.llm_result.prompt_messages = chunk.prompt_messages

@ -80,6 +80,23 @@ class OceanBaseVector(BaseVector):
self.delete() self.delete()
vals = []
params = self._client.perform_raw_text_sql("SHOW PARAMETERS LIKE '%ob_vector_memory_limit_percentage%'")
for row in params:
val = int(row[6])
vals.append(val)
if len(vals) == 0:
raise ValueError("ob_vector_memory_limit_percentage not found in parameters.")
if any(val == 0 for val in vals):
try:
self._client.perform_raw_text_sql("ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30")
except Exception as e:
raise Exception(
"Failed to set ob_vector_memory_limit_percentage. "
+ "Maybe the database user has insufficient privilege.",
e,
)
cols = [ cols = [
Column("id", String(36), primary_key=True, autoincrement=False), Column("id", String(36), primary_key=True, autoincrement=False),
Column("vector", VECTOR(self._vec_dim)), Column("vector", VECTOR(self._vec_dim)),
@ -110,22 +127,6 @@ class OceanBaseVector(BaseVector):
+ "to support fulltext index and vector index in the same table", + "to support fulltext index and vector index in the same table",
e, e,
) )
vals = []
params = self._client.perform_raw_text_sql("SHOW PARAMETERS LIKE '%ob_vector_memory_limit_percentage%'")
for row in params:
val = int(row[6])
vals.append(val)
if len(vals) == 0:
raise ValueError("ob_vector_memory_limit_percentage not found in parameters.")
if any(val == 0 for val in vals):
try:
self._client.perform_raw_text_sql("ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30")
except Exception as e:
raise Exception(
"Failed to set ob_vector_memory_limit_percentage. "
+ "Maybe the database user has insufficient privilege.",
e,
)
redis_client.set(collection_exist_cache_key, 1, ex=3600) redis_client.set(collection_exist_cache_key, 1, ex=3600)
def _check_hybrid_search_support(self) -> bool: def _check_hybrid_search_support(self) -> bool:

@ -6,7 +6,7 @@ import json
import logging import logging
from typing import Optional, Union from typing import Optional, Union
from sqlalchemy import select from sqlalchemy import func, select
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -151,11 +151,11 @@ class SQLAlchemyWorkflowExecutionRepository(WorkflowExecutionRepository):
existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_)) existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_))
if not existing: if not existing:
# For new records, get the next sequence number # For new records, get the next sequence number
stmt = select(WorkflowRun.sequence_number).where( stmt = select(func.max(WorkflowRun.sequence_number)).where(
WorkflowRun.app_id == self._app_id, WorkflowRun.app_id == self._app_id,
WorkflowRun.tenant_id == self._tenant_id, WorkflowRun.tenant_id == self._tenant_id,
) )
max_sequence = session.scalar(stmt.order_by(WorkflowRun.sequence_number.desc())) max_sequence = session.scalar(stmt)
db_model.sequence_number = (max_sequence or 0) + 1 db_model.sequence_number = (max_sequence or 0) + 1
else: else:
# For updates, keep the existing sequence number # For updates, keep the existing sequence number

@ -6,7 +6,6 @@ from pydantic import BaseModel, Field
from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.entities.llm_entities import LLMUsage
from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.rag.entities.citation_metadata import RetrievalSourceMetadata
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
class RunCompletedEvent(BaseModel): class RunCompletedEvent(BaseModel):
@ -39,11 +38,3 @@ class RunRetryEvent(BaseModel):
error: str = Field(..., description="error") error: str = Field(..., description="error")
retry_index: int = Field(..., description="Retry attempt number") retry_index: int = Field(..., description="Retry attempt number")
start_at: datetime = Field(..., description="Retry start time") start_at: datetime = Field(..., description="Retry start time")
class SingleStepRetryEvent(NodeRunResult):
"""Single step retry event"""
status: WorkflowNodeExecutionStatus = WorkflowNodeExecutionStatus.RETRY
elapsed_time: float = Field(..., description="elapsed time")

@ -525,6 +525,8 @@ class LLMNode(BaseNode[LLMNodeData]):
# Set appropriate response format based on model capabilities # Set appropriate response format based on model capabilities
self._set_response_format(completion_params, model_schema.parameter_rules) self._set_response_format(completion_params, model_schema.parameter_rules)
model_config_with_cred.parameters = completion_params model_config_with_cred.parameters = completion_params
# NOTE(-LAN-): This line modify the `self.node_data.model`, which is used in `_invoke_llm()`.
node_data_model.completion_params = completion_params
return model, model_config_with_cred return model, model_config_with_cred
def _fetch_prompt_messages( def _fetch_prompt_messages(

@ -42,10 +42,6 @@ from core.workflow.constants import (
) )
class InvalidSelectorError(ValueError):
pass
class UnsupportedSegmentTypeError(Exception): class UnsupportedSegmentTypeError(Exception):
pass pass

@ -4,7 +4,6 @@ from . import (
app_model_config, app_model_config,
audio, audio,
base, base,
completion,
conversation, conversation,
dataset, dataset,
document, document,
@ -19,7 +18,6 @@ __all__ = [
"app_model_config", "app_model_config",
"audio", "audio",
"base", "base",
"completion",
"conversation", "conversation",
"dataset", "dataset",
"document", "document",

@ -55,7 +55,3 @@ class MemberNotInTenantError(BaseServiceError):
class RoleAlreadyAssignedError(BaseServiceError): class RoleAlreadyAssignedError(BaseServiceError):
pass pass
class RateLimitExceededError(BaseServiceError):
pass

@ -1,5 +0,0 @@
from services.errors.base import BaseServiceError
class CompletionStoppedError(BaseServiceError):
pass

@ -30,11 +30,11 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str]):
logging.info(click.style("Dataset not found: {}".format(dataset_id), fg="red")) logging.info(click.style("Dataset not found: {}".format(dataset_id), fg="red"))
db.session.close() db.session.close()
return return
tenant_id = dataset.tenant_id
for document_id in document_ids: for document_id in document_ids:
retry_indexing_cache_key = "document_{}_is_retried".format(document_id) retry_indexing_cache_key = "document_{}_is_retried".format(document_id)
# check document limit # check document limit
features = FeatureService.get_features(dataset.tenant_id) features = FeatureService.get_features(tenant_id)
try: try:
if features.billing.enabled: if features.billing.enabled:
vector_space = features.vector_space vector_space = features.vector_space

@ -0,0 +1,49 @@
import time
import pymysql
def check_oceanbase_ready() -> bool:
try:
connection = pymysql.connect(
host="localhost",
port=2881,
user="root",
password="difyai123456",
)
affected_rows = connection.query("SELECT 1")
return affected_rows == 1
except Exception as e:
print(f"Oceanbase is not ready. Exception: {e}")
return False
finally:
if connection:
connection.close()
def main():
max_attempts = 50
retry_interval_seconds = 2
is_oceanbase_ready = False
for attempt in range(max_attempts):
try:
is_oceanbase_ready = check_oceanbase_ready()
except Exception as e:
print(f"Oceanbase is not ready. Exception: {e}")
is_oceanbase_ready = False
if is_oceanbase_ready:
break
else:
print(f"Attempt {attempt + 1} failed, retry in {retry_interval_seconds} seconds...")
time.sleep(retry_interval_seconds)
if is_oceanbase_ready:
print("Oceanbase is ready.")
else:
print(f"Oceanbase is not ready after {max_attempts} attempting checks.")
exit(1)
if __name__ == "__main__":
main()

@ -1,15 +1,11 @@
from unittest.mock import MagicMock, patch
import pytest import pytest
from core.rag.datasource.vdb.oceanbase.oceanbase_vector import ( from core.rag.datasource.vdb.oceanbase.oceanbase_vector import (
OceanBaseVector, OceanBaseVector,
OceanBaseVectorConfig, OceanBaseVectorConfig,
) )
from tests.integration_tests.vdb.__mock.tcvectordb import setup_tcvectordb_mock
from tests.integration_tests.vdb.test_vector_store import ( from tests.integration_tests.vdb.test_vector_store import (
AbstractVectorTest, AbstractVectorTest,
get_example_text,
setup_mock_redis, setup_mock_redis,
) )
@ -20,10 +16,11 @@ def oceanbase_vector():
"dify_test_collection", "dify_test_collection",
config=OceanBaseVectorConfig( config=OceanBaseVectorConfig(
host="127.0.0.1", host="127.0.0.1",
port="2881", port=2881,
user="root@test", user="root",
database="test", database="test",
password="test", password="difyai123456",
enable_hybrid_search=True,
), ),
) )
@ -33,39 +30,13 @@ class OceanBaseVectorTest(AbstractVectorTest):
super().__init__() super().__init__()
self.vector = vector self.vector = vector
def search_by_vector(self):
hits_by_vector = self.vector.search_by_vector(query_vector=self.example_embedding)
assert len(hits_by_vector) == 0
def search_by_full_text(self):
hits_by_full_text = self.vector.search_by_full_text(query=get_example_text())
assert len(hits_by_full_text) == 0
def text_exists(self):
exist = self.vector.text_exists(self.example_doc_id)
assert exist == True
def get_ids_by_metadata_field(self): def get_ids_by_metadata_field(self):
ids = self.vector.get_ids_by_metadata_field(key="document_id", value=self.example_doc_id) ids = self.vector.get_ids_by_metadata_field(key="document_id", value=self.example_doc_id)
assert len(ids) == 0 assert len(ids) == 1
@pytest.fixture
def setup_mock_oceanbase_client():
with patch("core.rag.datasource.vdb.oceanbase.oceanbase_vector.ObVecClient", new_callable=MagicMock) as mock_client:
yield mock_client
@pytest.fixture
def setup_mock_oceanbase_vector(oceanbase_vector):
with patch.object(oceanbase_vector, "_client"):
yield oceanbase_vector
def test_oceanbase_vector( def test_oceanbase_vector(
setup_mock_redis, setup_mock_redis,
setup_mock_oceanbase_client,
setup_mock_oceanbase_vector,
oceanbase_vector, oceanbase_vector,
): ):
OceanBaseVectorTest(oceanbase_vector).run_all_tests() OceanBaseVectorTest(oceanbase_vector).run_all_tests()

@ -1067,6 +1067,7 @@ PLUGIN_MEDIA_CACHE_PATH=assets
# Plugin oss bucket # Plugin oss bucket
PLUGIN_STORAGE_OSS_BUCKET= PLUGIN_STORAGE_OSS_BUCKET=
# Plugin oss s3 credentials # Plugin oss s3 credentials
PLUGIN_S3_USE_AWS=
PLUGIN_S3_USE_AWS_MANAGED_IAM=false PLUGIN_S3_USE_AWS_MANAGED_IAM=false
PLUGIN_S3_ENDPOINT= PLUGIN_S3_ENDPOINT=
PLUGIN_S3_USE_PATH_STYLE=false PLUGIN_S3_USE_PATH_STYLE=false

@ -168,6 +168,7 @@ services:
PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-}
S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false}
S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-}
S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}
AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-}
@ -434,7 +435,7 @@ services:
# OceanBase vector database # OceanBase vector database
oceanbase: oceanbase:
image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818 image: oceanbase/oceanbase-ce:4.3.5-lts
container_name: oceanbase container_name: oceanbase
profiles: profiles:
- oceanbase - oceanbase
@ -449,9 +450,7 @@ services:
OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai}
OB_SERVER_IP: 127.0.0.1 OB_SERVER_IP: 127.0.0.1
MODE: MINI MODE: mini
ports:
- "${OCEANBASE_VECTOR_PORT:-2881}:2881"
# Oracle vector database # Oracle vector database
oracle: oracle:

@ -103,6 +103,7 @@ services:
PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages}
PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-}
S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-}
S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false}
S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}

@ -467,6 +467,7 @@ x-shared-env: &shared-api-worker-env
PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages}
PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-}
PLUGIN_S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-}
PLUGIN_S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} PLUGIN_S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false}
PLUGIN_S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} PLUGIN_S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
PLUGIN_S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} PLUGIN_S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}
@ -674,6 +675,7 @@ services:
PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-}
S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false}
S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-}
S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}
AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-}
@ -940,7 +942,7 @@ services:
# OceanBase vector database # OceanBase vector database
oceanbase: oceanbase:
image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818 image: oceanbase/oceanbase-ce:4.3.5-lts
container_name: oceanbase container_name: oceanbase
profiles: profiles:
- oceanbase - oceanbase
@ -955,9 +957,7 @@ services:
OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai}
OB_SERVER_IP: 127.0.0.1 OB_SERVER_IP: 127.0.0.1
MODE: MINI MODE: mini
ports:
- "${OCEANBASE_VECTOR_PORT:-2881}:2881"
# Oracle vector database # Oracle vector database
oracle: oracle:

@ -133,6 +133,7 @@ PLUGIN_MEDIA_CACHE_PATH=assets
PLUGIN_STORAGE_OSS_BUCKET= PLUGIN_STORAGE_OSS_BUCKET=
# Plugin oss s3 credentials # Plugin oss s3 credentials
PLUGIN_S3_USE_AWS_MANAGED_IAM=false PLUGIN_S3_USE_AWS_MANAGED_IAM=false
PLUGIN_S3_USE_AWS=
PLUGIN_S3_ENDPOINT= PLUGIN_S3_ENDPOINT=
PLUGIN_S3_USE_PATH_STYLE=false PLUGIN_S3_USE_PATH_STYLE=false
PLUGIN_AWS_ACCESS_KEY= PLUGIN_AWS_ACCESS_KEY=

@ -25,9 +25,8 @@ import Loading from '@/app/components/base/loading'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
import { DataSourceType } from '@/models/datasets' import { DataSourceType } from '@/models/datasets'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { LanguagesSupported } from '@/i18n/language'
import { useStore } from '@/app/components/app/store' import { useStore } from '@/app/components/app/store'
import { getLocaleOnClient } from '@/i18n' import { useDocLink } from '@/context/i18n'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
@ -45,9 +44,9 @@ type IExtraInfoProps = {
} }
const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
const locale = getLocaleOnClient()
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
const relatedAppsTotal = relatedApps?.data?.length || 0 const relatedAppsTotal = relatedApps?.data?.length || 0
@ -97,11 +96,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
<div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div> <div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div>
<a <a
className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent' className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent'
href={ href={docLink('/guides/knowledge-base/integrate-knowledge-within-application')}
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
> >
<RiBookOpenLine className='mr-1 text-text-accent' /> <RiBookOpenLine className='mr-1 text-text-accent' />

@ -1,13 +1,11 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import OperationBtn from '@/app/components/app/configuration/base/operation-btn' import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
import Panel from '@/app/components/app/configuration/base/feature-panel' import Panel from '@/app/components/app/configuration/base/feature-panel'
import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general'
import I18n from '@/context/i18n' import { useDocLink } from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
type Props = { type Props = {
showWarning: boolean showWarning: boolean
@ -19,7 +17,7 @@ const HistoryPanel: FC<Props> = ({
onShowEditModal, onShowEditModal,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
return ( return (
<Panel <Panel
@ -45,9 +43,8 @@ const HistoryPanel: FC<Props> = ({
{showWarning && ( {showWarning && (
<div className='flex justify-between rounded-b-xl bg-background-section-burn px-3 py-2 text-xs text-text-secondary'> <div className='flex justify-between rounded-b-xl bg-background-section-burn px-3 py-2 text-xs text-text-secondary'>
<div>{t('appDebug.feature.conversationHistory.tip')} <div>{t('appDebug.feature.conversationHistory.tip')}
<a href={`${locale === LanguagesSupported[1] <a href={docLink('/learn-more/extended-reading/what-is-llmops',
? 'https://docs.dify.ai/zh-hans/learn-more/extended-reading/prompt-engineering/README' { 'zh-Hans': '/learn-more/extended-reading/prompt-engineering/README' })}
: 'https://docs.dify.ai/en/features/prompt-engineering'}`}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
className='text-[#155EEF]'>{t('appDebug.feature.conversationHistory.learnMore')} className='text-[#155EEF]'>{t('appDebug.feature.conversationHistory.learnMore')}
</a> </a>

@ -31,6 +31,7 @@ import {
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { fetchMembers } from '@/service/common' import { fetchMembers } from '@/service/common'
import type { Member } from '@/models/common' import type { Member } from '@/models/common'
import { useDocLink } from '@/context/i18n'
type SettingsModalProps = { type SettingsModalProps = {
currentDataset: DataSet currentDataset: DataSet
@ -58,6 +59,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
currentModel: isRerankDefaultModelValid, currentModel: isRerankDefaultModelValid,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useToastContext() const { notify } = useToastContext()
const ref = useRef(null) const ref = useRef(null)
const isExternal = currentDataset.provider === 'external' const isExternal = currentDataset.provider === 'external'
@ -328,7 +330,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div> <div>
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
<div className='text-xs font-normal leading-[18px] text-text-tertiary'> <div className='text-xs font-normal leading-[18px] text-text-tertiary'>
<a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> <a target='_blank' rel='noopener noreferrer' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')} className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
{t('datasetSettings.form.retrievalSetting.description')} {t('datasetSettings.form.retrievalSetting.description')}
</div> </div>
</div> </div>

@ -2,9 +2,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useDocLink } from '@/context/i18n'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
type Props = { type Props = {
onReturnToSimpleMode: () => void onReturnToSimpleMode: () => void
} }
@ -13,7 +11,7 @@ const AdvancedModeWarning: FC<Props> = ({
onReturnToSimpleMode, onReturnToSimpleMode,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const [show, setShow] = React.useState(true) const [show, setShow] = React.useState(true)
if (!show) if (!show)
return null return null
@ -25,7 +23,7 @@ const AdvancedModeWarning: FC<Props> = ({
<span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span> <span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
<a <a
className='font-medium text-[#155EEF]' className='font-medium text-[#155EEF]'
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? '/guides/features/prompt-engineering' : 'features/prompt-engineering'}`} href={docLink('/guides/features/prompt-engineering')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
> >
{t('appDebug.promptMode.advancedWarning.learnMore')} {t('appDebug.promptMode.advancedWarning.learnMore')}

@ -29,6 +29,7 @@ import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { getRedirection } from '@/utils/app-redirection' import { getRedirection } from '@/utils/app-redirection'
import FullScreenModal from '@/app/components/base/fullscreen-modal' import FullScreenModal from '@/app/components/base/fullscreen-modal'
import useTheme from '@/hooks/use-theme' import useTheme from '@/hooks/use-theme'
import { useDocLink } from '@/context/i18n'
type CreateAppProps = { type CreateAppProps = {
onSuccess: () => void onSuccess: () => void
@ -303,31 +304,33 @@ function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardP
function AppPreview({ mode }: { mode: AppMode }) { function AppPreview({ mode }: { mode: AppMode }) {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const modeToPreviewInfoMap = { const modeToPreviewInfoMap = {
'chat': { 'chat': {
title: t('app.types.chatbot'), title: t('app.types.chatbot'),
description: t('app.newApp.chatbotUserDescription'), description: t('app.newApp.chatbotUserDescription'),
link: 'https://docs.dify.ai/guides/application-orchestrate/readme', link: docLink('/guides/application-orchestrate/chatbot-application'),
}, },
'advanced-chat': { 'advanced-chat': {
title: t('app.types.advanced'), title: t('app.types.advanced'),
description: t('app.newApp.advancedUserDescription'), description: t('app.newApp.advancedUserDescription'),
link: 'https://docs.dify.ai/en/guides/workflow/README', link: docLink('/guides/workflow/readme'),
}, },
'agent-chat': { 'agent-chat': {
title: t('app.types.agent'), title: t('app.types.agent'),
description: t('app.newApp.agentUserDescription'), description: t('app.newApp.agentUserDescription'),
link: 'https://docs.dify.ai/en/guides/application-orchestrate/agent', link: docLink('/guides/application-orchestrate/agent'),
}, },
'completion': { 'completion': {
title: t('app.newApp.completeApp'), title: t('app.newApp.completeApp'),
description: t('app.newApp.completionUserDescription'), description: t('app.newApp.completionUserDescription'),
link: null, link: docLink('/guides/application-orchestrate/text-generator',
{ 'zh-Hans': '/guides/application-orchestrate/readme' }),
}, },
'workflow': { 'workflow': {
title: t('app.types.workflow'), title: t('app.types.workflow'),
description: t('app.newApp.workflowUserDescription'), description: t('app.newApp.workflowUserDescription'),
link: 'https://docs.dify.ai/en/guides/workflow/README', link: docLink('/guides/workflow/readme'),
}, },
} }
const previewInfo = modeToPreviewInfoMap[mode] const previewInfo = modeToPreviewInfoMap[mode]

@ -354,7 +354,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
} }
useEffect(() => { useEffect(() => {
adjustModalWidth() const raf = requestAnimationFrame(adjustModalWidth)
return () => cancelAnimationFrame(raf)
}, []) }, [])
return ( return (

@ -3,13 +3,11 @@ import type { FC } from 'react'
import React from 'react' import React from 'react'
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useDocLink } from '@/context/i18n'
import type { AppMode } from '@/types/app' import type { AppMode } from '@/types/app'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Tag from '@/app/components/base/tag' import Tag from '@/app/components/base/tag'
import { LanguagesSupported } from '@/i18n/language'
type IShareLinkProps = { type IShareLinkProps = {
isShow: boolean isShow: boolean
@ -43,7 +41,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
mode, mode,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const isChatApp = mode === 'chat' || mode === 'advanced-chat' const isChatApp = mode === 'chat' || mode === 'advanced-chat'
return <Modal return <Modal
@ -101,10 +99,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
className='mt-2' className='mt-2'
onClick={() => onClick={() =>
window.open( window.open(
`https://docs.dify.ai/${locale !== LanguagesSupported[1] docLink('/guides/application-publishing/developing-with-apis'),
? 'user-guide/launching-dify-apps/developing-with-apis'
: `${locale.toLowerCase()}/guides/application-publishing/developing-with-apis`
}`,
'_blank', '_blank',
) )
} }

@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useState } from 'react'
import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react'
import Link from 'next/link' import Link from 'next/link'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { SparklesSoft } from '@/app/components/base/icons/src/public/common' import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import ActionButton from '@/app/components/base/action-button' import ActionButton from '@/app/components/base/action-button'
@ -19,14 +18,14 @@ import { SimpleSelect } from '@/app/components/base/select'
import type { AppDetailResponse } from '@/models/app' import type { AppDetailResponse } from '@/models/app'
import type { AppIconType, AppSSO, Language } from '@/types/app' import type { AppIconType, AppSSO, Language } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { LanguagesSupported, languages } from '@/i18n/language' import { languages } from '@/i18n/language'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import AppIconPicker from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker'
import I18n from '@/context/i18n'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useDocLink } from '@/context/i18n'
export type ISettingsModalProps = { export type ISettingsModalProps = {
isChat: boolean isChat: boolean
@ -98,7 +97,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
const [language, setLanguage] = useState(default_language) const [language, setLanguage] = useState(default_language)
const [saveLoading, setSaveLoading] = useState(false) const [saveLoading, setSaveLoading] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [appIcon, setAppIcon] = useState<AppIconSelection>( const [appIcon, setAppIcon] = useState<AppIconSelection>(
@ -238,7 +237,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div> </div>
<div className='system-xs-regular mt-0.5 text-text-tertiary'> <div className='system-xs-regular mt-0.5 text-text-tertiary'>
<span>{t(`${prefixSettings}.modalTip`)}</span> <span>{t(`${prefixSettings}.modalTip`)}</span>
<Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/en/guides/application-publishing/launch-your-webapp-quickly/README'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> <Link href={docLink('/guides/application-publishing/launch-your-webapp-quickly/README')}
target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link>
</div> </div>
</div> </div>
{/* form body */} {/* form body */}

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { RiCloseLine, RiInformation2Fill } from '@remixicon/react' import { RiCloseLine, RiInformation2Fill } from '@remixicon/react'
import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper' import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
@ -19,8 +18,7 @@ import Moderation from '@/app/components/base/features/new-feature-panel/moderat
import AnnotationReply from '@/app/components/base/features/new-feature-panel/annotation-reply' import AnnotationReply from '@/app/components/base/features/new-feature-panel/annotation-reply'
import type { PromptVariable } from '@/models/debug' import type { PromptVariable } from '@/models/debug'
import type { InputVar } from '@/app/components/workflow/types' import type { InputVar } from '@/app/components/workflow/types'
import I18n from '@/context/i18n' import { useDocLink } from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
type Props = { type Props = {
show: boolean show: boolean
@ -48,7 +46,7 @@ const NewFeaturePanel = ({
onAutoAddPromptVariable, onAutoAddPromptVariable,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text) const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts) const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
@ -80,7 +78,7 @@ const NewFeaturePanel = ({
<span>{isChatMode ? t('workflow.common.fileUploadTip') : t('workflow.common.ImageUploadLegacyTip')}</span> <span>{isChatMode ? t('workflow.common.fileUploadTip') : t('workflow.common.ImageUploadLegacyTip')}</span>
<a <a
className='text-text-accent' className='text-text-accent'
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}guides/workflow/bulletin`} href={docLink('/guides/workflow/bulletin')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
>{t('workflow.common.featuresDocLink')}</a> >{t('workflow.common.featuresDocLink')}</a>
</div> </div>

@ -14,7 +14,7 @@ const MarkdownButton = ({ node }: any) => {
size={size} size={size}
className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')} className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')}
onClick={() => { onClick={() => {
if (isValidUrl(link)) { if (link && isValidUrl(link)) {
window.open(link, '_blank') window.open(link, '_blank')
return return
} }

@ -63,6 +63,7 @@ import CustomDialog from '@/app/components/base/dialog'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
const TextLabel: FC<PropsWithChildren> = (props) => { const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='system-sm-semibold text-text-secondary'>{props.children}</label> return <label className='system-sm-semibold text-text-secondary'>{props.children}</label>
@ -146,6 +147,7 @@ const StepTwo = ({
updateRetrievalMethodCache, updateRetrievalMethodCache,
}: StepTwoProps) => { }: StepTwoProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
@ -962,7 +964,9 @@ const StepTwo = ({
<div className={'mb-1'}> <div className={'mb-1'}>
<div className='system-md-semibold mb-0.5 text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='system-md-semibold mb-0.5 text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
<div className='body-xs-regular text-text-tertiary'> <div className='body-xs-regular text-text-tertiary'>
<a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> <a target='_blank' rel='noopener noreferrer'
href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents')}
className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
{t('datasetSettings.form.retrievalSetting.longDescription')} {t('datasetSettings.form.retrievalSetting.longDescription')}
</div> </div>
</div> </div>

@ -4,6 +4,7 @@ import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Input from './input' import Input from './input'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useDocLink } from '@/context/i18n'
const I18N_PREFIX = 'datasetCreation.stepOne.website' const I18N_PREFIX = 'datasetCreation.stepOne.website'
@ -17,6 +18,7 @@ const UrlInput: FC<Props> = ({
onRun, onRun,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
const handleUrlChange = useCallback((url: string | number) => { const handleUrlChange = useCallback((url: string | number) => {
setUrl(url as string) setUrl(url as string)
@ -32,7 +34,7 @@ const UrlInput: FC<Props> = ({
<Input <Input
value={url} value={url}
onChange={handleUrlChange} onChange={handleUrlChange}
placeholder='https://docs.dify.ai' placeholder={docLink()}
/> />
<Button <Button
variant='primary' variant='primary'

@ -4,6 +4,7 @@ import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Input from './input' import Input from './input'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useDocLink } from '@/context/i18n'
const I18N_PREFIX = 'datasetCreation.stepOne.website' const I18N_PREFIX = 'datasetCreation.stepOne.website'
@ -17,6 +18,7 @@ const UrlInput: FC<Props> = ({
onRun, onRun,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
const handleUrlChange = useCallback((url: string | number) => { const handleUrlChange = useCallback((url: string | number) => {
setUrl(url as string) setUrl(url as string)
@ -32,7 +34,7 @@ const UrlInput: FC<Props> = ({
<Input <Input
value={url} value={url}
onChange={handleUrlChange} onChange={handleUrlChange}
placeholder='https://docs.dify.ai' placeholder={docLink()}
/> />
<Button <Button
variant='primary' variant='primary'

@ -29,8 +29,7 @@ import { useChildSegmentListKey, useSegmentListKey } from '@/service/knowledge/u
import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata' import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata'
import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer' import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer'
import StatusWithAction from '../common/document-status-with-action/status-with-action' import StatusWithAction from '../common/document-status-with-action/status-with-action'
import { LanguagesSupported } from '@/i18n/language' import { useDocLink } from '@/context/i18n'
import { getLocaleOnClient } from '@/i18n'
const FolderPlusIcon = ({ className }: React.SVGProps<SVGElement>) => { const FolderPlusIcon = ({ className }: React.SVGProps<SVGElement>) => {
return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
@ -86,6 +85,7 @@ const DEFAULT_LIMIT = 10
const Documents: FC<IDocumentsProps> = ({ datasetId }) => { const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { plan } = useProviderContext() const { plan } = useProviderContext()
const isFreePlan = plan.type === 'sandbox' const isFreePlan = plan.type === 'sandbox'
const [inputValue, setInputValue] = useState<string>('') // the input value const [inputValue, setInputValue] = useState<string>('') // the input value
@ -100,7 +100,6 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
const isDataSourceWeb = dataset?.data_source_type === DataSourceType.WEB const isDataSourceWeb = dataset?.data_source_type === DataSourceType.WEB
const isDataSourceFile = dataset?.data_source_type === DataSourceType.FILE const isDataSourceFile = dataset?.data_source_type === DataSourceType.FILE
const embeddingAvailable = !!dataset?.embedding_available const embeddingAvailable = !!dataset?.embedding_available
const locale = getLocaleOnClient()
const debouncedSearchValue = useDebounce(searchValue, { wait: 500 }) const debouncedSearchValue = useDebounce(searchValue, { wait: 500 })
const { data: documentsRes, isFetching: isListLoading } = useDocumentList({ const { data: documentsRes, isFetching: isListLoading } = useDocumentList({
@ -262,11 +261,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
<a <a
className='flex items-center text-text-accent' className='flex items-center text-text-accent'
target='_blank' target='_blank'
href={ href={docLink('/guides/knowledge-base/integrate-knowledge-within-application')}
locale === LanguagesSupported[1]
? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
: 'https://docs.dify.ai/en/guides/knowledge-base/integrate-knowledge-within-application'
}
> >
<span>{t('datasetDocuments.list.learnMore')}</span> <span>{t('datasetDocuments.list.learnMore')}</span>
<RiExternalLinkLine className='h-3 w-3' /> <RiExternalLinkLine className='h-3 w-3' />

@ -5,6 +5,7 @@ import { RiBookOpenLine } from '@remixicon/react'
import type { CreateExternalAPIReq, FormSchema } from '../declarations' import type { CreateExternalAPIReq, FormSchema } from '../declarations'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useDocLink } from '@/context/i18n'
type FormProps = { type FormProps = {
className?: string className?: string
@ -26,6 +27,7 @@ const Form: FC<FormProps> = React.memo(({
inputClassName, inputClassName,
}) => { }) => {
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
const docLink = useDocLink()
const [changeKey, setChangeKey] = useState('') const [changeKey, setChangeKey] = useState('')
const handleFormChange = (key: string, val: string) => { const handleFormChange = (key: string, val: string) => {
@ -57,7 +59,7 @@ const Form: FC<FormProps> = React.memo(({
</label> </label>
{variable === 'endpoint' && ( {variable === 'endpoint' && (
<a <a
href={'https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' || '/'} href={docLink('/guides/knowledge-base/external-knowledge-api-documentation') || '/'}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className='body-xs-regular flex items-center text-text-accent' className='body-xs-regular flex items-center text-text-accent'

@ -12,6 +12,7 @@ import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import { useDocLink } from '@/context/i18n'
type ExternalAPIPanelProps = { type ExternalAPIPanelProps = {
onClose: () => void onClose: () => void
@ -19,6 +20,7 @@ type ExternalAPIPanelProps = {
const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => { const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { setShowExternalKnowledgeAPIModal } = useModalContext() const { setShowExternalKnowledgeAPIModal } = useModalContext()
const { externalKnowledgeApiList, mutateExternalKnowledgeApis, isLoading } = useExternalKnowledgeApi() const { externalKnowledgeApiList, mutateExternalKnowledgeApis, isLoading } = useExternalKnowledgeApi()
@ -50,7 +52,8 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => {
<div className='flex grow flex-col items-start gap-1'> <div className='flex grow flex-col items-start gap-1'>
<div className='system-xl-semibold self-stretch text-text-primary'>{t('dataset.externalAPIPanelTitle')}</div> <div className='system-xl-semibold self-stretch text-text-primary'>{t('dataset.externalAPIPanelTitle')}</div>
<div className='body-xs-regular self-stretch text-text-tertiary'>{t('dataset.externalAPIPanelDescription')}</div> <div className='body-xs-regular self-stretch text-text-tertiary'>{t('dataset.externalAPIPanelDescription')}</div>
<a className='flex cursor-pointer items-center justify-center gap-1 self-stretch' href='https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' target='_blank'> <a className='flex cursor-pointer items-center justify-center gap-1 self-stretch'
href={docLink('/guides/knowledge-base/external-knowledge-api-documentation')} target='_blank'>
<RiBookOpenLine className='h-3 w-3 text-text-accent' /> <RiBookOpenLine className='h-3 w-3 text-text-accent' />
<div className='body-xs-regular grow text-text-accent'>{t('dataset.externalAPIPanelDocumentation')}</div> <div className='body-xs-regular grow text-text-accent'>{t('dataset.externalAPIPanelDocumentation')}</div>
</a> </a>

@ -1,8 +1,10 @@
import { RiBookOpenLine } from '@remixicon/react' import { RiBookOpenLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
const InfoPanel = () => { const InfoPanel = () => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
return ( return (
<div className='flex w-[360px] flex-col items-start pb-2 pr-8 pt-[108px]'> <div className='flex w-[360px] flex-col items-start pb-2 pr-8 pt-[108px]'>
@ -16,12 +18,15 @@ const InfoPanel = () => {
</span> </span>
<span className='system-sm-regular text-text-tertiary'> <span className='system-sm-regular text-text-tertiary'>
{t('dataset.connectDatasetIntro.content.front')} {t('dataset.connectDatasetIntro.content.front')}
<a className='system-sm-regular ml-1 text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/external-knowledge-api' target='_blank' rel="noopener noreferrer"> <a className='system-sm-regular ml-1 text-text-accent' href={docLink('/guides/knowledge-base/external-knowledge-api')} target='_blank' rel="noopener noreferrer">
{t('dataset.connectDatasetIntro.content.link')} {t('dataset.connectDatasetIntro.content.link')}
</a> </a>
{t('dataset.connectDatasetIntro.content.end')} {t('dataset.connectDatasetIntro.content.end')}
</span> </span>
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/connect-external-knowledge-base' target='_blank' rel="noopener noreferrer"> <a className='system-sm-regular self-stretch text-text-accent'
href={docLink('/guides/knowledge-base/connect-external-knowledge-base')}
target='_blank'
rel="noopener noreferrer">
{t('dataset.connectDatasetIntro.learnMore')} {t('dataset.connectDatasetIntro.learnMore')}
</a> </a>
</p> </p>

@ -11,6 +11,7 @@ import InfoPanel from './InfoPanel'
import type { CreateKnowledgeBaseReq } from './declarations' import type { CreateKnowledgeBaseReq } from './declarations'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useDocLink } from '@/context/i18n'
type ExternalKnowledgeBaseCreateProps = { type ExternalKnowledgeBaseCreateProps = {
onConnect: (formValue: CreateKnowledgeBaseReq) => void onConnect: (formValue: CreateKnowledgeBaseReq) => void
@ -19,6 +20,7 @@ type ExternalKnowledgeBaseCreateProps = {
const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect, loading }) => { const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect, loading }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter() const router = useRouter()
const [formData, setFormData] = useState<CreateKnowledgeBaseReq>({ const [formData, setFormData] = useState<CreateKnowledgeBaseReq>({
name: '', name: '',
@ -59,7 +61,7 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
<span>{t('dataset.connectHelper.helper1')}</span> <span>{t('dataset.connectHelper.helper1')}</span>
<span className='system-sm-medium text-text-secondary'>{t('dataset.connectHelper.helper2')}</span> <span className='system-sm-medium text-text-secondary'>{t('dataset.connectHelper.helper2')}</span>
<span>{t('dataset.connectHelper.helper3')}</span> <span>{t('dataset.connectHelper.helper3')}</span>
<a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/en/guides/knowledge-base/connect-external-knowledge-base' target='_blank' rel="noopener noreferrer"> <a className='system-sm-regular self-stretch text-text-accent' href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} target='_blank' rel="noopener noreferrer">
{t('dataset.connectHelper.helper4')} {t('dataset.connectHelper.helper4')}
</a> </a>
<span>{t('dataset.connectHelper.helper5')} </span> <span>{t('dataset.connectHelper.helper5')} </span>

@ -11,6 +11,7 @@ import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/ec
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { useDocLink } from '@/context/i18n'
type Props = { type Props = {
indexMethod: string indexMethod: string
@ -29,6 +30,7 @@ const ModifyRetrievalModal: FC<Props> = ({
}) => { }) => {
const ref = useRef(null) const ref = useRef(null)
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const [retrievalConfig, setRetrievalConfig] = useState(value) const [retrievalConfig, setRetrievalConfig] = useState(value)
// useClickAway(() => { // useClickAway(() => {
@ -72,7 +74,7 @@ const ModifyRetrievalModal: FC<Props> = ({
<a <a
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings')}
className='text-text-accent' className='text-text-accent'
> >
{t('datasetSettings.form.retrievalSetting.learnMore')} {t('datasetSettings.form.retrievalSetting.learnMore')}

@ -32,6 +32,7 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
import { fetchMembers } from '@/service/common' import { fetchMembers } from '@/service/common'
import type { Member } from '@/models/common' import type { Member } from '@/models/common'
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle' import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
import { useDocLink } from '@/context/i18n'
const rowClass = 'flex' const rowClass = 'flex'
const labelClass = ` const labelClass = `
@ -46,6 +47,7 @@ const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
const Form = () => { const Form = () => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { mutate } = useSWRConfig() const { mutate } = useSWRConfig()
const { isCurrentWorkspaceDatasetOperator } = useAppContext() const { isCurrentWorkspaceDatasetOperator } = useAppContext()
@ -308,7 +310,7 @@ const Form = () => {
<div> <div>
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
<div className='body-xs-regular text-text-tertiary'> <div className='body-xs-regular text-text-tertiary'>
<a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> <a target='_blank' rel='noopener noreferrer' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings')} className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
{t('datasetSettings.form.retrievalSetting.description')} {t('datasetSettings.form.retrievalSetting.description')}
</div> </div>
</div> </div>

@ -57,8 +57,8 @@ The text generation application offers non-session support and is ideal for tran
<i>Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds.</i> <i>Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds.</i>
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
User identifier, used to define the identity of the end-user for retrieval and statistics. User identifier, used to define the identity of the end-user, convenient for retrieval and statistics.
Should be uniquely defined by the developer within the application. The rules are defined by the developer and need to ensure that the user identifier is unique within the application. The Service API does not share conversations created by the WebApp.
</Property> </Property>
<Property name='files' type='array[object]' key='files'> <Property name='files' type='array[object]' key='files'>
File list, suitable for inputting files (images) combined with text understanding and answering questions, available only when the model supports Vision capability. File list, suitable for inputting files (images) combined with text understanding and answering questions, available only when the model supports Vision capability.
@ -220,7 +220,7 @@ The text generation application offers non-session support and is ideal for tran
- `file` (File) Required - `file` (File) Required
The file to be uploaded. The file to be uploaded.
- `user` (string) Required - `user` (string) Required
User identifier, defined by the developer's rules, must be unique within the application. User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp.
### Response ### Response
After a successful upload, the server will return the file's ID and related information. After a successful upload, the server will return the file's ID and related information.
@ -290,7 +290,7 @@ The text generation application offers non-session support and is ideal for tran
- `task_id` (string) Task ID, can be obtained from the streaming chunk return - `task_id` (string) Task ID, can be obtained from the streaming chunk return
Request Body Request Body
- `user` (string) Required - `user` (string) Required
User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. The Service API does not share conversations created by the WebApp.
### Response ### Response
- `result` (string) Always returns "success" - `result` (string) Always returns "success"
</Col> </Col>
@ -512,6 +512,8 @@ The text generation application offers non-session support and is ideal for tran
- `name` (string) application name - `name` (string) application name
- `description` (string) application description - `description` (string) application description
- `tags` (array[string]) application tags - `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) author name
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -528,7 +530,9 @@ The text generation application offers non-session support and is ideal for tran
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -220,7 +220,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `file` (File) 必須 - `file` (File) 必須
アップロードするファイル。 アップロードするファイル。
- `user` (string) 必須 - `user` (string) 必須
開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。 開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。サービス API は WebApp によって作成された会話を共有しません。
### レスポンス ### レスポンス
アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。
@ -289,7 +289,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `task_id` (string) タスク ID、ストリーミングチャンクの返信から取得可能 - `task_id` (string) タスク ID、ストリーミングチャンクの返信から取得可能
リクエストボディ リクエストボディ
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子。エンドユーザーの身元を定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致する必要があります。 ユーザー識別子。エンドユーザーの身元を定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致する必要があります。サービス API は WebApp によって作成された会話を共有しません。
### レスポンス ### レスポンス
- `result` (string) 常に"success"を返します - `result` (string) 常に"success"を返します
</Col> </Col>
@ -510,6 +510,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `name` (string) アプリケーションの名前 - `name` (string) アプリケーションの名前
- `description` (string) アプリケーションの説明 - `description` (string) アプリケーションの説明
- `tags` (array[string]) アプリケーションのタグ - `tags` (array[string]) アプリケーションのタグ
- `mode` (string) アプリケーションのモード
- `author_name` (string) 作者の名前
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -526,7 +528,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -266,7 +266,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。
### Response ### Response
- `result` (string) 固定返回 success - `result` (string) 固定返回 success
</Col> </Col>
@ -485,6 +485,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
- `name` (string) 应用名称 - `name` (string) 应用名称
- `description` (string) 应用描述 - `description` (string) 应用描述
- `tags` (array[string]) 应用标签 - `tags` (array[string]) 应用标签
- `mode` (string) 应用模式
- 'author_name' (string) 作者名称
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -501,7 +503,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -59,7 +59,7 @@ Chat applications support session persistence, allowing previous chat history to
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
User identifier, used to define the identity of the end-user for retrieval and statistics. User identifier, used to define the identity of the end-user for retrieval and statistics.
Should be uniquely defined by the developer within the application. Should be uniquely defined by the developer within the application. The Service API does not share conversations created by the WebApp.
</Property> </Property>
<Property name='conversation_id' type='string' key='conversation_id'> <Property name='conversation_id' type='string' key='conversation_id'>
Conversation ID, to continue the conversation based on previous chat records, it is necessary to pass the previous message's conversation_id. Conversation ID, to continue the conversation based on previous chat records, it is necessary to pass the previous message's conversation_id.
@ -324,7 +324,7 @@ Chat applications support session persistence, allowing previous chat history to
- `file` (File) Required - `file` (File) Required
The file to be uploaded. The file to be uploaded.
- `user` (string) Required - `user` (string) Required
User identifier, defined by the developer's rules, must be unique within the application. User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp.
### Response ### Response
After a successful upload, the server will return the file's ID and related information. After a successful upload, the server will return the file's ID and related information.
@ -394,7 +394,7 @@ Chat applications support session persistence, allowing previous chat history to
- `task_id` (string) Task ID, can be obtained from the streaming chunk return - `task_id` (string) Task ID, can be obtained from the streaming chunk return
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.
### Response ### Response
- `result` (string) Always returns "success" - `result` (string) Always returns "success"
</Col> </Col>
@ -448,7 +448,7 @@ Chat applications support session persistence, allowing previous chat history to
Upvote as `like`, downvote as `dislike`, revoke upvote as `null` Upvote as `like`, downvote as `dislike`, revoke upvote as `null`
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
User identifier, defined by the developer's rules, must be unique within the application. User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp.
</Property> </Property>
<Property name='content' type='string' key='content'> <Property name='content' type='string' key='content'>
The specific content of message feedback. The specific content of message feedback.
@ -1123,6 +1123,8 @@ Chat applications support session persistence, allowing previous chat history to
- `name` (string) application name - `name` (string) application name
- `description` (string) application description - `description` (string) application description
- `tags` (array[string]) application tags - `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -1139,7 +1141,9 @@ Chat applications support session persistence, allowing previous chat history to
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "advanced-chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -59,7 +59,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
ユーザー識別子、エンドユーザーの身元を定義するために使用され、統計のために使用されます。 ユーザー識別子、エンドユーザーの身元を定義するために使用され、統計のために使用されます。
アプリケーション内で開発者によって一意に定義されるべきです。 アプリケーション内で開発者によって一意に定義されるべきです。サービス API は WebApp によって作成された会話を共有しません。
</Property> </Property>
<Property name='conversation_id' type='string' key='conversation_id'> <Property name='conversation_id' type='string' key='conversation_id'>
会話ID、以前のチャット記録に基づいて会話を続けるには、以前のメッセージのconversation_idを渡す必要があります。 会話ID、以前のチャット記録に基づいて会話を続けるには、以前のメッセージのconversation_idを渡す必要があります。
@ -324,7 +324,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `file` (File) 必須 - `file` (File) 必須
アップロードするファイル。 アップロードするファイル。
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、開発者のルールによって定義され、アプリケーション内で一意でなければなりません。 ユーザー識別子、開発者のルールによって定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。
@ -394,7 +394,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます
### リクエストボディ ### リクエストボディ
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、エンドユーザーの身元を定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。 ユーザー識別子、エンドユーザーの身元を定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
- `result` (string) 常に"success"を返します - `result` (string) 常に"success"を返します
</Col> </Col>
@ -1123,6 +1123,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `name` (string) アプリケーションの名前 - `name` (string) アプリケーションの名前
- `description` (string) アプリケーションの説明 - `description` (string) アプリケーションの説明
- `tags` (array[string]) アプリケーションのタグ - `tags` (array[string]) アプリケーションのタグ
- `mode` (string) アプリケーションのモード
- `author_name` (string) 作者の名前
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -1139,7 +1141,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "advanced-chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -56,7 +56,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
用户标识,用于定义终端用户的身份,方便检索、统计。 用户标识,用于定义终端用户的身份,方便检索、统计。
由开发者定义规则,需保证用户标识在应用内唯一。 由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。
</Property> </Property>
<Property name='conversation_id' type='string' key='conversation_id'> <Property name='conversation_id' type='string' key='conversation_id'>
(选填)会话 ID需要基于之前的聊天记录继续对话必须传之前消息的 conversation_id。 (选填)会话 ID需要基于之前的聊天记录继续对话必须传之前消息的 conversation_id。
@ -402,7 +402,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。
### Response ### Response
- `result` (string) 固定返回 success - `result` (string) 固定返回 success
</Col> </Col>
@ -1173,7 +1173,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "advanced-chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -287,7 +287,7 @@ Chat applications support session persistence, allowing previous chat history to
- `file` (File) Required - `file` (File) Required
The file to be uploaded. The file to be uploaded.
- `user` (string) Required - `user` (string) Required
User identifier, defined by the developer's rules, must be unique within the application. User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp.
### Response ### Response
After a successful upload, the server will return the file's ID and related information. After a successful upload, the server will return the file's ID and related information.
@ -357,7 +357,7 @@ Chat applications support session persistence, allowing previous chat history to
- `task_id` (string) Task ID, can be obtained from the streaming chunk return - `task_id` (string) Task ID, can be obtained from the streaming chunk return
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.
### Response ### Response
- `result` (string) Always returns "success" - `result` (string) Always returns "success"
</Col> </Col>
@ -1151,6 +1151,8 @@ Chat applications support session persistence, allowing previous chat history to
- `name` (string) application name - `name` (string) application name
- `description` (string) application description - `description` (string) application description
- `tags` (array[string]) application tags - `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -1167,7 +1169,9 @@ Chat applications support session persistence, allowing previous chat history to
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "advanced-chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -287,7 +287,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `file` (File) 必須 - `file` (File) 必須
アップロードするファイル。 アップロードするファイル。
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。 ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。
@ -357,7 +357,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます
### リクエストボディ ### リクエストボディ
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致している必要があります。 ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
- `result` (string) 常に"success"を返します - `result` (string) 常に"success"を返します
</Col> </Col>
@ -1150,6 +1150,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `name` (string) アプリケーションの名前 - `name` (string) アプリケーションの名前
- `description` (string) アプリケーションの説明 - `description` (string) アプリケーションの説明
- `tags` (array[string]) アプリケーションのタグ - `tags` (array[string]) アプリケーションのタグ
- `mode` (string) アプリケーションのモード
- `author_name` (string) 作者の名前
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -1166,7 +1168,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -56,7 +56,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
用户标识,用于定义终端用户的身份,方便检索、统计。 用户标识,用于定义终端用户的身份,方便检索、统计。
由开发者定义规则,需保证用户标识在应用内唯一。 由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。
</Property> </Property>
<Property name='conversation_id' type='string' key='conversation_id'> <Property name='conversation_id' type='string' key='conversation_id'>
(选填)会话 ID需要基于之前的聊天记录继续对话必须传之前消息的 conversation_id。 (选填)会话 ID需要基于之前的聊天记录继续对话必须传之前消息的 conversation_id。
@ -306,7 +306,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
要上传的文件。 要上传的文件。
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。服务 API 不会共享 WebApp 创建的对话。
</Property> </Property>
</Properties> </Properties>
@ -373,7 +373,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。
### Response ### Response
- `result` (string) 固定返回 success - `result` (string) 固定返回 success
</Col> </Col>
@ -425,7 +425,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
点赞 like, 点踩 dislike, 撤销点赞 null 点赞 like, 点踩 dislike, 撤销点赞 null
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
用户标识,由开发者定义规则,需保证用户标识在应用内唯一。 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。
</Property> </Property>
<Property name='content' type='string' key='content'> <Property name='content' type='string' key='content'>
消息反馈的具体信息。 消息反馈的具体信息。
@ -1162,6 +1162,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
- `name` (string) 应用名称 - `name` (string) 应用名称
- `description` (string) 应用描述 - `description` (string) 应用描述
- `tags` (array[string]) 应用标签 - `tags` (array[string]) 应用标签
- `mode` (string) 应用模式
- 'author_name' (string) 作者名称
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -1178,7 +1180,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "chat",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -64,6 +64,8 @@ Workflow applications offers non-session support and is ideal for translation, a
- `user` (string) Required - `user` (string) Required
User identifier, used to define the identity of the end-user for retrieval and statistics. User identifier, used to define the identity of the end-user for retrieval and statistics.
Should be uniquely defined by the developer within the application. Should be uniquely defined by the developer within the application.
<br/>
<i>The user identifier should be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.</i>
### Response ### Response
When `response_mode` is `blocking`, return a CompletionResponse object. When `response_mode` is `blocking`, return a CompletionResponse object.
@ -401,7 +403,7 @@ Workflow applications offers non-session support and is ideal for translation, a
- `task_id` (string) Task ID, can be obtained from the streaming chunk return - `task_id` (string) Task ID, can be obtained from the streaming chunk return
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.
### Response ### Response
- `result` (string) Always returns "success" - `result` (string) Always returns "success"
</Col> </Col>
@ -448,7 +450,7 @@ Workflow applications offers non-session support and is ideal for translation, a
- `file` (File) Required - `file` (File) Required
The file to be uploaded. The file to be uploaded.
- `user` (string) Required - `user` (string) Required
User identifier, defined by the developer's rules, must be unique within the application. User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp.
### Response ### Response
After a successful upload, the server will return the file's ID and related information. After a successful upload, the server will return the file's ID and related information.
@ -625,6 +627,8 @@ Workflow applications offers non-session support and is ideal for translation, a
- `name` (string) application name - `name` (string) application name
- `description` (string) application description - `description` (string) application description
- `tags` (array[string]) application tags - `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -641,7 +645,9 @@ Workflow applications offers non-session support and is ideal for translation, a
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "workflow",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -404,7 +404,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得可能 - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得可能
### リクエストボディ ### リクエストボディ
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。 ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
- `result` (string) 常に"success"を返します - `result` (string) 常に"success"を返します
</Col> </Col>
@ -451,7 +451,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `file` (File) 必須 - `file` (File) 必須
アップロードするファイル。 アップロードするファイル。
- `user` (string) 必須 - `user` (string) 必須
ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。 ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。
### 応答 ### 応答
アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。
@ -628,6 +628,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
- `name` (string) アプリケーションの名前 - `name` (string) アプリケーションの名前
- `description` (string) アプリケーションの説明 - `description` (string) アプリケーションの説明
- `tags` (array[string]) アプリケーションのタグ - `tags` (array[string]) アプリケーションのタグ
- `mode` (string) アプリケーションのモード
- `author_name` (string) 作者の名前
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -644,7 +646,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "workflow",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -59,7 +59,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
<i>由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。</i> <i>由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。</i>
- `user` (string) Required - `user` (string) Required
用户标识,用于定义终端用户的身份,方便检索、统计。 用户标识,用于定义终端用户的身份,方便检索、统计。
由开发者定义规则,需保证用户标识在应用内唯一。 由开发者定义规则,需保证用户标识在应用内唯一。API 无法访问 WebApp 创建的会话。
### Response ### Response
@ -394,7 +394,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
- `task_id` (string) 任务 ID可在流式返回 Chunk 中获取 - `task_id` (string) 任务 ID可在流式返回 Chunk 中获取
### Request Body ### Request Body
- `user` (string) Required - `user` (string) Required
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。
### Response ### Response
- `result` (string) 固定返回 "success" - `result` (string) 固定返回 "success"
</Col> </Col>
@ -443,7 +443,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
要上传的文件。 要上传的文件。
</Property> </Property>
<Property name='user' type='string' key='user'> <Property name='user' type='string' key='user'>
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。服务 API 不会共享 WebApp 创建的对话。
</Property> </Property>
</Properties> </Properties>
@ -615,6 +615,8 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
- `name` (string) 应用名称 - `name` (string) 应用名称
- `description` (string) 应用描述 - `description` (string) 应用描述
- `tags` (array[string]) 应用标签 - `tags` (array[string]) 应用标签
- `mode` (string) 应用模式
- 'author_name' (string) 作者名称
</Col> </Col>
<Col> <Col>
<CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}>
@ -631,7 +633,9 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
"tags": [ "tags": [
"tag1", "tag1",
"tag2" "tag2"
] ],
"mode": "workflow",
"author_name": "Dify"
} }
``` ```
</CodeGroup> </CodeGroup>

@ -23,7 +23,6 @@ import GithubStar from '../github-star'
import Support from './support' import Support from './support'
import Compliance from './compliance' import Compliance from './compliance'
import PremiumBadge from '@/app/components/base/premium-badge' import PremiumBadge from '@/app/components/base/premium-badge'
import { useGetDocLanguage } from '@/context/i18n'
import Avatar from '@/app/components/base/avatar' import Avatar from '@/app/components/base/avatar'
import ThemeSwitcher from '@/app/components/base/theme-switcher' import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { logout } from '@/service/common' import { logout } from '@/service/common'
@ -33,6 +32,7 @@ import { useModalContext } from '@/context/modal-context'
import { IS_CLOUD_EDITION } from '@/config' import { IS_CLOUD_EDITION } from '@/config'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
import { useDocLink } from '@/context/i18n'
export default function AppSelector() { export default function AppSelector() {
const itemClassName = ` const itemClassName = `
@ -44,10 +44,10 @@ export default function AppSelector() {
const { systemFeatures } = useGlobalPublicStore() const { systemFeatures } = useGlobalPublicStore()
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext() const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { isEducationAccount } = useProviderContext() const { isEducationAccount } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext() const { setShowAccountSettingModal } = useModalContext()
const docLanguage = useGetDocLanguage()
const handleLogout = async () => { const handleLogout = async () => {
await logout({ await logout({
@ -133,7 +133,7 @@ export default function AppSelector() {
className={cn(itemClassName, 'group justify-between', className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover', 'data-[active]:bg-state-base-hover',
)} )}
href={`https://docs.dify.ai/${docLanguage}/introduction`} href={docLink('/introduction')}
target='_blank' rel='noopener noreferrer'> target='_blank' rel='noopener noreferrer'>
<RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' /> <RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div> <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div>

@ -1,6 +1,6 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useDocLink } from '@/context/i18n'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { import {
RiAddLine, RiAddLine,
@ -20,8 +20,6 @@ import {
useInvalidateEndpointList, useInvalidateEndpointList,
} from '@/service/use-endpoints' } from '@/service/use-endpoints'
import type { PluginDetail } from '@/app/components/plugins/types' import type { PluginDetail } from '@/app/components/plugins/types'
import { LanguagesSupported } from '@/i18n/language'
import I18n from '@/context/i18n'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
@ -29,7 +27,7 @@ type Props = {
} }
const EndpointList = ({ detail }: Props) => { const EndpointList = ({ detail }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const pluginUniqueID = detail.plugin_unique_identifier const pluginUniqueID = detail.plugin_unique_identifier
const declaration = detail.declaration.endpoint const declaration = detail.declaration.endpoint
const showTopBorder = detail.declaration.tool const showTopBorder = detail.declaration.tool
@ -79,7 +77,7 @@ const EndpointList = ({ detail }: Props) => {
</div> </div>
<div className='system-xs-regular text-text-tertiary'>{t('plugin.detailPanel.endpointsTip')}</div> <div className='system-xs-regular text-text-tertiary'>{t('plugin.detailPanel.endpointsTip')}</div>
<a <a
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}plugins/schema-definition/endpoint`} href={docLink('/plugins/schema-definition/endpoint')}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
> >

@ -14,6 +14,7 @@ import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-m
import { createCustomCollection } from '@/service/tools' import { createCustomCollection } from '@/service/tools'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import { useDocLink } from '@/context/i18n'
type Props = { type Props = {
onRefreshData: () => void onRefreshData: () => void
@ -25,10 +26,11 @@ const Contribute = ({ onRefreshData }: Props) => {
const language = getLanguage(locale) const language = getLanguage(locale)
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const docLink = useDocLink()
const linkUrl = useMemo(() => { const linkUrl = useMemo(() => {
if (language.startsWith('zh_')) return docLink('/guides/tools#how-to-create-custom-tools', {
return 'https://docs.dify.ai/zh-hans/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju' 'zh-Hans': '/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju',
return 'https://docs.dify.ai/en/guides/tools#how-to-create-custom-tools' })
}, [language]) }, [language])
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)

@ -19,9 +19,7 @@ import { useWorkflowStore } from '../../../store'
import { useRenderI18nObject } from '@/hooks/use-i18n' import { useRenderI18nObject } from '@/hooks/use-i18n'
import type { NodeOutPutVar } from '../../../types' import type { NodeOutPutVar } from '../../../types'
import type { Node } from 'reactflow' import type { Node } from 'reactflow'
import { useContext } from 'use-context-selector' import { useDocLink } from '@/context/i18n'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
export type Strategy = { export type Strategy = {
agent_strategy_provider_name: string agent_strategy_provider_name: string
@ -52,7 +50,7 @@ type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
export const AgentStrategy = memo((props: AgentStrategyProps) => { export const AgentStrategy = memo((props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration)
const renderI18nObject = useRenderI18nObject() const renderI18nObject = useRenderI18nObject()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
@ -223,11 +221,10 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
title={t('workflow.nodes.agent.strategy.configureTip')} title={t('workflow.nodes.agent.strategy.configureTip')}
description={<div className='text-xs text-text-tertiary'> description={<div className='text-xs text-text-tertiary'>
{t('workflow.nodes.agent.strategy.configureTipDesc')} <br /> {t('workflow.nodes.agent.strategy.configureTipDesc')} <br />
<Link href={ <Link href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', {
locale === LanguagesSupported[1] 'zh-Hans': '/guides/workflow/node/agent#xuan-ze-agent-ce-le',
? 'https://docs.dify.ai/zh-hans/guides/workflow/node/agent#xuan-ze-agent-ce-le' })}
: 'https://docs.dify.ai/en/guides/workflow/node/agent#select-an-agent-strategy' className='text-text-accent-secondary' target='_blank'>
} className='text-text-accent-secondary' target='_blank'>
{t('workflow.nodes.agent.learnMore')} {t('workflow.nodes.agent.learnMore')}
</Link> </Link>
</div>} </div>}

@ -5,6 +5,7 @@ import Input from '@/app/components/base/input'
import { VarType } from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { useDocLink } from '@/context/i18n'
type DefaultValueProps = { type DefaultValueProps = {
forms: DefaultValueForm[] forms: DefaultValueForm[]
@ -15,6 +16,7 @@ const DefaultValue = ({
onFormChange, onFormChange,
}: DefaultValueProps) => { }: DefaultValueProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => { const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => {
return (payload: any) => { return (payload: any) => {
let value let value
@ -34,7 +36,7 @@ const DefaultValue = ({
{t('workflow.nodes.common.errorHandle.defaultValue.desc')} {t('workflow.nodes.common.errorHandle.defaultValue.desc')}
&nbsp; &nbsp;
<a <a
href='https://docs.dify.ai/en/guides/workflow/error-handling/README' href={docLink('/guides/workflow/error-handling/README')}
target='_blank' target='_blank'
className='text-text-accent' className='text-text-accent'
> >

@ -1,8 +1,10 @@
import { RiMindMap } from '@remixicon/react' import { RiMindMap } from '@remixicon/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
const FailBranchCard = () => { const FailBranchCard = () => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
return ( return (
<div className='px-4 pt-2'> <div className='px-4 pt-2'>
@ -17,7 +19,7 @@ const FailBranchCard = () => {
{t('workflow.nodes.common.errorHandle.failBranch.customizeTip')} {t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}
&nbsp; &nbsp;
<a <a
href='https://docs.dify.ai/guides/workflow/error-handling' href={docLink('/guides/workflow/error-handling/error-type')}
target='_blank' target='_blank'
className='text-text-accent' className='text-text-accent'
> >

@ -2,12 +2,10 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import VarReferenceVars from './var-reference-vars' import VarReferenceVars from './var-reference-vars'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import ListEmpty from '@/app/components/base/list-empty' import ListEmpty from '@/app/components/base/list-empty'
import { LanguagesSupported } from '@/i18n/language' import { useDocLink } from '@/context/i18n'
import I18n from '@/context/i18n'
type Props = { type Props = {
vars: NodeOutPutVar[] vars: NodeOutPutVar[]
@ -24,7 +22,7 @@ const VarReferencePopup: FC<Props> = ({
isSupportFileVar = true, isSupportFileVar = true,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
// max-h-[300px] overflow-y-auto todo: use portal to handle long list // max-h-[300px] overflow-y-auto todo: use portal to handle long list
return ( return (
<div className='space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{ <div className='space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{
@ -46,8 +44,10 @@ const VarReferencePopup: FC<Props> = ({
description={<div className='system-xs-regular text-text-tertiary'> description={<div className='system-xs-regular text-text-tertiary'>
{t('workflow.variableReference.assignedVarsDescription')} {t('workflow.variableReference.assignedVarsDescription')}
<a target='_blank' rel='noopener noreferrer' <a target='_blank' rel='noopener noreferrer'
className='text-text-accent-secondary' className='text-text-accent-secondary'
href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/guides/workflow/variables#conversation-variables' : `https://docs.dify.ai/${locale.toLowerCase()}/guides/workflow/variables#hui-hua-bian-liang`}>{t('workflow.variableReference.conversationVars')}</a> href={docLink('/guides/workflow/variables#conversation-variables', { 'zh-Hans': '/guides/workflow/variables#hui-hua-bian-liang' })}>
{t('workflow.variableReference.conversationVars')}
</a>
</div>} </div>}
/> />
)) ))

@ -1,14 +1,12 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { useGetLanguage } from '@/context/i18n' import { useDocLink, useGetLanguage } from '@/context/i18n'
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
export const useNodeHelpLink = (nodeType: BlockEnum) => { export const useNodeHelpLink = (nodeType: BlockEnum) => {
const language = useGetLanguage() const language = useGetLanguage()
const docLink = useDocLink()
const prefixLink = useMemo(() => { const prefixLink = useMemo(() => {
if (language === 'zh_Hans') return docLink('/guides/workflow/node/')
return 'https://docs.dify.ai/zh-hans/guides/workflow/node/'
return 'https://docs.dify.ai/en/guides/workflow/node/'
}, [language]) }, [language])
const linkMap = useMemo(() => { const linkMap = useMemo(() => {
if (language === 'zh_Hans') { if (language === 'zh_Hans') {

@ -21,8 +21,8 @@ import { MittProvider, VisualEditorContextProvider, useMittContext } from './vis
import ErrorMessage from './error-message' import ErrorMessage from './error-message'
import { useVisualEditorStore } from './visual-editor/store' import { useVisualEditorStore } from './visual-editor/store'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useGetDocLanguage } from '@/context/i18n'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
import { useDocLink } from '@/context/i18n'
type JsonSchemaConfigProps = { type JsonSchemaConfigProps = {
defaultSchema?: SchemaRoot defaultSchema?: SchemaRoot
@ -53,7 +53,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
onClose, onClose,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLanguage = useGetDocLanguage() const docLink = useDocLink()
const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor) const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor)
const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA) const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA)
const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2)) const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2))
@ -252,7 +252,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
<div className='flex items-center gap-x-2 p-6 pt-5'> <div className='flex items-center gap-x-2 p-6 pt-5'>
<a <a
className='flex grow items-center gap-x-1 text-text-accent' className='flex grow items-center gap-x-1 text-text-accent'
href={`https://docs.dify.ai/${docLanguage}/guides/workflow/structured-outputs`} href={docLink('/guides/workflow/structured-outputs')}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
> >

@ -3,7 +3,6 @@ import {
useCallback, useCallback,
useState, useState,
} from 'react' } from 'react'
import { useContext } from 'use-context-selector'
import { import {
useStoreApi, useStoreApi,
} from 'reactflow' } from 'reactflow'
@ -22,13 +21,12 @@ import type {
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft' import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
import I18n from '@/context/i18n' import { useDocLink } from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
const ChatVariablePanel = () => { const ChatVariablePanel = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const docLink = useDocLink()
const store = useStoreApi() const store = useStoreApi()
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel) const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
const varList = useStore(s => s.conversationVariables) as ConversationVariable[] const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
@ -139,8 +137,11 @@ const ChatVariablePanel = () => {
<div className='system-2xs-medium-uppercase inline-block rounded-[5px] border border-divider-deep px-[5px] py-[3px] text-text-tertiary'>TIPS</div> <div className='system-2xs-medium-uppercase inline-block rounded-[5px] border border-divider-deep px-[5px] py-[3px] text-text-tertiary'>TIPS</div>
<div className='system-sm-regular mb-4 mt-1 text-text-secondary'> <div className='system-sm-regular mb-4 mt-1 text-text-secondary'>
{t('workflow.chatVariable.panelDescription')} {t('workflow.chatVariable.panelDescription')}
<a target='_blank' rel='noopener noreferrer' className='text-text-accent' href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/guides/workflow/variables#conversation-variables' : `https://docs.dify.ai/${locale.toLowerCase()}/guides/workflow/variables#hui-hua-bian-liang`}>{t('workflow.chatVariable.docLink')}</a> <a target='_blank' rel='noopener noreferrer' className='text-text-accent'
</div> href={docLink('/guides/workflow/variables#conversation-variables', { 'zh-Hans': '/guides/workflow/variables#hui-hua-bian-liang' })}>
{t('workflow.chatVariable.docLink')}
</a>
</div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<div className='radius-lg flex flex-col border border-workflow-block-border bg-workflow-block-bg p-3 pb-4 shadow-md'> <div className='radius-lg flex flex-col border border-workflow-block-border bg-workflow-block-bg p-3 pb-4 shadow-md'>
<BubbleX className='mb-1 h-4 w-4 shrink-0 text-util-colors-teal-teal-700' /> <BubbleX className='mb-1 h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />

@ -28,6 +28,7 @@ import type {
} from '@/types/workflow' } from '@/types/workflow'
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
import { hasRetryNode } from '@/app/components/workflow/utils' import { hasRetryNode } from '@/app/components/workflow/utils'
import { useDocLink } from '@/context/i18n'
type Props = { type Props = {
className?: string className?: string
@ -65,6 +66,7 @@ const NodePanel: FC<Props> = ({
doSetCollapseState(state) doSetCollapseState(state)
}, [hideProcessDetail]) }, [hideProcessDetail])
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const getTime = (time: number) => { const getTime = (time: number) => {
if (time < 1) if (time < 1)
@ -195,7 +197,7 @@ const NodePanel: FC<Props> = ({
<StatusContainer status='stopped'> <StatusContainer status='stopped'>
{nodeInfo.error} {nodeInfo.error}
<a <a
href='https://docs.dify.ai/guides/workflow/error-handling/error-type' href={docLink('/guides/workflow/error-handling/error-type')}
target='_blank' target='_blank'
className='text-text-accent' className='text-text-accent'
> >

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
import StatusContainer from '@/app/components/workflow/run/status-container' import StatusContainer from '@/app/components/workflow/run/status-container'
import { useDocLink } from '@/context/i18n'
type ResultProps = { type ResultProps = {
status: string status: string
@ -21,6 +22,7 @@ const StatusPanel: FC<ResultProps> = ({
exceptionCounts, exceptionCounts,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
return ( return (
<StatusContainer status={status}> <StatusContainer status={status}>
@ -134,7 +136,7 @@ const StatusPanel: FC<ResultProps> = ({
<div className='system-xs-medium text-text-warning'> <div className='system-xs-medium text-text-warning'>
{error} {error}
<a <a
href='https://docs.dify.ai/guides/workflow/error-handling/error-type' href={docLink('/guides/workflow/error-handling/error-type')}
target='_blank' target='_blank'
className='text-text-accent' className='text-text-accent'
> >

@ -1,7 +1,6 @@
'use client' 'use client'
import { import {
useMemo,
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -23,13 +22,11 @@ import {
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants' import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
import { getLocaleOnClient } from '@/i18n'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import DifyLogo from '../components/base/logo/dify-logo' import DifyLogo from '../components/base/logo/dify-logo'
import { useDocLink } from '@/context/i18n'
const EducationApplyAge = () => { const EducationApplyAge = () => {
const { t } = useTranslation() const { t } = useTranslation()
const locale = getLocaleOnClient()
const [schoolName, setSchoolName] = useState('') const [schoolName, setSchoolName] = useState('')
const [role, setRole] = useState('Student') const [role, setRole] = useState('Student')
const [ageChecked, setAgeChecked] = useState(false) const [ageChecked, setAgeChecked] = useState(false)
@ -43,14 +40,7 @@ const EducationApplyAge = () => {
const updateEducationStatus = useInvalidateEducationStatus() const updateEducationStatus = useInvalidateEducationStatus()
const { notify } = useToastContext() const { notify } = useToastContext()
const router = useRouter() const router = useRouter()
const docLink = useDocLink()
const docLink = useMemo(() => {
if (locale === 'zh-Hans')
return 'https://docs.dify.ai/zh-hans/getting-started/dify-for-education'
if (locale === 'ja-JP')
return 'https://docs.dify.ai/ja-jp/getting-started/dify-for-education'
return 'https://docs.dify.ai/getting-started/dify-for-education'
}, [locale])
const handleModalConfirm = () => { const handleModalConfirm = () => {
setShowModal(undefined) setShowModal(undefined)
@ -167,7 +157,7 @@ const EducationApplyAge = () => {
<div className='mb-4 mt-5 h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]'></div> <div className='mb-4 mt-5 h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]'></div>
<a <a
className='system-xs-regular flex items-center text-text-accent' className='system-xs-regular flex items-center text-text-accent'
href={docLink} href={docLink('/getting-started/dify-for-education')}
target='_blank' target='_blank'
> >
{t('education.learn')} {t('education.learn')}

@ -1,11 +1,11 @@
import React, { useEffect, useMemo, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
RiExternalLinkLine, RiExternalLinkLine,
} from '@remixicon/react' } from '@remixicon/react'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { getLocaleOnClient } from '@/i18n' import { useDocLink } from '@/context/i18n'
export type IConfirm = { export type IConfirm = {
className?: string className?: string
@ -30,20 +30,13 @@ function Confirm({
email, email,
}: IConfirm) { }: IConfirm) {
const { t } = useTranslation() const { t } = useTranslation()
const locale = getLocaleOnClient() const docLink = useDocLink()
const dialogRef = useRef<HTMLDivElement>(null) const dialogRef = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(isShow) const [isVisible, setIsVisible] = useState(isShow)
const eduDocLink = docLink('/getting-started/dify-for-education')
const docLink = useMemo(() => {
if (locale === 'zh-Hans')
return 'https://docs.dify.ai/zh-hans/getting-started/dify-for-education'
if (locale === 'ja-JP')
return 'https://docs.dify.ai/ja-jp/getting-started/dify-for-education'
return 'https://docs.dify.ai/getting-started/dify-for-education'
}, [locale])
const handleClick = () => { const handleClick = () => {
window.open(docLink, '_blank', 'noopener,noreferrer') window.open(eduDocLink, '_blank', 'noopener,noreferrer')
} }
useEffect(() => { useEffect(() => {
@ -106,7 +99,7 @@ function Confirm({
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
{showLink && ( {showLink && (
<> <>
<a onClick={handleClick} href={docLink} target='_blank' className='system-xs-regular cursor-pointer text-text-accent'>{t('education.learn')}</a> <a onClick={handleClick} href={eduDocLink} target='_blank' className='system-xs-regular cursor-pointer text-text-accent'>{t('education.learn')}</a>
<RiExternalLinkLine className='h-3 w-3 text-text-accent' /> <RiExternalLinkLine className='h-3 w-3 text-text-accent' />
</> </>
)} )}

@ -17,6 +17,7 @@ import Button from '@/app/components/base/button'
import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common' import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common' import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
import useDocumentTitle from '@/hooks/use-document-title' import useDocumentTitle from '@/hooks/use-document-title'
import { useDocLink } from '@/context/i18n'
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
@ -36,6 +37,7 @@ type AccountFormValues = z.infer<typeof accountFormSchema>
const InstallForm = () => { const InstallForm = () => {
useDocumentTitle('') useDocumentTitle('')
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter() const router = useRouter()
const [showPassword, setShowPassword] = React.useState(false) const [showPassword, setShowPassword] = React.useState(false)
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true)
@ -174,7 +176,7 @@ const InstallForm = () => {
<Link <Link
className='text-text-accent' className='text-text-accent'
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
href={'https://docs.dify.ai/user-agreement/open-source'} href={docLink('/policies/open-source')}
>{t('login.license.link')}</Link> >{t('login.license.link')}</Link>
</div> </div>
</div> </div>

@ -1,5 +1,6 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
@ -18,10 +19,11 @@ import Toast from '@/app/components/base/toast'
export default function InviteSettingsPage() { export default function InviteSettingsPage() {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const token = decodeURIComponent(searchParams.get('invite_token') as string) const token = decodeURIComponent(searchParams.get('invite_token') as string)
const { locale, setLocaleOnClient } = useContext(I18n) const { setLocaleOnClient } = useContext(I18n)
const [name, setName] = useState('') const [name, setName] = useState('')
const [language, setLanguage] = useState(LanguagesSupported[0]) const [language, setLanguage] = useState(LanguagesSupported[0])
const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles') const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles')
@ -147,7 +149,7 @@ export default function InviteSettingsPage() {
<Link <Link
className='system-xs-medium text-text-accent-secondary' className='system-xs-medium text-text-accent-secondary'
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
href={`https://docs.dify.ai/${language !== LanguagesSupported[1] ? 'user-agreement' : `v/${locale.toLowerCase()}/policies`}/open-source`} href={docLink('/policies/open-source')}
>{t('login.license.link')}</Link> >{t('login.license.link')}</Link>
</div> </div>
</div> </div>

@ -12,6 +12,7 @@ import { timezones } from '@/utils/timezone'
import { LanguagesSupported, languages } from '@/i18n/language' import { LanguagesSupported, languages } from '@/i18n/language'
import { oneMoreStep } from '@/service/common' import { oneMoreStep } from '@/service/common'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useDocLink } from '@/context/i18n'
type IState = { type IState = {
formState: 'processing' | 'error' | 'success' | 'initial' formState: 'processing' | 'error' | 'success' | 'initial'
@ -51,6 +52,7 @@ const reducer: Reducer<IState, IAction> = (state: IState, action: IAction) => {
const OneMoreStep = () => { const OneMoreStep = () => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
@ -164,7 +166,7 @@ const OneMoreStep = () => {
<Link <Link
className='system-xs-medium text-text-accent-secondary' className='system-xs-medium text-text-accent-secondary'
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
href={'https://docs.dify.ai/en/policies/agreement/README'} href={docLink('/policies/agreement/README')}
>{t('login.license.link')}</Link> >{t('login.license.link')}</Link>
</div> </div>
</div> </div>

@ -35,4 +35,17 @@ export const useGetPricingPageLanguage = () => {
return getPricingPageLanguage(locale) return getPricingPageLanguage(locale)
} }
const defaultDocBaseUrl = 'https://docs.dify.ai'
export const useDocLink = (baseUrl?: string): ((path?: string, pathMap?: { [index: string]: string }) => string) => {
let baseDocUrl = baseUrl || defaultDocBaseUrl
baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl
const { locale } = useI18N()
const docLanguage = getDocLanguage(locale)
return (path?: string, pathMap?: { [index: string]: string }): string => {
const pathUrl = path || ''
let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
targetPath = (targetPath.startsWith('/')) ? targetPath.slice(0, -1) : targetPath
return `${baseDocUrl}/${docLanguage}/${targetPath}`
}
}
export default I18NContext export default I18NContext

@ -2,7 +2,7 @@ const translation = {
toVerified: '教育認証を取得', toVerified: '教育認証を取得',
toVerifiedTip: { toVerifiedTip: {
front: '現在、教育認証ステータスを取得する資格があります。以下に教育情報を入力し、認証プロセスを完了すると、Dify プロフェッショナルプランの', front: '現在、教育認証ステータスを取得する資格があります。以下に教育情報を入力し、認証プロセスを完了すると、Dify プロフェッショナルプランの',
coupon: '50割引クーポン', coupon: '100割引クーポン',
end: 'を受け取ることができます。', end: 'を受け取ることができます。',
}, },
currentSigned: '現在ログイン中のアカウントは', currentSigned: '現在ログイン中のアカウントは',
@ -38,9 +38,9 @@ const translation = {
submitError: 'フォームの送信に失敗しました。しばらくしてから再度ご提出ください。', submitError: 'フォームの送信に失敗しました。しばらくしてから再度ご提出ください。',
learn: '教育認証の取得方法はこちら', learn: '教育認証の取得方法はこちら',
successTitle: 'Dify 教育認証を取得しました!', successTitle: 'Dify 教育認証を取得しました!',
successContent: 'お客様のアカウントに Dify プロフェッショナルプランの 50% 割引クーポン を発行しました。有効期間は 1 年間 ですので、期限内にご利用ください。', successContent: 'お客様のアカウントに Dify プロフェッショナルプランの 100% 割引クーポン を発行しました。有効期間は 1 年間 ですので、期限内にご利用ください。',
rejectTitle: 'Dify 教育認証が拒否されました', rejectTitle: 'Dify 教育認証が拒否されました',
rejectContent: '申し訳ございませんが、このメールアドレスでは 教育認証 の資格を取得できず、Dify プロフェッショナルプランの 50割引クーポン を受け取ることはできません。', rejectContent: '申し訳ございませんが、このメールアドレスでは 教育認証 の資格を取得できず、Dify プロフェッショナルプランの 100割引クーポン を受け取ることはできません。',
emailLabel: '現在のメールアドレス', emailLabel: '現在のメールアドレス',
} }

Loading…
Cancel
Save