Merge branch 'main' into feat/r2

This commit is contained in:
jyong 2025-06-16 14:08:02 +08:00
commit b277acc298
74 changed files with 420 additions and 280 deletions

28
.github/workflows/deploy-rag-dev.yml vendored Normal file
View File

@ -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 }}

View File

@ -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 += ["11210:11210"]' -i docker/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
echo "Ports exposed for sandbox, weaviate, tidb, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase, opengauss"

View File

@ -31,6 +31,13 @@ jobs:
with:
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
uses: ./.github/actions/setup-uv
with:
@ -59,7 +66,7 @@ jobs:
tidb
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
with:
compose-file: |
@ -75,9 +82,12 @@ jobs:
pgvector
chroma
elasticsearch
oceanbase
- name: Check TiDB Ready
run: uv run --project api python api/tests/integration_tests/vdb/tidb_vector/check_tiflash_ready.py
- name: Check VDB Ready (TiDB, Oceanbase)
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
run: uv run --project api bash dev/pytest/pytest_vdb.sh

View File

@ -27,7 +27,7 @@ from models.dataset import Dataset, DatasetCollectionBinding, DatasetMetadata, D
from models.dataset import Document as DatasetDocument
from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation
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.plugin.data_migration import PluginDataMigration
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_salt = base64_salt
db.session.commit()
AccountService.reset_login_error_rate_limit(email)
click.echo(click.style("Password reset successfully.", fg="green"))

View File

@ -47,7 +47,13 @@ class AppInfoApi(Resource):
def get(self, app_model: App):
"""Get app information"""
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")

View File

