merge main

pull/21398/head
zxhlyh 10 months ago
commit 3164f90327

3
.gitignore vendored

@ -210,3 +210,6 @@ mise.toml
# Next.js build output # Next.js build output
.next/ .next/
# AI Assistant
.roo/

@ -5,7 +5,7 @@ import uuid
from collections.abc import Generator, Mapping from collections.abc import Generator, Mapping
from typing import Any, Literal, Optional, Union, overload from typing import Any, Literal, Optional, Union, overload
from flask import Flask, copy_current_request_context, current_app, has_request_context from flask import Flask, current_app
from pydantic import ValidationError from pydantic import ValidationError
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -31,6 +31,7 @@ from core.workflow.repositories.workflow_execution_repository import WorkflowExe
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from libs.flask_utils import preserve_flask_contexts
from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom
from models.enums import WorkflowRunTriggeredFrom from models.enums import WorkflowRunTriggeredFrom
from services.conversation_service import ConversationService from services.conversation_service import ConversationService
@ -399,20 +400,17 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
# new thread with request context and contextvars # new thread with request context and contextvars
context = contextvars.copy_context() context = contextvars.copy_context()
@copy_current_request_context worker_thread = threading.Thread(
def worker_with_context(): target=self._generate_worker,
# Run the worker within the copied context kwargs={
return context.run( "flask_app": current_app._get_current_object(), # type: ignore
self._generate_worker, "application_generate_entity": application_generate_entity,
flask_app=current_app._get_current_object(), # type: ignore "queue_manager": queue_manager,
application_generate_entity=application_generate_entity, "conversation_id": conversation.id,
queue_manager=queue_manager, "message_id": message.id,
conversation_id=conversation.id, "context": context,
message_id=message.id, },
context=context, )
)
worker_thread = threading.Thread(target=worker_with_context)
worker_thread.start() worker_thread.start()
@ -449,24 +447,9 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
:param message_id: message ID :param message_id: message ID
:return: :return:
""" """
for var, val in context.items():
var.set(val)
# FIXME(-LAN-): Save current user before entering new app context
from flask import g
saved_user = None with preserve_flask_contexts(flask_app, context_vars=context):
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
with flask_app.app_context():
try: try:
# Restore user in new app context
if saved_user is not None:
from flask import g
g._login_user = saved_user
# get conversation and message # get conversation and message
conversation = self._get_conversation(conversation_id) conversation = self._get_conversation(conversation_id)
message = self._get_message(message_id) message = self._get_message(message_id)

@ -5,7 +5,7 @@ import uuid
from collections.abc import Generator, Mapping from collections.abc import Generator, Mapping
from typing import Any, Literal, Union, overload from typing import Any, Literal, Union, overload
from flask import Flask, copy_current_request_context, current_app, has_request_context from flask import Flask, current_app
from pydantic import ValidationError from pydantic import ValidationError
from configs import dify_config from configs import dify_config
@ -23,6 +23,7 @@ from core.model_runtime.errors.invoke import InvokeAuthorizationError
from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from libs.flask_utils import preserve_flask_contexts
from models import Account, App, EndUser from models import Account, App, EndUser
from services.conversation_service import ConversationService from services.conversation_service import ConversationService
from services.errors.message import MessageNotExistsError from services.errors.message import MessageNotExistsError
@ -182,20 +183,17 @@ class AgentChatAppGenerator(MessageBasedAppGenerator):
# new thread with request context and contextvars # new thread with request context and contextvars
context = contextvars.copy_context() context = contextvars.copy_context()
@copy_current_request_context worker_thread = threading.Thread(
def worker_with_context(): target=self._generate_worker,
# Run the worker within the copied context kwargs={
return context.run( "flask_app": current_app._get_current_object(), # type: ignore
self._generate_worker, "context": context,
flask_app=current_app._get_current_object(), # type: ignore "application_generate_entity": application_generate_entity,
context=context, "queue_manager": queue_manager,
application_generate_entity=application_generate_entity, "conversation_id": conversation.id,
queue_manager=queue_manager, "message_id": message.id,
conversation_id=conversation.id, },
message_id=message.id, )
)
worker_thread = threading.Thread(target=worker_with_context)
worker_thread.start() worker_thread.start()
@ -229,24 +227,9 @@ class AgentChatAppGenerator(MessageBasedAppGenerator):
:param message_id: message ID :param message_id: message ID
:return: :return:
""" """
for var, val in context.items():
var.set(val)
# FIXME(-LAN-): Save current user before entering new app context
from flask import g
saved_user = None with preserve_flask_contexts(flask_app, context_vars=context):
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
with flask_app.app_context():
try: try:
# Restore user in new app context
if saved_user is not None:
from flask import g
g._login_user = saved_user
# get conversation and message # get conversation and message
conversation = self._get_conversation(conversation_id) conversation = self._get_conversation(conversation_id)
message = self._get_message(message_id) message = self._get_message(message_id)

