diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index 9d229185f3..10c3cdcf0e 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -163,7 +163,7 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: ) db.session.add(end_user) db.session.commit() - exp_dt = datetime.now(UTC) + timedelta(hours=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES * 24) + exp_dt = datetime.now(UTC) + timedelta(minutes=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES) exp = int(exp_dt.timestamp()) payload = { "iss": site.id, diff --git a/api/core/app/apps/pipeline/pipeline_generator.py b/api/core/app/apps/pipeline/pipeline_generator.py index ec565fe2e5..e726ad4841 100644 --- a/api/core/app/apps/pipeline/pipeline_generator.py +++ b/api/core/app/apps/pipeline/pipeline_generator.py @@ -32,6 +32,7 @@ from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchem from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from extensions.ext_database import db +from libs.flask_utils import preserve_flask_contexts from models import Account, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom from models.dataset import Document, DocumentPipelineExecutionLog, Pipeline from models.enums import WorkflowRunTriggeredFrom @@ -209,25 +210,22 @@ class PipelineGenerator(BaseAppGenerator): # run in child thread context = contextvars.copy_context() - @copy_current_request_context - def worker_with_context(): - # Run the worker within the copied context - return context.run( - self._generate, - flask_app=current_app._get_current_object(), # type: ignore - context=context, - pipeline=pipeline, - workflow_id=workflow.id, - user=user, - application_generate_entity=application_generate_entity, - invoke_from=invoke_from, - workflow_execution_repository=workflow_execution_repository, - workflow_node_execution_repository=workflow_node_execution_repository, - streaming=streaming, - workflow_thread_pool_id=workflow_thread_pool_id, - ) - - worker_thread = threading.Thread(target=worker_with_context) + worker_thread = threading.Thread( + target=self._generate, + kwargs={ + "flask_app": current_app._get_current_object(), # type: ignore + "context": context, + "pipeline": pipeline, + "workflow_id": workflow.id, + "user": user, + "application_generate_entity": application_generate_entity, + "invoke_from": invoke_from, + "workflow_execution_repository": workflow_execution_repository, + "workflow_node_execution_repository": workflow_node_execution_repository, + "streaming": streaming, + "workflow_thread_pool_id": workflow_thread_pool_id, + }, + ) worker_thread.start() # return batch, dataset, documents @@ -282,23 +280,7 @@ class PipelineGenerator(BaseAppGenerator): :param streaming: is stream :param workflow_thread_pool_id: workflow thread pool id """ - print("jin ru la 1") - 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 - print("jin ru la 2") - if saved_user is not None: - from flask import g - - g._login_user = saved_user + with preserve_flask_contexts(flask_app, context_vars=context): # init queue manager workflow = db.session.query(Workflow).filter(Workflow.id == workflow_id).first() if not workflow: @@ -311,20 +293,17 @@ class PipelineGenerator(BaseAppGenerator): ) context = contextvars.copy_context() - @copy_current_request_context - def worker_with_context(): - # Run the worker within the copied context - return context.run( - self._generate_worker, - flask_app=current_app._get_current_object(), # type: ignore - context=context, - queue_manager=queue_manager, - application_generate_entity=application_generate_entity, - workflow_thread_pool_id=workflow_thread_pool_id, - ) - # new thread - worker_thread = threading.Thread(target=worker_with_context) + worker_thread = threading.Thread( + target=self._generate_worker, + kwargs={ + "flask_app": current_app._get_current_object(), # type: ignore + "context": context, + "queue_manager": queue_manager, + "application_generate_entity": application_generate_entity, + "workflow_thread_pool_id": workflow_thread_pool_id, + }, + ) worker_thread.start() @@ -521,20 +500,9 @@ class PipelineGenerator(BaseAppGenerator): :param workflow_thread_pool_id: workflow thread pool id :return: """ - print("jin ru la 3") - for var, val in context.items(): - var.set(val) - 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(): + with preserve_flask_contexts(flask_app, context_vars=context): try: - if saved_user is not None: - from flask import g - - g._login_user = saved_user # workflow app runner = PipelineRunner( application_generate_entity=application_generate_entity, diff --git a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py index 8fe6199517..c6cf0d2b27 100644 --- a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py +++ b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py @@ -41,6 +41,12 @@ class WeaviateVector(BaseVector): weaviate.connect.connection.has_grpc = False + # Fix to minimize the performance impact of the deprecation check in weaviate-client 3.24.0, + # by changing the connection timeout to pypi.org from 1 second to 0.001 seconds. + # TODO: This can be removed once weaviate-client is updated to 3.26.7 or higher, + # which does not contain the deprecation check. + weaviate.connect.connection.PYPI_TIMEOUT = 0.001 + try: client = weaviate.Client( url=config.endpoint, auth_client_secret=auth_config, timeout_config=(5, 60), startup_period=None diff --git a/api/core/workflow/nodes/agent/agent_node.py b/api/core/workflow/nodes/agent/agent_node.py index faa8f90bea..22c564c1fc 100644 --- a/api/core/workflow/nodes/agent/agent_node.py +++ b/api/core/workflow/nodes/agent/agent_node.py @@ -214,7 +214,7 @@ class AgentNode(ToolNode): ) if tool_runtime.entity.description: tool_runtime.entity.description.llm = ( - extra.get("descrption", "") or tool_runtime.entity.description.llm + extra.get("description", "") or tool_runtime.entity.description.llm ) for tool_runtime_params in tool_runtime.entity.parameters: tool_runtime_params.form = ( diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 0079bad7c7..a03364aa3d 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -31,10 +31,13 @@ const TestApi: FC = ({ const language = getLanguage(locale) const [credentialsModalShow, setCredentialsModalShow] = useState(false) const [tempCredential, setTempCredential] = React.useState(customCollection.credentials) + const [testing, setTesting] = useState(false) const [result, setResult] = useState('') const { operation_id: toolName, parameters } = tool const [parametersValue, setParametersValue] = useState>({}) const handleTest = async () => { + if (testing) return + setTesting(true) // clone test schema const credentials = JSON.parse(JSON.stringify(tempCredential)) as Credential if (credentials.auth_type === AuthType.none) { @@ -52,6 +55,7 @@ const TestApi: FC = ({ } const res = await testAPIAvailable(data) as any setResult(res.error || res.result) + setTesting(false) } return ( @@ -107,7 +111,7 @@ const TestApi: FC = ({ - +
{t('tools.test.testResult')}
diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 674c768aa5..4ca8746137 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -118,6 +118,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { title={<> {renderI18nObject(def.label)} {def.required && *} } + key={def.variable} tooltip={def.tooltip && renderI18nObject(def.tooltip)} inline > diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index 1e195c5d40..3540c60a39 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -140,6 +140,7 @@ const CodeEditor: FC = ({ language={languageMap[language] || 'javascript'} theme={isMounted ? theme : 'default-theme'} // sometimes not load the default theme value={outPutValue} + loading={Loading...} onChange={handleEditorChange} // https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOptions.html options={{ diff --git a/web/app/components/workflow/nodes/agent/node.tsx b/web/app/components/workflow/nodes/agent/node.tsx index 57ad2a0b81..d2267fd00f 100644 --- a/web/app/components/workflow/nodes/agent/node.tsx +++ b/web/app/components/workflow/nodes/agent/node.tsx @@ -54,9 +54,9 @@ const AgentNode: FC> = (props) => { const field = param.name const value = inputs.agent_parameters?.[field]?.value if (value) { - (value as unknown as any[]).forEach((item) => { + (value as unknown as any[]).forEach((item, idx) => { tools.push({ - id: `${param.name}-${i}`, + id: `${param.name}-${idx}`, providerName: item.provider_name, }) }) diff --git a/web/i18n/es-ES/app.ts b/web/i18n/es-ES/app.ts index c1147720e7..41c90090b5 100644 --- a/web/i18n/es-ES/app.ts +++ b/web/i18n/es-ES/app.ts @@ -207,6 +207,7 @@ const translation = { modelNotSupportedTip: 'El modelo actual no admite esta función y se degrada automáticamente a inyección de comandos.', structuredTip: 'Las Salidas Estructuradas son una función que garantiza que el modelo siempre generará respuestas que se ajusten a su esquema JSON proporcionado.', modelNotSupported: 'Modelo no soportado', + structured: 'sistemático', }, accessItemsDescription: { anyone: 'Cualquiera puede acceder a la aplicación web.', diff --git a/web/i18n/es-ES/dataset.ts b/web/i18n/es-ES/dataset.ts index b759f26ec8..69c395287d 100644 --- a/web/i18n/es-ES/dataset.ts +++ b/web/i18n/es-ES/dataset.ts @@ -202,6 +202,7 @@ const translation = { builtInDescription: 'Los metadatos integrados se extraen y generan automáticamente. Deben estar habilitados antes de su uso y no se pueden editar.', name: 'Nombre', description: 'Puedes gestionar todos los metadatos en este conocimiento aquí. Las modificaciones se sincronizarán en todos los documentos.', + disabled: 'desactivar', }, documentMetadata: { technicalParameters: 'Parámetros técnicos', diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts index 3701d50fe7..e797b7d5e8 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -188,6 +188,7 @@ const translation = { nodeResize: '노드 크기 조정됨', nodeDragStop: '노드가 이동했습니다.', edgeDelete: '노드가 연결이 끊어졌습니다.', + nodeTitleChange: '노드 제목이 변경됨', }, errorMsg: { fieldRequired: '{{field}}가 필요합니다', diff --git a/web/i18n/ro-RO/workflow.ts b/web/i18n/ro-RO/workflow.ts index ae025a1103..5b6aba8762 100644 --- a/web/i18n/ro-RO/workflow.ts +++ b/web/i18n/ro-RO/workflow.ts @@ -188,6 +188,7 @@ const translation = { nodeDescriptionChange: 'Descrierea nodului a fost modificată', edgeDelete: 'Nod deconectat', nodeAdd: 'Nod adăugat', + nodeDragStop: 'Nod mutat', }, errorMsg: { fieldRequired: '{{field}} este obligatoriu', diff --git a/web/i18n/sl-SI/workflow.ts b/web/i18n/sl-SI/workflow.ts index d050d42f4e..b24435fd69 100644 --- a/web/i18n/sl-SI/workflow.ts +++ b/web/i18n/sl-SI/workflow.ts @@ -112,6 +112,7 @@ const translation = { pointerMode: 'Način s kazalcem', autoSaved: 'Samodejno shranjeno', configure: 'Konfiguriraj', + inRunMode: 'V načinu izvajanja', }, env: { modal: { @@ -185,6 +186,7 @@ const translation = { clearHistory: 'Počisti zgodovino', hintText: 'Vaša dejanja urejanja so sledena v zgodovini sprememb, ki se hrani na vaši napravi za čas trajanja te seje. Ta zgodovina bo izbrisana, ko zapustite urejevalnik.', placeholder: 'Še niste spremenili ničesar.', + stepForward_one: '{{count}} korak naprej', }, errorMsg: { fields: { @@ -836,6 +838,7 @@ const translation = { upload_file_id: 'Naložite ID datoteke', title: 'datoteke, ki jih je ustvaril agent', url: 'URL slike', + transfer_method: 'Metoda prenosa. Vrednost je remote_url ali local_file.', }, json: 'agent generiran json', text: 'vsebina, ki jo je ustvaril agent',