@ -1,3 +1,4 @@
import logging
import time
from collections.abc import Generator, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Optional, Union
@ -33,6 +34,8 @@ from models.model import App, AppMode, Message, MessageAnnotation
if TYPE_CHECKING:
from core.file.models import File
_logger = logging.getLogger(__name__)
class AppRunner:
def get_pre_calculate_rest_tokens(
@ -298,7 +301,7 @@ class AppRunner:
)
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:
"""
Handle invoke result
@ -317,18 +320,28 @@ class AppRunner:
else:
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:
model = result.model
if not prompt_messages:
prompt_messages = result.prompt_messages
prompt_messages = list(result.prompt_messages)
if result.delta.usage:
usage = result.delta.usage
if not usage:
if usage is None:
usage = LLMUsage.empty_usage()
llm_result = LLMResult(

View File

@ -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.message_entities import (
AssistantPromptMessage,
TextPromptMessageContent,
)
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.ops.entities.trace_entity import TraceTaskName
@ -309,6 +310,23 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
delta_text = chunk.delta.message.content
if delta_text is None:
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:
self._task_state.llm_result.prompt_messages = chunk.prompt_messages

View File

@ -80,6 +80,23 @@ class OceanBaseVector(BaseVector):
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 = [
Column("id", String(36), primary_key=True, autoincrement=False),
Column("vector", VECTOR(self._vec_dim)),
@ -110,22 +127,6 @@ class OceanBaseVector(BaseVector):
+ "to support fulltext index and vector index in the same table",
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)
def _check_hybrid_search_support(self) -> bool:

View File

@ -6,7 +6,7 @@ import json
import logging
from typing import Optional, Union
from sqlalchemy import select
from sqlalchemy import func, select
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker
@ -151,11 +151,11 @@ class SQLAlchemyWorkflowExecutionRepository(WorkflowExecutionRepository):
existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_))
if not existing:
# 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.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
else:
# For updates, keep the existing sequence number

View File

@ -6,7 +6,6 @@ from pydantic import BaseModel, Field
from core.model_runtime.entities.llm_entities import LLMUsage
from core.rag.entities.citation_metadata import RetrievalSourceMetadata
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
class RunCompletedEvent(BaseModel):
@ -39,11 +38,3 @@ class RunRetryEvent(BaseModel):
error: str = Field(..., description="error")
retry_index: int = Field(..., description="Retry attempt number")
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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"))
db.session.close()
return
tenant_id = dataset.tenant_id
for document_id in document_ids:
retry_indexing_cache_key = "document_{}_is_retried".format(document_id)
# check document limit
features = FeatureService.get_features(dataset.tenant_id)
features = FeatureService.get_features(tenant_id)
try:
if features.billing.enabled:
vector_space = features.vector_space

View File

@ -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()

View File

@ -1,15 +1,11 @@
from unittest.mock import MagicMock, patch
import pytest
from core.rag.datasource.vdb.oceanbase.oceanbase_vector import (
OceanBaseVector,
OceanBaseVectorConfig,
)
from tests.integration_tests.vdb.__mock.tcvectordb import setup_tcvectordb_mock
from tests.integration_tests.vdb.test_vector_store import (
AbstractVectorTest,
get_example_text,
setup_mock_redis,
)
@ -20,10 +16,11 @@ def oceanbase_vector():
"dify_test_collection",
config=OceanBaseVectorConfig(
host="127.0.0.1",
port="2881",
user="root@test",
port=2881,
user="root",
database="test",
password="test",
password="difyai123456",
enable_hybrid_search=True,
),
)
@ -33,39 +30,13 @@ class OceanBaseVectorTest(AbstractVectorTest):
super().__init__()
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):
ids = self.vector.get_ids_by_metadata_field(key="document_id", value=self.example_doc_id)
assert len(ids) == 0
@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
assert len(ids) == 1
def test_oceanbase_vector(
setup_mock_redis,
setup_mock_oceanbase_client,
setup_mock_oceanbase_vector,
oceanbase_vector,
):
OceanBaseVectorTest(oceanbase_vector).run_all_tests()

View File

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

View File

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

View File

@ -103,6 +103,7 @@ services:
PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages}
PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
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_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@ import {
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { fetchMembers } from '@/service/common'
import type { Member } from '@/models/common'
import { useDocLink } from '@/context/i18n'
type SettingsModalProps = {
currentDataset: DataSet
@ -58,6 +59,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
currentModel: isRerankDefaultModelValid,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useToastContext()
const ref = useRef(null)
const isExternal = currentDataset.provider === 'external'
@ -328,7 +330,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
<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'>
<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')}
</div>
</div>

View File

@ -2,9 +2,7 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import { useDocLink } from '@/context/i18n'
type Props = {
onReturnToSimpleMode: () => void
}
@ -13,7 +11,7 @@ const AdvancedModeWarning: FC<Props> = ({
onReturnToSimpleMode,
}) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const docLink = useDocLink()
const [show, setShow] = React.useState(true)
if (!show)
return null
@ -25,7 +23,7 @@ const AdvancedModeWarning: FC<Props> = ({
<span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
<a
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'
>
{t('appDebug.promptMode.advancedWarning.learnMore')}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useState } from 'react'
import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react'
import Link from 'next/link'
import { Trans, useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
import Modal from '@/app/components/base/modal'
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 { AppIconType, AppSSO, Language } from '@/types/app'
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 { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import type { AppIconSelection } 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 { useDocLink } from '@/context/i18n'
export type ISettingsModalProps = {
isChat: boolean
@ -98,7 +97,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
const [language, setLanguage] = useState(default_language)
const [saveLoading, setSaveLoading] = useState(false)
const { t } = useTranslation()
const { locale } = useContext(I18n)
const docLink = useDocLink()
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [appIcon, setAppIcon] = useState<AppIconSelection>(
@ -238,7 +237,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
<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>
{/* form body */}

View File

@ -1,6 +1,5 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { RiCloseLine, RiInformation2Fill } from '@remixicon/react'
import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper'
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 type { PromptVariable } from '@/models/debug'
import type { InputVar } from '@/app/components/workflow/types'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import { useDocLink } from '@/context/i18n'
type Props = {
show: boolean
@ -48,7 +46,7 @@ const NewFeaturePanel = ({
onAutoAddPromptVariable,
}: Props) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const docLink = useDocLink()
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
@ -80,7 +78,7 @@ const NewFeaturePanel = ({
<span>{isChatMode ? t('workflow.common.fileUploadTip') : t('workflow.common.ImageUploadLegacyTip')}</span>
<a
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'
>{t('workflow.common.featuresDocLink')}</a>
</div>

View File

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

View File

@ -63,6 +63,7 @@ import CustomDialog from '@/app/components/base/dialog'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
const TextLabel: FC<PropsWithChildren> = (props) => {
return <label className='system-sm-semibold text-text-secondary'>{props.children}</label>
@ -146,6 +147,7 @@ const StepTwo = ({
updateRetrievalMethodCache,
}: StepTwoProps) => {
const { t } = useTranslation()
const docLink = useDocLink()
const { locale } = useContext(I18n)
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
@ -962,7 +964,9 @@ const StepTwo = ({
<div className={'mb-1'}>
<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'>
<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')}
</div>
</div>

View File

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

View File

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

View File

@ -29,8 +29,7 @@ import { useChildSegmentListKey, useSegmentListKey } from '@/service/knowledge/u
import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata'
import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer'
import StatusWithAction from '../common/document-status-with-action/status-with-action'
import { LanguagesSupported } from '@/i18n/language'
import { getLocaleOnClient } from '@/i18n'
import { useDocLink } from '@/context/i18n'
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 ?? ''}>
@ -86,6 +85,7 @@ const DEFAULT_LIMIT = 10
const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
const { t } = useTranslation()
const docLink = useDocLink()
const { plan } = useProviderContext()
const isFreePlan = plan.type === 'sandbox'
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 isDataSourceFile = dataset?.data_source_type === DataSourceType.FILE
const embeddingAvailable = !!dataset?.embedding_available
const locale = getLocaleOnClient()
const debouncedSearchValue = useDebounce(searchValue, { wait: 500 })
const { data: documentsRes, isFetching: isListLoading } = useDocumentList({
@ -262,11 +261,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
<a
className='flex items-center text-text-accent'
target='_blank'
href={
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'
}
href={docLink('/guides/knowledge-base/integrate-knowledge-within-application')}
>
<span>{t('datasetDocuments.list.learnMore')}</span>
<RiExternalLinkLine className='h-3 w-3' />

View File

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

View File

@ -12,6 +12,7 @@ import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading'
import { useModalContext } from '@/context/modal-context'
import { useDocLink } from '@/context/i18n'
type ExternalAPIPanelProps = {
onClose: () => void
@ -19,6 +20,7 @@ type ExternalAPIPanelProps = {
const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => {
const { t } = useTranslation()
const docLink = useDocLink()
const { setShowExternalKnowledgeAPIModal } = useModalContext()
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='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>
<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' />
<div className='body-xs-regular grow text-text-accent'>{t('dataset.externalAPIPanelDocumentation')}</div>
</a>

View File

@ -1,8 +1,10 @@
import { RiBookOpenLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
const InfoPanel = () => {
const { t } = useTranslation()
const docLink = useDocLink()
return (
<div className='flex w-[360px] flex-col items-start pb-2 pr-8 pt-[108px]'>
@ -16,12 +18,15 @@ const InfoPanel = () => {
</span>
<span className='system-sm-regular text-text-tertiary'>
{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')}
</a>
{t('dataset.connectDatasetIntro.content.end')}
</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')}
</a>
</p>

View File

@ -11,6 +11,7 @@ import InfoPanel from './InfoPanel'
import type { CreateKnowledgeBaseReq } from './declarations'
import Divider from '@/app/components/base/divider'
import Button from '@/app/components/base/button'
import { useDocLink } from '@/context/i18n'
type ExternalKnowledgeBaseCreateProps = {
onConnect: (formValue: CreateKnowledgeBaseReq) => void
@ -19,6 +20,7 @@ type ExternalKnowledgeBaseCreateProps = {
const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect, loading }) => {
const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter()
const [formData, setFormData] = useState<CreateKnowledgeBaseReq>({
name: '',
@ -59,7 +61,7 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
<span>{t('dataset.connectHelper.helper1')}</span>
<span className='system-sm-medium text-text-secondary'>{t('dataset.connectHelper.helper2')}</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')}
</a>
<span>{t('dataset.connectHelper.helper5')} </span>

View File

@ -11,6 +11,7 @@ import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/ec
import Button from '@/app/components/base/button'
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { useDocLink } from '@/context/i18n'
type Props = {
indexMethod: string
@ -29,6 +30,7 @@ const ModifyRetrievalModal: FC<Props> = ({
}) => {
const ref = useRef(null)
const { t } = useTranslation()
const docLink = useDocLink()
const [retrievalConfig, setRetrievalConfig] = useState(value)
// useClickAway(() => {
@ -72,7 +74,7 @@ const ModifyRetrievalModal: FC<Props> = ({
<a
target='_blank'
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'
>
{t('datasetSettings.form.retrievalSetting.learnMore')}

View File

@ -32,6 +32,7 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
import { fetchMembers } from '@/service/common'
import type { Member } from '@/models/common'
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
import { useDocLink } from '@/context/i18n'
const rowClass = 'flex'
const labelClass = `
@ -46,6 +47,7 @@ const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
const Form = () => {
const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useContext(ToastContext)
const { mutate } = useSWRConfig()
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
@ -308,7 +310,7 @@ const Form = () => {
<div>
<div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div>
<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')}
</div>
</div>

View File

@ -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>
</Property>
<Property name='user' type='string' key='user'>
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.
User identifier, used to define the identity of the end-user, convenient for retrieval and statistics.
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 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.
@ -220,7 +220,7 @@ The text generation application offers non-session support and is ideal for tran
- `file` (File) Required
The file to be uploaded.
- `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
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
Request Body
- `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
- `result` (string) Always returns "success"
</Col>
@ -512,6 +512,8 @@ The text generation application offers non-session support and is ideal for tran
- `name` (string) application name
- `description` (string) application description
- `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) author name
</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}'`}>
@ -528,7 +530,9 @@ The text generation application offers non-session support and is ideal for tran
"tags": [
"tag1",
"tag2"
]
],
"mode": "chat",
"author_name": "Dify"
}
```
</CodeGroup>

View File

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

View File

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

View File

@ -59,7 +59,7 @@ Chat applications support session persistence, allowing previous chat history to
</Property>
<Property name='user' type='string' key='user'>
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 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.
@ -324,7 +324,7 @@ Chat applications support session persistence, allowing previous chat history to
- `file` (File) Required
The file to be uploaded.
- `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
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
### Request Body
- `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
- `result` (string) Always returns "success"
</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`
</Property>
<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 name='content' type='string' key='content'>
The specific content of message feedback.
@ -1123,6 +1123,8 @@ Chat applications support session persistence, allowing previous chat history to
- `name` (string) application name
- `description` (string) application description
- `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</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}'`}>
@ -1139,7 +1141,9 @@ Chat applications support session persistence, allowing previous chat history to
"tags": [
"tag1",
"tag2"
]
],
"mode": "advanced-chat",
"author_name": "Dify"
}
```
</CodeGroup>

View File

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

View File

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

View File

@ -287,7 +287,7 @@ Chat applications support session persistence, allowing previous chat history to
- `file` (File) Required
The file to be uploaded.
- `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
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
### Request Body
- `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
- `result` (string) Always returns "success"
</Col>
@ -1151,6 +1151,8 @@ Chat applications support session persistence, allowing previous chat history to
- `name` (string) application name
- `description` (string) application description
- `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</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}'`}>
@ -1167,7 +1169,9 @@ Chat applications support session persistence, allowing previous chat history to
"tags": [
"tag1",
"tag2"
]
],
"mode": "advanced-chat",
"author_name": "Dify"
}
```
</CodeGroup>

View File

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

View File

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

View File

@ -64,6 +64,8 @@ Workflow applications offers non-session support and is ideal for translation, a
- `user` (string) Required
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.
<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
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
### Request Body
- `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
- `result` (string) Always returns "success"
</Col>
@ -448,7 +450,7 @@ Workflow applications offers non-session support and is ideal for translation, a
- `file` (File) Required
The file to be uploaded.
- `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
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
- `description` (string) application description
- `tags` (array[string]) application tags
- `mode` (string) application mode
- `author_name` (string) application author name
</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}'`}>
@ -641,7 +645,9 @@ Workflow applications offers non-session support and is ideal for translation, a
"tags": [
"tag1",
"tag2"
]
],
"mode": "workflow",
"author_name": "Dify"
}
```
</CodeGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,12 +2,10 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import VarReferenceVars from './var-reference-vars'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import ListEmpty from '@/app/components/base/list-empty'
import { LanguagesSupported } from '@/i18n/language'
import I18n from '@/context/i18n'
import { useDocLink } from '@/context/i18n'
type Props = {
vars: NodeOutPutVar[]
@ -24,7 +22,7 @@ const VarReferencePopup: FC<Props> = ({
isSupportFileVar = true,
}) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const docLink = useDocLink()
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
return (
<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'>
{t('workflow.variableReference.assignedVarsDescription')}
<a target='_blank' rel='noopener noreferrer'
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>
className='text-text-accent-secondary'
href={docLink('/guides/workflow/variables#conversation-variables', { 'zh-Hans': '/guides/workflow/variables#hui-hua-bian-liang' })}>
{t('workflow.variableReference.conversationVars')}
</a>
</div>}
/>
))

View File

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

View File

@ -21,8 +21,8 @@ import { MittProvider, VisualEditorContextProvider, useMittContext } from './vis
import ErrorMessage from './error-message'
import { useVisualEditorStore } from './visual-editor/store'
import Toast from '@/app/components/base/toast'
import { useGetDocLanguage } from '@/context/i18n'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
import { useDocLink } from '@/context/i18n'
type JsonSchemaConfigProps = {
defaultSchema?: SchemaRoot
@ -53,7 +53,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
onClose,
}) => {
const { t } = useTranslation()
const docLanguage = useGetDocLanguage()
const docLink = useDocLink()
const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor)
const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA)
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'>
<a
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'
rel='noopener noreferrer'
>

View File

@ -3,7 +3,6 @@ import {
useCallback,
useState,
} from 'react'
import { useContext } from 'use-context-selector'
import {
useStoreApi,
} from 'reactflow'
@ -22,13 +21,12 @@ import type {
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
import { BlockEnum } from '@/app/components/workflow/types'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import { useDocLink } from '@/context/i18n'
import cn from '@/utils/classnames'
const ChatVariablePanel = () => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const docLink = useDocLink()
const store = useStoreApi()
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
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-sm-regular mb-4 mt-1 text-text-secondary'>
{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>
</div>
<a target='_blank' rel='noopener noreferrer' className='text-text-accent'
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='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' />

View File

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

View File

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

View File

@ -1,7 +1,6 @@
'use client'
import {
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
@ -23,13 +22,11 @@ import {
import { useProviderContext } from '@/context/provider-context'
import { useToastContext } from '@/app/components/base/toast'
import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
import { getLocaleOnClient } from '@/i18n'
import { noop } from 'lodash-es'
import DifyLogo from '../components/base/logo/dify-logo'
import { useDocLink } from '@/context/i18n'
const EducationApplyAge = () => {
const { t } = useTranslation()
const locale = getLocaleOnClient()
const [schoolName, setSchoolName] = useState('')
const [role, setRole] = useState('Student')
const [ageChecked, setAgeChecked] = useState(false)
@ -43,14 +40,7 @@ const EducationApplyAge = () => {
const updateEducationStatus = useInvalidateEducationStatus()
const { notify } = useToastContext()
const router = useRouter()
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 docLink = useDocLink()
const handleModalConfirm = () => {
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>
<a
className='system-xs-regular flex items-center text-text-accent'
href={docLink}
href={docLink('/getting-started/dify-for-education')}
target='_blank'
>
{t('education.learn')}

View File

@ -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 { useTranslation } from 'react-i18next'
import {
RiExternalLinkLine,
} from '@remixicon/react'
import Button from '@/app/components/base/button'
import { getLocaleOnClient } from '@/i18n'
import { useDocLink } from '@/context/i18n'
export type IConfirm = {
className?: string
@ -30,20 +30,13 @@ function Confirm({
email,
}: IConfirm) {
const { t } = useTranslation()
const locale = getLocaleOnClient()
const docLink = useDocLink()
const dialogRef = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(isShow)
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 eduDocLink = docLink('/getting-started/dify-for-education')
const handleClick = () => {
window.open(docLink, '_blank', 'noopener,noreferrer')
window.open(eduDocLink, '_blank', 'noopener,noreferrer')
}
useEffect(() => {
@ -106,7 +99,7 @@ function Confirm({
<div className='flex items-center gap-1'>
{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' />
</>
)}

View File

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

View File

@ -1,5 +1,6 @@
'use client'
import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
import { useCallback, useState } from 'react'
import Link from 'next/link'
import { useContext } from 'use-context-selector'
@ -18,10 +19,11 @@ import Toast from '@/app/components/base/toast'
export default function InviteSettingsPage() {
const { t } = useTranslation()
const docLink = useDocLink()
const router = useRouter()
const searchParams = useSearchParams()
const token = decodeURIComponent(searchParams.get('invite_token') as string)
const { locale, setLocaleOnClient } = useContext(I18n)
const { setLocaleOnClient } = useContext(I18n)
const [name, setName] = useState('')
const [language, setLanguage] = useState(LanguagesSupported[0])
const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles')
@ -147,7 +149,7 @@ export default function InviteSettingsPage() {
<Link
className='system-xs-medium text-text-accent-secondary'
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>
</div>
</div>

View File

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

View File

@ -35,4 +35,17 @@ export const useGetPricingPageLanguage = () => {
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

View File

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