@ -5,7 +5,7 @@ import uuid
from collections.abc import Generator, Mapping, Sequence from collections.abc import Generator, Mapping, Sequence
from typing import Any, Literal, Optional, Union, overload from typing import Any, Literal, Optional, Union, overload
from flask import Flask, copy_current_request_context, current_app, has_request_context from flask import Flask, current_app
from pydantic import ValidationError from pydantic import ValidationError
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -29,6 +29,7 @@ from core.workflow.repositories.workflow_execution_repository import WorkflowExe
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from libs.flask_utils import preserve_flask_contexts
from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom
from models.enums import WorkflowRunTriggeredFrom from models.enums import WorkflowRunTriggeredFrom
@ -209,19 +210,16 @@ class WorkflowAppGenerator(BaseAppGenerator):
# new thread with request context and contextvars # new thread with request context and contextvars
context = contextvars.copy_context() context = contextvars.copy_context()
@copy_current_request_context worker_thread = threading.Thread(
def worker_with_context(): target=self._generate_worker,
# Run the worker within the copied context kwargs={
return context.run( "flask_app": current_app._get_current_object(), # type: ignore
self._generate_worker, "application_generate_entity": application_generate_entity,
flask_app=current_app._get_current_object(), # type: ignore "queue_manager": queue_manager,
application_generate_entity=application_generate_entity, "context": context,
queue_manager=queue_manager, "workflow_thread_pool_id": workflow_thread_pool_id,
context=context, },
workflow_thread_pool_id=workflow_thread_pool_id, )
)
worker_thread = threading.Thread(target=worker_with_context)
worker_thread.start() worker_thread.start()
@ -408,24 +406,9 @@ class WorkflowAppGenerator(BaseAppGenerator):
:param workflow_thread_pool_id: workflow thread pool id :param workflow_thread_pool_id: workflow thread pool id
:return: :return:
""" """
for var, val in context.items():
var.set(val)
# FIXME(-LAN-): Save current user before entering new app context
from flask import g
saved_user = None with preserve_flask_contexts(flask_app, context_vars=context):
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
with flask_app.app_context():
try: try:
# Restore user in new app context
if saved_user is not None:
from flask import g
g._login_user = saved_user
# workflow app # workflow app
runner = WorkflowAppRunner( runner = WorkflowAppRunner(
application_generate_entity=application_generate_entity, application_generate_entity=application_generate_entity,

@ -9,7 +9,7 @@ from copy import copy, deepcopy
from datetime import UTC, datetime from datetime import UTC, datetime
from typing import Any, Optional, cast from typing import Any, Optional, cast
from flask import Flask, current_app, has_request_context from flask import Flask, current_app
from configs import dify_config from configs import dify_config
from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError
@ -53,6 +53,7 @@ from core.workflow.nodes.end.end_stream_processor import EndStreamProcessor
from core.workflow.nodes.enums import ErrorStrategy, FailBranchSourceHandle from core.workflow.nodes.enums import ErrorStrategy, FailBranchSourceHandle
from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from libs.flask_utils import preserve_flask_contexts
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowType from models.workflow import WorkflowType
@ -537,24 +538,9 @@ class GraphEngine:
""" """
Run parallel nodes Run parallel nodes
""" """
for var, val in context.items():
var.set(val)
# FIXME(-LAN-): Save current user before entering new app context with preserve_flask_contexts(flask_app, context_vars=context):
from flask import g
saved_user = None
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
with flask_app.app_context():
try: try:
# Restore user in new app context
if saved_user is not None:
from flask import g
g._login_user = saved_user
q.put( q.put(
ParallelBranchRunStartedEvent( ParallelBranchRunStartedEvent(
parallel_id=parallel_id, parallel_id=parallel_id,

@ -7,7 +7,7 @@ from datetime import UTC, datetime
from queue import Empty, Queue from queue import Empty, Queue
from typing import TYPE_CHECKING, Any, Optional, cast from typing import TYPE_CHECKING, Any, Optional, cast
from flask import Flask, current_app, has_request_context from flask import Flask, current_app
from configs import dify_config from configs import dify_config
from core.variables import ArrayVariable, IntegerVariable, NoneVariable from core.variables import ArrayVariable, IntegerVariable, NoneVariable
@ -37,6 +37,7 @@ from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.event import NodeEvent, RunCompletedEvent
from core.workflow.nodes.iteration.entities import ErrorHandleMode, IterationNodeData from core.workflow.nodes.iteration.entities import ErrorHandleMode, IterationNodeData
from libs.flask_utils import preserve_flask_contexts
from .exc import ( from .exc import (
InvalidIteratorValueError, InvalidIteratorValueError,
@ -583,23 +584,8 @@ class IterationNode(BaseNode[IterationNodeData]):
""" """
run single iteration in parallel mode run single iteration in parallel mode
""" """
for var, val in context.items():
var.set(val)
# FIXME(-LAN-): Save current user before entering new app context
from flask import g
saved_user = None
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
with flask_app.app_context():
# Restore user in new app context
if saved_user is not None:
from flask import g
g._login_user = saved_user
with preserve_flask_contexts(flask_app, context_vars=context):
parallel_mode_run_id = uuid.uuid4().hex parallel_mode_run_id = uuid.uuid4().hex
graph_engine_copy = graph_engine.create_copy() graph_engine_copy = graph_engine.create_copy()
variable_pool_copy = graph_engine_copy.graph_runtime_state.variable_pool variable_pool_copy = graph_engine_copy.graph_runtime_state.variable_pool

@ -0,0 +1,65 @@
import contextvars
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TypeVar
from flask import Flask, g, has_request_context
T = TypeVar("T")
@contextmanager
def preserve_flask_contexts(
flask_app: Flask,
context_vars: contextvars.Context,
) -> Iterator[None]:
"""
A context manager that handles:
1. flask-login's UserProxy copy
2. ContextVars copy
3. flask_app.app_context()
This context manager ensures that the Flask application context is properly set up,
the current user is preserved across context boundaries, and any provided context variables
are set within the new context.
Note:
This manager aims to allow use current_user cross thread and app context,
but it's not the recommend use, it's better to pass user directly in parameters.
Args:
flask_app: The Flask application instance
context_vars: contextvars.Context object containing context variables to be set in the new context
Yields:
None
Example:
```python
with preserve_flask_contexts(flask_app, context_vars=context_vars):
# Code that needs Flask app context and context variables
# Current user will be preserved if available
```
"""
# Set context variables if provided
if context_vars:
for var, val in context_vars.items():
var.set(val)
# Save current user before entering new app context
saved_user = None
if has_request_context() and hasattr(g, "_login_user"):
saved_user = g._login_user
# Enter Flask app context
with flask_app.app_context():
try:
# Restore user in new app context if it was saved
if saved_user is not None:
g._login_user = saved_user
# Yield control back to the caller
yield
finally:
# Any cleanup can be added here if needed
pass

@ -0,0 +1,124 @@
import contextvars
import threading
from typing import Optional
import pytest
from flask import Flask
from flask_login import LoginManager, UserMixin, current_user, login_user
from libs.flask_utils import preserve_flask_contexts
class User(UserMixin):
"""Simple User class for testing."""
def __init__(self, id: str):
self.id = id
def get_id(self) -> str:
return self.id
@pytest.fixture
def login_app(app: Flask) -> Flask:
"""Set up a Flask app with flask-login."""
# Set a secret key for the app
app.config["SECRET_KEY"] = "test-secret-key"
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id: str) -> Optional[User]:
if user_id == "test_user":
return User("test_user")
return None
return app
@pytest.fixture
def test_user() -> User:
"""Create a test user."""
return User("test_user")
def test_current_user_not_accessible_across_threads(login_app: Flask, test_user: User):
"""
Test that current_user is not accessible in a different thread without preserve_flask_contexts.
This test demonstrates that without the preserve_flask_contexts, we cannot access
current_user in a different thread, even with app_context.
"""
# Log in the user in the main thread
with login_app.test_request_context():
login_user(test_user)
assert current_user.is_authenticated
assert current_user.id == "test_user"
# Store the result of the thread execution
result = {"user_accessible": True, "error": None}
# Define a function to run in a separate thread
def check_user_in_thread():
try:
# Try to access current_user in a different thread with app_context
with login_app.app_context():
# This should fail because current_user is not accessible across threads
# without preserve_flask_contexts
result["user_accessible"] = current_user.is_authenticated
except Exception as e:
result["error"] = str(e) # type: ignore
# Run the function in a separate thread
thread = threading.Thread(target=check_user_in_thread)
thread.start()
thread.join()
# Verify that we got an error or current_user is not authenticated
assert result["error"] is not None or (result["user_accessible"] is not None and not result["user_accessible"])
def test_current_user_accessible_with_preserve_flask_contexts(login_app: Flask, test_user: User):
"""
Test that current_user is accessible in a different thread with preserve_flask_contexts.
This test demonstrates that with the preserve_flask_contexts, we can access
current_user in a different thread.
"""
# Log in the user in the main thread
with login_app.test_request_context():
login_user(test_user)
assert current_user.is_authenticated
assert current_user.id == "test_user"
# Save the context variables
context_vars = contextvars.copy_context()
# Store the result of the thread execution
result = {"user_accessible": False, "user_id": None, "error": None}
# Define a function to run in a separate thread
def check_user_in_thread_with_manager():
try:
# Use preserve_flask_contexts to access current_user in a different thread
with preserve_flask_contexts(login_app, context_vars):
from flask_login import current_user
if current_user:
result["user_accessible"] = True
result["user_id"] = current_user.id
else:
result["user_accessible"] = False
except Exception as e:
result["error"] = str(e) # type: ignore
# Run the function in a separate thread
thread = threading.Thread(target=check_user_in_thread_with_manager)
thread.start()
thread.join()
# Verify that current_user is accessible and has the correct ID
assert result["error"] is None
assert result["user_accessible"] is True
assert result["user_id"] == "test_user"

@ -15,7 +15,7 @@ const Overview = async (props: IDevelopProps) => {
} = params } = params
return ( return (
<div className="h-full overflow-scroll bg-chatbot-bg px-4 py-6 sm:px-12"> <div className="h-full overflow-y-auto bg-chatbot-bg px-4 py-6 sm:px-12">
<ApikeyInfoPanel /> <ApikeyInfoPanel />
<ChartView <ChartView
appId={appId} appId={appId}

@ -8,15 +8,15 @@ import { useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
export default function DatasetsLayout({ children }: { children: React.ReactNode }) { export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
const { isCurrentWorkspaceEditor } = useAppContext() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
if (!isCurrentWorkspaceEditor) if (!isCurrentWorkspaceEditor && !isCurrentWorkspaceDatasetOperator)
router.replace('/apps') router.replace('/apps')
}, [isCurrentWorkspaceEditor, router]) }, [isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, router])
if (!isCurrentWorkspaceEditor) if (!isCurrentWorkspaceEditor && !isCurrentWorkspaceDatasetOperator)
return <Loading type='app' /> return <Loading type='app' />
return ( return (
<ExternalKnowledgeApiProvider> <ExternalKnowledgeApiProvider>

@ -19,7 +19,7 @@ const Layout: FC<{
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!systemFeatures.webapp_auth.enabled) { if (!isGlobalPending && !systemFeatures.webapp_auth.enabled) {
setIsLoading(false) setIsLoading(false)
return return
} }
@ -37,7 +37,7 @@ const Layout: FC<{
setWebAppAccessMode(ret?.accessMode || AccessMode.PUBLIC) setWebAppAccessMode(ret?.accessMode || AccessMode.PUBLIC)
setIsLoading(false) setIsLoading(false)
})() })()
}, [pathname, redirectUrl, setWebAppAccessMode]) }, [pathname, redirectUrl, setWebAppAccessMode, isGlobalPending, systemFeatures.webapp_auth.enabled])
if (isLoading || isGlobalPending) { if (isLoading || isGlobalPending) {
return <div className='flex h-full w-full items-center justify-center'> return <div className='flex h-full w-full items-center justify-center'>
<Loading /> <Loading />

@ -20,6 +20,7 @@ import type {
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
const systemTypes = ['api'] const systemTypes = ['api']
type ExternalDataToolModalProps = { type ExternalDataToolModalProps = {
@ -40,6 +41,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
onValidateBeforeSave, onValidateBeforeSave,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useToastContext() const { notify } = useToastContext()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const [localeData, setLocaleData] = useState(data.type ? data : { ...data, type: 'api' }) const [localeData, setLocaleData] = useState(data.type ? data : { ...data, type: 'api' })
@ -243,7 +245,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
<div className='flex h-9 items-center justify-between text-sm font-medium text-gray-900'> <div className='flex h-9 items-center justify-between text-sm font-medium text-gray-900'>
{t('common.apiBasedExtension.selector.title')} {t('common.apiBasedExtension.selector.title')}
<a <a
href={t('common.apiBasedExtension.linkUrl') || '/'} href={docLink('/guides/extension/api-based-extension/README')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
className='group flex items-center text-xs font-normal text-gray-500 hover:text-primary-600' className='group flex items-center text-xs font-normal text-gray-500 hover:text-primary-600'
> >

@ -25,6 +25,7 @@ import { useModalContext } from '@/context/modal-context'
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
const systemTypes = ['openai_moderation', 'keywords', 'api'] const systemTypes = ['openai_moderation', 'keywords', 'api']
@ -46,6 +47,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
onSave, onSave,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const { notify } = useToastContext() const { notify } = useToastContext()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const { data: modelProviders, isLoading, mutate } = useSWR('/workspaces/current/model-providers', fetchModelProviders) const { data: modelProviders, isLoading, mutate } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
@ -316,7 +318,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
<div className='flex h-9 items-center justify-between'> <div className='flex h-9 items-center justify-between'>
<div className='text-sm font-medium text-text-primary'>{t('common.apiBasedExtension.selector.title')}</div> <div className='text-sm font-medium text-text-primary'>{t('common.apiBasedExtension.selector.title')}</div>
<a <a
href={t('common.apiBasedExtension.linkUrl') || '/'} href={docLink('/guides/extension/api-based-extension/README')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
className='group flex items-center text-xs text-text-tertiary hover:text-primary-600' className='group flex items-center text-xs text-text-tertiary hover:text-primary-600'
> >

@ -167,6 +167,7 @@ const ComponentPicker = ({
onBlur={handleClose} onBlur={handleClose}
showManageInputField={workflowVariableBlock.showManageInputField} showManageInputField={workflowVariableBlock.showManageInputField}
onManageInputField={workflowVariableBlock.onManageInputField} onManageInputField={workflowVariableBlock.onManageInputField}
autoFocus={false}
/> />
</div> </div>
) )

@ -54,7 +54,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
</Property> </Property>
<Property name='indexing_technique' type='string' key='indexing_technique'> <Property name='indexing_technique' type='string' key='indexing_technique'>
Index mode Index mode
- <code>high_quality</code> High quality: embedding using embedding model, built as vector database index - <code>high_quality</code> High quality: Embedding using embedding model, built as vector database index
- <code>economy</code> Economy: Build using inverted index of keyword table index - <code>economy</code> Economy: Build using inverted index of keyword table index
</Property> </Property>
<Property name='doc_form' type='string' key='doc_form'> <Property name='doc_form' type='string' key='doc_form'>

@ -55,7 +55,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
<Property name='indexing_technique' type='string' key='indexing_technique'> <Property name='indexing_technique' type='string' key='indexing_technique'>
索引方式 索引方式
- <code>high_quality</code> 高质量:使用 - <code>high_quality</code> 高质量:使用
ding 模型进行嵌入,构建为向量数据库索引 Embedding 模型进行嵌入,构建为向量数据库索引
- <code>economy</code> 经济:使用 keyword table index 的倒排索引进行构建 - <code>economy</code> 经济:使用 keyword table index 的倒排索引进行构建
</Property> </Property>
<Property name='doc_form' type='string' key='doc_form'> <Property name='doc_form' type='string' key='doc_form'>

@ -31,22 +31,22 @@ const WorkplaceSelector = () => {
} }
return ( return (
<Menu as="div" className="relative h-full w-full"> <Menu as="div" className="min-w-0">
{ {
({ open }) => ( ({ open }) => (
<> <>
<MenuButton className={cn( <MenuButton className={cn(
` `
group flex w-full cursor-pointer items-center group flex w-full cursor-pointer items-center
gap-1.5 p-0.5 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} rounded-[10px] p-0.5 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} rounded-[10px]
`, `,
)}> )}>
<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'> <div className='mr-1.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px] max-[800px]:mr-0'>
<span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{currentWorkspace?.name[0]?.toLocaleUpperCase()}</span> <span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{currentWorkspace?.name[0]?.toLocaleUpperCase()}</span>
</div> </div>
<div className='flex flex-row'> <div className='flex min-w-0 items-center'>
<div className={'system-sm-medium max-w-[160px] truncate text-text-secondary'}>{currentWorkspace?.name}</div> <div className={'system-sm-medium min-w-0 max-w-[149px] truncate text-text-secondary max-[800px]:hidden'}>{currentWorkspace?.name}</div>
<RiArrowDownSLine className='h-4 w-4 text-text-secondary' /> <RiArrowDownSLine className='h-4 w-4 shrink-0 text-text-secondary' />
</div> </div>
</MenuButton> </MenuButton>
<Transition <Transition
@ -59,10 +59,11 @@ const WorkplaceSelector = () => {
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<MenuItems <MenuItems
anchor="bottom start"
className={cn( className={cn(
` `
shadows-shadow-lg absolute left-[-15px] mt-1 flex max-h-[400px] w-[280px] flex-col items-start overflow-y-auto rounded-xl shadows-shadow-lg absolute left-[-15px] z-[1000] mt-1 flex max-h-[400px] w-[280px] flex-col items-start overflow-y-auto
bg-components-panel-bg-blur backdrop-blur-[5px] rounded-xl bg-components-panel-bg-blur backdrop-blur-[5px]
`, `,
)} )}
> >
@ -73,7 +74,7 @@ const WorkplaceSelector = () => {
{ {
workspaces.map(workspace => ( workspaces.map(workspace => (
<div className='flex items-center gap-2 self-stretch rounded-lg py-1 pl-3 pr-2 hover:bg-state-base-hover' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}> <div className='flex items-center gap-2 self-stretch rounded-lg py-1 pl-3 pr-2 hover:bg-state-base-hover' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'> <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]'>
<span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{workspace?.name[0]?.toLocaleUpperCase()}</span> <span className='h-6 bg-gradient-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle font-semibold uppercase leading-6 text-shadow-shadow-1 opacity-90'>{workspace?.name[0]?.toLocaleUpperCase()}</span>
</div> </div>
<div className='system-md-regular line-clamp-1 grow cursor-pointer overflow-hidden text-ellipsis text-text-secondary'>{workspace.name}</div> <div className='system-md-regular line-clamp-1 grow cursor-pointer overflow-hidden text-ellipsis text-text-secondary'>{workspace.name}</div>

@ -3,9 +3,11 @@ import {
RiExternalLinkLine, RiExternalLinkLine,
RiPuzzle2Line, RiPuzzle2Line,
} from '@remixicon/react' } from '@remixicon/react'
import { useDocLink } from '@/context/i18n'
const Empty = () => { const Empty = () => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
return ( return (
<div className='mb-2 rounded-xl bg-background-section p-6'> <div className='mb-2 rounded-xl bg-background-section p-6'>
@ -15,7 +17,7 @@ const Empty = () => {
<div className='system-sm-medium mb-1 text-text-secondary'>{t('common.apiBasedExtension.title')}</div> <div className='system-sm-medium mb-1 text-text-secondary'>{t('common.apiBasedExtension.title')}</div>
<a <a
className='system-xs-regular flex items-center text-text-accent' className='system-xs-regular flex items-center text-text-accent'
href={t('common.apiBasedExtension.linkUrl') || '/'} href={docLink('/guides/extension/api-based-extension/README')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
> >
{t('common.apiBasedExtension.link')} {t('common.apiBasedExtension.link')}

@ -1,6 +1,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
@ -29,6 +30,7 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({
onSave, onSave,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink()
const [localeData, setLocaleData] = useState(data) const [localeData, setLocaleData] = useState(data)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { notify } = useToastContext() const { notify } = useToastContext()
@ -100,7 +102,7 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({
<div className='flex h-9 items-center justify-between text-sm font-medium text-text-primary'> <div className='flex h-9 items-center justify-between text-sm font-medium text-text-primary'>
{t('common.apiBasedExtension.modal.apiEndpoint.title')} {t('common.apiBasedExtension.modal.apiEndpoint.title')}
<a <a
href={t('common.apiBasedExtension.linkUrl') || '/'} href={docLink('/guides/extension/api-based-extension/README')}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
className='group flex items-center text-xs font-normal text-text-accent' className='group flex items-center text-xs font-normal text-text-accent'
> >

@ -96,7 +96,7 @@ const AppNav = () => {
link, link,
} }
}) })
setNavItems(navItems) setNavItems(navItems as any)
} }
}, [appsData, isCurrentWorkspaceEditor, setNavItems]) }, [appsData, isCurrentWorkspaceEditor, setNavItems])

@ -20,22 +20,22 @@ const EnvNav = () => {
return ( return (
<div className={` <div className={`
mr-4 flex h-[22px] items-center rounded-md border px-2 text-xs font-medium mr-1 flex h-[22px] items-center rounded-md border px-2 text-xs font-medium
${headerEnvClassName[langeniusVersionInfo.current_env]} ${headerEnvClassName[langeniusVersionInfo.current_env]}
`}> `}>
{ {
langeniusVersionInfo.current_env === 'TESTING' && ( langeniusVersionInfo.current_env === 'TESTING' && (
<> <>
<Beaker02 className='mr-1 h-3 w-3' /> <Beaker02 className='h-3 w-3' />
{t('common.environment.testing')} <div className='ml-1 max-[1280px]:hidden'>{t('common.environment.testing')}</div>
</> </>
) )
} }
{ {
langeniusVersionInfo.current_env === 'DEVELOPMENT' && ( langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
<> <>
<TerminalSquare className='mr-1 h-3 w-3' /> <TerminalSquare className='h-3 w-3' />
{t('common.environment.development')} <div className='ml-1 max-[1280px]:hidden'>{t('common.environment.development')}</div>
</> </>
) )
} }

@ -27,10 +27,12 @@ const ExploreNav = ({
)}> )}>
{ {
activated activated
? <RiPlanetFill className='mr-2 h-4 w-4' /> ? <RiPlanetFill className='h-4 w-4' />
: <RiPlanetLine className='mr-2 h-4 w-4' /> : <RiPlanetLine className='h-4 w-4' />
} }
{t('common.menus.explore')} <div className='ml-2 max-[1024px]:hidden'>
{t('common.menus.explore')}
</div>
</Link> </Link>
) )
} }

@ -1,9 +1,6 @@
'use client' 'use client'
import { useCallback, useEffect } from 'react' import { useCallback } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useBoolean } from 'ahooks'
import { useSelectedLayoutSegment } from 'next/navigation'
import { Bars3Icon } from '@heroicons/react/20/solid'
import AccountDropdown from './account-dropdown' import AccountDropdown from './account-dropdown'
import AppNav from './app-nav' import AppNav from './app-nav'
import DatasetNav from './dataset-nav' import DatasetNav from './dataset-nav'
@ -24,17 +21,15 @@ import { Plan } from '../billing/type'
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
const navClassName = ` const navClassName = `
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl flex items-center relative px-3 h-8 rounded-xl
font-medium text-sm font-medium text-sm
cursor-pointer cursor-pointer
` `
const Header = () => { const Header = () => {
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
const selectedSegment = useSelectedLayoutSegment()
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)
const { enableBilling, plan } = useProviderContext() const { enableBilling, plan } = useProviderContext()
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
@ -46,23 +41,12 @@ const Header = () => {
setShowAccountSettingModal({ payload: 'billing' }) setShowAccountSettingModal({ payload: 'billing' })
}, [isFreePlan, setShowAccountSettingModal, setShowPricingModal]) }, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
useEffect(() => { if (isMobile) {
hideNavMenu() return (
// eslint-disable-next-line react-hooks/exhaustive-deps <div className=''>
}, [selectedSegment]) <div className='flex items-center justify-between px-2'>
return ( <div className='flex items-center'>
<div className='relative flex flex-1 items-center justify-between bg-background-body'> <Link href="/apps" className='flex h-8 shrink-0 items-center justify-center px-0.5'>
<div className='flex items-center'>
{isMobile && <div
className='flex h-8 w-8 cursor-pointer items-center justify-center'
onClick={toggle}
>
<Bars3Icon className="h-4 w-4 text-gray-500" />
</div>}
{
!isMobile
&& <div className='flex shrink-0 items-center gap-1.5 self-stretch pl-3'>
<Link href="/apps" className='flex h-8 shrink-0 items-center justify-center gap-2 px-0.5'>
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? <img ? <img
src={systemFeatures.branding.workspace_logo} src={systemFeatures.branding.workspace_logo}
@ -71,59 +55,61 @@ const Header = () => {
/> />
: <DifyLogo />} : <DifyLogo />}
</Link> </Link>
<div className='font-light text-divider-deep'>/</div> <div className='mx-1.5 shrink-0 font-light text-divider-deep'>/</div>
<div className='flex items-center gap-0.5'> <WorkspaceProvider>
<WorkspaceProvider> <WorkplaceSelector />
<WorkplaceSelector /> </WorkspaceProvider>
</WorkspaceProvider> {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
</div>
</div> </div>
} <div className='flex items-center'>
</div > <div className='mr-2'>
{isMobile && ( <PluginsNav />
<div className='flex'> </div>
<Link href="/apps" className='mr-4 flex items-center'> <AccountDropdown />
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? <img
src={systemFeatures.branding.workspace_logo}
className='block h-[22px] w-auto object-contain'
alt='logo'
/>
: <DifyLogo />}
</Link>
<div className='font-light text-divider-deep'>/</div>
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
</div >
)}
{
!isMobile && (
<div className='absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center'>
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
</div> </div>
) </div>
} <div className='my-1 flex items-center justify-center space-x-1'>
<div className='flex shrink-0 items-center pr-3'> {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
</div>
</div>
)
}
return (
<div className='flex h-[60px] items-center'>
<div className='flex min-w-0 flex-[1] items-center pl-3 pr-2 min-[1280px]:pr-3'>
<Link href="/apps" className='flex h-8 shrink-0 items-center justify-center px-0.5'>
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? <img
src={systemFeatures.branding.workspace_logo}
className='block h-[22px] w-auto object-contain'
alt='logo'
/>
: <DifyLogo />}
</Link>
<div className='mx-1.5 shrink-0 font-light text-divider-deep'>/</div>
<WorkspaceProvider>
<WorkplaceSelector />
</WorkspaceProvider>
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
</div>
<div className='flex items-center space-x-2'>
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
</div>
<div className='flex min-w-0 flex-[1] items-center justify-end pl-2 pr-3 min-[1280px]:pl-3'>
<EnvNav /> <EnvNav />
<div className='mr-2'> <div className='mr-2'>
<PluginsNav /> <PluginsNav />
</div> </div>
<AccountDropdown /> <AccountDropdown />
</div> </div>
{ </div>
(isMobile && isShowNavMenu) && (
<div className='flex w-full flex-col gap-y-1 p-2'>
{!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
{!isCurrentWorkspaceDatasetOperator && <AppNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
</div>
)
}
</div >
) )
} }
export default Header export default Header

@ -46,7 +46,7 @@ const Nav = ({
return ( return (
<div className={` <div className={`
mr-0 flex h-8 shrink-0 items-center rounded-xl px-0.5 text-sm font-medium sm:mr-3 flex h-8 max-w-[670px] shrink-0 items-center rounded-xl px-0.5 text-sm font-medium max-[1024px]:max-w-[400px]
${isActivated && 'bg-components-main-nav-nav-button-bg-active font-semibold shadow-md'} ${isActivated && 'bg-components-main-nav-nav-button-bg-active font-semibold shadow-md'}
${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'} ${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'}
`}> `}>
@ -61,7 +61,7 @@ const Nav = ({
onMouseEnter={() => setHovered(true)} onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)} onMouseLeave={() => setHovered(false)}
> >
<div className='mr-2'> <div>
{ {
(hovered && curNav) (hovered && curNav)
? <ArrowNarrowLeft className='h-4 w-4' /> ? <ArrowNarrowLeft className='h-4 w-4' />
@ -70,7 +70,9 @@ const Nav = ({
: icon : icon
} }
</div> </div>
{text} <div className='ml-2 max-[1024px]:hidden'>
{text}
</div>
</div> </div>
</Link> </Link>
{ {

@ -53,136 +53,134 @@ const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onL
}, 50), []) }, 50), [])
return ( return (
<div className=""> <Menu as="div" className="relative">
<Menu as="div" className="relative inline-block text-left"> {({ open }) => (
{({ open }) => ( <>
<> <MenuButton className={cn(
<MenuButton className={cn( 'hover:hover:bg-components-main-nav-nav-button-bg-active-hover group inline-flex h-7 w-full items-center justify-center rounded-[10px] pl-2 pr-2.5 text-[14px] font-semibold text-components-main-nav-nav-button-text-active',
'hover:hover:bg-components-main-nav-nav-button-bg-active-hover group inline-flex h-7 w-full items-center justify-center rounded-[10px] pl-2 pr-2.5 text-[14px] font-semibold text-components-main-nav-nav-button-text-active', open && 'bg-components-main-nav-nav-button-bg-active',
open && 'bg-components-main-nav-nav-button-bg-active', )}>
)}> <div className='max-w-[157px] truncate' title={curNav?.name}>{curNav?.name}</div>
<div className='max-w-[180px] truncate' title={curNav?.name}>{curNav?.name}</div> <RiArrowDownSLine
<RiArrowDownSLine className={cn('ml-1 h-3 w-3 shrink-0 opacity-50 group-hover:opacity-100', open && '!opacity-100')}
className={cn('ml-1 h-3 w-3 shrink-0 opacity-50 group-hover:opacity-100', open && '!opacity-100')} aria-hidden="true"
aria-hidden="true" />
/> </MenuButton>
</MenuButton> <MenuItems
<MenuItems className="
className=" absolute -left-11 right-0 mt-1.5 w-60 max-w-80
absolute -left-11 right-0 mt-1.5 w-60 max-w-80 origin-top-right divide-y divide-divider-regular rounded-lg bg-components-panel-bg-blur
origin-top-right divide-y divide-divider-regular rounded-lg bg-components-panel-bg-blur shadow-lg
shadow-lg "
" >
> <div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
<div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}> {
{ navigationItems.map(nav => (
navigationItems.map(nav => ( <MenuItem key={nav.id}>
<MenuItem key={nav.id}> <div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-text-secondary hover:bg-state-base-hover' onClick={() => {
<div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-text-secondary hover:bg-state-base-hover' onClick={() => { if (curNav?.id === nav.id)
if (curNav?.id === nav.id) return
return setAppDetail()
setAppDetail() router.push(nav.link)
router.push(nav.link) }} title={nav.name}>
}} title={nav.name}> <div className='relative mr-2 h-6 w-6 rounded-md'>
<div className='relative mr-2 h-6 w-6 rounded-md'> <AppIcon size='tiny' iconType={nav.icon_type} icon={nav.icon} background={nav.icon_background} imageUrl={nav.icon_url} />
<AppIcon size='tiny' iconType={nav.icon_type} icon={nav.icon} background={nav.icon_background} imageUrl={nav.icon_url} /> {!!nav.mode && (
{!!nav.mode && ( <span className={cn(
<span className={cn( 'absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded border-[0.5px] border-[rgba(0,0,0,0.02)] bg-white p-0.5 shadow-sm',
'absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded border-[0.5px] border-[rgba(0,0,0,0.02)] bg-white p-0.5 shadow-sm', )}>
)}> {nav.mode === 'advanced-chat' && (
{nav.mode === 'advanced-chat' && ( <ChatBot className='h-2.5 w-2.5 text-[#1570EF]' />
<ChatBot className='h-2.5 w-2.5 text-[#1570EF]' /> )}
)} {nav.mode === 'agent-chat' && (
{nav.mode === 'agent-chat' && ( <CuteRobot className='h-2.5 w-2.5 text-indigo-600' />
<CuteRobot className='h-2.5 w-2.5 text-indigo-600' /> )}
)} {nav.mode === 'chat' && (
{nav.mode === 'chat' && ( <ChatBot className='h-2.5 w-2.5 text-[#1570EF]' />
<ChatBot className='h-2.5 w-2.5 text-[#1570EF]' /> )}
)} {nav.mode === 'completion' && (
{nav.mode === 'completion' && ( <AiText className='h-2.5 w-2.5 text-[#0E9384]' />
<AiText className='h-2.5 w-2.5 text-[#0E9384]' /> )}
)} {nav.mode === 'workflow' && (
{nav.mode === 'workflow' && ( <Route className='h-2.5 w-2.5 text-[#f79009]' />
<Route className='h-2.5 w-2.5 text-[#f79009]' /> )}
)} </span>
</span> )}
)} </div>
</div> <div className='truncate'>
<div className='truncate'> {nav.name}
{nav.name}
</div>
</div> </div>
</MenuItem>
))
}
</div>
{!isApp && isCurrentWorkspaceEditor && (
<MenuItem as="div" className='w-full p-1'>
<div onClick={() => onCreate('')} className={cn(
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover ',
)}>
<div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'>
<RiAddLine className='h-4 w-4 text-text-primary' />
</div> </div>
<div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div> </MenuItem>
))
}
</div>
{!isApp && isCurrentWorkspaceEditor && (
<MenuItem as="div" className='w-full p-1'>
<div onClick={() => onCreate('')} className={cn(
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover ',
)}>
<div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'>
<RiAddLine className='h-4 w-4 text-text-primary' />
</div> </div>
</MenuItem> <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div>
)} </div>
{isApp && isCurrentWorkspaceEditor && ( </MenuItem>
<Menu as="div" className="relative h-full w-full"> )}
{({ open }) => ( {isApp && isCurrentWorkspaceEditor && (
<> <Menu as="div" className="relative h-full w-full">
<MenuButton className='w-full p-1'> {({ open }) => (
<div className={cn( <>
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover', <MenuButton className='w-full p-1'>
open && '!bg-state-base-hover', <div className={cn(
)}> 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover',
<div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'> open && '!bg-state-base-hover',
<RiAddLine className='h-4 w-4 text-text-primary' /> )}>
</div> <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'>
<div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div> <RiAddLine className='h-4 w-4 text-text-primary' />
<RiArrowRightSLine className='h-3.5 w-3.5 shrink-0 text-text-primary' />
</div> </div>
</MenuButton> <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div>
<Transition <RiArrowRightSLine className='h-3.5 w-3.5 shrink-0 text-text-primary' />
as={Fragment} </div>
enter="transition ease-out duration-100" </MenuButton>
enterFrom="transform opacity-0 scale-95" <Transition
enterTo="transform opacity-100 scale-100" as={Fragment}
leave="transition ease-in duration-75" enter="transition ease-out duration-100"
leaveFrom="transform opacity-100 scale-100" enterFrom="transform opacity-0 scale-95"
leaveTo="transform opacity-0 scale-95" enterTo="transform opacity-100 scale-100"
> leave="transition ease-in duration-75"
<MenuItems className={cn( leaveFrom="transform opacity-100 scale-100"
'absolute right-[-198px] top-[3px] z-10 min-w-[200px] rounded-lg bg-components-panel-bg-blur shadow-lg', leaveTo="transform opacity-0 scale-95"
)}> >
<div className='p-1'> <MenuItems className={cn(
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('blank')}> 'absolute right-[-198px] top-[3px] z-10 min-w-[200px] rounded-lg bg-components-panel-bg-blur shadow-lg',
<FilePlus01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> )}>
{t('app.newApp.startFromBlank')} <div className='p-1'>
</div> <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('blank')}>
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('template')}> <FilePlus01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
<FilePlus02 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> {t('app.newApp.startFromBlank')}
{t('app.newApp.startFromTemplate')} </div>
</div> <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('template')}>
<FilePlus02 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
{t('app.newApp.startFromTemplate')}
</div> </div>
<div className='border-t border-divider-regular p-1'> </div>
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('dsl')}> <div className='border-t border-divider-regular p-1'>
<FileArrow01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('dsl')}>
{t('app.importDSL')} <FileArrow01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
</div> {t('app.importDSL')}
</div> </div>
</MenuItems> </div>
</Transition> </MenuItems>
</> </Transition>
)} </>
</Menu> )}
)} </Menu>
</MenuItems> )}
</> </MenuItems>
)} </>
</Menu> )}
</div> </Menu>
) )
} }

@ -28,10 +28,12 @@ const ToolsNav = ({
)}> )}>
{ {
activated activated
? <RiHammerFill className='mr-2 h-4 w-4' /> ? <RiHammerFill className='h-4 w-4' />
: <RiHammerLine className='mr-2 h-4 w-4' /> : <RiHammerLine className='h-4 w-4' />
} }
{t('common.menus.tools')} <div className='ml-2 max-[1024px]:hidden'>
{t('common.menus.tools')}
</div>
</Link> </Link>
) )
} }

@ -83,6 +83,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
installedValue={updatePayload?.originalPackageInfo.version} installedValue={updatePayload?.originalPackageInfo.version}
placeholder={t(`${i18nPrefix}.selectVersionPlaceholder`) || ''} placeholder={t(`${i18nPrefix}.selectVersionPlaceholder`) || ''}
popupClassName='w-[512px] z-[1001]' popupClassName='w-[512px] z-[1001]'
triggerClassName='text-components-input-text-filled'
/> />
<label <label
htmlFor='package' htmlFor='package'
@ -97,6 +98,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
readonly={!selectedVersion} readonly={!selectedVersion}
placeholder={t(`${i18nPrefix}.selectPackagePlaceholder`) || ''} placeholder={t(`${i18nPrefix}.selectPackagePlaceholder`) || ''}
popupClassName='w-[512px] z-[1001]' popupClassName='w-[512px] z-[1001]'
triggerClassName='text-components-input-text-filled'
/> />
<div className='mt-4 flex items-center justify-end gap-2 self-stretch'> <div className='mt-4 flex items-center justify-end gap-2 self-stretch'>
{!isEdit {!isEdit

@ -61,7 +61,7 @@ export const useShortcuts = (): void => {
return !showFeaturesPanel && !isEventTargetInputArea(e.target as HTMLElement) return !showFeaturesPanel && !isEventTargetInputArea(e.target as HTMLElement)
}, [workflowStore]) }, [workflowStore])
useKeyPress(['delete', 'backspace'], (e) => { useKeyPress(['delete'], (e) => {
if (shouldHandleShortcut(e)) { if (shouldHandleShortcut(e)) {
e.preventDefault() e.preventDefault()
handleNodesDelete() handleNodesDelete()

@ -8,6 +8,8 @@ import VarReferencePicker from './var-reference-picker'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { checkKeys } from '@/utils/var'
import Toast from '@/app/components/base/toast'
type Props = { type Props = {
nodeId: string nodeId: string
@ -36,9 +38,27 @@ const VarList: FC<Props> = ({
const handleVarNameChange = useCallback((index: number) => { const handleVarNameChange = useCallback((index: number) => {
return (e: React.ChangeEvent<HTMLInputElement>) => { return (e: React.ChangeEvent<HTMLInputElement>) => {
onVarNameChange?.(list[index].variable, e.target.value) const newKey = e.target.value
const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
})
return
}
if (list.map(item => item.variable?.trim()).includes(newKey.trim())) {
Toast.notify({
type: 'error',
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
})
return
}
onVarNameChange?.(list[index].variable, newKey)
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
draft[index].variable = e.target.value draft[index].variable = newKey
}) })
onChange(newList) onChange(newList)
} }

@ -269,6 +269,7 @@ type Props = {
onBlur?: () => void onBlur?: () => void
showManageInputField?: boolean showManageInputField?: boolean
onManageInputField?: () => void onManageInputField?: () => void
autoFocus?: boolean
} }
const VarReferenceVars: FC<Props> = ({ const VarReferenceVars: FC<Props> = ({
hideSearch, hideSearch,
@ -282,6 +283,7 @@ const VarReferenceVars: FC<Props> = ({
onBlur, onBlur,
showManageInputField, showManageInputField,
onManageInputField, onManageInputField,
autoFocus = true,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
@ -334,7 +336,7 @@ const VarReferenceVars: FC<Props> = ({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onClear={() => setSearchText('')} onClear={() => setSearchText('')}
onBlur={onBlur} onBlur={onBlur}
autoFocus autoFocus={autoFocus}
/> />
</div> </div>
<div className='relative left-[-4px] h-[0.5px] bg-black/5' style={{ <div className='relative left-[-4px] h-[0.5px] bg-black/5' style={{

@ -139,7 +139,7 @@ const VariableModal = ({
<div className='flex'> <div className='flex'>
{ {
type !== 'number' ? <textarea type !== 'number' ? <textarea
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
value={value} value={value}
placeholder={t('workflow.env.modal.valuePlaceholder') || ''} placeholder={t('workflow.env.modal.valuePlaceholder') || ''}
onChange={e => setValue(e.target.value)} onChange={e => setValue(e.target.value)}

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.', title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.',
link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.', link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'API-Erweiterung hinzufügen', add: 'API-Erweiterung hinzufügen',
selector: { selector: {
title: 'API-Erweiterung', title: 'API-Erweiterung',

@ -69,7 +69,6 @@ const translation = {
unknownError: 'Unbekannter Fehler', unknownError: 'Unbekannter Fehler',
resetAll: 'Alles zurücksetzen', resetAll: 'Alles zurücksetzen',
extractOnlyMainContent: 'Extrahieren Sie nur den Hauptinhalt (keine Kopf-, Navigations- und Fußzeilen usw.)', extractOnlyMainContent: 'Extrahieren Sie nur den Hauptinhalt (keine Kopf-, Navigations- und Fußzeilen usw.)',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
firecrawlTitle: 'Extrahieren von Webinhalten mit 🔥Firecrawl', firecrawlTitle: 'Extrahieren von Webinhalten mit 🔥Firecrawl',
maxDepthTooltip: 'Maximale Tiefe für das Crawlen relativ zur eingegebenen URL. Tiefe 0 kratzt nur die Seite der eingegebenen URL, Tiefe 1 kratzt die URL und alles nach der eingegebenen URL + ein / und so weiter.', maxDepthTooltip: 'Maximale Tiefe für das Crawlen relativ zur eingegebenen URL. Tiefe 0 kratzt nur die Seite der eingegebenen URL, Tiefe 1 kratzt die URL und alles nach der eingegebenen URL + ein / und so weiter.',
crawlSubPage: 'Unterseiten crawlen', crawlSubPage: 'Unterseiten crawlen',
@ -85,7 +84,6 @@ const translation = {
configureJinaReader: 'Jina Reader konfigurieren', configureJinaReader: 'Jina Reader konfigurieren',
waterCrawlNotConfigured: 'Watercrawl ist nicht konfiguriert', waterCrawlNotConfigured: 'Watercrawl ist nicht konfiguriert',
configureWatercrawl: 'Wasserkrabbe konfigurieren', configureWatercrawl: 'Wasserkrabbe konfigurieren',
watercrawlDocLink: 'https://docs.dify.ai/de/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlTitle: 'Webinhalt mit Watercrawl extrahieren', watercrawlTitle: 'Webinhalt mit Watercrawl extrahieren',
watercrawlDoc: 'Wasserkriechen-Dokumente', watercrawlDoc: 'Wasserkriechen-Dokumente',
configureFirecrawl: 'Firecrawl konfigurieren', configureFirecrawl: 'Firecrawl konfigurieren',

@ -487,7 +487,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API extensions provide centralized API management, simplifying configuration for easy use across Dify\'s applications.', title: 'API extensions provide centralized API management, simplifying configuration for easy use across Dify\'s applications.',
link: 'Learn how to develop your own API Extension.', link: 'Learn how to develop your own API Extension.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Add API Extension', add: 'Add API Extension',
selector: { selector: {
title: 'API Extension', title: 'API Extension',

@ -81,10 +81,8 @@ const translation = {
running: 'Running', running: 'Running',
firecrawlTitle: 'Extract web content with 🔥Firecrawl', firecrawlTitle: 'Extract web content with 🔥Firecrawl',
firecrawlDoc: 'Firecrawl docs', firecrawlDoc: 'Firecrawl docs',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlTitle: 'Extract web content with Watercrawl', watercrawlTitle: 'Extract web content with Watercrawl',
watercrawlDoc: 'Watercrawl docs', watercrawlDoc: 'Watercrawl docs',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
jinaReaderTitle: 'Convert the entire site to Markdown', jinaReaderTitle: 'Convert the entire site to Markdown',
jinaReaderDoc: 'Learn more about Jina Reader', jinaReaderDoc: 'Learn more about Jina Reader',
jinaReaderDocLink: 'https://jina.ai/reader', jinaReaderDocLink: 'https://jina.ai/reader',

@ -471,7 +471,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'Las extensiones basadas en API proporcionan una gestión centralizada de API, simplificando la configuración para su fácil uso en las aplicaciones de Dify.', title: 'Las extensiones basadas en API proporcionan una gestión centralizada de API, simplificando la configuración para su fácil uso en las aplicaciones de Dify.',
link: 'Aprende cómo desarrollar tu propia Extensión API.', link: 'Aprende cómo desarrollar tu propia Extensión API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Agregar Extensión API', add: 'Agregar Extensión API',
selector: { selector: {
title: 'Extensión API', title: 'Extensión API',

@ -63,7 +63,6 @@ const translation = {
run: 'Ejecutar', run: 'Ejecutar',
firecrawlTitle: 'Extraer contenido web con 🔥Firecrawl', firecrawlTitle: 'Extraer contenido web con 🔥Firecrawl',
firecrawlDoc: 'Documentación de Firecrawl', firecrawlDoc: 'Documentación de Firecrawl',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
options: 'Opciones', options: 'Opciones',
crawlSubPage: 'Rastrear subpáginas', crawlSubPage: 'Rastrear subpáginas',
limit: 'Límite', limit: 'Límite',
@ -92,7 +91,6 @@ const translation = {
configureFirecrawl: 'Configurar Firecrawl', configureFirecrawl: 'Configurar Firecrawl',
watercrawlDoc: 'Documentos de Watercrawl', watercrawlDoc: 'Documentos de Watercrawl',
configureJinaReader: 'Configurar Jina Reader', configureJinaReader: 'Configurar Jina Reader',
watercrawlDocLink: 'https://docs.dify.ai/es/guías/base-de-conocimientos/crear-conocimientos-y-subir-documentos/importar-datos-de-contenido/sincronizar-desde-el-sitio-web',
configureWatercrawl: 'Configurar Watercrawl', configureWatercrawl: 'Configurar Watercrawl',
waterCrawlNotConfiguredDescription: 'Configura Watercrawl con la clave de API para usarlo.', waterCrawlNotConfiguredDescription: 'Configura Watercrawl con la clave de API para usarlo.',
}, },

@ -471,7 +471,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'افزونه‌های مبتنی بر API مدیریت متمرکز API را فراهم می‌کنند و پیکربندی را برای استفاده آسان در برنامه‌های Dify ساده می‌کنند.', title: 'افزونه‌های مبتنی بر API مدیریت متمرکز API را فراهم می‌کنند و پیکربندی را برای استفاده آسان در برنامه‌های Dify ساده می‌کنند.',
link: 'نحوه توسعه افزونه API خود را بیاموزید.', link: 'نحوه توسعه افزونه API خود را بیاموزید.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'افزودن افزونه API', add: 'افزودن افزونه API',
selector: { selector: {
title: 'افزونه API', title: 'افزونه API',

@ -63,7 +63,6 @@ const translation = {
run: 'اجرا', run: 'اجرا',
firecrawlTitle: 'استخراج محتوای وب با fireFirecrawl', firecrawlTitle: 'استخراج محتوای وب با fireFirecrawl',
firecrawlDoc: 'مستندات Firecrawl', firecrawlDoc: 'مستندات Firecrawl',
firecrawlDocLink: '<a href="https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website">https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website</a>',
options: 'گزینهها', options: 'گزینهها',
crawlSubPage: 'خزش صفحات فرعی', crawlSubPage: 'خزش صفحات فرعی',
limit: 'محدودیت', limit: 'محدودیت',
@ -92,7 +91,6 @@ const translation = {
waterCrawlNotConfiguredDescription: 'برای استفاده از Watercrawl، آن را با کلید API پیکربندی کنید.', waterCrawlNotConfiguredDescription: 'برای استفاده از Watercrawl، آن را با کلید API پیکربندی کنید.',
waterCrawlNotConfigured: 'Watercrawl پیکربندی نشده است', waterCrawlNotConfigured: 'Watercrawl پیکربندی نشده است',
configureJinaReader: 'پیکربندی خواننده جینا', configureJinaReader: 'پیکربندی خواننده جینا',
watercrawlDocLink: 'https://docs.dify.ai/fa/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlTitle: 'محتوای وب را با واترکرال استخراج کنید', watercrawlTitle: 'محتوای وب را با واترکرال استخراج کنید',
configureWatercrawl: 'تنظیم واترکراول', configureWatercrawl: 'تنظیم واترکراول',
}, },

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'Les extensions API fournissent une gestion centralisée des API, simplifiant la configuration pour une utilisation facile à travers les applications de Dify.', title: 'Les extensions API fournissent une gestion centralisée des API, simplifiant la configuration pour une utilisation facile à travers les applications de Dify.',
link: 'Apprenez comment développer votre propre Extension API.', link: 'Apprenez comment développer votre propre Extension API.',
linkUrl: 'https://docs.dify.ai/fonctionnalites/extension/extension_basee_sur_api',
add: 'Ajouter l\'extension API', add: 'Ajouter l\'extension API',
selector: { selector: {
title: 'Extension de l\'API', title: 'Extension de l\'API',

@ -61,7 +61,6 @@ const translation = {
preview: 'Aperçu', preview: 'Aperçu',
crawlSubPage: 'Explorer les sous-pages', crawlSubPage: 'Explorer les sous-pages',
configure: 'Configurer', configure: 'Configurer',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
maxDepth: 'Profondeur maximale', maxDepth: 'Profondeur maximale',
fireCrawlNotConfigured: 'Firecrawl nest pas configuré', fireCrawlNotConfigured: 'Firecrawl nest pas configuré',
firecrawlTitle: 'Extraire du contenu web avec 🔥Firecrawl', firecrawlTitle: 'Extraire du contenu web avec 🔥Firecrawl',
@ -88,7 +87,6 @@ const translation = {
configureJinaReader: 'Configurer le lecteur Jina', configureJinaReader: 'Configurer le lecteur Jina',
configureWatercrawl: 'Configurer Watercrawl', configureWatercrawl: 'Configurer Watercrawl',
waterCrawlNotConfigured: 'Watercrawl n\'est pas configuré', waterCrawlNotConfigured: 'Watercrawl n\'est pas configuré',
watercrawlDocLink: 'https://docs.dify.ai/fr/guide/base-de-connaissances/créer-des-connaissances-et-télécharger-des-documents/importer-des-données-de-contenu/synchroniser-depuis-un-site-web',
configureFirecrawl: 'Configurer Firecrawl', configureFirecrawl: 'Configurer Firecrawl',
}, },
cancel: 'Annuler', cancel: 'Annuler',

@ -488,7 +488,6 @@ const translation = {
title: title:
'एपीआई एक्सटेंशन केंद्रीकृत एपीआई प्रबंधन प्रदान करते हैं, जो Dify के अनुप्रयोगों में आसान उपयोग के लिए कॉन्फ़िगरेशन को सरल बनाते हैं।', 'एपीआई एक्सटेंशन केंद्रीकृत एपीआई प्रबंधन प्रदान करते हैं, जो Dify के अनुप्रयोगों में आसान उपयोग के लिए कॉन्फ़िगरेशन को सरल बनाते हैं।',
link: 'अपना खुद का एपीआई एक्सटेंशन कैसे विकसित करें, यह जानें।', link: 'अपना खुद का एपीआई एक्सटेंशन कैसे विकसित करें, यह जानें।',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'एपीआई एक्सटेंशन जोड़ें', add: 'एपीआई एक्सटेंशन जोड़ें',
selector: { selector: {
title: 'एपीआई एक्सटेंशन', title: 'एपीआई एक्सटेंशन',

@ -65,8 +65,6 @@ const translation = {
run: 'चलाएं', run: 'चलाएं',
firecrawlTitle: '🔥फायरक्रॉल के साथ वेब सामग्री निकालें', firecrawlTitle: '🔥फायरक्रॉल के साथ वेब सामग्री निकालें',
firecrawlDoc: 'फायरक्रॉल दस्तावेज़', firecrawlDoc: 'फायरक्रॉल दस्तावेज़',
firecrawlDocLink:
'https://docs.dify.ai/guides/knowledge-base/sync_from_website',
options: 'विकल्प', options: 'विकल्प',
crawlSubPage: 'उप-पृष्ठों को क्रॉल करें', crawlSubPage: 'उप-पृष्ठों को क्रॉल करें',
limit: 'सीमा', limit: 'सीमा',
@ -97,7 +95,6 @@ const translation = {
configureFirecrawl: 'फायरक्रॉल को कॉन्फ़िगर करें', configureFirecrawl: 'फायरक्रॉल को कॉन्फ़िगर करें',
watercrawlDoc: 'वाटरक्रॉल दस्तावेज़', watercrawlDoc: 'वाटरक्रॉल दस्तावेज़',
waterCrawlNotConfiguredDescription: 'इसे उपयोग करने के लिए वॉटरक्रॉल को एपीआई कुंजी के साथ कॉन्फ़िगर करें।', waterCrawlNotConfiguredDescription: 'इसे उपयोग करने के लिए वॉटरक्रॉल को एपीआई कुंजी के साथ कॉन्फ़िगर करें।',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureJinaReader: 'जिना रीडर कॉन्फ़िगर करें', configureJinaReader: 'जिना रीडर कॉन्फ़िगर करें',
configureWatercrawl: 'वाटरक्रॉल कॉन्फ़िगर करें', configureWatercrawl: 'वाटरक्रॉल कॉन्फ़िगर करें',
}, },

@ -495,7 +495,6 @@ const translation = {
title: title:
'Le estensioni API forniscono una gestione centralizzata delle API, semplificando la configurazione per un facile utilizzo nelle applicazioni di Dify.', 'Le estensioni API forniscono una gestione centralizzata delle API, semplificando la configurazione per un facile utilizzo nelle applicazioni di Dify.',
link: 'Scopri come sviluppare la tua estensione API.', link: 'Scopri come sviluppare la tua estensione API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Aggiungi Estensione API', add: 'Aggiungi Estensione API',
selector: { selector: {
title: 'Estensione API', title: 'Estensione API',

@ -66,8 +66,6 @@ const translation = {
run: 'Esegui', run: 'Esegui',
firecrawlTitle: 'Estrai contenuti web con 🔥Firecrawl', firecrawlTitle: 'Estrai contenuti web con 🔥Firecrawl',
firecrawlDoc: 'Documenti Firecrawl', firecrawlDoc: 'Documenti Firecrawl',
firecrawlDocLink:
'https://docs.dify.ai/guides/knowledge-base/sync_from_website',
options: 'Opzioni', options: 'Opzioni',
crawlSubPage: 'Crawl sotto-pagine', crawlSubPage: 'Crawl sotto-pagine',
limit: 'Limite', limit: 'Limite',
@ -101,7 +99,6 @@ const translation = {
configureJinaReader: 'Configura Jina Reader', configureJinaReader: 'Configura Jina Reader',
configureWatercrawl: 'Configura Watercrawl', configureWatercrawl: 'Configura Watercrawl',
waterCrawlNotConfigured: 'Watercrawl non è configurato', waterCrawlNotConfigured: 'Watercrawl non è configurato',
watercrawlDocLink: 'https://docs.dify.ai/it/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
}, },
cancel: 'Annulla', cancel: 'Annulla',
}, },

@ -485,7 +485,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API 拡張機能は、Dify のアプリケーション全体での簡単な使用のための設定を簡素化し、集中的な API 管理を提供します。', title: 'API 拡張機能は、Dify のアプリケーション全体での簡単な使用のための設定を簡素化し、集中的な API 管理を提供します。',
link: '独自の API 拡張機能を開発する方法について学ぶ。', link: '独自の API 拡張機能を開発する方法について学ぶ。',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'API 拡張機能を追加', add: 'API 拡張機能を追加',
selector: { selector: {
title: 'API 拡張機能', title: 'API 拡張機能',

@ -72,7 +72,6 @@ const translation = {
run: '実行', run: '実行',
firecrawlTitle: '🔥Firecrawl を使っでウエブコンテンツを抽出', firecrawlTitle: '🔥Firecrawl を使っでウエブコンテンツを抽出',
firecrawlDoc: 'Firecrawl ドキュメント', firecrawlDoc: 'Firecrawl ドキュメント',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
jinaReaderTitle: 'サイト全体を Markdown に変換する', jinaReaderTitle: 'サイト全体を Markdown に変換する',
jinaReaderDoc: 'Jina Reader の詳細', jinaReaderDoc: 'Jina Reader の詳細',
jinaReaderDocLink: 'https://jina.ai/reader', jinaReaderDocLink: 'https://jina.ai/reader',
@ -98,7 +97,6 @@ const translation = {
watercrawlDoc: 'ウォータークローリングの文書', watercrawlDoc: 'ウォータークローリングの文書',
watercrawlTitle: 'Watercrawl を使用してウェブコンテンツを抽出する', watercrawlTitle: 'Watercrawl を使用してウェブコンテンツを抽出する',
waterCrawlNotConfigured: 'Watercrawl は設定されていません', waterCrawlNotConfigured: 'Watercrawl は設定されていません',
watercrawlDocLink: 'https://docs.dify.ai/ja/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
}, },
}, },
stepTwo: { stepTwo: {

@ -463,7 +463,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API 기반 확장은 Dify 애플리케이션 전체에서 간편한 사용을 위한 설정을 단순화하고 집중적인 API 관리를 제공합니다.', title: 'API 기반 확장은 Dify 애플리케이션 전체에서 간편한 사용을 위한 설정을 단순화하고 집중적인 API 관리를 제공합니다.',
link: '사용자 정의 API 기반 확장을 개발하는 방법 배우기', link: '사용자 정의 API 기반 확장을 개발하는 방법 배우기',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'API 기반 확장 추가', add: 'API 기반 확장 추가',
selector: { selector: {
title: 'API 기반 확장', title: 'API 기반 확장',

@ -52,7 +52,6 @@ const translation = {
failed: '생성에 실패했습니다', failed: '생성에 실패했습니다',
}, },
website: { website: {
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
limit: '한계', limit: '한계',
options: '옵션', options: '옵션',
firecrawlDoc: 'Firecrawl 문서', firecrawlDoc: 'Firecrawl 문서',
@ -86,7 +85,6 @@ const translation = {
waterCrawlNotConfiguredDescription: 'API 키로 Watercrawl 을 구성하여 사용하십시오.', waterCrawlNotConfiguredDescription: 'API 키로 Watercrawl 을 구성하여 사용하십시오.',
watercrawlTitle: 'Watercrawl 로 웹 콘텐츠 추출하기', watercrawlTitle: 'Watercrawl 로 웹 콘텐츠 추출하기',
configureFirecrawl: '파이어크롤 구성하기', configureFirecrawl: '파이어크롤 구성하기',
watercrawlDocLink: '웹사이트에서 동기화하기',
configureJinaReader: '지나 리더 설정하기', configureJinaReader: '지나 리더 설정하기',
waterCrawlNotConfigured: 'Watercrawl 이 설정되어 있지 않습니다.', waterCrawlNotConfigured: 'Watercrawl 이 설정되어 있지 않습니다.',
configureWatercrawl: '워터크롤 구성하기', configureWatercrawl: '워터크롤 구성하기',

@ -481,7 +481,6 @@ const translation = {
title: title:
'Rozszerzenia oparte na interfejsie API zapewniają scentralizowane zarządzanie interfejsami API, upraszczając konfigurację dla łatwego użytkowania w aplikacjach Dify.', 'Rozszerzenia oparte na interfejsie API zapewniają scentralizowane zarządzanie interfejsami API, upraszczając konfigurację dla łatwego użytkowania w aplikacjach Dify.',
link: 'Dowiedz się, jak opracować własne rozszerzenie interfejsu API.', link: 'Dowiedz się, jak opracować własne rozszerzenie interfejsu API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Dodaj rozszerzenie interfejsu API', add: 'Dodaj rozszerzenie interfejsu API',
selector: { selector: {
title: 'Rozszerzenie interfejsu API', title: 'Rozszerzenie interfejsu API',

@ -54,7 +54,6 @@ const translation = {
}, },
website: { website: {
limit: 'Ograniczać', limit: 'Ograniczać',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
firecrawlDoc: 'Dokumentacja Firecrawl', firecrawlDoc: 'Dokumentacja Firecrawl',
unknownError: 'Nieznany błąd', unknownError: 'Nieznany błąd',
fireCrawlNotConfiguredDescription: 'Skonfiguruj Firecrawl z kluczem API, aby z niego korzystać.', fireCrawlNotConfiguredDescription: 'Skonfiguruj Firecrawl z kluczem API, aby z niego korzystać.',
@ -85,7 +84,6 @@ const translation = {
jinaReaderNotConfiguredDescription: 'Skonfiguruj Jina Reader, wprowadzając bezpłatny klucz API, aby uzyskać dostęp.', jinaReaderNotConfiguredDescription: 'Skonfiguruj Jina Reader, wprowadzając bezpłatny klucz API, aby uzyskać dostęp.',
watercrawlTitle: 'Wyodrębnij treści z sieci za pomocą Watercrawl', watercrawlTitle: 'Wyodrębnij treści z sieci za pomocą Watercrawl',
configureWatercrawl: 'Skonfiguruj Watercrawl', configureWatercrawl: 'Skonfiguruj Watercrawl',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureJinaReader: 'Skonfiguruj Czytnik Jina', configureJinaReader: 'Skonfiguruj Czytnik Jina',
configureFirecrawl: 'Skonfiguruj Firecrawl', configureFirecrawl: 'Skonfiguruj Firecrawl',
watercrawlDoc: 'Dokumentacja Watercrawl', watercrawlDoc: 'Dokumentacja Watercrawl',

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'As extensões de API fornecem gerenciamento centralizado de API, simplificando a configuração para uso fácil em todos os aplicativos da Dify.', title: 'As extensões de API fornecem gerenciamento centralizado de API, simplificando a configuração para uso fácil em todos os aplicativos da Dify.',
link: 'Saiba como desenvolver sua própria Extensão de API.', link: 'Saiba como desenvolver sua própria Extensão de API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Adicionar Extensão de API', add: 'Adicionar Extensão de API',
selector: { selector: {
title: 'Extensão de API', title: 'Extensão de API',

@ -58,7 +58,6 @@ const translation = {
crawlSubPage: 'Rastrear subpáginas', crawlSubPage: 'Rastrear subpáginas',
selectAll: 'Selecionar tudo', selectAll: 'Selecionar tudo',
resetAll: 'Redefinir tudo', resetAll: 'Redefinir tudo',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
includeOnlyPaths: 'Incluir apenas caminhos', includeOnlyPaths: 'Incluir apenas caminhos',
configure: 'Configurar', configure: 'Configurar',
limit: 'Limite', limit: 'Limite',
@ -87,7 +86,6 @@ const translation = {
configureJinaReader: 'Configurar o Leitor Jina', configureJinaReader: 'Configurar o Leitor Jina',
waterCrawlNotConfigured: 'Watercrawl não está configurado', waterCrawlNotConfigured: 'Watercrawl não está configurado',
waterCrawlNotConfiguredDescription: 'Configure o Watercrawl com a chave da API para usá-lo.', waterCrawlNotConfiguredDescription: 'Configure o Watercrawl com a chave da API para usá-lo.',
watercrawlDocLink: 'https://docs.dify.ai/pt/guias/base-de-conhecimentos/criar-conhecimento-e-enviar-documentos/importar-dados-de-conteudo/sincronizar-a-partir-do-site',
watercrawlDoc: 'Documentos do Watercrawl', watercrawlDoc: 'Documentos do Watercrawl',
configureWatercrawl: 'Configurar Watercrawl', configureWatercrawl: 'Configurar Watercrawl',
}, },

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'Extensiile bazate pe API oferă o gestionare centralizată a API-urilor, simplificând configurația pentru o utilizare ușoară în aplicațiile Dify.', title: 'Extensiile bazate pe API oferă o gestionare centralizată a API-urilor, simplificând configurația pentru o utilizare ușoară în aplicațiile Dify.',
link: 'Aflați cum să dezvoltați propria extensie bazată pe API.', link: 'Aflați cum să dezvoltați propria extensie bazată pe API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Adăugați extensie API', add: 'Adăugați extensie API',
selector: { selector: {
title: 'Extensie API', title: 'Extensie API',

@ -65,7 +65,6 @@ const translation = {
firecrawlTitle: 'Extrageți conținut web cu 🔥Firecrawl', firecrawlTitle: 'Extrageți conținut web cu 🔥Firecrawl',
unknownError: 'Eroare necunoscută', unknownError: 'Eroare necunoscută',
scrapTimeInfo: 'Pagini răzuite {{total}} în total în {{timp}}s', scrapTimeInfo: 'Pagini răzuite {{total}} în total în {{timp}}s',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
excludePaths: 'Excluderea căilor', excludePaths: 'Excluderea căilor',
resetAll: 'Resetați toate', resetAll: 'Resetați toate',
extractOnlyMainContent: 'Extrageți doar conținutul principal (fără anteturi, navigări, subsoluri etc.)', extractOnlyMainContent: 'Extrageți doar conținutul principal (fără anteturi, navigări, subsoluri etc.)',
@ -86,7 +85,6 @@ const translation = {
watercrawlTitle: 'Extrageți conținut web cu Watercrawl', watercrawlTitle: 'Extrageți conținut web cu Watercrawl',
configureJinaReader: 'Configurează Jina Reader', configureJinaReader: 'Configurează Jina Reader',
waterCrawlNotConfiguredDescription: 'Configurează Watercrawl cu cheia API pentru a-l folosi.', waterCrawlNotConfiguredDescription: 'Configurează Watercrawl cu cheia API pentru a-l folosi.',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureFirecrawl: 'Configurează Firecrawl', configureFirecrawl: 'Configurează Firecrawl',
watercrawlDoc: 'Documentele Watercrawl', watercrawlDoc: 'Documentele Watercrawl',
configureWatercrawl: 'Configurează Watercrawl', configureWatercrawl: 'Configurează Watercrawl',

@ -471,7 +471,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API-расширения обеспечивают централизованное управление API, упрощая настройку для удобного использования в приложениях Dify.', title: 'API-расширения обеспечивают централизованное управление API, упрощая настройку для удобного использования в приложениях Dify.',
link: 'Узнайте, как разработать собственное API-расширение.', link: 'Узнайте, как разработать собственное API-расширение.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Добавить API Extension', add: 'Добавить API Extension',
selector: { selector: {
title: 'API Extension', title: 'API Extension',

@ -63,7 +63,6 @@ const translation = {
run: 'Запустить', run: 'Запустить',
firecrawlTitle: 'Извлечь веб-контент с помощью 🔥Firecrawl', firecrawlTitle: 'Извлечь веб-контент с помощью 🔥Firecrawl',
firecrawlDoc: 'Документация Firecrawl', firecrawlDoc: 'Документация Firecrawl',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
options: 'Опции', options: 'Опции',
crawlSubPage: 'Сканировать подстраницы', crawlSubPage: 'Сканировать подстраницы',
limit: 'Лимит', limit: 'Лимит',
@ -88,7 +87,6 @@ const translation = {
jinaReaderTitle: 'Конвертируйте весь сайт в Markdown', jinaReaderTitle: 'Конвертируйте весь сайт в Markdown',
useSitemapTooltip: 'Следуйте карте сайта, чтобы просканировать сайт. Если нет, Jina Reader будет сканировать итеративно в зависимости от релевантности страницы, выдавая меньшее количество страниц, но более высокого качества.', useSitemapTooltip: 'Следуйте карте сайта, чтобы просканировать сайт. Если нет, Jina Reader будет сканировать итеративно в зависимости от релевантности страницы, выдавая меньшее количество страниц, но более высокого качества.',
watercrawlTitle: 'Извлечение веб-контента с помощью Watercrawl', watercrawlTitle: 'Извлечение веб-контента с помощью Watercrawl',
watercrawlDocLink: 'https://docs.dify.ai/ru/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureWatercrawl: 'Настроить Watercrawl', configureWatercrawl: 'Настроить Watercrawl',
waterCrawlNotConfigured: 'Watercrawl не настроен', waterCrawlNotConfigured: 'Watercrawl не настроен',
configureFirecrawl: 'Настроить Firecrawl', configureFirecrawl: 'Настроить Firecrawl',

@ -464,7 +464,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'Razširitve API omogočajo centralizirano upravljanje API, kar poenostavi konfiguracijo za enostavno uporabo v aplikacijah Dify.', title: 'Razširitve API omogočajo centralizirano upravljanje API, kar poenostavi konfiguracijo za enostavno uporabo v aplikacijah Dify.',
link: 'Naučite se, kako razviti svojo API razširitev.', link: 'Naučite se, kako razviti svojo API razširitev.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Dodaj API razširitev', add: 'Dodaj API razširitev',
selector: { selector: {
title: 'API razširitev', title: 'API razširitev',
@ -693,7 +692,6 @@ const translation = {
type: 'Vrsta', type: 'Vrsta',
link: 'Preberite, kako razvijete lastno razširitev API-ja.', link: 'Preberite, kako razvijete lastno razširitev API-ja.',
title: 'Razširitve API zagotavljajo centralizirano upravljanje API, kar poenostavlja konfiguracijo za enostavno uporabo v aplikacijah Dify.', title: 'Razširitve API zagotavljajo centralizirano upravljanje API, kar poenostavlja konfiguracijo za enostavno uporabo v aplikacijah Dify.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Dodajanje razširitve API-ja', add: 'Dodajanje razširitve API-ja',
}, },
about: { about: {

@ -71,7 +71,6 @@ const translation = {
run: 'Zaženi', run: 'Zaženi',
firecrawlTitle: 'Izvleci spletno vsebino z 🔥Firecrawl', firecrawlTitle: 'Izvleci spletno vsebino z 🔥Firecrawl',
firecrawlDoc: 'Firecrawl dokumentacija', firecrawlDoc: 'Firecrawl dokumentacija',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
jinaReaderTitle: 'Pretvori celotno stran v Markdown', jinaReaderTitle: 'Pretvori celotno stran v Markdown',
jinaReaderDoc: 'Več o Jina Reader', jinaReaderDoc: 'Več o Jina Reader',
jinaReaderDocLink: 'https://jina.ai/reader', jinaReaderDocLink: 'https://jina.ai/reader',
@ -97,7 +96,6 @@ const translation = {
waterCrawlNotConfigured: 'Watercrawl ni konfiguriran', waterCrawlNotConfigured: 'Watercrawl ni konfiguriran',
watercrawlDoc: 'Watercrawl dokumentacija', watercrawlDoc: 'Watercrawl dokumentacija',
configureJinaReader: 'Konfigurirajte Jina Reader', configureJinaReader: 'Konfigurirajte Jina Reader',
watercrawlDocLink: 'https://docs.dify.ai/sl/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureFirecrawl: 'Konfigurirajte Firecrawl', configureFirecrawl: 'Konfigurirajte Firecrawl',
watercrawlTitle: 'Izvleci vsebino z interneta z Watercrawl', watercrawlTitle: 'Izvleci vsebino z interneta z Watercrawl',
}, },

@ -466,7 +466,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'ส่วนขยาย API ให้การจัดการ API แบบรวมศูนย์ ทําให้การกําหนดค่าง่ายขึ้นเพื่อให้ใช้งานได้ง่ายในแอปพลิเคชันของ Dify', title: 'ส่วนขยาย API ให้การจัดการ API แบบรวมศูนย์ ทําให้การกําหนดค่าง่ายขึ้นเพื่อให้ใช้งานได้ง่ายในแอปพลิเคชันของ Dify',
link: 'เรียนรู้วิธีพัฒนาส่วนขยาย API ของคุณเอง', link: 'เรียนรู้วิธีพัฒนาส่วนขยาย API ของคุณเอง',
linkUrl: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
add: 'เพิ่มส่วนขยาย API', add: 'เพิ่มส่วนขยาย API',
selector: { selector: {
title: 'ส่วนขยาย API', title: 'ส่วนขยาย API',

@ -71,7 +71,6 @@ const translation = {
run: 'วิ่ง', run: 'วิ่ง',
firecrawlTitle: 'แยกเนื้อหาเว็บด้วย 🔥Firecrawl', firecrawlTitle: 'แยกเนื้อหาเว็บด้วย 🔥Firecrawl',
firecrawlDoc: 'เอกสาร Firecrawl', firecrawlDoc: 'เอกสาร Firecrawl',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
jinaReaderTitle: 'แปลงทั้งไซต์เป็น Markdown', jinaReaderTitle: 'แปลงทั้งไซต์เป็น Markdown',
jinaReaderDoc: 'เรียนรู้เพิ่มเติมเกี่ยวกับ Jina Reader', jinaReaderDoc: 'เรียนรู้เพิ่มเติมเกี่ยวกับ Jina Reader',
jinaReaderDocLink: 'https://jina.ai/reader', jinaReaderDocLink: 'https://jina.ai/reader',
@ -94,7 +93,6 @@ const translation = {
maxDepthTooltip: 'ความลึกสูงสุดในการรวบรวมข้อมูลเมื่อเทียบกับ URL ที่ป้อน ความลึก 0 เพียงแค่ขูดหน้าของ URL ที่ป้อนความลึก 1 ขูด url และทุกอย่างหลังจาก enteredURL + หนึ่ง / เป็นต้น', maxDepthTooltip: 'ความลึกสูงสุดในการรวบรวมข้อมูลเมื่อเทียบกับ URL ที่ป้อน ความลึก 0 เพียงแค่ขูดหน้าของ URL ที่ป้อนความลึก 1 ขูด url และทุกอย่างหลังจาก enteredURL + หนึ่ง / เป็นต้น',
watercrawlTitle: 'ดึงเนื้อหาจากเว็บด้วย Watercrawl', watercrawlTitle: 'ดึงเนื้อหาจากเว็บด้วย Watercrawl',
configureJinaReader: 'ตั้งค่า Jina Reader', configureJinaReader: 'ตั้งค่า Jina Reader',
watercrawlDocLink: 'https://docs.dify.ai/th/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureFirecrawl: 'กำหนดค่า Firecrawl', configureFirecrawl: 'กำหนดค่า Firecrawl',
configureWatercrawl: 'กำหนดค่าการเข้าถึงน้ำ', configureWatercrawl: 'กำหนดค่าการเข้าถึงน้ำ',
waterCrawlNotConfiguredDescription: 'กำหนดค่า Watercrawl ด้วย API key เพื่อใช้งาน.', waterCrawlNotConfiguredDescription: 'กำหนดค่า Watercrawl ด้วย API key เพื่อใช้งาน.',

@ -471,7 +471,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API uzantıları merkezi API yönetimi sağlar, Dify\'nin uygulamaları arasında kolay kullanım için yapılandırmayı basitleştirir.', title: 'API uzantıları merkezi API yönetimi sağlar, Dify\'nin uygulamaları arasında kolay kullanım için yapılandırmayı basitleştirir.',
link: 'Kendi API Uzantınızı nasıl geliştireceğinizi öğrenin.', link: 'Kendi API Uzantınızı nasıl geliştireceğinizi öğrenin.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'API Uzantısı Ekle', add: 'API Uzantısı Ekle',
selector: { selector: {
title: 'API Uzantısı', title: 'API Uzantısı',

@ -63,7 +63,6 @@ const translation = {
run: 'Çalıştır', run: 'Çalıştır',
firecrawlTitle: '🔥Firecrawl ile web içeriğini çıkarın', firecrawlTitle: '🔥Firecrawl ile web içeriğini çıkarın',
firecrawlDoc: 'Firecrawl dokümanları', firecrawlDoc: 'Firecrawl dokümanları',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
options: 'Seçenekler', options: 'Seçenekler',
crawlSubPage: 'Alt sayfaları tarayın', crawlSubPage: 'Alt sayfaları tarayın',
limit: 'Sınır', limit: 'Sınır',
@ -93,7 +92,6 @@ const translation = {
waterCrawlNotConfigured: 'Watercrawl yapılandırılmamış', waterCrawlNotConfigured: 'Watercrawl yapılandırılmamış',
watercrawlTitle: 'Watercrawl ile web içeriğini çıkar', watercrawlTitle: 'Watercrawl ile web içeriğini çıkar',
configureJinaReader: 'Jina Okuyucusunu Yapılandır', configureJinaReader: 'Jina Okuyucusunu Yapılandır',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
configureWatercrawl: 'Watercrawl\'ı yapılandır', configureWatercrawl: 'Watercrawl\'ı yapılandır',
}, },
cancel: 'İptal', cancel: 'İptal',

@ -468,7 +468,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API-розширення забезпечують централізоване керування API, спрощуючи конфігурацію для зручного використання в різних програмах Dify.', title: 'API-розширення забезпечують централізоване керування API, спрощуючи конфігурацію для зручного використання в різних програмах Dify.',
link: 'Дізнайтеся, як розробити власне розширення API.', link: 'Дізнайтеся, як розробити власне розширення API.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Додати розширення API', add: 'Додати розширення API',
selector: { selector: {
title: 'Розширення API', title: 'Розширення API',

@ -60,7 +60,6 @@ const translation = {
unknownError: 'Невідома помилка', unknownError: 'Невідома помилка',
maxDepth: 'Максимальна глибина', maxDepth: 'Максимальна глибина',
crawlSubPage: 'Сканування підсторінок', crawlSubPage: 'Сканування підсторінок',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
preview: 'Попередній перегляд', preview: 'Попередній перегляд',
fireCrawlNotConfigured: 'Firecrawl не налаштовано', fireCrawlNotConfigured: 'Firecrawl не налаштовано',
includeOnlyPaths: 'Включати лише контури', includeOnlyPaths: 'Включати лише контури',
@ -88,7 +87,6 @@ const translation = {
configureFirecrawl: 'Налаштування Firecrawl', configureFirecrawl: 'Налаштування Firecrawl',
configureWatercrawl: 'Налаштування Watercrawl', configureWatercrawl: 'Налаштування Watercrawl',
watercrawlTitle: 'Витягуйте веб-контент за допомогою Watercrawl', watercrawlTitle: 'Витягуйте веб-контент за допомогою Watercrawl',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlDoc: 'Документація Watercrawl', watercrawlDoc: 'Документація Watercrawl',
}, },
cancel: 'Скасувати', cancel: 'Скасувати',

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'Các tiện ích API cung cấp quản lý API tập trung, giúp cấu hình dễ dàng sử dụng trên các ứng dụng của Dify.', title: 'Các tiện ích API cung cấp quản lý API tập trung, giúp cấu hình dễ dàng sử dụng trên các ứng dụng của Dify.',
link: 'Tìm hiểu cách phát triển Phần mở rộng API của riêng bạn.', link: 'Tìm hiểu cách phát triển Phần mở rộng API của riêng bạn.',
linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README',
add: 'Thêm Phần mở rộng API', add: 'Thêm Phần mở rộng API',
selector: { selector: {
title: 'Phần mở rộng API', title: 'Phần mở rộng API',

@ -63,7 +63,6 @@ const translation = {
unknownError: 'Lỗi không xác định', unknownError: 'Lỗi không xác định',
extractOnlyMainContent: 'Chỉ trích xuất nội dung chính (không có đầu trang, điều hướng, chân trang, v.v.)', extractOnlyMainContent: 'Chỉ trích xuất nội dung chính (không có đầu trang, điều hướng, chân trang, v.v.)',
exceptionErrorTitle: 'Một ngoại lệ xảy ra trong khi chạy tác vụ Firecrawl:', exceptionErrorTitle: 'Một ngoại lệ xảy ra trong khi chạy tác vụ Firecrawl:',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
selectAll: 'Chọn tất cả', selectAll: 'Chọn tất cả',
firecrawlTitle: 'Trích xuất nội dung web bằng 🔥Firecrawl', firecrawlTitle: 'Trích xuất nội dung web bằng 🔥Firecrawl',
totalPageScraped: 'Tổng số trang được cạo:', totalPageScraped: 'Tổng số trang được cạo:',
@ -86,7 +85,6 @@ const translation = {
configureFirecrawl: 'Cấu hình Firecrawl', configureFirecrawl: 'Cấu hình Firecrawl',
configureJinaReader: 'Cấu hình Jina Reader', configureJinaReader: 'Cấu hình Jina Reader',
waterCrawlNotConfiguredDescription: 'Cấu hình Watercrawl với khóa API để sử dụng nó.', waterCrawlNotConfiguredDescription: 'Cấu hình Watercrawl với khóa API để sử dụng nó.',
watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
watercrawlTitle: 'Trích xuất nội dung web bằng Watercrawl', watercrawlTitle: 'Trích xuất nội dung web bằng Watercrawl',
watercrawlDoc: 'Tài liệu Watercrawl', watercrawlDoc: 'Tài liệu Watercrawl',
waterCrawlNotConfigured: 'Watercrawl chưa được cấu hình', waterCrawlNotConfigured: 'Watercrawl chưa được cấu hình',

@ -487,7 +487,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify 上的各类应用中直接使用。', title: 'API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify 上的各类应用中直接使用。',
link: '了解如何开发您自己的 API 扩展。', link: '了解如何开发您自己的 API 扩展。',
linkUrl: 'https://docs.dify.ai/zh-hans/guides/extension/api-based-extension',
add: '新增 API 扩展', add: '新增 API 扩展',
selector: { selector: {
title: 'API 扩展', title: 'API 扩展',

@ -80,7 +80,6 @@ const translation = {
running: '运行中', running: '运行中',
firecrawlTitle: '使用 🔥Firecrawl 提取网页内容', firecrawlTitle: '使用 🔥Firecrawl 提取网页内容',
firecrawlDoc: 'Firecrawl 文档', firecrawlDoc: 'Firecrawl 文档',
firecrawlDocLink: 'https://docs.dify.ai/zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
jinaReaderTitle: '将整个站点内容转换为 Markdown 格式', jinaReaderTitle: '将整个站点内容转换为 Markdown 格式',
jinaReaderDoc: '了解更多关于 Jina Reader', jinaReaderDoc: '了解更多关于 Jina Reader',
jinaReaderDocLink: 'https://jina.ai/reader', jinaReaderDocLink: 'https://jina.ai/reader',
@ -101,7 +100,6 @@ const translation = {
scrapTimeInfo: '总共在 {{time}}秒 内抓取了 {{total}} 个页面', scrapTimeInfo: '总共在 {{time}}秒 内抓取了 {{total}} 个页面',
preview: '预览', preview: '预览',
maxDepthTooltip: '相对于输入 URL 的最大抓取深度。深度 0 仅抓取输入 URL 本身的页面,深度 1 抓取输入 URL 及其后的一层目录(一个 /),依此类推。', maxDepthTooltip: '相对于输入 URL 的最大抓取深度。深度 0 仅抓取输入 URL 本身的页面,深度 1 抓取输入 URL 及其后的一层目录(一个 /),依此类推。',
watercrawlDocLink: '从网站同步',
watercrawlDoc: 'Watercrawl 文档', watercrawlDoc: 'Watercrawl 文档',
configureWatercrawl: '配置水爬行', configureWatercrawl: '配置水爬行',
watercrawlTitle: '使用 Watercrawl 提取网页内容', watercrawlTitle: '使用 Watercrawl 提取网页内容',

@ -467,7 +467,6 @@ const translation = {
apiBasedExtension: { apiBasedExtension: {
title: 'API 擴充套件提供了一個集中式的 API 管理,在此統一新增 API 配置後,方便在 Dify 上的各類應用中直接使用。', title: 'API 擴充套件提供了一個集中式的 API 管理,在此統一新增 API 配置後,方便在 Dify 上的各類應用中直接使用。',
link: '瞭解如何開發您自己的 API 擴充套件。', link: '瞭解如何開發您自己的 API 擴充套件。',
linkUrl: 'https://docs.dify.ai/zh-hans/guides/tools/extensions/api-based/api-based-extension',
add: '新增 API 擴充套件', add: '新增 API 擴充套件',
selector: { selector: {
title: 'API 擴充套件', title: 'API 擴充套件',

@ -61,7 +61,6 @@ const translation = {
fireCrawlNotConfiguredDescription: '使用 API 金鑰配置 Firecrawl 以使用它。', fireCrawlNotConfiguredDescription: '使用 API 金鑰配置 Firecrawl 以使用它。',
limit: '限制', limit: '限制',
crawlSubPage: '抓取子頁面', crawlSubPage: '抓取子頁面',
firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
preview: '預覽', preview: '預覽',
configure: '配置', configure: '配置',
excludePaths: '排除路徑', excludePaths: '排除路徑',
@ -87,7 +86,6 @@ const translation = {
configureFirecrawl: '配置 Firecrawl', configureFirecrawl: '配置 Firecrawl',
configureWatercrawl: '配置水爬行', configureWatercrawl: '配置水爬行',
watercrawlTitle: '使用 Watercrawl 提取網頁內容', watercrawlTitle: '使用 Watercrawl 提取網頁內容',
watercrawlDocLink: 'https://docs.dify.ai/zh-TW/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website',
waterCrawlNotConfiguredDescription: '配置 Watercrawl 並使用 API 金鑰來使用它。', waterCrawlNotConfiguredDescription: '配置 Watercrawl 並使用 API 金鑰來使用它。',
configureJinaReader: '配置 Jina Reader', configureJinaReader: '配置 Jina Reader',
waterCrawlNotConfigured: 'Watercrawl 尚未配置', waterCrawlNotConfigured: 'Watercrawl 尚未配置',

Loading…
Cancel
Save