From ecade13455d7cb93cc1a9c39358aeff7f419c423 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:06:00 +0800 Subject: [PATCH 01/89] add MAX_TASK_PRE_CHILD for celery (#18985) --- api/docker/entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/docker/entrypoint.sh b/api/docker/entrypoint.sh index 68f3c65a4b..18d4f4885d 100755 --- a/api/docker/entrypoint.sh +++ b/api/docker/entrypoint.sh @@ -20,7 +20,8 @@ if [[ "${MODE}" == "worker" ]]; then CONCURRENCY_OPTION="-c ${CELERY_WORKER_AMOUNT:-1}" fi - exec celery -A app.celery worker -P ${CELERY_WORKER_CLASS:-gevent} $CONCURRENCY_OPTION --loglevel ${LOG_LEVEL:-INFO} \ + exec celery -A app.celery worker -P ${CELERY_WORKER_CLASS:-gevent} $CONCURRENCY_OPTION \ + --max-tasks-per-child ${MAX_TASK_PRE_CHILD:-50} --loglevel ${LOG_LEVEL:-INFO} \ -Q ${CELERY_QUEUES:-dataset,mail,ops_trace,app_deletion} elif [[ "${MODE}" == "beat" ]]; then From b8bb45b106563fb5a92499bf002efd314e68bc3a Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:26:30 +0800 Subject: [PATCH 02/89] remove unstructured api key check (#18989) --- api/core/workflow/nodes/document_extractor/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/workflow/nodes/document_extractor/node.py b/api/core/workflow/nodes/document_extractor/node.py index f14dd5d753..8fb1baec89 100644 --- a/api/core/workflow/nodes/document_extractor/node.py +++ b/api/core/workflow/nodes/document_extractor/node.py @@ -223,8 +223,8 @@ def _extract_text_from_doc(file_content: bytes) -> str: """ from unstructured.partition.api import partition_via_api - if not (dify_config.UNSTRUCTURED_API_URL and dify_config.UNSTRUCTURED_API_KEY): - raise TextExtractionError("UNSTRUCTURED_API_URL and UNSTRUCTURED_API_KEY must be set") + if not dify_config.UNSTRUCTURED_API_URL: + raise TextExtractionError("UNSTRUCTURED_API_URL must be set") try: with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as temp_file: @@ -235,7 +235,7 @@ def _extract_text_from_doc(file_content: bytes) -> str: file=file, metadata_filename=temp_file.name, api_url=dify_config.UNSTRUCTURED_API_URL, - api_key=dify_config.UNSTRUCTURED_API_KEY, + api_key=dify_config.UNSTRUCTURED_API_KEY, # type: ignore ) os.unlink(temp_file.name) return "\n".join([getattr(element, "text", "") for element in elements]) From a54773fbff1aa15fdea77577ab1a546318fa9d08 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 28 Apr 2025 19:51:50 +0800 Subject: [PATCH 03/89] refactor: switch to dynamic versioning in package configuration (#19019) Signed-off-by: -LAN- --- api/pyproject.toml | 6 +++++- api/uv.lock | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 185a97408c..72210b0774 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dify-api" -version = "1.3.1" +dynamic = ["version"] requires-python = ">=3.11,<3.13" dependencies = [ @@ -89,8 +89,12 @@ dependencies = [ # Before adding new dependency, consider place it in # alphabet order (a-z) and suitable group. +[tool.setuptools] +packages = [] + [tool.uv] default-groups = ["storage", "tools", "vdb"] +package = false [dependency-groups] diff --git a/api/uv.lock b/api/uv.lock index 3eb23f11ba..4604041ac4 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1155,7 +1155,6 @@ wheels = [ [[package]] name = "dify-api" -version = "1.3.1" source = { virtual = "." } dependencies = [ { name = "authlib" }, From a93a09e0f727f34390bf9946a31683eceac50088 Mon Sep 17 00:00:00 2001 From: kurokobo Date: Tue, 29 Apr 2025 09:57:42 +0900 Subject: [PATCH 04/89] feat: clean up message_files table first before proceeding to find orphaned files (#19035) --- api/commands.py | 71 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/api/commands.py b/api/commands.py index 3881439ddf..99a3211baf 100644 --- a/api/commands.py +++ b/api/commands.py @@ -818,8 +818,9 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[ click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green")) +@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.") @click.command("clear-orphaned-file-records", help="Clear orphaned file records.") -def clear_orphaned_file_records(): +def clear_orphaned_file_records(force: bool): """ Clear orphaned file records in the database. """ @@ -845,7 +846,15 @@ def clear_orphaned_file_records(): # notify user and ask for confirmation click.echo( - click.style("This command will find and delete orphaned file records in the following tables:", fg="yellow") + click.style( + "This command will first find and delete orphaned file records from the message_files table,", fg="yellow" + ) + ) + click.echo( + click.style( + "and then it will find and delete orphaned file records in the following tables:", + fg="yellow", + ) ) for files_table in files_tables: click.echo(click.style(f"- {files_table['table']}", fg="yellow")) @@ -878,11 +887,55 @@ def clear_orphaned_file_records(): fg="yellow", ) ) - click.confirm("Do you want to proceed?", abort=True) + if not force: + click.confirm("Do you want to proceed?", abort=True) # start the cleanup process click.echo(click.style("Starting orphaned file records cleanup.", fg="white")) + # clean up the orphaned records in the message_files table where message_id doesn't exist in messages table + try: + click.echo( + click.style("- Listing message_files records where message_id doesn't exist in messages table", fg="white") + ) + query = ( + "SELECT mf.id, mf.message_id " + "FROM message_files mf LEFT JOIN messages m ON mf.message_id = m.id " + "WHERE m.id IS NULL" + ) + orphaned_message_files = [] + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + orphaned_message_files.append({"id": str(i[0]), "message_id": str(i[1])}) + + if orphaned_message_files: + click.echo(click.style(f"Found {len(orphaned_message_files)} orphaned message_files records:", fg="white")) + for record in orphaned_message_files: + click.echo(click.style(f" - id: {record['id']}, message_id: {record['message_id']}", fg="black")) + + if not force: + click.confirm( + ( + f"Do you want to proceed " + f"to delete all {len(orphaned_message_files)} orphaned message_files records?" + ), + abort=True, + ) + + click.echo(click.style("- Deleting orphaned message_files records", fg="white")) + query = "DELETE FROM message_files WHERE id IN :ids" + with db.engine.begin() as conn: + conn.execute(db.text(query), {"ids": tuple([record["id"] for record in orphaned_message_files])}) + click.echo( + click.style(f"Removed {len(orphaned_message_files)} orphaned message_files records.", fg="green") + ) + else: + click.echo(click.style("No orphaned message_files records found. There is nothing to delete.", fg="green")) + except Exception as e: + click.echo(click.style(f"Error deleting orphaned message_files records: {str(e)}", fg="red")) + + # clean up the orphaned records in the rest of the *_files tables try: # fetch file id and keys from each table all_files_in_tables = [] @@ -964,7 +1017,8 @@ def clear_orphaned_file_records(): click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white")) for file in orphaned_files: click.echo(click.style(f"- orphaned file id: {file}", fg="black")) - click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True) + if not force: + click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True) # delete orphaned records for each file try: @@ -979,8 +1033,9 @@ def clear_orphaned_file_records(): click.echo(click.style(f"Removed {len(orphaned_files)} orphaned file records.", fg="green")) +@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.") @click.command("remove-orphaned-files-on-storage", help="Remove orphaned files on the storage.") -def remove_orphaned_files_on_storage(): +def remove_orphaned_files_on_storage(force: bool): """ Remove orphaned files on the storage. """ @@ -1028,7 +1083,8 @@ def remove_orphaned_files_on_storage(): fg="yellow", ) ) - click.confirm("Do you want to proceed?", abort=True) + if not force: + click.confirm("Do you want to proceed?", abort=True) # start the cleanup process click.echo(click.style("Starting orphaned files cleanup.", fg="white")) @@ -1069,7 +1125,8 @@ def remove_orphaned_files_on_storage(): click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white")) for file in orphaned_files: click.echo(click.style(f"- orphaned file: {file}", fg="black")) - click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True) + if not force: + click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True) # delete orphaned files removed_files = 0 From 36521e42759d519a824d6d851c32d470a4f39bca Mon Sep 17 00:00:00 2001 From: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Date: Tue, 29 Apr 2025 09:57:58 +0900 Subject: [PATCH 05/89] fixes: fix entrypoint script with missing environment variables (#19039) --- web/docker/entrypoint.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh index adb1d7d3af..59e9c472b3 100755 --- a/web/docker/entrypoint.sh +++ b/web/docker/entrypoint.sh @@ -32,4 +32,7 @@ export NEXT_PUBLIC_MAX_TOOLS_NUM=${MAX_TOOLS_NUM} export NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=${ENABLE_WEBSITE_JINAREADER:-true} export NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=${ENABLE_WEBSITE_FIRECRAWL:-true} export NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL=${ENABLE_WEBSITE_WATERCRAWL:-true} +export NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=${LOOP_NODE_MAX_COUNT} +export NEXT_PUBLIC_MAX_PARALLEL_LIMIT=${MAX_PARALLEL_LIMIT} +export NEXT_PUBLIC_MAX_ITERATIONS_NUM=${MAX_ITERATIONS_NUM} pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon From f0ca828544b10c2429f28410c5eed26aea29568f Mon Sep 17 00:00:00 2001 From: Panpan Date: Tue, 29 Apr 2025 08:58:10 +0800 Subject: [PATCH 06/89] fix: fix embedded chatbot styles on a relatively wide screen (#19030) --- web/public/embed.js | 2 +- web/public/embed.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/public/embed.js b/web/public/embed.js index 7060a97053..0d6d72f909 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -57,7 +57,7 @@ left: unset; min-width: 24rem; width: 48%; - max-width: calc(100vw - 2rem); + max-width: 40rem; /* Match mobile breakpoint*/ min-height: 43.75rem; height: 88%; max-height: calc(100vh - 6rem); diff --git a/web/public/embed.min.js b/web/public/embed.min.js index 8bcdfcbbec..0ba42eb508 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -39,4 +39,4 @@ - `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",t),y.draggable){var a=n;var l=y.dragAxis||"both";let s,d,t,r;function o(e){u=!1,r=("touchstart"===e.type?(s=e.touches[0].clientX-a.offsetLeft,d=e.touches[0].clientY-a.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-a.offsetLeft,d=e.clientY-a.offsetTop,t=e.clientX,e)).clientY,document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-r;if(u=8{u=!1},0),a.style.transition="",a.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}a.addEventListener("mousedown",o),a.addEventListener("touchstart",o)}}n.style.display="none",document.body.appendChild(n),2048{var t,n;e.origin===o&&(t=document.getElementById(m))&&e.source===t.contentWindow&&("dify-chatbot-iframe-ready"===e.data.type&&t.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:!0,isDraggable:!!y.draggable}},o),"dify-chatbot-expand-change"===e.data.type)&&(a=!a,n=document.getElementById(m))&&(a?n.style.cssText="\n position: absolute;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n top: unset;\n right: var(--dify-chatbot-bubble-button-right, 1rem); /* Align with dify-chatbot-bubble-button. */\n bottom: var(--dify-chatbot-bubble-button-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */\n left: unset;\n min-width: 24rem;\n width: 48%;\n max-width: calc(100vw - 2rem);\n min-height: 43.75rem;\n height: 88%;\n max-height: calc(100vh - 6rem);\n border: none;\n z-index: 2147483640;\n overflow: hidden;\n user-select: none;\n transition-property: width, height;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n ":n.style.cssText=l,d())}),document.getElementById(h)||r()}else console.error(t+" is empty or token is not provided")}function p(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function b(e){"Escape"===e.key&&(e=document.getElementById(m))&&"none"!==e.style.display&&(e.style.display="none",p("open"))}h,h,document.addEventListener("keydown",b),y?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file + `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",t),y.draggable){var a=n;var l=y.dragAxis||"both";let s,d,t,r;function o(e){u=!1,r=("touchstart"===e.type?(s=e.touches[0].clientX-a.offsetLeft,d=e.touches[0].clientY-a.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-a.offsetLeft,d=e.clientY-a.offsetTop,t=e.clientX,e)).clientY,document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-r;if(u=8{u=!1},0),a.style.transition="",a.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}a.addEventListener("mousedown",o),a.addEventListener("touchstart",o)}}n.style.display="none",document.body.appendChild(n),2048{var t,n;e.origin===o&&(t=document.getElementById(m))&&e.source===t.contentWindow&&("dify-chatbot-iframe-ready"===e.data.type&&t.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:!0,isDraggable:!!y.draggable}},o),"dify-chatbot-expand-change"===e.data.type)&&(a=!a,n=document.getElementById(m))&&(a?n.style.cssText="\n position: absolute;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n top: unset;\n right: var(--dify-chatbot-bubble-button-right, 1rem); /* Align with dify-chatbot-bubble-button. */\n bottom: var(--dify-chatbot-bubble-button-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */\n left: unset;\n min-width: 24rem;\n width: 48%;\n max-width: 40rem; /* Match mobile breakpoint*/\n min-height: 43.75rem;\n height: 88%;\n max-height: calc(100vh - 6rem);\n border: none;\n z-index: 2147483640;\n overflow: hidden;\n user-select: none;\n transition-property: width, height;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n ":n.style.cssText=l,d())}),document.getElementById(h)||r()}else console.error(t+" is empty or token is not provided")}function p(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function b(e){"Escape"===e.key&&(e=document.getElementById(m))&&"none"!==e.style.display&&(e.style.display="none",p("open"))}h,h,document.addEventListener("keydown",b),y?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file From bf46b894e98707dfd6825dc93f25e2452c9f2852 Mon Sep 17 00:00:00 2001 From: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:05:04 +0900 Subject: [PATCH 07/89] chore: Improve FILES_URL Configuration Comments (#19040) --- docker/.env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/.env.example b/docker/.env.example index 83d975cec5..1adb07ca64 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -39,6 +39,12 @@ APP_WEB_URL= # File preview or download Url prefix. # used to display File preview or download Url to the front-end or as Multi-model inputs; # Url is signed and has expiration time. +# Setting FILES_URL is required for file processing plugins. +# - For https://example.com, use FILES_URL=https://example.com +# - For http://example.com, use FILES_URL=http://example.com +# Recommendation: use a dedicated domain (e.g., https://upload.example.com). +# Alternatively, use http://:5001 or http://api:5001, +# ensuring port 5001 is externally accessible (see docker-compose.yaml). FILES_URL= # ------------------------------ From e36b1a7016f64bebf8811a8e1cb47c6032f49374 Mon Sep 17 00:00:00 2001 From: Novice <857526207@qq.com> Date: Tue, 29 Apr 2025 09:51:42 +0800 Subject: [PATCH 08/89] test(graph-engine-test): modify the assert condition (#19041) --- .../core/workflow/graph_engine/test_graph_engine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py b/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py index 2a29ad3e41..f3dbd1836b 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py @@ -864,10 +864,11 @@ def test_condition_parallel_correct_output(mock_close, mock_remove, app): with patch.object(CodeNode, "_run", new=code_generator): generator = graph_engine.run() stream_content = "" - res_content = "VAT:\ndify 123" + wrong_content = ["Stamp Duty", "other"] for item in generator: if isinstance(item, NodeRunStreamChunkEvent): stream_content += f"{item.chunk_content}\n" if isinstance(item, GraphRunSucceededEvent): - assert item.outputs == {"answer": res_content} - assert stream_content == res_content + "\n" + assert item.outputs is not None + answer = item.outputs["answer"] + assert all(rc not in answer for rc in wrong_content) From 94cc0b7a121f5e6dd11b4c17424c1d677c8fc7c2 Mon Sep 17 00:00:00 2001 From: Kevin9703 <51311316+Kevin9703@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:31:08 +0800 Subject: [PATCH 09/89] fix(workflow_cycle_manage): failed nodes were not updated in workflow_node_executions (#18994) --- api/core/app/task_pipeline/workflow_cycle_manage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/app/task_pipeline/workflow_cycle_manage.py index d720eff73d..09e2ee74e6 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/app/task_pipeline/workflow_cycle_manage.py @@ -381,6 +381,8 @@ class WorkflowCycleManage: workflow_node_execution.elapsed_time = elapsed_time workflow_node_execution.execution_metadata = execution_metadata + self._workflow_node_execution_repository.update(workflow_node_execution) + return workflow_node_execution def _handle_workflow_node_execution_retried( From 28a59ba344072a7c6c767e1c4a45efb1b936ec45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 29 Apr 2025 11:43:50 +0800 Subject: [PATCH 10/89] chore: improve diagram picture of docker compose (#19054) --- docker/README.md | 1 - docker/docker-compose.png | Bin 63694 -> 174447 bytes 2 files changed, 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 38b11a677f..22dfe2c91c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,7 +14,6 @@ Welcome to the new `docker` directory for deploying Dify using Docker Compose. T - **Unified Vector Database Services**: All vector database services are now managed from a single Docker Compose file `docker-compose.yaml`. You can switch between different vector databases by setting the `VECTOR_STORE` environment variable in your `.env` file. - **Mandatory .env File**: A `.env` file is now required to run `docker compose up`. This file is crucial for configuring your deployment and for any custom settings to persist through upgrades. -- **Legacy Support**: Previous deployment files are now located in the `docker-legacy` directory and will no longer be maintained. ### How to Deploy Dify with `docker-compose.yaml` diff --git a/docker/docker-compose.png b/docker/docker-compose.png index bdac113086d870f2117baee3f9d8d64600e28065..015d450236ae236f8708db8b857631d2b2b56615 100644 GIT binary patch literal 174447 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
    *zfCX_T!oQW+i07=IM4i z`yKBE4ZW%Fr_8p$|9L}SPmcGpkKQYHA>Q~_<|5sf$x2IwI=um1rNd%klJT=%C-PTV?8`xRrs+Synt8sfxcTdK!B8dX}c$Z;@Ju4POK|D zGr4v4o?3lIAh_>n2xUmJ<$&kO2J-4)z)d7?xxWShP%=!I=B}*)^_N_s`J74*3`IXf zMhvU#M_Gfwyu=Ts=4Qu_F!xhF-qNs^CO^zQ-Y-AicIjRf>~xmaeWWyd8Vvs#WItEd z<6}R^LU-M+xTWUAy3W-Y{D)6x); {mheQ`J>-n> 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�@^u%6f6-S!2c}e)_9{ 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 literal 63694 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 aySGnq@AkU~imk-I8O>iZFZHKie)}J;`?2Bx From a147d2a200f6ceb6a67698108eddaca185719fff Mon Sep 17 00:00:00 2001 From: Ganondorf <364776488@qq.com> Date: Tue, 29 Apr 2025 12:39:13 +0800 Subject: [PATCH 11/89] feat(api): use `json_repair` to fix invalid json while generating structured output (#18977) When generating JSON schema using an LLM in the structured output feature, models may occasionally return invalid JSON, which prevents clients from correctly parsing the response and can lead to UI breakage. This commit addresses the issue by introducing `json_repair` to automatically fix invalid JSON strings returned by the LLM, ensuring smoother functionality and better client-side handling of structured outputs. Co-authored-by: lizb --- api/core/llm_generator/llm_generator.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/api/core/llm_generator/llm_generator.py b/api/core/llm_generator/llm_generator.py index d5d2ca60fa..e5dbc30689 100644 --- a/api/core/llm_generator/llm_generator.py +++ b/api/core/llm_generator/llm_generator.py @@ -3,6 +3,8 @@ import logging import re from typing import Optional, cast +import json_repair + from core.llm_generator.output_parser.rule_config_generator import RuleConfigGeneratorOutputParser from core.llm_generator.output_parser.suggested_questions_after_answer import SuggestedQuestionsAfterAnswerOutputParser from core.llm_generator.prompts import ( @@ -366,7 +368,20 @@ class LLMGenerator: ), ) - generated_json_schema = cast(str, response.message.content) + raw_content = response.message.content + + if not isinstance(raw_content, str): + raise ValueError(f"LLM response content must be a string, got: {type(raw_content)}") + + try: + parsed_content = json.loads(raw_content) + except json.JSONDecodeError: + parsed_content = json_repair.loads(raw_content) + + if not isinstance(parsed_content, dict | list): + raise ValueError(f"Failed to parse structured output from llm: {raw_content}") + + generated_json_schema = json.dumps(parsed_content, indent=2, ensure_ascii=False) return {"output": generated_json_schema, "error": ""} except InvokeError as e: From 3c09b57059c0600ccc3a522b0317f682b30fb8f8 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:39:59 +0800 Subject: [PATCH 12/89] fix(web): fix the wrong components usage(#18995) (#19065) --- web/app/components/header/nav/nav-selector/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/app/components/header/nav/nav-selector/index.tsx b/web/app/components/header/nav/nav-selector/index.tsx index 3af75ab009..65c7cb163f 100644 --- a/web/app/components/header/nav/nav-selector/index.tsx +++ b/web/app/components/header/nav/nav-selector/index.tsx @@ -6,7 +6,7 @@ import { RiArrowDownSLine, RiArrowRightSLine, } from '@remixicon/react' -import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react' +import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' import { useRouter } from 'next/navigation' import { debounce } from 'lodash-es' import cn from '@/utils/classnames' @@ -77,7 +77,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:

    { navs.map(nav => ( - +
    { if (curNav?.id === nav.id) return @@ -112,12 +112,12 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: {nav.name}
    - + )) } {!isApp && isCurrentWorkspaceEditor && ( - +
    onCreate('')} className={cn( 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-gray-100', )}> @@ -126,7 +126,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
    {createText}
    -
    + )} {isApp && isCurrentWorkspaceEditor && ( From 83187b30c0a440e1b8cc70f8893d087fcde9b6d4 Mon Sep 17 00:00:00 2001 From: Panpan Date: Tue, 29 Apr 2025 14:51:21 +0800 Subject: [PATCH 13/89] fix: fix rerank model runner usage (#19008) --- api/core/rag/rerank/rerank_model.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/api/core/rag/rerank/rerank_model.py b/api/core/rag/rerank/rerank_model.py index ac7a3f8bb8..693535413a 100644 --- a/api/core/rag/rerank/rerank_model.py +++ b/api/core/rag/rerank/rerank_model.py @@ -52,14 +52,16 @@ class RerankModelRunner(BaseRerankRunner): rerank_documents = [] for result in rerank_result.docs: - # format document - rerank_document = Document( - page_content=result.text, - metadata=documents[result.index].metadata, - provider=documents[result.index].provider, - ) - if rerank_document.metadata is not None: - rerank_document.metadata["score"] = result.score - rerank_documents.append(rerank_document) + if score_threshold is None or result.score >= score_threshold: + # format document + rerank_document = Document( + page_content=result.text, + metadata=documents[result.index].metadata, + provider=documents[result.index].provider, + ) + if rerank_document.metadata is not None: + rerank_document.metadata["score"] = result.score + rerank_documents.append(rerank_document) - return rerank_documents + rerank_documents.sort(key=lambda x: x.metadata.get("score", 0.0), reverse=True) + return rerank_documents[:top_n] if top_n else rerank_documents From 8b4ea01810d5a7bb1ab03bdb4a254923685d9e95 Mon Sep 17 00:00:00 2001 From: Ethan <118581835+realethanhsu@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:52:13 +0800 Subject: [PATCH 14/89] feat: support access milvus with token (#19034) --- .../datasource/vdb/milvus/milvus_vector.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/api/core/rag/datasource/vdb/milvus/milvus_vector.py b/api/core/rag/datasource/vdb/milvus/milvus_vector.py index 100bcb198c..7b3f826082 100644 --- a/api/core/rag/datasource/vdb/milvus/milvus_vector.py +++ b/api/core/rag/datasource/vdb/milvus/milvus_vector.py @@ -27,8 +27,8 @@ class MilvusConfig(BaseModel): uri: str # Milvus server URI token: Optional[str] = None # Optional token for authentication - user: str # Username for authentication - password: str # Password for authentication + user: Optional[str] = None # Username for authentication + password: Optional[str] = None # Password for authentication batch_size: int = 100 # Batch size for operations database: str = "default" # Database name enable_hybrid_search: bool = False # Flag to enable hybrid search @@ -43,10 +43,11 @@ class MilvusConfig(BaseModel): """ if not values.get("uri"): raise ValueError("config MILVUS_URI is required") - if not values.get("user"): - raise ValueError("config MILVUS_USER is required") - if not values.get("password"): - raise ValueError("config MILVUS_PASSWORD is required") + if not values.get("token"): + if not values.get("user"): + raise ValueError("config MILVUS_USER is required") + if not values.get("password"): + raise ValueError("config MILVUS_PASSWORD is required") return values def to_milvus_params(self): @@ -356,11 +357,14 @@ class MilvusVector(BaseVector): ) redis_client.set(collection_exist_cache_key, 1, ex=3600) - def _init_client(self, config) -> MilvusClient: + def _init_client(self, config: MilvusConfig) -> MilvusClient: """ Initialize and return a Milvus client. """ - client = MilvusClient(uri=config.uri, user=config.user, password=config.password, db_name=config.database) + if config.token: + client = MilvusClient(uri=config.uri, token=config.token, db_name=config.database) + else: + client = MilvusClient(uri=config.uri, user=config.user, password=config.password, db_name=config.database) return client From 8266815cdae6b3fd2e3cb1ad5ba51fed39b13053 Mon Sep 17 00:00:00 2001 From: Ahmad Zidan <6338730+lan666as@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:10:08 +0700 Subject: [PATCH 15/89] feat: add AWS Managed IAM auth for OpenSearch vector DB (#18963) --- .../middleware/vdb/opensearch_config.py | 31 ++++++++-- .../vdb/opensearch/opensearch_vector.py | 59 ++++++++++++++----- .../vdb/opensearch/test_opensearch.py | 59 ++++++++++++++++++- docker/.env.example | 6 +- docker/docker-compose.yaml | 5 +- 5 files changed, 138 insertions(+), 22 deletions(-) diff --git a/api/configs/middleware/vdb/opensearch_config.py b/api/configs/middleware/vdb/opensearch_config.py index 81dde4c04d..96f478e9a6 100644 --- a/api/configs/middleware/vdb/opensearch_config.py +++ b/api/configs/middleware/vdb/opensearch_config.py @@ -1,4 +1,5 @@ -from typing import Optional +import enum +from typing import Literal, Optional from pydantic import Field, PositiveInt from pydantic_settings import BaseSettings @@ -9,6 +10,14 @@ class OpenSearchConfig(BaseSettings): Configuration settings for OpenSearch """ + class AuthMethod(enum.StrEnum): + """ + Authentication method for OpenSearch + """ + + BASIC = "basic" + AWS_MANAGED_IAM = "aws_managed_iam" + OPENSEARCH_HOST: Optional[str] = Field( description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')", default=None, @@ -19,6 +28,16 @@ class OpenSearchConfig(BaseSettings): default=9200, ) + OPENSEARCH_SECURE: bool = Field( + description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)", + default=False, + ) + + OPENSEARCH_AUTH_METHOD: AuthMethod = Field( + description="Authentication method for OpenSearch connection (default is 'basic')", + default=AuthMethod.BASIC, + ) + OPENSEARCH_USER: Optional[str] = Field( description="Username for authenticating with OpenSearch", default=None, @@ -29,7 +48,11 @@ class OpenSearchConfig(BaseSettings): default=None, ) - OPENSEARCH_SECURE: bool = Field( - description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)", - default=False, + OPENSEARCH_AWS_REGION: Optional[str] = Field( + description="AWS region for OpenSearch (e.g. 'us-west-2')", + default=None, + ) + + OPENSEARCH_AWS_SERVICE: Optional[Literal["es", "aoss"]] = Field( + description="AWS service for OpenSearch (e.g. 'aoss' for OpenSearch Serverless)", default=None ) diff --git a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py index 6636646cff..e23b8d197f 100644 --- a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py +++ b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py @@ -1,10 +1,9 @@ import json import logging -import ssl -from typing import Any, Optional +from typing import Any, Literal, Optional from uuid import uuid4 -from opensearchpy import OpenSearch, helpers +from opensearchpy import OpenSearch, Urllib3AWSV4SignerAuth, Urllib3HttpConnection, helpers from opensearchpy.helpers import BulkIndexError from pydantic import BaseModel, model_validator @@ -24,9 +23,12 @@ logger = logging.getLogger(__name__) class OpenSearchConfig(BaseModel): host: str port: int + secure: bool = False + auth_method: Literal["basic", "aws_managed_iam"] = "basic" user: Optional[str] = None password: Optional[str] = None - secure: bool = False + aws_region: Optional[str] = None + aws_service: Optional[str] = None @model_validator(mode="before") @classmethod @@ -35,24 +37,40 @@ class OpenSearchConfig(BaseModel): raise ValueError("config OPENSEARCH_HOST is required") if not values.get("port"): raise ValueError("config OPENSEARCH_PORT is required") + if values.get("auth_method") == "aws_managed_iam": + if not values.get("aws_region"): + raise ValueError("config OPENSEARCH_AWS_REGION is required for AWS_MANAGED_IAM auth method") + if not values.get("aws_service"): + raise ValueError("config OPENSEARCH_AWS_SERVICE is required for AWS_MANAGED_IAM auth method") return values - def create_ssl_context(self) -> ssl.SSLContext: - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE # Disable Certificate Validation - return ssl_context + def create_aws_managed_iam_auth(self) -> Urllib3AWSV4SignerAuth: + import boto3 # type: ignore + + return Urllib3AWSV4SignerAuth( + credentials=boto3.Session().get_credentials(), + region=self.aws_region, + service=self.aws_service, # type: ignore[arg-type] + ) def to_opensearch_params(self) -> dict[str, Any]: params = { "hosts": [{"host": self.host, "port": self.port}], "use_ssl": self.secure, "verify_certs": self.secure, + "connection_class": Urllib3HttpConnection, + "pool_maxsize": 20, } - if self.user and self.password: + + if self.auth_method == "basic": + logger.info("Using basic authentication for OpenSearch Vector DB") + params["http_auth"] = (self.user, self.password) - if self.secure: - params["ssl_context"] = self.create_ssl_context() + elif self.auth_method == "aws_managed_iam": + logger.info("Using AWS managed IAM role for OpenSearch Vector DB") + + params["http_auth"] = self.create_aws_managed_iam_auth() + return params @@ -76,16 +94,23 @@ class OpenSearchVector(BaseVector): action = { "_op_type": "index", "_index": self._collection_name.lower(), - "_id": uuid4().hex, "_source": { Field.CONTENT_KEY.value: documents[i].page_content, Field.VECTOR.value: embeddings[i], # Make sure you pass an array here Field.METADATA_KEY.value: documents[i].metadata, }, } + # See https://github.com/langchain-ai/langchainjs/issues/4346#issuecomment-1935123377 + if self._client_config.aws_service not in ["aoss"]: + action["_id"] = uuid4().hex actions.append(action) - helpers.bulk(self._client, actions) + helpers.bulk( + client=self._client, + actions=actions, + timeout=30, + max_retries=3, + ) def get_ids_by_metadata_field(self, key: str, value: str): query = {"query": {"term": {f"{Field.METADATA_KEY.value}.{key}": value}}} @@ -234,6 +259,7 @@ class OpenSearchVector(BaseVector): }, } + logger.info(f"Creating OpenSearch index {self._collection_name.lower()}") self._client.indices.create(index=self._collection_name.lower(), body=index_body) redis_client.set(collection_exist_cache_key, 1, ex=3600) @@ -252,9 +278,12 @@ class OpenSearchVectorFactory(AbstractVectorFactory): open_search_config = OpenSearchConfig( host=dify_config.OPENSEARCH_HOST or "localhost", port=dify_config.OPENSEARCH_PORT, + secure=dify_config.OPENSEARCH_SECURE, + auth_method=dify_config.OPENSEARCH_AUTH_METHOD.value, user=dify_config.OPENSEARCH_USER, password=dify_config.OPENSEARCH_PASSWORD, - secure=dify_config.OPENSEARCH_SECURE, + aws_region=dify_config.OPENSEARCH_AWS_REGION, + aws_service=dify_config.OPENSEARCH_AWS_SERVICE, ) return OpenSearchVector(collection_name=collection_name, config=open_search_config) diff --git a/api/tests/integration_tests/vdb/opensearch/test_opensearch.py b/api/tests/integration_tests/vdb/opensearch/test_opensearch.py index 35eed75c2f..2d44dd2924 100644 --- a/api/tests/integration_tests/vdb/opensearch/test_opensearch.py +++ b/api/tests/integration_tests/vdb/opensearch/test_opensearch.py @@ -23,13 +23,70 @@ def setup_mock_redis(): ext_redis.redis_client.lock = MagicMock(return_value=mock_redis_lock) +class TestOpenSearchConfig: + def test_to_opensearch_params(self): + config = OpenSearchConfig( + host="localhost", + port=9200, + secure=True, + user="admin", + password="password", + ) + + params = config.to_opensearch_params() + + assert params["hosts"] == [{"host": "localhost", "port": 9200}] + assert params["use_ssl"] is True + assert params["verify_certs"] is True + assert params["connection_class"].__name__ == "Urllib3HttpConnection" + assert params["http_auth"] == ("admin", "password") + + @patch("boto3.Session") + @patch("core.rag.datasource.vdb.opensearch.opensearch_vector.Urllib3AWSV4SignerAuth") + def test_to_opensearch_params_with_aws_managed_iam( + self, mock_aws_signer_auth: MagicMock, mock_boto_session: MagicMock + ): + mock_credentials = MagicMock() + mock_boto_session.return_value.get_credentials.return_value = mock_credentials + + mock_auth_instance = MagicMock() + mock_aws_signer_auth.return_value = mock_auth_instance + + aws_region = "ap-southeast-2" + aws_service = "aoss" + host = f"aoss-endpoint.{aws_region}.aoss.amazonaws.com" + port = 9201 + + config = OpenSearchConfig( + host=host, + port=port, + secure=True, + auth_method="aws_managed_iam", + aws_region=aws_region, + aws_service=aws_service, + ) + + params = config.to_opensearch_params() + + assert params["hosts"] == [{"host": host, "port": port}] + assert params["use_ssl"] is True + assert params["verify_certs"] is True + assert params["connection_class"].__name__ == "Urllib3HttpConnection" + assert params["http_auth"] is mock_auth_instance + + mock_aws_signer_auth.assert_called_once_with( + credentials=mock_credentials, region=aws_region, service=aws_service + ) + assert mock_boto_session.return_value.get_credentials.called + + class TestOpenSearchVector: def setup_method(self): self.collection_name = "test_collection" self.example_doc_id = "example_doc_id" self.vector = OpenSearchVector( collection_name=self.collection_name, - config=OpenSearchConfig(host="localhost", port=9200, user="admin", password="password", secure=False), + config=OpenSearchConfig(host="localhost", port=9200, secure=False, user="admin", password="password"), ) self.vector._client = MagicMock() diff --git a/docker/.env.example b/docker/.env.example index 1adb07ca64..7bff2975fb 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -526,9 +526,13 @@ RELYT_DATABASE=postgres # open search configuration, only available when VECTOR_STORE is `opensearch` OPENSEARCH_HOST=opensearch OPENSEARCH_PORT=9200 +OPENSEARCH_SECURE=true +OPENSEARCH_AUTH_METHOD=basic OPENSEARCH_USER=admin OPENSEARCH_PASSWORD=admin -OPENSEARCH_SECURE=true +# If using AWS managed IAM, e.g. Managed Cluster or OpenSearch Serverless +OPENSEARCH_AWS_REGION=ap-southeast-1 +OPENSEARCH_AWS_SERVICE=aoss # tencent vector configurations, only available when VECTOR_STORE is `tencent` TENCENT_VECTOR_DB_URL=http://127.0.0.1 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 1bf6954299..3ed0f60e96 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -225,9 +225,12 @@ x-shared-env: &shared-api-worker-env RELYT_DATABASE: ${RELYT_DATABASE:-postgres} OPENSEARCH_HOST: ${OPENSEARCH_HOST:-opensearch} OPENSEARCH_PORT: ${OPENSEARCH_PORT:-9200} + OPENSEARCH_SECURE: ${OPENSEARCH_SECURE:-true} + OPENSEARCH_AUTH_METHOD: ${OPENSEARCH_AUTH_METHOD:-basic} OPENSEARCH_USER: ${OPENSEARCH_USER:-admin} OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD:-admin} - OPENSEARCH_SECURE: ${OPENSEARCH_SECURE:-true} + OPENSEARCH_AWS_REGION: ${OPENSEARCH_AWS_REGION:-ap-southeast-1} + OPENSEARCH_AWS_SERVICE: ${OPENSEARCH_AWS_SERVICE:-aoss} TENCENT_VECTOR_DB_URL: ${TENCENT_VECTOR_DB_URL:-http://127.0.0.1} TENCENT_VECTOR_DB_API_KEY: ${TENCENT_VECTOR_DB_API_KEY:-dify} TENCENT_VECTOR_DB_TIMEOUT: ${TENCENT_VECTOR_DB_TIMEOUT:-30} From 2a3cc43b620886b06bcbeb3d8310f3bff8947896 Mon Sep 17 00:00:00 2001 From: Hao Cheng Date: Tue, 29 Apr 2025 09:10:38 +0200 Subject: [PATCH 16/89] fix: remove redundant Mermaid graph direction enforcement (#19024) --- web/app/components/base/mermaid/utils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/app/components/base/mermaid/utils.ts b/web/app/components/base/mermaid/utils.ts index 9c7ab4a7b5..7d94150d12 100644 --- a/web/app/components/base/mermaid/utils.ts +++ b/web/app/components/base/mermaid/utils.ts @@ -22,10 +22,6 @@ export function preprocessMermaidCode(code: string): string { .replace(/section\s+([^:]+):/g, (match, sectionName) => `section ${sectionName}:`) // Fix common syntax issues .replace(/fifopacket/g, 'rect') - // Ensure graph has direction - .replace(/^graph\s+((?:TB|BT|RL|LR)*)/, (match, direction) => { - return direction ? match : 'graph TD' - }) // Clean up empty lines and extra spaces .trim() } From 23f6914b9bf10f017bdf45fda6b233d430212db3 Mon Sep 17 00:00:00 2001 From: xiaotian Date: Tue, 29 Apr 2025 15:38:33 +0800 Subject: [PATCH 17/89] fix: image preview triggers binary download (#19070) --- api/controllers/files/image_preview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/files/image_preview.py b/api/controllers/files/image_preview.py index 5adfe16a79..5bb28b3897 100644 --- a/api/controllers/files/image_preview.py +++ b/api/controllers/files/image_preview.py @@ -75,7 +75,7 @@ class FilePreviewApi(Resource): if args["as_attachment"]: encoded_filename = quote(upload_file.name) response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}" - response.headers["Content-Type"] = "application/octet-stream" + response.headers["Content-Type"] = "application/octet-stream" return response From ce9737c6a04417cd0fb2b20e5f083c6e6cc4fa6f Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:49:31 +0800 Subject: [PATCH 18/89] fix: change provider should change the content (#19075) --- web/app/components/datasets/create/website/no-data.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/datasets/create/website/no-data.tsx b/web/app/components/datasets/create/website/no-data.tsx index 65a314f516..1db83a7417 100644 --- a/web/app/components/datasets/create/website/no-data.tsx +++ b/web/app/components/datasets/create/website/no-data.tsx @@ -17,6 +17,7 @@ type Props = { const NoData: FC = ({ onConfig, + provider, }) => { const { t } = useTranslation() @@ -38,7 +39,7 @@ const NoData: FC = ({ } : null, } - const currentProvider = Object.values(providerConfig).find(provider => provider !== null) || providerConfig[DataSourceProvider.jinaReader] + const currentProvider = providerConfig[provider] || providerConfig[DataSourceProvider.jinaReader] if (!currentProvider) return null From 226afd45508778c33481a8ff4c40c191444bdb80 Mon Sep 17 00:00:00 2001 From: feiyang_deepnova <736320652@qq.com> Date: Tue, 29 Apr 2025 18:01:11 +0800 Subject: [PATCH 19/89] Fix: the issue of getting empty environment variables. (#19085) --- api/core/workflow/workflow_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/core/workflow/workflow_entry.py b/api/core/workflow/workflow_entry.py index 50118a401c..7648947fca 100644 --- a/api/core/workflow/workflow_entry.py +++ b/api/core/workflow/workflow_entry.py @@ -9,6 +9,7 @@ from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError from core.app.entities.app_invoke_entities import InvokeFrom from core.file.models import File from core.workflow.callbacks import WorkflowCallback +from core.workflow.constants import ENVIRONMENT_VARIABLE_NODE_ID from core.workflow.entities.variable_pool import VariablePool from core.workflow.errors import WorkflowNodeRunFailedError from core.workflow.graph_engine.entities.event import GraphEngineEvent, GraphRunFailedEvent, InNodeEvent @@ -364,4 +365,5 @@ class WorkflowEntry: input_value = file_factory.build_from_mappings(mappings=input_value, tenant_id=tenant_id) # append variable and value to variable pool - variable_pool.add([variable_node_id] + variable_key_list, input_value) + if variable_node_id != ENVIRONMENT_VARIABLE_NODE_ID: + variable_pool.add([variable_node_id] + variable_key_list, input_value) From bd1bbfee4b2e3bbb3a93530024b13866dbc963d3 Mon Sep 17 00:00:00 2001 From: QuantumGhost Date: Tue, 29 Apr 2025 18:04:33 +0800 Subject: [PATCH 20/89] Enhance Code Consistency Across Repository with `.editorconfig` (#19023) --- .devcontainer/README.md | 2 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/noop.txt | 4 +- web/.editorconfig => .editorconfig | 25 +- .gitattributes | 2 +- .github/linters/editorconfig-checker.json | 22 + .github/workflows/style.yml | 17 + CONTRIBUTING_ES.md | 2 +- CONTRIBUTING_FR.md | 2 +- CONTRIBUTING_KR.md | 2 +- CONTRIBUTING_PT.md | 2 +- CONTRIBUTING_TR.md | 2 +- README_SI.md | 518 ++++++------ api/.dockerignore | 2 +- api/commands.py | 8 +- api/core/agent/prompt/template.py | 4 +- api/core/external_data_tool/api/__builtin__ | 2 +- .../javascript/javascript_transformer.py | 6 +- .../jinja2/jinja2_transformer.py | 16 +- .../python3/python3_transformer.py | 8 +- api/core/llm_generator/prompts.py | 44 +- .../en_US/customizable_model_scale_out.md | 2 +- .../docs/en_US/predefined_model_scale_out.md | 2 +- .../zh_Hans/customizable_model_scale_out.md | 2 +- .../zh_Hans/predefined_model_scale_out.md | 2 +- api/core/moderation/api/__builtin__ | 2 +- api/core/moderation/keywords/__builtin__ | 2 +- .../moderation/openai_moderation/__builtin__ | 2 +- api/core/plugin/backwards_invocation/model.py | 4 +- .../prompt_templates/baichuan_chat.json | 2 +- .../prompt_templates/baichuan_completion.json | 2 +- .../prompt_templates/common_completion.json | 2 +- .../vdb/analyticdb/analyticdb_vector_sql.py | 6 +- .../vdb/oceanbase/oceanbase_vector.py | 2 +- .../rag/datasource/vdb/opengauss/opengauss.py | 4 +- .../rag/datasource/vdb/oracle/oraclevector.py | 12 +- .../rag/datasource/vdb/pgvector/pgvector.py | 2 +- .../vdb/pyvastbase/vastbase_vector.py | 2 +- .../datasource/vdb/tidb_vector/tidb_vector.py | 4 +- api/core/rag/retrieval/template_prompts.py | 4 +- api/core/tools/builtin_tool/tool.py | 4 +- .../knowledge_retrieval/template_prompts.py | 4 +- .../nodes/parameter_extractor/prompts.py | 14 +- .../question_classifier/template_prompts.py | 4 +- ...a11becb_add_name_and_size_to_tool_files.py | 4 +- ...dd_retry_index_field_to_node_execution_.py | 2 +- ..._remove_workflow_node_executions_retry_.py | 2 +- api/migrations/versions/64b051264f32_init.py | 2 +- .../de95f5c77138_migration_serpapi_api_key.py | 4 +- api/services/plugin/data_migration.py | 16 +- .../clean_document_job_mail_template-US.html | 4 +- ...ete_account_code_email_template_en-US.html | 2 +- ...delete_account_success_template_en-US.html | 2 +- api/tests/integration_tests/.gitignore | 2 +- api/tests/unit_tests/.gitignore | 2 +- dev/pytest/pytest_all_tests.sh | 2 +- dev/pytest/pytest_model_runtime.sh | 2 +- docker/couchbase-server/Dockerfile | 4 +- docker/couchbase-server/init-cbserver.sh | 8 +- docker/middleware.env.example | 2 +- docker/nginx/docker-entrypoint.sh | 2 +- docker/nginx/https.conf.template | 2 +- docker/nginx/nginx.conf.template | 2 +- docker/ssrf_proxy/squid.conf.template | 4 +- docker/startupscripts/init.sh | 2 +- docker/startupscripts/init_user.script | 2 +- docker/tidb/config/pd.toml | 2 +- .../config/users.d/custom_users_config.xml | 2 +- .../volumes/oceanbase/init.d/vec_memory.sql | 2 +- sdks/nodejs-client/.gitignore | 2 +- sdks/nodejs-client/index.d.ts | 12 +- sdks/nodejs-client/index.js | 10 +- sdks/nodejs-client/index.test.js | 2 +- sdks/nodejs-client/package.json | 2 +- sdks/php-client/README.md | 2 +- sdks/php-client/dify-client.php | 10 +- sdks/python-client/MANIFEST.in | 2 +- sdks/python-client/build.sh | 2 +- web/.dockerignore | 2 +- web/.vscode/extensions.json | 2 +- .../[appId]/style.module.css | 2 +- web/app/(commonLayout)/list.module.css | 2 +- .../components/app-sidebar/style.module.css | 4 +- .../base/var-highlight/style.module.css | 2 +- .../base/warning-mask/style.module.css | 2 +- .../config-prompt/style.module.css | 2 +- .../config/automatic/style.module.css | 2 +- .../ctrl-btn-group/style.module.css | 2 +- .../dataset-config/card-item/style.module.css | 2 +- .../params-config/weighted-score.css | 2 +- .../app/configuration/style.module.css | 2 +- .../app/configuration/tools/index.tsx | 2 +- .../app/overview/embedded/style.module.css | 2 +- .../components/base/action-button/index.css | 2 +- .../components/base/app-icon/style.module.css | 2 +- .../base/audio-btn/style.module.css | 2 +- web/app/components/base/badge/index.css | 2 +- web/app/components/base/button/index.css | 112 +-- .../answer/__mocks__/markdownContentSVG.ts | 12 +- .../chat/chat/loading-anim/style.module.css | 2 +- .../components/base/copy-btn/style.module.css | 2 +- .../score-slider/base-slider/style.module.css | 2 +- .../base/grid-mask/style.module.css | 2 +- .../base/icons/src/public/avatar/Robot.json | 2 +- .../base/icons/src/public/avatar/User.json | 2 +- .../icons/src/public/billing/ArCube1.json | 2 +- .../icons/src/public/billing/Asterisk.json | 2 +- .../src/public/billing/AwsMarketplace.json | 2 +- .../base/icons/src/public/billing/Azure.json | 2 +- .../icons/src/public/billing/Buildings.json | 2 +- .../icons/src/public/billing/Diamond.json | 2 +- .../icons/src/public/billing/GoogleCloud.json | 2 +- .../base/icons/src/public/billing/Group2.json | 2 +- .../icons/src/public/billing/Keyframe.json | 2 +- .../icons/src/public/billing/Sparkles.json | 2 +- .../src/public/billing/SparklesSoft.json | 2 +- .../base/icons/src/public/common/D.json | 2 +- .../public/common/DiagonalDividingLine.json | 2 +- .../base/icons/src/public/common/Dify.json | 2 +- .../base/icons/src/public/common/Gdpr.json | 2 +- .../base/icons/src/public/common/Github.json | 2 +- .../icons/src/public/common/Highlight.json | 2 +- .../base/icons/src/public/common/Iso.json | 2 +- .../base/icons/src/public/common/Line3.json | 2 +- .../base/icons/src/public/common/Lock.json | 2 +- .../src/public/common/MessageChatSquare.json | 2 +- .../src/public/common/MultiPathRetrieval.json | 2 +- .../src/public/common/NTo1Retrieval.json | 2 +- .../base/icons/src/public/common/Notion.json | 2 +- .../base/icons/src/public/common/Soc2.json | 2 +- .../icons/src/public/common/SparklesSoft.json | 2 +- .../icons/src/public/education/Triangle.json | 2 +- .../base/icons/src/public/files/Csv.json | 2 +- .../base/icons/src/public/files/Doc.json | 2 +- .../base/icons/src/public/files/Docx.json | 2 +- .../base/icons/src/public/files/Html.json | 2 +- .../base/icons/src/public/files/Json.json | 2 +- .../base/icons/src/public/files/Md.json | 2 +- .../base/icons/src/public/files/Pdf.json | 2 +- .../base/icons/src/public/files/Txt.json | 2 +- .../base/icons/src/public/files/Unknown.json | 2 +- .../base/icons/src/public/files/Xlsx.json | 2 +- .../base/icons/src/public/files/Yaml.json | 2 +- .../icons/src/public/knowledge/Chunk.json | 2 +- .../icons/src/public/knowledge/Collapse.json | 2 +- .../src/public/knowledge/GeneralType.json | 2 +- .../public/knowledge/LayoutRight2LineMod.json | 2 +- .../src/public/knowledge/ParentChildType.json | 2 +- .../src/public/knowledge/SelectionMod.json | 2 +- .../base/icons/src/public/llm/Anthropic.json | 2 +- .../icons/src/public/llm/AnthropicDark.json | 2 +- .../icons/src/public/llm/AnthropicLight.json | 2 +- .../icons/src/public/llm/AnthropicText.json | 2 +- .../src/public/llm/AzureOpenaiService.json | 2 +- .../public/llm/AzureOpenaiServiceText.json | 2 +- .../base/icons/src/public/llm/Azureai.json | 2 +- .../icons/src/public/llm/AzureaiText.json | 2 +- .../base/icons/src/public/llm/Baichuan.json | 2 +- .../icons/src/public/llm/BaichuanText.json | 2 +- .../base/icons/src/public/llm/Chatglm.json | 2 +- .../icons/src/public/llm/ChatglmText.json | 2 +- .../base/icons/src/public/llm/Cohere.json | 2 +- .../base/icons/src/public/llm/CohereText.json | 2 +- .../base/icons/src/public/llm/Gpt3.json | 2 +- .../base/icons/src/public/llm/Gpt4.json | 2 +- .../icons/src/public/llm/Huggingface.json | 2 +- .../icons/src/public/llm/HuggingfaceText.json | 2 +- .../src/public/llm/HuggingfaceTextHub.json | 2 +- .../icons/src/public/llm/IflytekSpark.json | 2 +- .../src/public/llm/IflytekSparkText.json | 2 +- .../src/public/llm/IflytekSparkTextCn.json | 2 +- .../base/icons/src/public/llm/Jina.json | 2 +- .../base/icons/src/public/llm/JinaText.json | 2 +- .../base/icons/src/public/llm/Localai.json | 2 +- .../icons/src/public/llm/LocalaiText.json | 2 +- .../base/icons/src/public/llm/Microsoft.json | 2 +- .../icons/src/public/llm/OpenaiBlack.json | 2 +- .../base/icons/src/public/llm/OpenaiBlue.json | 2 +- .../icons/src/public/llm/OpenaiGreen.json | 2 +- .../base/icons/src/public/llm/OpenaiText.json | 2 +- .../src/public/llm/OpenaiTransparent.json | 2 +- .../icons/src/public/llm/OpenaiViolet.json | 2 +- .../base/icons/src/public/llm/Openllm.json | 2 +- .../icons/src/public/llm/OpenllmText.json | 2 +- .../base/icons/src/public/llm/Replicate.json | 2 +- .../icons/src/public/llm/ReplicateText.json | 2 +- .../src/public/llm/XorbitsInference.json | 2 +- .../src/public/llm/XorbitsInferenceText.json | 2 +- .../base/icons/src/public/llm/Zhipuai.json | 2 +- .../icons/src/public/llm/ZhipuaiText.json | 2 +- .../icons/src/public/llm/ZhipuaiTextCn.json | 2 +- .../base/icons/src/public/model/Checked.json | 2 +- .../src/public/other/DefaultToolIcon.json | 2 +- .../icons/src/public/other/Icon3Dots.json | 2 +- .../icons/src/public/other/Message3Fill.json | 2 +- .../icons/src/public/other/RowStruct.json | 2 +- .../base/icons/src/public/plugins/Google.json | 2 +- .../icons/src/public/plugins/PartnerDark.json | 2 +- .../src/public/plugins/PartnerLight.json | 2 +- .../src/public/plugins/VerifiedDark.json | 2 +- .../src/public/plugins/VerifiedLight.json | 2 +- .../icons/src/public/plugins/WebReader.json | 2 +- .../icons/src/public/plugins/Wikipedia.json | 2 +- .../icons/src/public/thought/DataSet.json | 2 +- .../icons/src/public/thought/Loading.json | 2 +- .../base/icons/src/public/thought/Search.json | 2 +- .../icons/src/public/thought/ThoughtList.json | 2 +- .../icons/src/public/thought/WebReader.json | 2 +- .../src/public/tracing/LangfuseIcon.json | 2 +- .../src/public/tracing/LangfuseIconBig.json | 2 +- .../src/public/tracing/LangsmithIcon.json | 2 +- .../src/public/tracing/LangsmithIconBig.json | 2 +- .../icons/src/public/tracing/OpikIcon.json | 2 +- .../icons/src/public/tracing/OpikIconBig.json | 2 +- .../icons/src/public/tracing/TracingIcon.json | 2 +- .../base/image-gallery/style.module.css | 2 +- .../text-generation-image-uploader.tsx | 2 +- .../components/base/install-button/index.tsx | 4 +- .../index.module.css | 2 +- .../components/base/premium-badge/index.css | 2 +- .../base/radio-card/simple/style.module.css | 2 +- web/app/components/base/select/index.tsx | 2 +- web/app/components/base/slider/style.css | 2 +- .../components/base/toast/style.module.css | 2 +- .../base/voice-input/index.module.css | 2 +- .../billing/annotation-full/style.module.css | 2 +- .../apps-full-in-dialog/style.module.css | 2 +- .../billing/upgrade-btn/style.module.css | 2 +- .../vector-space-full/style.module.css | 2 +- .../custom-web-app-brand/style.module.css | 2 +- web/app/components/custom/style.module.css | 2 +- .../create/file-preview/index.module.css | 15 +- .../notion-page-preview/index.module.css | 1 - .../datasets/create/step-one/index.module.css | 2 +- .../datasets/create/step-two/index.module.css | 2 +- .../external-knowledge-api-card/index.tsx | 4 +- .../create/ExternalApiSelect.tsx | 2 +- .../datasets/hit-testing/style.module.css | 2 +- .../develop/secret-key/style.module.css | 2 +- .../explore/app-list/style.module.css | 2 +- .../explore/item-operation/style.module.css | 2 +- .../workplace-selector/index.module.css | 2 +- .../Integrations-page/index.module.css | 2 +- .../data-source-page/panel/style.module.css | 2 +- .../language-page/index.module.css | 2 +- .../invited-modal/index.module.css | 2 +- web/app/components/header/index.module.css | 2 +- .../plugins-nav/downloading-icon.module.css | 14 +- .../permission-setting-modal/style.module.css | 2 +- .../edit-custom-collection-modal/examples.ts | 2 +- .../workflow/block-selector/index.tsx | 2 +- web/app/components/workflow/index.tsx | 2 +- .../components/editor/code-editor/style.css | 2 +- .../nodes/_base/components/node-handle.tsx | 2 +- .../_base/components/retry/style.module.css | 2 +- .../components/dataset-item.tsx | 4 +- .../note-node/note-editor/theme/theme.css | 2 +- .../workflow/operator/export-image.tsx | 262 +++--- .../workflow/operator/zoom-in-out.tsx | 2 +- web/app/components/workflow/style.css | 2 +- web/app/page.module.css | 2 +- web/app/signin/page.module.css | 2 +- web/app/styles/globals.css | 2 +- web/app/styles/preflight.css | 2 +- web/i18n/de-DE/app-debug.ts | 784 +++++++++--------- web/package.json | 2 +- web/public/embed.js | 2 +- web/themes/dark.css | 2 +- web/themes/light.css | 2 +- web/themes/manual-dark.css | 2 +- web/themes/manual-light.css | 2 +- web/themes/markdown-dark.css | 2 +- web/themes/markdown-light.css | 2 +- web/utils/timezone.json | 2 +- 274 files changed, 1271 insertions(+), 1217 deletions(-) rename web/.editorconfig => .editorconfig (51%) create mode 100644 .github/linters/editorconfig-checker.json diff --git a/.devcontainer/README.md b/.devcontainer/README.md index df12a3c2d6..2b18630a21 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -34,4 +34,4 @@ if you see such error message when you open this project in codespaces: ![Alt text](troubleshooting.png) a simple workaround is change `/signin` endpoint into another one, then login with GitHub account and close the tab, then change it back to `/signin` endpoint. Then all things will be fine. -The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204) \ No newline at end of file +The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 339ad60ce0..8246544061 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/anaconda { "name": "Python 3.12", - "build": { + "build": { "context": "..", "dockerfile": "Dockerfile" }, diff --git a/.devcontainer/noop.txt b/.devcontainer/noop.txt index dde8dc3c10..49de88dbd4 100644 --- a/.devcontainer/noop.txt +++ b/.devcontainer/noop.txt @@ -1,3 +1,3 @@ This file copied into the container along with environment.yml* from the parent -folder. This file is included to prevents the Dockerfile COPY instruction from -failing if no environment.yml is found. \ No newline at end of file +folder. This file is included to prevents the Dockerfile COPY instruction from +failing if no environment.yml is found. diff --git a/web/.editorconfig b/.editorconfig similarity index 51% rename from web/.editorconfig rename to .editorconfig index e1d3f0b992..374da0b5d2 100644 --- a/web/.editorconfig +++ b/.editorconfig @@ -5,18 +5,35 @@ root = true # Unix-style newlines with a newline ending every file [*] +charset = utf-8 end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 +indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.toml] +indent_size = 4 +indent_style = space + +# Markdown and MDX are whitespace sensitive languages. +# Do not remove trailing spaces. +[*.{md,mdx}] +trim_trailing_whitespace = false # Matches multiple files with brace expansion notation # Set default charset [*.{js,tsx}] -charset = utf-8 indent_style = space indent_size = 2 - -# Matches the exact files either package.json or .travis.yml -[{package.json,.travis.yml}] +# Matches the exact files package.json +[package.json] indent_style = space indent_size = 2 diff --git a/.gitattributes b/.gitattributes index a10da53408..a32a39f65c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Ensure that .sh scripts use LF as line separator, even if they are checked out -# to Windows(NTFS) file-system, by a user of Docker for Windows. +# to Windows(NTFS) file-system, by a user of Docker for Windows. # These .sh scripts will be run from the Container after `docker compose up -d`. # If they appear to be CRLF style, Dash from the Container will fail to execute # them. diff --git a/.github/linters/editorconfig-checker.json b/.github/linters/editorconfig-checker.json new file mode 100644 index 0000000000..ce6e9ae341 --- /dev/null +++ b/.github/linters/editorconfig-checker.json @@ -0,0 +1,22 @@ +{ + "Verbose": false, + "Debug": false, + "IgnoreDefaults": false, + "SpacesAfterTabs": false, + "NoColor": false, + "Exclude": [ + "^web/public/vs/", + "^web/public/pdf.worker.min.mjs$", + "web/app/components/base/icons/src/vender/" + ], + "AllowedContentTypes": [], + "PassedFiles": [], + "Disable": { + "EndOfLine": false, + "Indentation": false, + "IndentSize": true, + "InsertFinalNewline": false, + "TrimTrailingWhitespace": false, + "MaxLineLength": false + } +} diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 98e5fd5150..56f9b433f3 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -9,6 +9,12 @@ concurrency: group: style-${{ github.head_ref || github.run_id }} cancel-in-progress: true +permissions: + checks: write + statuses: write + contents: read + + jobs: python-style: name: Python Style @@ -163,3 +169,14 @@ jobs: VALIDATE_DOCKERFILE_HADOLINT: true VALIDATE_XML: true VALIDATE_YAML: true + + - name: EditorConfig checks + uses: super-linter/super-linter/slim@v7 + env: + DEFAULT_BRANCH: main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IGNORE_GENERATED_FILES: true + IGNORE_GITIGNORED_FILES: true + # EditorConfig validation + VALIDATE_EDITORCONFIG: true + EDITORCONFIG_FILE_NAME: editorconfig-checker.json diff --git a/CONTRIBUTING_ES.md b/CONTRIBUTING_ES.md index 261aa0fda1..98cbb5b457 100644 --- a/CONTRIBUTING_ES.md +++ b/CONTRIBUTING_ES.md @@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de proceder con la conf No dudes en contactarnos si encuentras algún problema durante el proceso de configuración. ## Obteniendo Ayuda -Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida. \ No newline at end of file +Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida. diff --git a/CONTRIBUTING_FR.md b/CONTRIBUTING_FR.md index c3418f86cc..fc8410dfd6 100644 --- a/CONTRIBUTING_FR.md +++ b/CONTRIBUTING_FR.md @@ -90,4 +90,4 @@ Nous recommandons de revoir attentivement ce document avant de procéder à la c N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration. ## Obtenir de l'aide -Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide. \ No newline at end of file +Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide. diff --git a/CONTRIBUTING_KR.md b/CONTRIBUTING_KR.md index fcf44d495a..78d3f38c47 100644 --- a/CONTRIBUTING_KR.md +++ b/CONTRIBUTING_KR.md @@ -90,4 +90,4 @@ PR 설명에 기존 이슈를 연결하거나 새 이슈를 여는 것을 잊지 설정 과정에서 문제가 발생하면 언제든지 연락해 주세요. ## 도움 받기 -기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요. \ No newline at end of file +기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요. diff --git a/CONTRIBUTING_PT.md b/CONTRIBUTING_PT.md index bba76c17ee..7347fd7f9c 100644 --- a/CONTRIBUTING_PT.md +++ b/CONTRIBUTING_PT.md @@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de prosseguir com a con Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração. ## Obtendo Ajuda -Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida. \ No newline at end of file +Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida. diff --git a/CONTRIBUTING_TR.md b/CONTRIBUTING_TR.md index 4e216d22a4..681f05689b 100644 --- a/CONTRIBUTING_TR.md +++ b/CONTRIBUTING_TR.md @@ -90,4 +90,4 @@ Kuruluma geçmeden önce bu belgeyi dikkatlice incelemenizi öneririz, çünkü Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin. ## Yardım Almak -Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın. \ No newline at end of file +Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın. diff --git a/README_SI.md b/README_SI.md index caa5975973..9a38b558b4 100644 --- a/README_SI.md +++ b/README_SI.md @@ -1,259 +1,259 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) - -

    - 📌 Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast -

    - -

    - Dify Cloud · - Samostojno gostovanje · - Dokumentacija · - Pregled ponudb izdelkov Dify -

    - -

    - - Static Badge - - Static Badge - - chat on Discord - - follow on X(Twitter) - - follow on LinkedIn - - Docker Pulls - - Commits last month - - Issues closed - - Discussion posts -

    - -

    - README in English - 简体中文版自述文件 - 日本語のREADME - README en Español - README en Français - README tlhIngan Hol - README in Korean - README بالعربية - Türkçe README - README Tiếng Việt - README Slovenščina - README in বাংলা -

    - - -Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje. - -## Hitri začetek -> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve: -> ->- CPU >= 2 Core ->- RAM >= 4 GiB - -
    - -Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku: - -```bash -cd dify -cd docker -cp .env.example .env -docker compose up -d -``` - -Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije. - -#### Iskanje pomoči -Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact). - -> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code) - -## Ključne značilnosti -**1. Potek dela**: - Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več. - - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - -**2. Celovita podpora za modele**: - Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers). - -![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) - - -**3. Prompt IDE**: - intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu. - -**4. RAG Pipeline**: - E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov. - -**5. Agent capabilities**: - definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha. - -**6. LLMOps**: - Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb. - -**7. Backend-as-a-Service**: - AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko. - -## Primerjava Funkcij - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FunkcijaDify.AILangChainFlowiseOpenAI Assistants API
    Programski pristopAPI + usmerjeno v aplikacijePython kodaUsmerjeno v aplikacijeUsmerjeno v API
    Podprti LLM-jiBogata izbiraBogata izbiraBogata izbiraSamo OpenAI
    RAG pogon
    Agent
    Potek dela
    Spremljanje
    Funkcija za podjetja (SSO/nadzor dostopa)
    Lokalna namestitev
    - -## Uporaba Dify - -- **Cloud
    ** -Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika. - -- **Self-hosting Dify Community Edition
    ** -Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) . - - -- **Dify za podjetja/organizacije
    ** -Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja.
    - > Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri. - - -## Staying ahead - -Star Dify on GitHub and be instantly notified of new releases. - -![star-us](https://github.com/langgenius/dify/assets/13230914/b823edc1-6388-4e25-ad45-2f6b187adbb4) - - -## Napredne nastavitve - -Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj . - -Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes. - -- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify) -- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm) -- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes) -- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s) - -#### Uporaba Terraform za uvajanje - -namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/) - -##### Azure Global -- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform) - -##### Google Cloud -- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform) - -#### Uporaba AWS CDK za uvajanje - -Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/) - -##### AWS -- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws) - -## Prispevam - -Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah. - - - -> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord . - -## Skupnost in stik - -* [Github Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj. -* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). -* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. -* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. - -**Contributors** - - - - - -## Star history - -[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date) - - -## Varnostno razkritje - -Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor. - -## Licenca - -To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami. +![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) + +

    + 📌 Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast +

    + +

    + Dify Cloud · + Samostojno gostovanje · + Dokumentacija · + Pregled ponudb izdelkov Dify +

    + +

    + + Static Badge + + Static Badge + + chat on Discord + + follow on X(Twitter) + + follow on LinkedIn + + Docker Pulls + + Commits last month + + Issues closed + + Discussion posts +

    + +

    + README in English + 简体中文版自述文件 + 日本語のREADME + README en Español + README en Français + README tlhIngan Hol + README in Korean + README بالعربية + Türkçe README + README Tiếng Việt + README Slovenščina + README in বাংলা +

    + + +Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje. + +## Hitri začetek +> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve: +> +>- CPU >= 2 Core +>- RAM >= 4 GiB + +
    + +Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku: + +```bash +cd dify +cd docker +cp .env.example .env +docker compose up -d +``` + +Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije. + +#### Iskanje pomoči +Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact). + +> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code) + +## Ključne značilnosti +**1. Potek dela**: + Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več. + + + https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa + + + +**2. Celovita podpora za modele**: + Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers). + +![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) + + +**3. Prompt IDE**: + intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu. + +**4. RAG Pipeline**: + E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov. + +**5. Agent capabilities**: + definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha. + +**6. LLMOps**: + Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb. + +**7. Backend-as-a-Service**: + AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko. + +## Primerjava Funkcij + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FunkcijaDify.AILangChainFlowiseOpenAI Assistants API
    Programski pristopAPI + usmerjeno v aplikacijePython kodaUsmerjeno v aplikacijeUsmerjeno v API
    Podprti LLM-jiBogata izbiraBogata izbiraBogata izbiraSamo OpenAI
    RAG pogon
    Agent
    Potek dela
    Spremljanje
    Funkcija za podjetja (SSO/nadzor dostopa)
    Lokalna namestitev
    + +## Uporaba Dify + +- **Cloud
    ** +Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika. + +- **Self-hosting Dify Community Edition
    ** +Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) . + + +- **Dify za podjetja/organizacije
    ** +Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja.
    + > Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri. + + +## Staying ahead + +Star Dify on GitHub and be instantly notified of new releases. + +![star-us](https://github.com/langgenius/dify/assets/13230914/b823edc1-6388-4e25-ad45-2f6b187adbb4) + + +## Napredne nastavitve + +Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj . + +Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes. + +- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify) +- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm) +- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes) +- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s) + +#### Uporaba Terraform za uvajanje + +namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/) + +##### Azure Global +- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform) + +##### Google Cloud +- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform) + +#### Uporaba AWS CDK za uvajanje + +Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/) + +##### AWS +- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws) + +## Prispevam + +Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah. + + + +> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord . + +## Skupnost in stik + +* [Github Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj. +* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). +* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. +* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. + +**Contributors** + + + + + +## Star history + +[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date) + + +## Varnostno razkritje + +Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor. + +## Licenca + +To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami. diff --git a/api/.dockerignore b/api/.dockerignore index 447edcda08..a0ce59d221 100644 --- a/api/.dockerignore +++ b/api/.dockerignore @@ -16,4 +16,4 @@ logs .ruff_cache # venv -.venv \ No newline at end of file +.venv diff --git a/api/commands.py b/api/commands.py index 99a3211baf..07bc6cd927 100644 --- a/api/commands.py +++ b/api/commands.py @@ -444,13 +444,13 @@ def convert_to_agent_apps(): WHERE a.mode = 'chat' AND am.agent_mode is not null AND ( - am.agent_mode like '%"strategy": "function_call"%' + am.agent_mode like '%"strategy": "function_call"%' OR am.agent_mode like '%"strategy": "react"%' - ) + ) AND ( - am.agent_mode like '{"enabled": true%' + am.agent_mode like '{"enabled": true%' OR am.agent_mode like '{"max_iteration": %' - ) ORDER BY a.created_at DESC LIMIT 1000 + ) ORDER BY a.created_at DESC LIMIT 1000 """ with db.engine.begin() as conn: diff --git a/api/core/agent/prompt/template.py b/api/core/agent/prompt/template.py index ef64fd29fc..f5ba2119f4 100644 --- a/api/core/agent/prompt/template.py +++ b/api/core/agent/prompt/template.py @@ -1,4 +1,4 @@ -ENGLISH_REACT_COMPLETION_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. +ENGLISH_REACT_COMPLETION_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. {{instruction}} @@ -47,7 +47,7 @@ Thought:""" # noqa: E501 ENGLISH_REACT_COMPLETION_AGENT_SCRATCHPAD_TEMPLATES = """Observation: {{observation}} Thought:""" -ENGLISH_REACT_CHAT_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. +ENGLISH_REACT_CHAT_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. {{instruction}} diff --git a/api/core/external_data_tool/api/__builtin__ b/api/core/external_data_tool/api/__builtin__ index 56a6051ca2..d00491fd7e 100644 --- a/api/core/external_data_tool/api/__builtin__ +++ b/api/core/external_data_tool/api/__builtin__ @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/api/core/helper/code_executor/javascript/javascript_transformer.py b/api/core/helper/code_executor/javascript/javascript_transformer.py index d67a0903aa..62489cdf29 100644 --- a/api/core/helper/code_executor/javascript/javascript_transformer.py +++ b/api/core/helper/code_executor/javascript/javascript_transformer.py @@ -10,13 +10,13 @@ class NodeJsTemplateTransformer(TemplateTransformer): f""" // declare main function {cls._code_placeholder} - + // decode and prepare input object var inputs_obj = JSON.parse(Buffer.from('{cls._inputs_placeholder}', 'base64').toString('utf-8')) - + // execute main function var output_obj = main(inputs_obj) - + // convert output to json and print var output_json = JSON.stringify(output_obj) var result = `<>${{output_json}}<>` diff --git a/api/core/helper/code_executor/jinja2/jinja2_transformer.py b/api/core/helper/code_executor/jinja2/jinja2_transformer.py index 63d58edbc7..54c78cdf92 100644 --- a/api/core/helper/code_executor/jinja2/jinja2_transformer.py +++ b/api/core/helper/code_executor/jinja2/jinja2_transformer.py @@ -21,20 +21,20 @@ class Jinja2TemplateTransformer(TemplateTransformer): import jinja2 template = jinja2.Template('''{cls._code_placeholder}''') return template.render(**inputs) - + import json from base64 import b64decode - + # decode and prepare input dict inputs_obj = json.loads(b64decode('{cls._inputs_placeholder}').decode('utf-8')) - + # execute main function output = main(**inputs_obj) - + # convert output and print result = f'''<>{{output}}<>''' print(result) - + """) return runner_script @@ -43,15 +43,15 @@ class Jinja2TemplateTransformer(TemplateTransformer): preload_script = dedent(""" import jinja2 from base64 import b64decode - + def _jinja2_preload_(): # prepare jinja2 environment, load template and render before to avoid sandbox issue template = jinja2.Template('{{s}}') template.render(s='a') - + if __name__ == '__main__': _jinja2_preload_() - + """) return preload_script diff --git a/api/core/helper/code_executor/python3/python3_transformer.py b/api/core/helper/code_executor/python3/python3_transformer.py index 75a5a44d08..836fd273ae 100644 --- a/api/core/helper/code_executor/python3/python3_transformer.py +++ b/api/core/helper/code_executor/python3/python3_transformer.py @@ -9,16 +9,16 @@ class Python3TemplateTransformer(TemplateTransformer): runner_script = dedent(f""" # declare main function {cls._code_placeholder} - + import json from base64 import b64decode - + # decode and prepare input dict inputs_obj = json.loads(b64decode('{cls._inputs_placeholder}').decode('utf-8')) - + # execute main function output_obj = main(**inputs_obj) - + # convert output to json and print output_json = json.dumps(output_obj, indent=4) result = f'''<>{{output_json}}<>''' diff --git a/api/core/llm_generator/prompts.py b/api/core/llm_generator/prompts.py index fad7cea01c..34ea3aec26 100644 --- a/api/core/llm_generator/prompts.py +++ b/api/core/llm_generator/prompts.py @@ -1,5 +1,5 @@ # Written by YORKI MINAKO🤡, Edited by Xiaoyi -CONVERSATION_TITLE_PROMPT = """You need to decompose the user's input into "subject" and "intention" in order to accurately figure out what the user's input language actually is. +CONVERSATION_TITLE_PROMPT = """You need to decompose the user's input into "subject" and "intention" in order to accurately figure out what the user's input language actually is. Notice: the language type user uses could be diverse, which can be English, Chinese, Italian, Español, Arabic, Japanese, French, and etc. ENSURE your output is in the SAME language as the user's input! Your output is restricted only to: (Input language) Intention + Subject(short as possible) @@ -58,7 +58,7 @@ User Input: yo, 你今天咋样? "Your Output": "查询今日我的状态☺️" } -User Input: +User Input: """ # noqa: E501 PYTHON_CODE_GENERATOR_PROMPT_TEMPLATE = ( @@ -163,11 +163,11 @@ Here is a task description for which I would like you to create a high-quality p {{TASK_DESCRIPTION}} Based on task description, please create a well-structured prompt template that another AI could use to consistently complete the task. The prompt template should include: -- Do not include or section and variables in the prompt, assume user will add them at their own will. -- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. -- Relevant examples if needed to clarify the task further, demarcated with tags. Do not include variables in the prompt. Give three pairs of input and output examples. +- Do not include or section and variables in the prompt, assume user will add them at their own will. +- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. +- Relevant examples if needed to clarify the task further, demarcated with tags. Do not include variables in the prompt. Give three pairs of input and output examples. - Include other relevant sections demarcated with appropriate XML tags like , . -- Use the same language as task description. +- Use the same language as task description. - Output in ``` xml ``` and start with Please generate the full prompt template with at least 300 words and output only the prompt template. """ # noqa: E501 @@ -178,28 +178,28 @@ Here is a task description for which I would like you to create a high-quality p {{TASK_DESCRIPTION}} Based on task description, please create a well-structured prompt template that another AI could use to consistently complete the task. The prompt template should include: -- Descriptive variable names surrounded by {{ }} (two curly brackets) to indicate where the actual values will be substituted in. Choose variable names that clearly indicate the type of value expected. Variable names have to be composed of number, english alphabets and underline and nothing else. -- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. -- Relevant examples if needed to clarify the task further, demarcated with tags. Do not use curly brackets any other than in section. +- Descriptive variable names surrounded by {{ }} (two curly brackets) to indicate where the actual values will be substituted in. Choose variable names that clearly indicate the type of value expected. Variable names have to be composed of number, english alphabets and underline and nothing else. +- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. +- Relevant examples if needed to clarify the task further, demarcated with tags. Do not use curly brackets any other than in section. - Any other relevant sections demarcated with appropriate XML tags like , , etc. -- Use the same language as task description. +- Use the same language as task description. - Output in ``` xml ``` and start with Please generate the full prompt template and output only the prompt template. """ # noqa: E501 RULE_CONFIG_PARAMETER_GENERATE_TEMPLATE = """ -I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. +I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. -variables name bounded two double curly brackets. Variable name has to be composed of number, english alphabets and underline and nothing else. +variables name bounded two double curly brackets. Variable name has to be composed of number, english alphabets and underline and nothing else. Step 1: Carefully read the input and understand the structure of the expected output. -Step 2: Extract relevant parameters from the provided text based on the name and description of object. +Step 2: Extract relevant parameters from the provided text based on the name and description of object. Step 3: Structure the extracted parameters to JSON object as specified in . -Step 4: Ensure that the list of variable_names is properly formatted and valid. The output should not contain any XML tags. Output an empty list if there is no valid variable name in input text. +Step 4: Ensure that the list of variable_names is properly formatted and valid. The output should not contain any XML tags. Output an empty list if there is no valid variable name in input text. ### Structure -Here is the structure of the expected output, I should always follow the output structure. +Here is the structure of the expected output, I should always follow the output structure. ["variable_name_1", "variable_name_2"] ### Input Text @@ -214,13 +214,13 @@ I should always output a valid list. Output nothing other than the list of varia RULE_CONFIG_STATEMENT_GENERATE_TEMPLATE = """ -Step 1: Identify the purpose of the chatbot from the variable {{TASK_DESCRIPTION}} and infer chatbot's tone (e.g., friendly, professional, etc.) to add personality traits. +Step 1: Identify the purpose of the chatbot from the variable {{TASK_DESCRIPTION}} and infer chatbot's tone (e.g., friendly, professional, etc.) to add personality traits. Step 2: Create a coherent and engaging opening statement. Step 3: Ensure the output is welcoming and clearly explains what the chatbot is designed to do. Do not include any XML tags in the output. -Please use the same language as the user's input language. If user uses chinese then generate opening statement in chinese, if user uses english then generate opening statement in english. -Example Input: +Please use the same language as the user's input language. If user uses chinese then generate opening statement in chinese, if user uses english then generate opening statement in english. +Example Input: Provide customer support for an e-commerce website -Example Output: +Example Output: Welcome! I'm here to assist you with any questions or issues you might have with your shopping experience. Whether you're looking for product information, need help with your order, or have any other inquiries, feel free to ask. I'm friendly, helpful, and ready to support you in any way I can. Here is the task description: {{INPUT_TEXT}} @@ -276,15 +276,15 @@ Your task is to convert simple user descriptions into properly formatted JSON Sc { "type": "object", "properties": { - "email": { + "email": { "type": "string", "format": "email" }, - "password": { + "password": { "type": "string", "minLength": 8 }, - "age": { + "age": { "type": "integer", "minimum": 18 } diff --git a/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md b/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md index 2d71e99fce..d845c4bd09 100644 --- a/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md +++ b/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md @@ -307,4 +307,4 @@ Runtime Errors: """ ``` -For interface method details, see: [Interfaces](./interfaces.md). For specific implementations, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). \ No newline at end of file +For interface method details, see: [Interfaces](./interfaces.md). For specific implementations, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). diff --git a/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md b/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md index 3e16257452..a770ed157b 100644 --- a/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md +++ b/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md @@ -170,4 +170,4 @@ Runtime Errors: """ ``` -For interface method explanations, see: [Interfaces](./interfaces.md). For detailed implementation, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). \ No newline at end of file +For interface method explanations, see: [Interfaces](./interfaces.md). For detailed implementation, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). diff --git a/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md b/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md index 88ec6861fe..7d30655469 100644 --- a/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md +++ b/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md @@ -294,4 +294,4 @@ provider_credential_schema: """ ``` -接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 \ No newline at end of file +接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 diff --git a/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md b/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md index b33dc7c94b..80e7982e9f 100644 --- a/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md +++ b/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md @@ -169,4 +169,4 @@ pricing: # 价格信息 """ ``` -接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 \ No newline at end of file +接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 diff --git a/api/core/moderation/api/__builtin__ b/api/core/moderation/api/__builtin__ index e440e5c842..00750edc07 100644 --- a/api/core/moderation/api/__builtin__ +++ b/api/core/moderation/api/__builtin__ @@ -1 +1 @@ -3 \ No newline at end of file +3 diff --git a/api/core/moderation/keywords/__builtin__ b/api/core/moderation/keywords/__builtin__ index d8263ee986..0cfbf08886 100644 --- a/api/core/moderation/keywords/__builtin__ +++ b/api/core/moderation/keywords/__builtin__ @@ -1 +1 @@ -2 \ No newline at end of file +2 diff --git a/api/core/moderation/openai_moderation/__builtin__ b/api/core/moderation/openai_moderation/__builtin__ index 56a6051ca2..d00491fd7e 100644 --- a/api/core/moderation/openai_moderation/__builtin__ +++ b/api/core/moderation/openai_moderation/__builtin__ @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/api/core/plugin/backwards_invocation/model.py b/api/core/plugin/backwards_invocation/model.py index 490a475c16..5ec9620f22 100644 --- a/api/core/plugin/backwards_invocation/model.py +++ b/api/core/plugin/backwards_invocation/model.py @@ -239,8 +239,8 @@ class PluginModelBackwardsInvocation(BaseBackwardsInvocation): content = payload.text SUMMARY_PROMPT = """You are a professional language researcher, you are interested in the language -and you can quickly aimed at the main point of an webpage and reproduce it in your own words but -retain the original meaning and keep the key points. +and you can quickly aimed at the main point of an webpage and reproduce it in your own words but +retain the original meaning and keep the key points. however, the text you got is too long, what you got is possible a part of the text. Please summarize the text you got. diff --git a/api/core/prompt/prompt_templates/baichuan_chat.json b/api/core/prompt/prompt_templates/baichuan_chat.json index 03b6a53cff..b3f7cdaa18 100644 --- a/api/core/prompt/prompt_templates/baichuan_chat.json +++ b/api/core/prompt/prompt_templates/baichuan_chat.json @@ -10,4 +10,4 @@ ], "query_prompt": "\n\n用户:{{#query#}}", "stops": ["用户:"] -} \ No newline at end of file +} diff --git a/api/core/prompt/prompt_templates/baichuan_completion.json b/api/core/prompt/prompt_templates/baichuan_completion.json index ae8c0dac53..cee9ea47cd 100644 --- a/api/core/prompt/prompt_templates/baichuan_completion.json +++ b/api/core/prompt/prompt_templates/baichuan_completion.json @@ -6,4 +6,4 @@ ], "query_prompt": "{{#query#}}", "stops": null -} \ No newline at end of file +} diff --git a/api/core/prompt/prompt_templates/common_completion.json b/api/core/prompt/prompt_templates/common_completion.json index c148772010..706a8140d1 100644 --- a/api/core/prompt/prompt_templates/common_completion.json +++ b/api/core/prompt/prompt_templates/common_completion.json @@ -6,4 +6,4 @@ ], "query_prompt": "{{#query#}}", "stops": null -} \ No newline at end of file +} diff --git a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py index c1792943bb..14481b1f10 100644 --- a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py +++ b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py @@ -156,8 +156,8 @@ class AnalyticdbVectorBySql: values = [] id_prefix = str(uuid.uuid4()) + "_" sql = f""" - INSERT INTO {self.table_name} - (id, ref_doc_id, vector, page_content, metadata_, to_tsvector) + INSERT INTO {self.table_name} + (id, ref_doc_id, vector, page_content, metadata_, to_tsvector) VALUES (%s, %s, %s, %s, %s, to_tsvector('zh_cn', %s)); """ for i, doc in enumerate(documents): @@ -242,7 +242,7 @@ class AnalyticdbVectorBySql: where_clause += f"AND metadata_->>'document_id' IN ({document_ids})" with self._get_cursor() as cur: cur.execute( - f"""SELECT id, vector, page_content, metadata_, + f"""SELECT id, vector, page_content, metadata_, ts_rank(to_tsvector, to_tsquery_from_text(%s, 'zh_cn'), 32) AS score FROM {self.table_name} WHERE to_tsvector@@to_tsquery_from_text(%s, 'zh_cn') {where_clause} diff --git a/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py b/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py index ae6b0c51ab..2b47d179d2 100644 --- a/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py +++ b/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py @@ -203,7 +203,7 @@ class OceanBaseVector(BaseVector): full_sql = f"""SELECT metadata, text, MATCH (text) AGAINST (:query) AS score FROM {self._collection_name} - WHERE MATCH (text) AGAINST (:query) > 0 + WHERE MATCH (text) AGAINST (:query) > 0 {where_clause} ORDER BY score DESC LIMIT {top_k}""" diff --git a/api/core/rag/datasource/vdb/opengauss/opengauss.py b/api/core/rag/datasource/vdb/opengauss/opengauss.py index dae908f67d..2548881b9c 100644 --- a/api/core/rag/datasource/vdb/opengauss/opengauss.py +++ b/api/core/rag/datasource/vdb/opengauss/opengauss.py @@ -59,12 +59,12 @@ CREATE TABLE IF NOT EXISTS {table_name} ( """ SQL_CREATE_INDEX_PQ = """ -CREATE INDEX IF NOT EXISTS embedding_{table_name}_pq_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_{table_name}_pq_idx ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64, enable_pq=on, pq_m={pq_m}); """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS embedding_cosine_{table_name}_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_cosine_{table_name}_idx ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); """ diff --git a/api/core/rag/datasource/vdb/oracle/oraclevector.py b/api/core/rag/datasource/vdb/oracle/oraclevector.py index 63695e6f3f..0a3738ac93 100644 --- a/api/core/rag/datasource/vdb/oracle/oraclevector.py +++ b/api/core/rag/datasource/vdb/oracle/oraclevector.py @@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS {table_name} ( ) """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS idx_docs_{table_name} ON {table_name}(text) -INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS +CREATE INDEX IF NOT EXISTS idx_docs_{table_name} ON {table_name}(text) +INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('FILTER CTXSYS.NULL_FILTER SECTION GROUP CTXSYS.HTML_SECTION_GROUP LEXER world_lexer') """ @@ -164,7 +164,7 @@ class OracleVector(BaseVector): with conn.cursor() as cur: try: cur.execute( - f"""INSERT INTO {self.table_name} (id, text, meta, embedding) + f"""INSERT INTO {self.table_name} (id, text, meta, embedding) VALUES (:1, :2, :3, :4)""", value, ) @@ -227,8 +227,8 @@ class OracleVector(BaseVector): conn.outputtypehandler = self.output_type_handler with conn.cursor() as cur: cur.execute( - f"""SELECT meta, text, vector_distance(embedding,(select to_vector(:1) from dual),cosine) - AS distance FROM {self.table_name} + f"""SELECT meta, text, vector_distance(embedding,(select to_vector(:1) from dual),cosine) + AS distance FROM {self.table_name} {where_clause} ORDER BY distance fetch first {top_k} rows only""", [numpy.array(query_vector)], ) @@ -290,7 +290,7 @@ class OracleVector(BaseVector): document_ids = ", ".join(f"'{id}'" for id in document_ids_filter) where_clause = f" AND metadata->>'document_id' in ({document_ids}) " cur.execute( - f"""select meta, text, embedding FROM {self.table_name} + f"""select meta, text, embedding FROM {self.table_name} WHERE CONTAINS(text, :kk, 1) > 0 {where_clause} order by score(1) desc fetch first {top_k} rows only""", kk=" ACCUM ".join(entities), diff --git a/api/core/rag/datasource/vdb/pgvector/pgvector.py b/api/core/rag/datasource/vdb/pgvector/pgvector.py index eab51ab01d..366a21c381 100644 --- a/api/core/rag/datasource/vdb/pgvector/pgvector.py +++ b/api/core/rag/datasource/vdb/pgvector/pgvector.py @@ -61,7 +61,7 @@ CREATE TABLE IF NOT EXISTS {table_name} ( """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); """ diff --git a/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py index a61d571e16..156730ff37 100644 --- a/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py +++ b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py @@ -58,7 +58,7 @@ CREATE TABLE IF NOT EXISTS {table_name} ( """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} USING hnsw (embedding floatvector_cosine_ops) WITH (m = 16, ef_construction = 64); """ diff --git a/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py b/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py index 00229ce700..61c68b939e 100644 --- a/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py +++ b/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py @@ -205,9 +205,9 @@ class TiDBVector(BaseVector): with Session(self._engine) as session: select_statement = sql_text(f""" - SELECT meta, text, distance + SELECT meta, text, distance FROM ( - SELECT + SELECT meta, text, {tidb_dist_func}(vector, :query_vector_str) AS distance diff --git a/api/core/rag/retrieval/template_prompts.py b/api/core/rag/retrieval/template_prompts.py index 7abd55d798..acde842702 100644 --- a/api/core/rag/retrieval/template_prompts.py +++ b/api/core/rag/retrieval/template_prompts.py @@ -50,7 +50,7 @@ You are a text metadata extract engine that extract text's metadata based on use # Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -59,7 +59,7 @@ User:{{"input_text": ["I want to know which company’s email address test@examp Assistant:{{"metadata_map": [{{"metadata_field_name": "email", "metadata_field_value": "test@example.com", "comparison_operator": "="}}]}} User:{{"input_text": "What are the movies with a score of more than 9 in 2024?", "metadata_fields": ["name", "year", "rating", "country"]}} Assistant:{{"metadata_map": [{{"metadata_field_name": "year", "metadata_field_value": "2024", "comparison_operator": "="}, {{"metadata_field_name": "rating", "metadata_field_value": "9", "comparison_operator": ">"}}]}} - + ### User Input {{"input_text" : "{input_text}", "metadata_fields" : {metadata_fields}}} ### Assistant Output diff --git a/api/core/tools/builtin_tool/tool.py b/api/core/tools/builtin_tool/tool.py index 7f37f98d0c..724a2291c6 100644 --- a/api/core/tools/builtin_tool/tool.py +++ b/api/core/tools/builtin_tool/tool.py @@ -6,8 +6,8 @@ from core.tools.entities.tool_entities import ToolProviderType from core.tools.utils.model_invocation_utils import ModelInvocationUtils _SUMMARY_PROMPT = """You are a professional language researcher, you are interested in the language -and you can quickly aimed at the main point of an webpage and reproduce it in your own words but -retain the original meaning and keep the key points. +and you can quickly aimed at the main point of an webpage and reproduce it in your own words but +retain the original meaning and keep the key points. however, the text you got is too long, what you got is possible a part of the text. Please summarize the text you got. """ diff --git a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py index 7abd55d798..acde842702 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py +++ b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py @@ -50,7 +50,7 @@ You are a text metadata extract engine that extract text's metadata based on use # Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -59,7 +59,7 @@ User:{{"input_text": ["I want to know which company’s email address test@examp Assistant:{{"metadata_map": [{{"metadata_field_name": "email", "metadata_field_value": "test@example.com", "comparison_operator": "="}}]}} User:{{"input_text": "What are the movies with a score of more than 9 in 2024?", "metadata_fields": ["name", "year", "rating", "country"]}} Assistant:{{"metadata_map": [{{"metadata_field_name": "year", "metadata_field_value": "2024", "comparison_operator": "="}, {{"metadata_field_name": "rating", "metadata_field_value": "9", "comparison_operator": ">"}}]}} - + ### User Input {{"input_text" : "{input_text}", "metadata_fields" : {metadata_fields}}} ### Assistant Output diff --git a/api/core/workflow/nodes/parameter_extractor/prompts.py b/api/core/workflow/nodes/parameter_extractor/prompts.py index 6c3155ac9a..ab7ddcc32a 100644 --- a/api/core/workflow/nodes/parameter_extractor/prompts.py +++ b/api/core/workflow/nodes/parameter_extractor/prompts.py @@ -17,7 +17,7 @@ Some additional information is provided below. Always adhere to these instructio Steps: 1. Review the chat history provided within the tags. -2. Extract the relevant information based on the criteria given, output multiple values if there is multiple relevant information that match the criteria in the given text. +2. Extract the relevant information based on the criteria given, output multiple values if there is multiple relevant information that match the criteria in the given text. 3. Generate a well-formatted output using the defined functions and arguments. 4. Use the `extract_parameter` function to create structured outputs with appropriate parameters. 5. Do not include any XML tags in your output. @@ -89,13 +89,13 @@ Some extra information are provided below, I should always follow the instructio ### Extract parameter Workflow -I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. +I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. {{ structure }} Step 1: Carefully read the input and understand the structure of the expected output. -Step 2: Extract relevant parameters from the provided text based on the name and description of object. +Step 2: Extract relevant parameters from the provided text based on the name and description of object. Step 3: Structure the extracted parameters to JSON object as specified in . Step 4: Ensure that the JSON object is properly formatted and valid. The output should not contain any XML tags. Only the JSON object should be outputted. @@ -106,10 +106,10 @@ Here are the chat histories between human and assistant, inside ### Structure -Here is the structure of the expected output, I should always follow the output structure. +Here is the structure of the expected output, I should always follow the output structure. {{γγγ - 'properties1': 'relevant text extracted from input', - 'properties2': 'relevant text extracted from input', + 'properties1': 'relevant text extracted from input', + 'properties2': 'relevant text extracted from input', }}γγγ ### Input Text @@ -119,7 +119,7 @@ Inside XML tags, there is a text that I should extract parameters ### Answer -I should always output a valid JSON object. Output nothing other than the JSON object. +I should always output a valid JSON object. Output nothing other than the JSON object. ```JSON """ # noqa: E501 diff --git a/api/core/workflow/nodes/question_classifier/template_prompts.py b/api/core/workflow/nodes/question_classifier/template_prompts.py index 70178ed934..a615c32383 100644 --- a/api/core/workflow/nodes/question_classifier/template_prompts.py +++ b/api/core/workflow/nodes/question_classifier/template_prompts.py @@ -55,7 +55,7 @@ You are a text classification engine that analyzes text data and assigns categor Your task is to assign one categories ONLY to the input text and only one category may be assigned returned in the output. Additionally, you need to extract the key words from the text that are related to the classification. ### Format The input text is in the variable input_text. Categories are specified as a category list with two filed category_id and category_name in the variable categories. Classification instructions may be included to improve the classification accuracy. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -64,7 +64,7 @@ User:{{"input_text": ["I recently had a great experience with your company. The Assistant:{{"keywords": ["recently", "great experience", "company", "service", "prompt", "staff", "friendly"],"category_id": "f5660049-284f-41a7-b301-fd24176a711c","category_name": "Customer Service"}} User:{{"input_text": ["bad service, slow to bring the food"], "categories": [{{"category_id":"80fb86a0-4454-4bf5-924c-f253fdd83c02","category_name":"Food Quality"}},{{"category_id":"f6ff5bc3-aca0-4e4a-8627-e760d0aca78f","category_name":"Experience"}},{{"category_id":"cc771f63-74e7-4c61-882e-3eda9d8ba5d7","category_name":"Price"}}], "classification_instructions": []}} Assistant:{{"keywords": ["bad service", "slow", "food", "tip", "terrible", "waitresses"],"category_id": "f6ff5bc3-aca0-4e4a-8627-e760d0aca78f","category_name": "Experience"}} - + ### Memory Here are the chat histories between human and assistant, inside XML tags. diff --git a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py index c17d1db77a..5b5656e7ed 100644 --- a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py +++ b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py @@ -21,14 +21,14 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### # Get the database connection conn = op.get_bind() - + # Use SQLAlchemy inspector to get the columns of the 'tool_files' table inspector = sa.inspect(conn) columns = [col['name'] for col in inspector.get_columns('tool_files')] # If 'name' or 'size' columns already exist, exit the upgrade function if 'name' in columns or 'size' in columns: - return + return with op.batch_alter_table('tool_files', schema=None) as batch_op: batch_op.add_column(sa.Column('name', sa.String(), nullable=True)) diff --git a/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py b/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py index 0facd0ecc0..ae9f2de9b1 100644 --- a/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py +++ b/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py @@ -35,4 +35,4 @@ def downgrade(): # batch_op.drop_column('retry_index') pass - # ### end Alembic commands ### \ No newline at end of file + # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py index ea129d15f7..07454b0917 100644 --- a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py +++ b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py @@ -23,7 +23,7 @@ def upgrade(): conn = op.get_bind() inspector = inspect(conn) has_column = 'retry_index' in [col['name'] for col in inspector.get_columns('workflow_node_executions')] - + if has_column: with op.batch_alter_table('workflow_node_executions', schema=None) as batch_op: batch_op.drop_column('retry_index') diff --git a/api/migrations/versions/64b051264f32_init.py b/api/migrations/versions/64b051264f32_init.py index 8c45ae898d..b0fb3deac6 100644 --- a/api/migrations/versions/64b051264f32_init.py +++ b/api/migrations/versions/64b051264f32_init.py @@ -1,7 +1,7 @@ """init Revision ID: 64b051264f32 -Revises: +Revises: Create Date: 2023-05-13 14:26:59.085018 """ diff --git a/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py b/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py index fcca705d21..c18126286c 100644 --- a/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py +++ b/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py @@ -99,12 +99,12 @@ def upgrade(): id=id, tenant_id=tenant_id, user_id=user_id, - provider='google', + provider='google', encrypted_credentials=encrypted_credentials, created_at=created_at, updated_at=updated_at ) - + # ### end Alembic commands ### diff --git a/api/services/plugin/data_migration.py b/api/services/plugin/data_migration.py index 597585588b..1c5abfecba 100644 --- a/api/services/plugin/data_migration.py +++ b/api/services/plugin/data_migration.py @@ -86,9 +86,9 @@ limit 1000""" update_retrieval_model_sql = ", retrieval_model = :retrieval_model" params["retrieval_model"] = json.dumps(retrieval_model) - sql = f"""update {table_name} - set {provider_column_name} = - concat('{DEFAULT_PLUGIN_ID}/', {provider_column_name}, '/', {provider_column_name}) + sql = f"""update {table_name} + set {provider_column_name} = + concat('{DEFAULT_PLUGIN_ID}/', {provider_column_name}, '/', {provider_column_name}) {update_retrieval_model_sql} where id = :record_id""" conn.execute(db.text(sql), params) @@ -131,10 +131,10 @@ limit 1000""" while True: sql = f""" - SELECT id, {provider_column_name} AS provider_name + SELECT id, {provider_column_name} AS provider_name FROM {table_name} - WHERE {provider_column_name} NOT LIKE '%/%' - AND {provider_column_name} IS NOT NULL + WHERE {provider_column_name} NOT LIKE '%/%' + AND {provider_column_name} IS NOT NULL AND {provider_column_name} != '' AND id > :last_id ORDER BY id ASC @@ -183,8 +183,8 @@ limit 1000""" if batch_updates: update_sql = f""" - UPDATE {table_name} - SET {provider_column_name} = :updated_value + UPDATE {table_name} + SET {provider_column_name} = :updated_value WHERE id = :record_id """ conn.execute(db.text(update_sql), [{"updated_value": u, "record_id": r} for u, r in batch_updates]) diff --git a/api/templates/clean_document_job_mail_template-US.html b/api/templates/clean_document_job_mail_template-US.html index 88e78f41c7..0f7ddc62a9 100644 --- a/api/templates/clean_document_job_mail_template-US.html +++ b/api/templates/clean_document_job_mail_template-US.html @@ -77,7 +77,7 @@

    Some Documents in Your Knowledge Base Have Been Disabled

    Dear {{userName}},

    - We're sorry for the inconvenience. To ensure optimal performance, documents + We're sorry for the inconvenience. To ensure optimal performance, documents that haven’t been updated or accessed in the past 30 days have been disabled in your knowledge bases:

    @@ -97,4 +97,4 @@ - \ No newline at end of file + diff --git a/api/templates/delete_account_code_email_template_en-US.html b/api/templates/delete_account_code_email_template_en-US.html index 7707385334..eca3dedf72 100644 --- a/api/templates/delete_account_code_email_template_en-US.html +++ b/api/templates/delete_account_code_email_template_en-US.html @@ -122,4 +122,4 @@ - \ No newline at end of file + diff --git a/api/templates/delete_account_success_template_en-US.html b/api/templates/delete_account_success_template_en-US.html index c5df75cabc..b96eee1172 100644 --- a/api/templates/delete_account_success_template_en-US.html +++ b/api/templates/delete_account_success_template_en-US.html @@ -102,4 +102,4 @@ - \ No newline at end of file + diff --git a/api/tests/integration_tests/.gitignore b/api/tests/integration_tests/.gitignore index 426667562b..ed9875073f 100644 --- a/api/tests/integration_tests/.gitignore +++ b/api/tests/integration_tests/.gitignore @@ -1 +1 @@ -.env.test \ No newline at end of file +.env.test diff --git a/api/tests/unit_tests/.gitignore b/api/tests/unit_tests/.gitignore index 426667562b..ed9875073f 100644 --- a/api/tests/unit_tests/.gitignore +++ b/api/tests/unit_tests/.gitignore @@ -1 +1 @@ -.env.test \ No newline at end of file +.env.test diff --git a/dev/pytest/pytest_all_tests.sh b/dev/pytest/pytest_all_tests.sh index c4318fb922..f0c8a78548 100755 --- a/dev/pytest/pytest_all_tests.sh +++ b/dev/pytest/pytest_all_tests.sh @@ -11,4 +11,4 @@ dev/pytest/pytest_tools.sh dev/pytest/pytest_workflow.sh # Unit tests -dev/pytest/pytest_unit_tests.sh \ No newline at end of file +dev/pytest/pytest_unit_tests.sh diff --git a/dev/pytest/pytest_model_runtime.sh b/dev/pytest/pytest_model_runtime.sh index 63891eb9f8..dc6c6ac627 100755 --- a/dev/pytest/pytest_model_runtime.sh +++ b/dev/pytest/pytest_model_runtime.sh @@ -10,4 +10,4 @@ pytest api/tests/integration_tests/model_runtime/anthropic \ api/tests/integration_tests/model_runtime/fireworks \ api/tests/integration_tests/model_runtime/nomic \ api/tests/integration_tests/model_runtime/mixedbread \ - api/tests/integration_tests/model_runtime/voyage \ No newline at end of file + api/tests/integration_tests/model_runtime/voyage diff --git a/docker/couchbase-server/Dockerfile b/docker/couchbase-server/Dockerfile index bd8af64150..23e487e4ed 100644 --- a/docker/couchbase-server/Dockerfile +++ b/docker/couchbase-server/Dockerfile @@ -1,4 +1,4 @@ FROM couchbase/server:latest AS stage_base -# FROM couchbase:latest AS stage_base +# FROM couchbase:latest AS stage_base COPY init-cbserver.sh /opt/couchbase/init/ -RUN chmod +x /opt/couchbase/init/init-cbserver.sh \ No newline at end of file +RUN chmod +x /opt/couchbase/init/init-cbserver.sh diff --git a/docker/couchbase-server/init-cbserver.sh b/docker/couchbase-server/init-cbserver.sh index e66bc18530..e19a650f23 100755 --- a/docker/couchbase-server/init-cbserver.sh +++ b/docker/couchbase-server/init-cbserver.sh @@ -1,8 +1,8 @@ #!/bin/bash -# used to start couchbase server - can't get around this as docker compose only allows you to start one command - so we have to start couchbase like the standard couchbase Dockerfile would +# used to start couchbase server - can't get around this as docker compose only allows you to start one command - so we have to start couchbase like the standard couchbase Dockerfile would # https://github.com/couchbase/docker/blob/master/enterprise/couchbase-server/7.2.0/Dockerfile#L88 -/entrypoint.sh couchbase-server & +/entrypoint.sh couchbase-server & # track if setup is complete so we don't try to setup again FILE=/opt/couchbase/init/setupComplete.txt @@ -36,9 +36,9 @@ if ! [ -f "$FILE" ]; then --bucket-ramsize $COUCHBASE_BUCKET_RAMSIZE \ --bucket-type couchbase - # create file so we know that the cluster is setup and don't run the setup again + # create file so we know that the cluster is setup and don't run the setup again touch $FILE -fi +fi # docker compose will stop the container from running unless we do this # known issue and workaround tail -f /dev/null diff --git a/docker/middleware.env.example b/docker/middleware.env.example index 1a4484a9b5..2ac09ea264 100644 --- a/docker/middleware.env.example +++ b/docker/middleware.env.example @@ -144,4 +144,4 @@ PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= # Plugin oss tencent cos PLUGIN_TENCENT_COS_SECRET_KEY= PLUGIN_TENCENT_COS_SECRET_ID= -PLUGIN_TENCENT_COS_REGION= \ No newline at end of file +PLUGIN_TENCENT_COS_REGION= diff --git a/docker/nginx/docker-entrypoint.sh b/docker/nginx/docker-entrypoint.sh index 8e1110ffa9..763254e37b 100755 --- a/docker/nginx/docker-entrypoint.sh +++ b/docker/nginx/docker-entrypoint.sh @@ -39,4 +39,4 @@ envsubst "$env_vars" < /etc/nginx/proxy.conf.template > /etc/nginx/proxy.conf envsubst "$env_vars" < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf # Start Nginx using the default entrypoint -exec nginx -g 'daemon off;' \ No newline at end of file +exec nginx -g 'daemon off;' diff --git a/docker/nginx/https.conf.template b/docker/nginx/https.conf.template index 95ea36f463..296908d8be 100644 --- a/docker/nginx/https.conf.template +++ b/docker/nginx/https.conf.template @@ -6,4 +6,4 @@ ssl_certificate_key ${SSL_CERTIFICATE_KEY_PATH}; ssl_protocols ${NGINX_SSL_PROTOCOLS}; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; -ssl_session_timeout 10m; \ No newline at end of file +ssl_session_timeout 10m; diff --git a/docker/nginx/nginx.conf.template b/docker/nginx/nginx.conf.template index 32a571653e..20446fae2e 100644 --- a/docker/nginx/nginx.conf.template +++ b/docker/nginx/nginx.conf.template @@ -31,4 +31,4 @@ http { client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file +} diff --git a/docker/ssrf_proxy/squid.conf.template b/docker/ssrf_proxy/squid.conf.template index c74c1fb67b..1775a1fff9 100644 --- a/docker/ssrf_proxy/squid.conf.template +++ b/docker/ssrf_proxy/squid.conf.template @@ -44,7 +44,7 @@ refresh_pattern . 0 20% 4320 # cache_dir ufs /var/spool/squid 100 16 256 # upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks -# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default +# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default ################################## Reverse Proxy To Sandbox ################################ http_port ${REVERSE_PROXY_PORT} accel vhost @@ -53,4 +53,4 @@ acl src_all src all http_access allow src_all # Unless the option's size is increased, an error will occur when uploading more than two files. -client_request_buffer_max_size 100 MB \ No newline at end of file +client_request_buffer_max_size 100 MB diff --git a/docker/startupscripts/init.sh b/docker/startupscripts/init.sh index c6e6e1966f..dcee1e1978 100755 --- a/docker/startupscripts/init.sh +++ b/docker/startupscripts/init.sh @@ -8,6 +8,6 @@ if [ -f ${DB_INITIALIZED} ]; then exit else echo 'File does not exist. Standards for first time Start up this DB' - "$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script"; + "$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script"; touch ${DB_INITIALIZED} fi diff --git a/docker/startupscripts/init_user.script b/docker/startupscripts/init_user.script index 0c5bff1ef6..e710d827e8 100755 --- a/docker/startupscripts/init_user.script +++ b/docker/startupscripts/init_user.script @@ -1,5 +1,5 @@ show pdbs; -ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE; +ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE; alter session set container= freepdb1; create user dify identified by dify DEFAULT TABLESPACE users quota unlimited on users; grant DB_DEVELOPER_ROLE to dify; diff --git a/docker/tidb/config/pd.toml b/docker/tidb/config/pd.toml index 042b251e46..01e352a86a 100644 --- a/docker/tidb/config/pd.toml +++ b/docker/tidb/config/pd.toml @@ -1,4 +1,4 @@ # PD Configuration File reference: # https://docs.pingcap.com/tidb/stable/pd-configuration-file#pd-configuration-file [replication] -max-replicas = 1 \ No newline at end of file +max-replicas = 1 diff --git a/docker/volumes/myscale/config/users.d/custom_users_config.xml b/docker/volumes/myscale/config/users.d/custom_users_config.xml index 67f24b69ee..b46e73a0e9 100644 --- a/docker/volumes/myscale/config/users.d/custom_users_config.xml +++ b/docker/volumes/myscale/config/users.d/custom_users_config.xml @@ -14,4 +14,4 @@ 1 - \ No newline at end of file + diff --git a/docker/volumes/oceanbase/init.d/vec_memory.sql b/docker/volumes/oceanbase/init.d/vec_memory.sql index f4c283fdf4..0d859e5f7c 100644 --- a/docker/volumes/oceanbase/init.d/vec_memory.sql +++ b/docker/volumes/oceanbase/init.d/vec_memory.sql @@ -1 +1 @@ -ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; \ No newline at end of file +ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; diff --git a/sdks/nodejs-client/.gitignore b/sdks/nodejs-client/.gitignore index 35d1a1461b..1d40ff2ece 100644 --- a/sdks/nodejs-client/.gitignore +++ b/sdks/nodejs-client/.gitignore @@ -45,4 +45,4 @@ package-lock.json .yarnrc.yml # pmpm -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml diff --git a/sdks/nodejs-client/index.d.ts b/sdks/nodejs-client/index.d.ts index a2e6e50aef..a8b7497f4f 100644 --- a/sdks/nodejs-client/index.d.ts +++ b/sdks/nodejs-client/index.d.ts @@ -26,7 +26,7 @@ export declare class DifyClient { params?: Params, stream?: boolean, headerParams?: HeaderParams - ): Promise; + ): Promise; messageFeedback(message_id: string, rating: number, user: User): Promise; @@ -64,9 +64,9 @@ export declare class ChatClient extends DifyClient { getConversations( - user: User, - first_id?: string | null, - limit?: number | null, + user: User, + first_id?: string | null, + limit?: number | null, pinned?: boolean | null ): Promise; @@ -80,7 +80,7 @@ export declare class ChatClient extends DifyClient { renameConversation(conversation_id: string, name: string, user: User,auto_generate:boolean): Promise; deleteConversation(conversation_id: string, user: User): Promise; - + audioToText(data: FormData): Promise; } @@ -88,4 +88,4 @@ export declare class WorkflowClient extends DifyClient { run(inputs: any, user: User, stream?: boolean,): Promise; stop(task_id: string, user: User): Promise; -} \ No newline at end of file +} diff --git a/sdks/nodejs-client/index.js b/sdks/nodejs-client/index.js index 858241ce5a..0ba7bba8bb 100644 --- a/sdks/nodejs-client/index.js +++ b/sdks/nodejs-client/index.js @@ -334,12 +334,12 @@ export class ChatClient extends DifyClient { export class WorkflowClient extends DifyClient { run(inputs,user,stream) { - const data = { - inputs, + const data = { + inputs, response_mode: stream ? "streaming" : "blocking", - user + user }; - + return this.sendRequest( routes.runWorkflow.method, routes.runWorkflow.url(), @@ -357,4 +357,4 @@ export class WorkflowClient extends DifyClient { data ); } -} \ No newline at end of file +} diff --git a/sdks/nodejs-client/index.test.js b/sdks/nodejs-client/index.test.js index f300b16fc9..1f5d6edb06 100644 --- a/sdks/nodejs-client/index.test.js +++ b/sdks/nodejs-client/index.test.js @@ -62,4 +62,4 @@ describe('Send Requests', () => { errorMessage ) }) -}) \ No newline at end of file +}) diff --git a/sdks/nodejs-client/package.json b/sdks/nodejs-client/package.json index cc27c5e0c0..cd3bcc4bce 100644 --- a/sdks/nodejs-client/package.json +++ b/sdks/nodejs-client/package.json @@ -32,4 +32,4 @@ "babel-jest": "^29.5.0", "jest": "^29.5.0" } -} \ No newline at end of file +} diff --git a/sdks/php-client/README.md b/sdks/php-client/README.md index 812980d834..91e77ad9ff 100644 --- a/sdks/php-client/README.md +++ b/sdks/php-client/README.md @@ -92,4 +92,4 @@ Replace 'your-api-key-here' with your actual Dify API key. ## License -This SDK is released under the MIT License. \ No newline at end of file +This SDK is released under the MIT License. diff --git a/sdks/php-client/dify-client.php b/sdks/php-client/dify-client.php index acb862093a..b6cf261b66 100644 --- a/sdks/php-client/dify-client.php +++ b/sdks/php-client/dify-client.php @@ -119,14 +119,14 @@ class ChatClient extends DifyClient { return $this->send_request('POST', 'chat-messages', $data, null, $response_mode === 'streaming'); } - + public function get_suggestions($message_id, $user) { $params = [ 'user' => $user ]; return $this->send_request('GET', "messages/{$message_id}/suggested", null, $params); } - + public function stop_message($task_id, $user) { $data = ['user' => $user]; return $this->send_request('POST', "chat-messages/{$task_id}/stop", $data); @@ -157,7 +157,7 @@ class ChatClient extends DifyClient { return $this->send_request('GET', 'messages', null, $params); } - + public function rename_conversation($conversation_id, $name,$auto_generate, $user) { $data = [ 'name' => $name, @@ -202,5 +202,5 @@ class WorkflowClient extends DifyClient{ ]; return $this->send_request('POST', "workflows/tasks/{$task_id}/stop",$data); } - -} \ No newline at end of file + +} diff --git a/sdks/python-client/MANIFEST.in b/sdks/python-client/MANIFEST.in index da331d5e5c..12f44237a2 100644 --- a/sdks/python-client/MANIFEST.in +++ b/sdks/python-client/MANIFEST.in @@ -1 +1 @@ -recursive-include dify_client *.py \ No newline at end of file +recursive-include dify_client *.py diff --git a/sdks/python-client/build.sh b/sdks/python-client/build.sh index ca1a762c99..525f57c1ef 100755 --- a/sdks/python-client/build.sh +++ b/sdks/python-client/build.sh @@ -6,4 +6,4 @@ rm -rf build dist *.egg-info pip install setuptools wheel twine python setup.py sdist bdist_wheel -twine upload dist/* \ No newline at end of file +twine upload dist/* diff --git a/web/.dockerignore b/web/.dockerignore index 45a8922ce9..31eb66c210 100644 --- a/web/.dockerignore +++ b/web/.dockerignore @@ -21,4 +21,4 @@ node_modules # Jetbrains -.idea \ No newline at end of file +.idea diff --git a/web/.vscode/extensions.json b/web/.vscode/extensions.json index a9afbcc640..e0e72ce11e 100644 --- a/web/.vscode/extensions.json +++ b/web/.vscode/extensions.json @@ -4,4 +4,4 @@ "firsttris.vscode-jest-runner", "kisstkondoros.vscode-codemetrics" ] -} \ No newline at end of file +} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css index 16392a5b4b..45c7d197b4 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css @@ -3,4 +3,4 @@ height: 0; border-radius: 16px 16px 0px 0px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.05), 0px 0px 2px -1px rgba(0, 0, 0, 0.03); -} \ No newline at end of file +} diff --git a/web/app/(commonLayout)/list.module.css b/web/app/(commonLayout)/list.module.css index 2fc6469a6d..c4d3aec29f 100644 --- a/web/app/(commonLayout)/list.module.css +++ b/web/app/(commonLayout)/list.module.css @@ -214,4 +214,4 @@ .listItem:hover .unavailable { @apply opacity-100; -} \ No newline at end of file +} diff --git a/web/app/components/app-sidebar/style.module.css b/web/app/components/app-sidebar/style.module.css index 722b35bc71..ca0978b760 100644 --- a/web/app/components/app-sidebar/style.module.css +++ b/web/app/components/app-sidebar/style.module.css @@ -5,7 +5,7 @@ .completionPic { background-image: url('./completion.png') } - + .expertPic { background-image: url('./expert.png') -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/base/var-highlight/style.module.css b/web/app/components/app/configuration/base/var-highlight/style.module.css index cd5c8f8d77..2bcef0dabb 100644 --- a/web/app/components/app/configuration/base/var-highlight/style.module.css +++ b/web/app/components/app/configuration/base/var-highlight/style.module.css @@ -1,3 +1,3 @@ .item { background-color: rgba(21, 94, 239, 0.05); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/base/warning-mask/style.module.css b/web/app/components/app/configuration/base/warning-mask/style.module.css index e1d6f10de9..87f226fd96 100644 --- a/web/app/components/app/configuration/base/warning-mask/style.module.css +++ b/web/app/components/app/configuration/base/warning-mask/style.module.css @@ -5,4 +5,4 @@ .icon { box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/config-prompt/style.module.css b/web/app/components/app/configuration/config-prompt/style.module.css index 66785620b3..224d59d9c8 100644 --- a/web/app/components/app/configuration/config-prompt/style.module.css +++ b/web/app/components/app/configuration/config-prompt/style.module.css @@ -25,4 +25,4 @@ .boxHeader:hover .optionWrap { display: flex; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/config/automatic/style.module.css b/web/app/components/app/configuration/config/automatic/style.module.css index 7ad3180a5a..15bedd84ca 100644 --- a/web/app/components/app/configuration/config/automatic/style.module.css +++ b/web/app/components/app/configuration/config/automatic/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/ctrl-btn-group/style.module.css b/web/app/components/app/configuration/ctrl-btn-group/style.module.css index c7250b8f96..3e874868a9 100644 --- a/web/app/components/app/configuration/ctrl-btn-group/style.module.css +++ b/web/app/components/app/configuration/ctrl-btn-group/style.module.css @@ -3,4 +3,4 @@ right: -16px; bottom: -16px; border-top: 1px solid #F3F4F6; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/dataset-config/card-item/style.module.css b/web/app/components/app/configuration/dataset-config/card-item/style.module.css index 4ddec9ea37..da07056cbc 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/style.module.css +++ b/web/app/components/app/configuration/dataset-config/card-item/style.module.css @@ -19,4 +19,4 @@ .settingBtn:hover { background-color: rgba(0, 0, 0, 0.05); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css index 5bfea0cac9..ef9350645a 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css +++ b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css @@ -4,4 +4,4 @@ .weightedScoreSliderTrack-1 { background: transparent !important; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/style.module.css b/web/app/components/app/configuration/style.module.css index f0e57cefbf..01f2c93167 100644 --- a/web/app/components/app/configuration/style.module.css +++ b/web/app/components/app/configuration/style.module.css @@ -11,4 +11,4 @@ height: 3px; background-color: rgba(68, 76, 231, 0.18); transform: skewX(-30deg); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/tools/index.tsx b/web/app/components/app/configuration/tools/index.tsx index cc34041ba3..ba586bb20d 100644 --- a/web/app/components/app/configuration/tools/index.tsx +++ b/web/app/components/app/configuration/tools/index.tsx @@ -87,7 +87,7 @@ const Tools = () => {
    setExpanded(v => !v)} diff --git a/web/app/components/app/overview/embedded/style.module.css b/web/app/components/app/overview/embedded/style.module.css index bea829b059..f2a4d2d0f4 100644 --- a/web/app/components/app/overview/embedded/style.module.css +++ b/web/app/components/app/overview/embedded/style.module.css @@ -17,4 +17,4 @@ } .pluginInstallIcon { background-image: url(../assets/chromeplugin-install.svg); -} \ No newline at end of file +} diff --git a/web/app/components/base/action-button/index.css b/web/app/components/base/action-button/index.css index 2cabe7aecc..3c1a10b86f 100644 --- a/web/app/components/base/action-button/index.css +++ b/web/app/components/base/action-button/index.css @@ -42,4 +42,4 @@ @apply text-text-destructive bg-state-destructive-hover } -} \ No newline at end of file +} diff --git a/web/app/components/base/app-icon/style.module.css b/web/app/components/base/app-icon/style.module.css index 151bc6d3fc..4ee84fb444 100644 --- a/web/app/components/base/app-icon/style.module.css +++ b/web/app/components/base/app-icon/style.module.css @@ -20,4 +20,4 @@ .appIcon.rounded { @apply rounded-full; -} \ No newline at end of file +} diff --git a/web/app/components/base/audio-btn/style.module.css b/web/app/components/base/audio-btn/style.module.css index b8a4da6b68..7e3175aa13 100644 --- a/web/app/components/base/audio-btn/style.module.css +++ b/web/app/components/base/audio-btn/style.module.css @@ -7,4 +7,4 @@ background-image: url(~@/app/components/develop/secret-key/assets/pause.svg); background-position: center; background-repeat: no-repeat; -} \ No newline at end of file +} diff --git a/web/app/components/base/badge/index.css b/web/app/components/base/badge/index.css index 99db573c9c..24c62cdebc 100644 --- a/web/app/components/base/badge/index.css +++ b/web/app/components/base/badge/index.css @@ -25,4 +25,4 @@ .badge.badge-accent { @apply text-text-accent-secondary border border-text-accent-secondary } -} \ No newline at end of file +} diff --git a/web/app/components/base/button/index.css b/web/app/components/base/button/index.css index 5656cb9fdb..47e59142cc 100644 --- a/web/app/components/base/button/index.css +++ b/web/app/components/base/button/index.css @@ -22,7 +22,7 @@ } .btn-primary { - @apply + @apply shadow bg-components-button-primary-bg border-components-button-primary-border @@ -32,7 +32,7 @@ } .btn-primary.btn-destructive { - @apply + @apply bg-components-button-destructive-primary-bg border-components-button-destructive-primary-border hover:bg-components-button-destructive-primary-bg-hover @@ -41,7 +41,7 @@ } .btn-primary.btn-disabled { - @apply + @apply shadow-none bg-components-button-primary-bg-disabled border-components-button-primary-border-disabled @@ -49,7 +49,7 @@ } .btn-primary.btn-destructive.btn-disabled { - @apply + @apply shadow-none bg-components-button-destructive-primary-bg-disabled border-components-button-destructive-primary-border-disabled @@ -57,130 +57,130 @@ } .btn-secondary { - @apply + @apply border-[0.5px] shadow-xs - bg-components-button-secondary-bg - border-components-button-secondary-border - hover:bg-components-button-secondary-bg-hover - hover:border-components-button-secondary-border-hover + bg-components-button-secondary-bg + border-components-button-secondary-border + hover:bg-components-button-secondary-bg-hover + hover:border-components-button-secondary-border-hover text-components-button-secondary-text; } .btn-secondary.btn-disabled { - @apply - bg-components-button-secondary-bg-disabled - border-components-button-secondary-border-disabled + @apply + bg-components-button-secondary-bg-disabled + border-components-button-secondary-border-disabled text-components-button-secondary-text-disabled; } .btn-secondary.btn-destructive { - @apply - bg-components-button-destructive-secondary-bg - border-components-button-destructive-secondary-border - hover:bg-components-button-destructive-secondary-bg-hover - hover:border-components-button-destructive-secondary-border-hover + @apply + bg-components-button-destructive-secondary-bg + border-components-button-destructive-secondary-border + hover:bg-components-button-destructive-secondary-bg-hover + hover:border-components-button-destructive-secondary-border-hover text-components-button-destructive-secondary-text; } .btn-secondary.btn-destructive.btn-disabled { - @apply - bg-components-button-destructive-secondary-bg-disabled - border-components-button-destructive-secondary-border-disabled + @apply + bg-components-button-destructive-secondary-bg-disabled + border-components-button-destructive-secondary-border-disabled text-components-button-destructive-secondary-text-disabled; } - + .btn-secondary-accent { - @apply + @apply border-[0.5px] shadow-xs - bg-components-button-secondary-bg - border-components-button-secondary-border - hover:bg-components-button-secondary-bg-hover - hover:border-components-button-secondary-border-hover + bg-components-button-secondary-bg + border-components-button-secondary-border + hover:bg-components-button-secondary-bg-hover + hover:border-components-button-secondary-border-hover text-components-button-secondary-accent-text; } .btn-secondary-accent.btn-disabled { - @apply - bg-components-button-secondary-bg-disabled - border-components-button-secondary-border-disabled + @apply + bg-components-button-secondary-bg-disabled + border-components-button-secondary-border-disabled text-components-button-secondary-accent-text-disabled; } .btn-warning { - @apply - bg-components-button-destructive-primary-bg - border-components-button-destructive-primary-border - hover:bg-components-button-destructive-primary-bg-hover - hover:border-components-button-destructive-primary-border-hover + @apply + bg-components-button-destructive-primary-bg + border-components-button-destructive-primary-border + hover:bg-components-button-destructive-primary-bg-hover + hover:border-components-button-destructive-primary-border-hover text-components-button-destructive-primary-text; } .btn-warning.btn-disabled { - @apply - bg-components-button-destructive-primary-bg-disabled - border-components-button-destructive-primary-border-disabled + @apply + bg-components-button-destructive-primary-bg-disabled + border-components-button-destructive-primary-border-disabled text-components-button-destructive-primary-text-disabled; } .btn-tertiary { - @apply - bg-components-button-tertiary-bg - hover:bg-components-button-tertiary-bg-hover + @apply + bg-components-button-tertiary-bg + hover:bg-components-button-tertiary-bg-hover text-components-button-tertiary-text; } .btn-tertiary.btn-disabled { - @apply - bg-components-button-tertiary-bg-disabled + @apply + bg-components-button-tertiary-bg-disabled text-components-button-tertiary-text-disabled; } .btn-tertiary.btn-destructive { - @apply - bg-components-button-destructive-tertiary-bg - hover:bg-components-button-destructive-tertiary-bg-hover + @apply + bg-components-button-destructive-tertiary-bg + hover:bg-components-button-destructive-tertiary-bg-hover text-components-button-destructive-tertiary-text; } .btn-tertiary.btn-destructive.btn-disabled { - @apply - bg-components-button-destructive-tertiary-bg-disabled + @apply + bg-components-button-destructive-tertiary-bg-disabled text-components-button-destructive-tertiary-text-disabled; } .btn-ghost { - @apply - hover:bg-components-button-ghost-bg-hover + @apply + hover:bg-components-button-ghost-bg-hover text-components-button-ghost-text; } .btn-ghost.btn-disabled { - @apply + @apply text-components-button-ghost-text-disabled; } .btn-ghost.btn-destructive { - @apply - hover:bg-components-button-destructive-ghost-bg-hover + @apply + hover:bg-components-button-destructive-ghost-bg-hover text-components-button-destructive-ghost-text; } .btn-ghost.btn-destructive.btn-disabled { - @apply + @apply text-components-button-destructive-ghost-text-disabled; } .btn-ghost-accent { - @apply + @apply hover:bg-state-accent-hover text-components-button-secondary-accent-text; } .btn-ghost-accent.btn-disabled { - @apply + @apply text-components-button-secondary-accent-text-disabled; } -} \ No newline at end of file +} diff --git a/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts b/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts index 67029cd163..bcc3ae628d 100644 --- a/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts +++ b/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts @@ -2,25 +2,25 @@ export const markdownContentSVG = ` \`\`\`svg - + 创意Logo设计 - + - + 科研 科学研究 - + 探索未知的灯塔, 照亮人类前进的道路。 科研,是永不熄灭的好奇心, 也是推动世界进步的引擎。 - + - + 探索 • 创新 • 进步 \`\`\` diff --git a/web/app/components/base/chat/chat/loading-anim/style.module.css b/web/app/components/base/chat/chat/loading-anim/style.module.css index 5a764db13c..b1371ec82a 100644 --- a/web/app/components/base/chat/chat/loading-anim/style.module.css +++ b/web/app/components/base/chat/chat/loading-anim/style.module.css @@ -79,4 +79,4 @@ .avatar::after { left: 5px; -} \ No newline at end of file +} diff --git a/web/app/components/base/copy-btn/style.module.css b/web/app/components/base/copy-btn/style.module.css index 56c756025b..83625d6189 100644 --- a/web/app/components/base/copy-btn/style.module.css +++ b/web/app/components/base/copy-btn/style.module.css @@ -12,4 +12,4 @@ .copyIcon.copied { background-image: url(~@/app/components/develop/secret-key/assets/copied.svg); -} \ No newline at end of file +} diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css index 4e93b39563..8ef23b54b5 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css @@ -17,4 +17,4 @@ .slider-track-1 { background-color: #E5E7EB; -} \ No newline at end of file +} diff --git a/web/app/components/base/grid-mask/style.module.css b/web/app/components/base/grid-mask/style.module.css index 4d135b3cfc..e051271fab 100644 --- a/web/app/components/base/grid-mask/style.module.css +++ b/web/app/components/base/grid-mask/style.module.css @@ -2,4 +2,4 @@ background-image: url(./Grid.svg); background-repeat: repeat; background-position: 0 0; -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/avatar/Robot.json b/web/app/components/base/icons/src/public/avatar/Robot.json index babc0f87a0..8969a2a649 100644 --- a/web/app/components/base/icons/src/public/avatar/Robot.json +++ b/web/app/components/base/icons/src/public/avatar/Robot.json @@ -89,4 +89,4 @@ ] }, "name": "Robot" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/avatar/User.json b/web/app/components/base/icons/src/public/avatar/User.json index 01fb8e39c3..4b9ad7615f 100644 --- a/web/app/components/base/icons/src/public/avatar/User.json +++ b/web/app/components/base/icons/src/public/avatar/User.json @@ -86,4 +86,4 @@ ] }, "name": "User" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/ArCube1.json b/web/app/components/base/icons/src/public/billing/ArCube1.json index f341c9218f..89d9786c04 100644 --- a/web/app/components/base/icons/src/public/billing/ArCube1.json +++ b/web/app/components/base/icons/src/public/billing/ArCube1.json @@ -26,4 +26,4 @@ ] }, "name": "ArCube1" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Asterisk.json b/web/app/components/base/icons/src/public/billing/Asterisk.json index 6f70b27a1f..d4a2e91b45 100644 --- a/web/app/components/base/icons/src/public/billing/Asterisk.json +++ b/web/app/components/base/icons/src/public/billing/Asterisk.json @@ -35,4 +35,4 @@ ] }, "name": "Asterisk" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/AwsMarketplace.json b/web/app/components/base/icons/src/public/billing/AwsMarketplace.json index 8a0b1003cd..8aeb93f7b2 100644 --- a/web/app/components/base/icons/src/public/billing/AwsMarketplace.json +++ b/web/app/components/base/icons/src/public/billing/AwsMarketplace.json @@ -176,4 +176,4 @@ ] }, "name": "AwsMarketplace" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Azure.json b/web/app/components/base/icons/src/public/billing/Azure.json index ad4cd429a6..fb6a9b9e95 100644 --- a/web/app/components/base/icons/src/public/billing/Azure.json +++ b/web/app/components/base/icons/src/public/billing/Azure.json @@ -190,4 +190,4 @@ ] }, "name": "Azure" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Buildings.json b/web/app/components/base/icons/src/public/billing/Buildings.json index f9dd338328..62d22f97c6 100644 --- a/web/app/components/base/icons/src/public/billing/Buildings.json +++ b/web/app/components/base/icons/src/public/billing/Buildings.json @@ -36,4 +36,4 @@ ] }, "name": "Buildings" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Diamond.json b/web/app/components/base/icons/src/public/billing/Diamond.json index 69ab74606b..6717026232 100644 --- a/web/app/components/base/icons/src/public/billing/Diamond.json +++ b/web/app/components/base/icons/src/public/billing/Diamond.json @@ -36,4 +36,4 @@ ] }, "name": "Diamond" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/GoogleCloud.json b/web/app/components/base/icons/src/public/billing/GoogleCloud.json index 244f05776f..0c55bdaf03 100644 --- a/web/app/components/base/icons/src/public/billing/GoogleCloud.json +++ b/web/app/components/base/icons/src/public/billing/GoogleCloud.json @@ -63,4 +63,4 @@ ] }, "name": "GoogleCloud" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Group2.json b/web/app/components/base/icons/src/public/billing/Group2.json index b2424ba881..8cc0896d5d 100644 --- a/web/app/components/base/icons/src/public/billing/Group2.json +++ b/web/app/components/base/icons/src/public/billing/Group2.json @@ -26,4 +26,4 @@ ] }, "name": "Group2" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Keyframe.json b/web/app/components/base/icons/src/public/billing/Keyframe.json index c721854d14..ed0dcb4fba 100644 --- a/web/app/components/base/icons/src/public/billing/Keyframe.json +++ b/web/app/components/base/icons/src/public/billing/Keyframe.json @@ -25,4 +25,4 @@ ] }, "name": "Keyframe" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Sparkles.json b/web/app/components/base/icons/src/public/billing/Sparkles.json index ea2bae44e7..5317b50936 100644 --- a/web/app/components/base/icons/src/public/billing/Sparkles.json +++ b/web/app/components/base/icons/src/public/billing/Sparkles.json @@ -92,4 +92,4 @@ ] }, "name": "Sparkles" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/SparklesSoft.json b/web/app/components/base/icons/src/public/billing/SparklesSoft.json index ce4f11f489..b6a5a6ddf4 100644 --- a/web/app/components/base/icons/src/public/billing/SparklesSoft.json +++ b/web/app/components/base/icons/src/public/billing/SparklesSoft.json @@ -33,4 +33,4 @@ ] }, "name": "SparklesSoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/D.json b/web/app/components/base/icons/src/public/common/D.json index 2090b8909d..ab4ed79135 100644 --- a/web/app/components/base/icons/src/public/common/D.json +++ b/web/app/components/base/icons/src/public/common/D.json @@ -122,4 +122,4 @@ ] }, "name": "D" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json b/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json index 04475c2288..a9e7cd7217 100644 --- a/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json +++ b/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json @@ -25,4 +25,4 @@ ] }, "name": "DiagonalDividingLine" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Dify.json b/web/app/components/base/icons/src/public/common/Dify.json index 9926e91986..a954b66757 100644 --- a/web/app/components/base/icons/src/public/common/Dify.json +++ b/web/app/components/base/icons/src/public/common/Dify.json @@ -59,4 +59,4 @@ ] }, "name": "Dify" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Gdpr.json b/web/app/components/base/icons/src/public/common/Gdpr.json index 3605030eb8..1e030b54d1 100644 --- a/web/app/components/base/icons/src/public/common/Gdpr.json +++ b/web/app/components/base/icons/src/public/common/Gdpr.json @@ -337,4 +337,4 @@ ] }, "name": "Gdpr" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Github.json b/web/app/components/base/icons/src/public/common/Github.json index abccde4f5e..523bcd55b8 100644 --- a/web/app/components/base/icons/src/public/common/Github.json +++ b/web/app/components/base/icons/src/public/common/Github.json @@ -33,4 +33,4 @@ ] }, "name": "Github" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Highlight.json b/web/app/components/base/icons/src/public/common/Highlight.json index d18386eb01..055d9f79ca 100644 --- a/web/app/components/base/icons/src/public/common/Highlight.json +++ b/web/app/components/base/icons/src/public/common/Highlight.json @@ -64,4 +64,4 @@ ] }, "name": "Highlight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Iso.json b/web/app/components/base/icons/src/public/common/Iso.json index 6864a591c4..50f0267b60 100644 --- a/web/app/components/base/icons/src/public/common/Iso.json +++ b/web/app/components/base/icons/src/public/common/Iso.json @@ -118,4 +118,4 @@ ] }, "name": "Iso" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Line3.json b/web/app/components/base/icons/src/public/common/Line3.json index 32f6d50bb8..2beb66a5f4 100644 --- a/web/app/components/base/icons/src/public/common/Line3.json +++ b/web/app/components/base/icons/src/public/common/Line3.json @@ -25,4 +25,4 @@ ] }, "name": "Line3" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Lock.json b/web/app/components/base/icons/src/public/common/Lock.json index 24af41a73d..a5a1f4b781 100644 --- a/web/app/components/base/icons/src/public/common/Lock.json +++ b/web/app/components/base/icons/src/public/common/Lock.json @@ -35,4 +35,4 @@ ] }, "name": "Lock" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/MessageChatSquare.json b/web/app/components/base/icons/src/public/common/MessageChatSquare.json index 18069eda39..71cf6d0c98 100644 --- a/web/app/components/base/icons/src/public/common/MessageChatSquare.json +++ b/web/app/components/base/icons/src/public/common/MessageChatSquare.json @@ -34,4 +34,4 @@ ] }, "name": "MessageChatSquare" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json b/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json index d37b263688..9d64edadd4 100644 --- a/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json +++ b/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json @@ -150,4 +150,4 @@ ] }, "name": "MultiPathRetrieval" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/NTo1Retrieval.json b/web/app/components/base/icons/src/public/common/NTo1Retrieval.json index 086522046f..74ca34573f 100644 --- a/web/app/components/base/icons/src/public/common/NTo1Retrieval.json +++ b/web/app/components/base/icons/src/public/common/NTo1Retrieval.json @@ -143,4 +143,4 @@ ] }, "name": "NTo1Retrieval" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Notion.json b/web/app/components/base/icons/src/public/common/Notion.json index 27bb0081d0..d27aeb8190 100644 --- a/web/app/components/base/icons/src/public/common/Notion.json +++ b/web/app/components/base/icons/src/public/common/Notion.json @@ -80,4 +80,4 @@ ] }, "name": "Notion" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Soc2.json b/web/app/components/base/icons/src/public/common/Soc2.json index 34080b0adb..38b9c5e606 100644 --- a/web/app/components/base/icons/src/public/common/Soc2.json +++ b/web/app/components/base/icons/src/public/common/Soc2.json @@ -935,4 +935,4 @@ ] }, "name": "Soc2" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/SparklesSoft.json b/web/app/components/base/icons/src/public/common/SparklesSoft.json index e22cec82a3..11ac030c5e 100644 --- a/web/app/components/base/icons/src/public/common/SparklesSoft.json +++ b/web/app/components/base/icons/src/public/common/SparklesSoft.json @@ -44,4 +44,4 @@ ] }, "name": "SparklesSoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/education/Triangle.json b/web/app/components/base/icons/src/public/education/Triangle.json index 92d7c82c43..ab00049ce1 100644 --- a/web/app/components/base/icons/src/public/education/Triangle.json +++ b/web/app/components/base/icons/src/public/education/Triangle.json @@ -24,4 +24,4 @@ ] }, "name": "Triangle" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Csv.json b/web/app/components/base/icons/src/public/files/Csv.json index d4d2bd9f3e..533dcd7525 100644 --- a/web/app/components/base/icons/src/public/files/Csv.json +++ b/web/app/components/base/icons/src/public/files/Csv.json @@ -178,4 +178,4 @@ ] }, "name": "Csv" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Doc.json b/web/app/components/base/icons/src/public/files/Doc.json index f4513177a6..9d219addd2 100644 --- a/web/app/components/base/icons/src/public/files/Doc.json +++ b/web/app/components/base/icons/src/public/files/Doc.json @@ -166,4 +166,4 @@ ] }, "name": "Doc" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Docx.json b/web/app/components/base/icons/src/public/files/Docx.json index 5054f083b4..ffa9ef8d3b 100644 --- a/web/app/components/base/icons/src/public/files/Docx.json +++ b/web/app/components/base/icons/src/public/files/Docx.json @@ -175,4 +175,4 @@ ] }, "name": "Docx" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Html.json b/web/app/components/base/icons/src/public/files/Html.json index 86134d1df9..f267073c47 100644 --- a/web/app/components/base/icons/src/public/files/Html.json +++ b/web/app/components/base/icons/src/public/files/Html.json @@ -175,4 +175,4 @@ ] }, "name": "Html" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Json.json b/web/app/components/base/icons/src/public/files/Json.json index ae2943dac6..0801fecc1c 100644 --- a/web/app/components/base/icons/src/public/files/Json.json +++ b/web/app/components/base/icons/src/public/files/Json.json @@ -175,4 +175,4 @@ ] }, "name": "Json" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Md.json b/web/app/components/base/icons/src/public/files/Md.json index da1669658c..4a3cb687e6 100644 --- a/web/app/components/base/icons/src/public/files/Md.json +++ b/web/app/components/base/icons/src/public/files/Md.json @@ -141,4 +141,4 @@ ] }, "name": "Md" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Pdf.json b/web/app/components/base/icons/src/public/files/Pdf.json index e5ff4bc33b..7770f2790d 100644 --- a/web/app/components/base/icons/src/public/files/Pdf.json +++ b/web/app/components/base/icons/src/public/files/Pdf.json @@ -166,4 +166,4 @@ ] }, "name": "Pdf" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Txt.json b/web/app/components/base/icons/src/public/files/Txt.json index e511b9271c..c689fc680d 100644 --- a/web/app/components/base/icons/src/public/files/Txt.json +++ b/web/app/components/base/icons/src/public/files/Txt.json @@ -177,4 +177,4 @@ ] }, "name": "Txt" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Unknown.json b/web/app/components/base/icons/src/public/files/Unknown.json index c39df990d0..f1351e039e 100644 --- a/web/app/components/base/icons/src/public/files/Unknown.json +++ b/web/app/components/base/icons/src/public/files/Unknown.json @@ -196,4 +196,4 @@ ] }, "name": "Unknown" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Xlsx.json b/web/app/components/base/icons/src/public/files/Xlsx.json index 9cd6a618bf..5f0e7a96fc 100644 --- a/web/app/components/base/icons/src/public/files/Xlsx.json +++ b/web/app/components/base/icons/src/public/files/Xlsx.json @@ -142,4 +142,4 @@ ] }, "name": "Xlsx" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Yaml.json b/web/app/components/base/icons/src/public/files/Yaml.json index e35087a8e8..aa05cb468e 100644 --- a/web/app/components/base/icons/src/public/files/Yaml.json +++ b/web/app/components/base/icons/src/public/files/Yaml.json @@ -178,4 +178,4 @@ ] }, "name": "Yaml" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/Chunk.json b/web/app/components/base/icons/src/public/knowledge/Chunk.json index 469d85d1a7..91e85f2ce1 100644 --- a/web/app/components/base/icons/src/public/knowledge/Chunk.json +++ b/web/app/components/base/icons/src/public/knowledge/Chunk.json @@ -113,4 +113,4 @@ ] }, "name": "Chunk" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/Collapse.json b/web/app/components/base/icons/src/public/knowledge/Collapse.json index 66d457155d..726b074007 100644 --- a/web/app/components/base/icons/src/public/knowledge/Collapse.json +++ b/web/app/components/base/icons/src/public/knowledge/Collapse.json @@ -59,4 +59,4 @@ ] }, "name": "Collapse" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/GeneralType.json b/web/app/components/base/icons/src/public/knowledge/GeneralType.json index 9a87d00a60..5cbfb1a83c 100644 --- a/web/app/components/base/icons/src/public/knowledge/GeneralType.json +++ b/web/app/components/base/icons/src/public/knowledge/GeneralType.json @@ -35,4 +35,4 @@ ] }, "name": "GeneralType" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json b/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json index 26c5cf1d4f..194bec705e 100644 --- a/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json +++ b/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json @@ -33,4 +33,4 @@ ] }, "name": "LayoutRight2LineMod" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/ParentChildType.json b/web/app/components/base/icons/src/public/knowledge/ParentChildType.json index 250da77fc8..2d3270e418 100644 --- a/web/app/components/base/icons/src/public/knowledge/ParentChildType.json +++ b/web/app/components/base/icons/src/public/knowledge/ParentChildType.json @@ -53,4 +53,4 @@ ] }, "name": "ParentChildType" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/SelectionMod.json b/web/app/components/base/icons/src/public/knowledge/SelectionMod.json index ff8174a572..c88e27809f 100644 --- a/web/app/components/base/icons/src/public/knowledge/SelectionMod.json +++ b/web/app/components/base/icons/src/public/knowledge/SelectionMod.json @@ -113,4 +113,4 @@ ] }, "name": "SelectionMod" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Anthropic.json b/web/app/components/base/icons/src/public/llm/Anthropic.json index f237bba80e..db33abd6cc 100644 --- a/web/app/components/base/icons/src/public/llm/Anthropic.json +++ b/web/app/components/base/icons/src/public/llm/Anthropic.json @@ -34,4 +34,4 @@ ] }, "name": "Anthropic" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicDark.json b/web/app/components/base/icons/src/public/llm/AnthropicDark.json index 4f3af3ce79..ca066c2e78 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicDark.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicDark.json @@ -1043,4 +1043,4 @@ ] }, "name": "AnthropicDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicLight.json b/web/app/components/base/icons/src/public/llm/AnthropicLight.json index 3e84eb4dd6..2d2b0aab3e 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicLight.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicLight.json @@ -1043,4 +1043,4 @@ ] }, "name": "AnthropicLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicText.json b/web/app/components/base/icons/src/public/llm/AnthropicText.json index 72b3e6ebb7..7f89795d2f 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicText.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicText.json @@ -536,4 +536,4 @@ ] }, "name": "AnthropicText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json b/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json index 42cba3143b..bf07b59a51 100644 --- a/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json +++ b/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json @@ -71,4 +71,4 @@ ] }, "name": "AzureOpenaiService" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json b/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json index 12cdeec971..f4342d7c39 100644 --- a/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json +++ b/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json @@ -233,4 +233,4 @@ ] }, "name": "AzureOpenaiServiceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Azureai.json b/web/app/components/base/icons/src/public/llm/Azureai.json index 8662cfb937..004da326da 100644 --- a/web/app/components/base/icons/src/public/llm/Azureai.json +++ b/web/app/components/base/icons/src/public/llm/Azureai.json @@ -177,4 +177,4 @@ ] }, "name": "Azureai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureaiText.json b/web/app/components/base/icons/src/public/llm/AzureaiText.json index 2eb359960e..44976aa8e2 100644 --- a/web/app/components/base/icons/src/public/llm/AzureaiText.json +++ b/web/app/components/base/icons/src/public/llm/AzureaiText.json @@ -240,4 +240,4 @@ ] }, "name": "AzureaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Baichuan.json b/web/app/components/base/icons/src/public/llm/Baichuan.json index ad93703002..196fbada8c 100644 --- a/web/app/components/base/icons/src/public/llm/Baichuan.json +++ b/web/app/components/base/icons/src/public/llm/Baichuan.json @@ -73,4 +73,4 @@ ] }, "name": "Baichuan" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/BaichuanText.json b/web/app/components/base/icons/src/public/llm/BaichuanText.json index cda52e97fd..c4dc1d1101 100644 --- a/web/app/components/base/icons/src/public/llm/BaichuanText.json +++ b/web/app/components/base/icons/src/public/llm/BaichuanText.json @@ -153,4 +153,4 @@ ] }, "name": "BaichuanText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Chatglm.json b/web/app/components/base/icons/src/public/llm/Chatglm.json index 37a6aa9913..c01787f8eb 100644 --- a/web/app/components/base/icons/src/public/llm/Chatglm.json +++ b/web/app/components/base/icons/src/public/llm/Chatglm.json @@ -69,4 +69,4 @@ ] }, "name": "Chatglm" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ChatglmText.json b/web/app/components/base/icons/src/public/llm/ChatglmText.json index 80b765cfc8..1fe28ea749 100644 --- a/web/app/components/base/icons/src/public/llm/ChatglmText.json +++ b/web/app/components/base/icons/src/public/llm/ChatglmText.json @@ -132,4 +132,4 @@ ] }, "name": "ChatglmText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Cohere.json b/web/app/components/base/icons/src/public/llm/Cohere.json index 255514e8b0..70628917da 100644 --- a/web/app/components/base/icons/src/public/llm/Cohere.json +++ b/web/app/components/base/icons/src/public/llm/Cohere.json @@ -109,4 +109,4 @@ ] }, "name": "Cohere" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/CohereText.json b/web/app/components/base/icons/src/public/llm/CohereText.json index 588b345814..89657ccac6 100644 --- a/web/app/components/base/icons/src/public/llm/CohereText.json +++ b/web/app/components/base/icons/src/public/llm/CohereText.json @@ -87,4 +87,4 @@ ] }, "name": "CohereText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Gpt3.json b/web/app/components/base/icons/src/public/llm/Gpt3.json index 253b9a3d3f..383cb98d3a 100644 --- a/web/app/components/base/icons/src/public/llm/Gpt3.json +++ b/web/app/components/base/icons/src/public/llm/Gpt3.json @@ -48,4 +48,4 @@ ] }, "name": "Gpt3" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Gpt4.json b/web/app/components/base/icons/src/public/llm/Gpt4.json index 0e50c5f712..b0d1941df1 100644 --- a/web/app/components/base/icons/src/public/llm/Gpt4.json +++ b/web/app/components/base/icons/src/public/llm/Gpt4.json @@ -48,4 +48,4 @@ ] }, "name": "Gpt4" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Huggingface.json b/web/app/components/base/icons/src/public/llm/Huggingface.json index b3bd943da7..57e10e2b45 100644 --- a/web/app/components/base/icons/src/public/llm/Huggingface.json +++ b/web/app/components/base/icons/src/public/llm/Huggingface.json @@ -155,4 +155,4 @@ ] }, "name": "Huggingface" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/HuggingfaceText.json b/web/app/components/base/icons/src/public/llm/HuggingfaceText.json index 4e80364b55..d113e64f17 100644 --- a/web/app/components/base/icons/src/public/llm/HuggingfaceText.json +++ b/web/app/components/base/icons/src/public/llm/HuggingfaceText.json @@ -319,4 +319,4 @@ ] }, "name": "HuggingfaceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json b/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json index 9dcc6d64a8..0500abf2db 100644 --- a/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json +++ b/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json @@ -347,4 +347,4 @@ ] }, "name": "HuggingfaceTextHub" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSpark.json b/web/app/components/base/icons/src/public/llm/IflytekSpark.json index 03f50d7e39..1803b5f573 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSpark.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSpark.json @@ -41,4 +41,4 @@ ] }, "name": "IflytekSpark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSparkText.json b/web/app/components/base/icons/src/public/llm/IflytekSparkText.json index bd51f88aeb..2b01c14a6d 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSparkText.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSparkText.json @@ -184,4 +184,4 @@ ] }, "name": "IflytekSparkText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json b/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json index 4c874ad6ec..22d1411037 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json @@ -95,4 +95,4 @@ ] }, "name": "IflytekSparkTextCn" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Jina.json b/web/app/components/base/icons/src/public/llm/Jina.json index fc40c022f5..88d70a3ff1 100644 --- a/web/app/components/base/icons/src/public/llm/Jina.json +++ b/web/app/components/base/icons/src/public/llm/Jina.json @@ -32,4 +32,4 @@ ] }, "name": "Jina" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/JinaText.json b/web/app/components/base/icons/src/public/llm/JinaText.json index 04831fa4aa..08e76ef580 100644 --- a/web/app/components/base/icons/src/public/llm/JinaText.json +++ b/web/app/components/base/icons/src/public/llm/JinaText.json @@ -79,4 +79,4 @@ ] }, "name": "JinaText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Localai.json b/web/app/components/base/icons/src/public/llm/Localai.json index 30b9786182..e0f85498d7 100644 --- a/web/app/components/base/icons/src/public/llm/Localai.json +++ b/web/app/components/base/icons/src/public/llm/Localai.json @@ -104,4 +104,4 @@ ] }, "name": "Localai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/LocalaiText.json b/web/app/components/base/icons/src/public/llm/LocalaiText.json index e7a45194aa..849f7ae4f4 100644 --- a/web/app/components/base/icons/src/public/llm/LocalaiText.json +++ b/web/app/components/base/icons/src/public/llm/LocalaiText.json @@ -167,4 +167,4 @@ ] }, "name": "LocalaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Microsoft.json b/web/app/components/base/icons/src/public/llm/Microsoft.json index 692cd25eae..ab2c0522c7 100644 --- a/web/app/components/base/icons/src/public/llm/Microsoft.json +++ b/web/app/components/base/icons/src/public/llm/Microsoft.json @@ -73,4 +73,4 @@ ] }, "name": "Microsoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiBlack.json b/web/app/components/base/icons/src/public/llm/OpenaiBlack.json index ad722849e7..9f4a9914d3 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiBlack.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiBlack.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiBlack" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiBlue.json b/web/app/components/base/icons/src/public/llm/OpenaiBlue.json index 60b3fc6cf8..5c716f7cdd 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiBlue.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiBlue.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiBlue" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiGreen.json b/web/app/components/base/icons/src/public/llm/OpenaiGreen.json index 9ca36b6aa4..8980e858ca 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiGreen.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiGreen.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiGreen" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiText.json b/web/app/components/base/icons/src/public/llm/OpenaiText.json index 469aacf9d3..f5fc3de6b9 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiText.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiText.json @@ -74,4 +74,4 @@ ] }, "name": "OpenaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json b/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json index 00a410dce0..13b9cb4905 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json @@ -23,4 +23,4 @@ ] }, "name": "OpenaiTransparent" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiViolet.json b/web/app/components/base/icons/src/public/llm/OpenaiViolet.json index 927699bec2..efff2feacf 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiViolet.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiViolet.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiViolet" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Openllm.json b/web/app/components/base/icons/src/public/llm/Openllm.json index 1c71fa9c6f..93eec11dfe 100644 --- a/web/app/components/base/icons/src/public/llm/Openllm.json +++ b/web/app/components/base/icons/src/public/llm/Openllm.json @@ -80,4 +80,4 @@ ] }, "name": "Openllm" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenllmText.json b/web/app/components/base/icons/src/public/llm/OpenllmText.json index ad5179e9ee..d5705de10e 100644 --- a/web/app/components/base/icons/src/public/llm/OpenllmText.json +++ b/web/app/components/base/icons/src/public/llm/OpenllmText.json @@ -140,4 +140,4 @@ ] }, "name": "OpenllmText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Replicate.json b/web/app/components/base/icons/src/public/llm/Replicate.json index 089d111eef..303c239193 100644 --- a/web/app/components/base/icons/src/public/llm/Replicate.json +++ b/web/app/components/base/icons/src/public/llm/Replicate.json @@ -36,4 +36,4 @@ ] }, "name": "Replicate" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ReplicateText.json b/web/app/components/base/icons/src/public/llm/ReplicateText.json index c163ccbe74..b2d597c2ed 100644 --- a/web/app/components/base/icons/src/public/llm/ReplicateText.json +++ b/web/app/components/base/icons/src/public/llm/ReplicateText.json @@ -113,4 +113,4 @@ ] }, "name": "ReplicateText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/XorbitsInference.json b/web/app/components/base/icons/src/public/llm/XorbitsInference.json index fae25b37f9..b2d3b1a072 100644 --- a/web/app/components/base/icons/src/public/llm/XorbitsInference.json +++ b/web/app/components/base/icons/src/public/llm/XorbitsInference.json @@ -173,4 +173,4 @@ ] }, "name": "XorbitsInference" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json b/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json index b8dac91644..967ee6d6c4 100644 --- a/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json +++ b/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json @@ -326,4 +326,4 @@ ] }, "name": "XorbitsInferenceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Zhipuai.json b/web/app/components/base/icons/src/public/llm/Zhipuai.json index 7f93c634d0..87955688a5 100644 --- a/web/app/components/base/icons/src/public/llm/Zhipuai.json +++ b/web/app/components/base/icons/src/public/llm/Zhipuai.json @@ -50,4 +50,4 @@ ] }, "name": "Zhipuai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ZhipuaiText.json b/web/app/components/base/icons/src/public/llm/ZhipuaiText.json index 455a60695c..12eb65a53a 100644 --- a/web/app/components/base/icons/src/public/llm/ZhipuaiText.json +++ b/web/app/components/base/icons/src/public/llm/ZhipuaiText.json @@ -41,4 +41,4 @@ ] }, "name": "ZhipuaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json b/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json index 6002e07f6f..c5b1755f0c 100644 --- a/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json +++ b/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json @@ -59,4 +59,4 @@ ] }, "name": "ZhipuaiTextCn" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/model/Checked.json b/web/app/components/base/icons/src/public/model/Checked.json index f8ea944818..7e96db728f 100644 --- a/web/app/components/base/icons/src/public/model/Checked.json +++ b/web/app/components/base/icons/src/public/model/Checked.json @@ -26,4 +26,4 @@ ] }, "name": "Checked" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/DefaultToolIcon.json b/web/app/components/base/icons/src/public/other/DefaultToolIcon.json index 32412e8d01..32786d2281 100644 --- a/web/app/components/base/icons/src/public/other/DefaultToolIcon.json +++ b/web/app/components/base/icons/src/public/other/DefaultToolIcon.json @@ -78,4 +78,4 @@ ] }, "name": "DefaultToolIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/Icon3Dots.json b/web/app/components/base/icons/src/public/other/Icon3Dots.json index 9c6d232839..b59b293cec 100644 --- a/web/app/components/base/icons/src/public/other/Icon3Dots.json +++ b/web/app/components/base/icons/src/public/other/Icon3Dots.json @@ -26,4 +26,4 @@ ] }, "name": "Icon3Dots" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/Message3Fill.json b/web/app/components/base/icons/src/public/other/Message3Fill.json index 250ce5cdea..ae84890abc 100644 --- a/web/app/components/base/icons/src/public/other/Message3Fill.json +++ b/web/app/components/base/icons/src/public/other/Message3Fill.json @@ -170,4 +170,4 @@ ] }, "name": "Message3Fill" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/RowStruct.json b/web/app/components/base/icons/src/public/other/RowStruct.json index 0d1ef43f4f..49ef71753c 100644 --- a/web/app/components/base/icons/src/public/other/RowStruct.json +++ b/web/app/components/base/icons/src/public/other/RowStruct.json @@ -53,4 +53,4 @@ ] }, "name": "RowStruct" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/Google.json b/web/app/components/base/icons/src/public/plugins/Google.json index 6f04dddb9b..198050e04c 100644 --- a/web/app/components/base/icons/src/public/plugins/Google.json +++ b/web/app/components/base/icons/src/public/plugins/Google.json @@ -50,4 +50,4 @@ ] }, "name": "Google" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/PartnerDark.json b/web/app/components/base/icons/src/public/plugins/PartnerDark.json index 37135d4b21..af3f2083e4 100644 --- a/web/app/components/base/icons/src/public/plugins/PartnerDark.json +++ b/web/app/components/base/icons/src/public/plugins/PartnerDark.json @@ -444,4 +444,4 @@ ] }, "name": "PartnerDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/PartnerLight.json b/web/app/components/base/icons/src/public/plugins/PartnerLight.json index f726fca7d8..3d7391bcaa 100644 --- a/web/app/components/base/icons/src/public/plugins/PartnerLight.json +++ b/web/app/components/base/icons/src/public/plugins/PartnerLight.json @@ -443,4 +443,4 @@ ] }, "name": "PartnerLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/VerifiedDark.json b/web/app/components/base/icons/src/public/plugins/VerifiedDark.json index 4da3a28a74..ed228262b5 100644 --- a/web/app/components/base/icons/src/public/plugins/VerifiedDark.json +++ b/web/app/components/base/icons/src/public/plugins/VerifiedDark.json @@ -454,4 +454,4 @@ ] }, "name": "VerifiedDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/VerifiedLight.json b/web/app/components/base/icons/src/public/plugins/VerifiedLight.json index b41bdb72e1..b31fe655b3 100644 --- a/web/app/components/base/icons/src/public/plugins/VerifiedLight.json +++ b/web/app/components/base/icons/src/public/plugins/VerifiedLight.json @@ -453,4 +453,4 @@ ] }, "name": "VerifiedLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/WebReader.json b/web/app/components/base/icons/src/public/plugins/WebReader.json index 42ec3d9e78..58c828309c 100644 --- a/web/app/components/base/icons/src/public/plugins/WebReader.json +++ b/web/app/components/base/icons/src/public/plugins/WebReader.json @@ -36,4 +36,4 @@ ] }, "name": "WebReader" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/Wikipedia.json b/web/app/components/base/icons/src/public/plugins/Wikipedia.json index 7a16433be7..af2d505c85 100644 --- a/web/app/components/base/icons/src/public/plugins/Wikipedia.json +++ b/web/app/components/base/icons/src/public/plugins/Wikipedia.json @@ -23,4 +23,4 @@ ] }, "name": "Wikipedia" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/DataSet.json b/web/app/components/base/icons/src/public/thought/DataSet.json index 55952fe9d2..5be61dac9d 100644 --- a/web/app/components/base/icons/src/public/thought/DataSet.json +++ b/web/app/components/base/icons/src/public/thought/DataSet.json @@ -61,4 +61,4 @@ ] }, "name": "DataSet" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/Loading.json b/web/app/components/base/icons/src/public/thought/Loading.json index f19a3b1009..23e68662c4 100644 --- a/web/app/components/base/icons/src/public/thought/Loading.json +++ b/web/app/components/base/icons/src/public/thought/Loading.json @@ -61,4 +61,4 @@ ] }, "name": "Loading" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/Search.json b/web/app/components/base/icons/src/public/thought/Search.json index 9213419bbc..1ad8876bcc 100644 --- a/web/app/components/base/icons/src/public/thought/Search.json +++ b/web/app/components/base/icons/src/public/thought/Search.json @@ -61,4 +61,4 @@ ] }, "name": "Search" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/ThoughtList.json b/web/app/components/base/icons/src/public/thought/ThoughtList.json index 8b97633444..d5e13c339f 100644 --- a/web/app/components/base/icons/src/public/thought/ThoughtList.json +++ b/web/app/components/base/icons/src/public/thought/ThoughtList.json @@ -80,4 +80,4 @@ ] }, "name": "ThoughtList" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/WebReader.json b/web/app/components/base/icons/src/public/thought/WebReader.json index ecf85d9ec9..ba2bc485f0 100644 --- a/web/app/components/base/icons/src/public/thought/WebReader.json +++ b/web/app/components/base/icons/src/public/thought/WebReader.json @@ -61,4 +61,4 @@ ] }, "name": "WebReader" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json b/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json index ab0b8fbc1c..c2c8a73112 100644 --- a/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json +++ b/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json @@ -233,4 +233,4 @@ ] }, "name": "LangfuseIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json b/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json index 0fee622bd8..8172de6cf6 100644 --- a/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json @@ -233,4 +233,4 @@ ] }, "name": "LangfuseIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json b/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json index 04d480bd20..293c4bfd18 100644 --- a/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json +++ b/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json @@ -185,4 +185,4 @@ ] }, "name": "LangsmithIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json b/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json index 4aa76acc8d..18b1761e7f 100644 --- a/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json @@ -185,4 +185,4 @@ ] }, "name": "LangsmithIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/OpikIcon.json b/web/app/components/base/icons/src/public/tracing/OpikIcon.json index 5bab796c78..c9f3ad7985 100644 --- a/web/app/components/base/icons/src/public/tracing/OpikIcon.json +++ b/web/app/components/base/icons/src/public/tracing/OpikIcon.json @@ -160,4 +160,4 @@ ] }, "name": "OpikIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/OpikIconBig.json b/web/app/components/base/icons/src/public/tracing/OpikIconBig.json index 1372a92c0e..44e1e2c521 100644 --- a/web/app/components/base/icons/src/public/tracing/OpikIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/OpikIconBig.json @@ -159,4 +159,4 @@ ] }, "name": "OpikIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/TracingIcon.json b/web/app/components/base/icons/src/public/tracing/TracingIcon.json index 508b555b0f..2157a08fa3 100644 --- a/web/app/components/base/icons/src/public/tracing/TracingIcon.json +++ b/web/app/components/base/icons/src/public/tracing/TracingIcon.json @@ -44,4 +44,4 @@ ] }, "name": "TracingIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/image-gallery/style.module.css b/web/app/components/base/image-gallery/style.module.css index 64756a5d69..2e4c62e456 100644 --- a/web/app/components/base/image-gallery/style.module.css +++ b/web/app/components/base/image-gallery/style.module.css @@ -19,4 +19,4 @@ .img-4 .item:nth-child(3n) { margin-right: 8px; -} \ No newline at end of file +} diff --git a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx index a3a57f1cb4..99aef56250 100644 --- a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx +++ b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx @@ -98,7 +98,7 @@ const TextGenerationImageUploader: FC = ({ { hovering => (
    diff --git a/web/app/components/base/install-button/index.tsx b/web/app/components/base/install-button/index.tsx index d8983bb68e..0d9e953d5e 100644 --- a/web/app/components/base/install-button/index.tsx +++ b/web/app/components/base/install-button/index.tsx @@ -10,8 +10,8 @@ type InstallButtonProps = { const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => { return (
    { - showDownloadAction && tmp_preview_url && ( + showDownloadAction && download_url && ( { e.stopPropagation() - downloadFile(tmp_preview_url || '', name) + downloadFile(download_url || '', name) }} > From bb1d1dc263df7f9922b6b29cc2d604a9756541a5 Mon Sep 17 00:00:00 2001 From: Hao Cheng Date: Thu, 1 May 2025 08:49:43 +0200 Subject: [PATCH 36/89] fix: fix API tool integration test (#19187) --- .github/workflows/api-tests.yml | 3 +++ api/tests/integration_tests/tools/__mock/http.py | 4 +++- api/tests/integration_tests/tools/api_tool/test_api_tool.py | 3 ++- dev/pytest/pytest_tools.sh | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 02583cda06..f08befefb8 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -88,3 +88,6 @@ jobs: - name: Run Workflow run: uv run --project api bash dev/pytest/pytest_workflow.sh + + - name: Run Tool + run: uv run --project api bash dev/pytest/pytest_tools.sh diff --git a/api/tests/integration_tests/tools/__mock/http.py b/api/tests/integration_tests/tools/__mock/http.py index 42cf87e317..de9711ab38 100644 --- a/api/tests/integration_tests/tools/__mock/http.py +++ b/api/tests/integration_tests/tools/__mock/http.py @@ -5,6 +5,8 @@ import httpx import pytest from _pytest.monkeypatch import MonkeyPatch +from core.helper import ssrf_proxy + class MockedHttp: @staticmethod @@ -29,6 +31,6 @@ class MockedHttp: @pytest.fixture def setup_http_mock(request, monkeypatch: MonkeyPatch): - monkeypatch.setattr(httpx, "request", MockedHttp.httpx_request) + monkeypatch.setattr(ssrf_proxy, "make_request", MockedHttp.httpx_request) yield monkeypatch.undo() diff --git a/api/tests/integration_tests/tools/api_tool/test_api_tool.py b/api/tests/integration_tests/tools/api_tool/test_api_tool.py index 9acc94e110..7c1a200c8f 100644 --- a/api/tests/integration_tests/tools/api_tool/test_api_tool.py +++ b/api/tests/integration_tests/tools/api_tool/test_api_tool.py @@ -34,10 +34,11 @@ parameters = { def test_api_tool(setup_http_mock): tool = ApiTool( entity=ToolEntity( - identity=ToolIdentity(provider="", author="", name="", label=I18nObject()), + identity=ToolIdentity(provider="", author="", name="", label=I18nObject(en_US="test tool")), ), api_bundle=ApiToolBundle(**tool_bundle), runtime=ToolRuntime(tenant_id="", credentials={"auth_type": "none"}), + provider_id="test_tool", ) headers = tool.assembling_request(parameters) response = tool.do_http_request(tool.api_bundle.server_url, tool.api_bundle.method, headers, parameters) diff --git a/dev/pytest/pytest_tools.sh b/dev/pytest/pytest_tools.sh index 5b1de8b6dd..0b1a8c9877 100755 --- a/dev/pytest/pytest_tools.sh +++ b/dev/pytest/pytest_tools.sh @@ -1,4 +1,4 @@ #!/bin/bash set -x -pytest api/tests/integration_tests/tools/test_all_provider.py +pytest api/tests/integration_tests/tools From 50fa0d15125c620af518a524dcde0b944b0583e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=97=E6=B5=93?= Date: Sat, 3 May 2025 20:03:56 +0800 Subject: [PATCH 37/89] feat: Plugin page related document supports multiple languages (#19197) --- .../components/plugins/plugin-page/debug-info.tsx | 6 +++++- web/app/components/plugins/plugin-page/index.tsx | 4 ++-- web/app/components/plugins/utils.ts | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/web/app/components/plugins/plugin-page/debug-info.tsx b/web/app/components/plugins/plugin-page/debug-info.tsx index bd59e5d737..28b8a7fc49 100644 --- a/web/app/components/plugins/plugin-page/debug-info.tsx +++ b/web/app/components/plugins/plugin-page/debug-info.tsx @@ -1,6 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' +import { useContext } from 'use-context-selector' +import I18n from '@/context/i18n' import { RiArrowRightUpLine, RiBugLine, @@ -9,12 +11,14 @@ import { useTranslation } from 'react-i18next' import KeyValueItem from '../base/key-value-item' import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import { getDocsUrl } from '@/app/components/plugins/utils' import { useDebugKey } from '@/service/use-plugins' const i18nPrefix = 'plugin.debugInfo' const DebugInfo: FC = () => { const { t } = useTranslation() + const { locale } = useContext(I18n) const { data: info, isLoading } = useDebugKey() // info.key likes 4580bdb7-b878-471c-a8a4-bfd760263a53 mask the middle part using *. @@ -30,7 +34,7 @@ const DebugInfo: FC = () => { <>
    {t(`${i18nPrefix}.title`)} - + {t(`${i18nPrefix}.viewDocs`)} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index a4bc7a6e6c..cb57b7f4c6 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -34,10 +34,10 @@ import { import type { Dependency } from '../types' import type { PluginDeclaration, PluginManifestInMarket } from '../types' import { sleep } from '@/utils' +import { getDocsUrl } from '@/app/components/plugins/utils' import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' import { marketplaceApiPrefix } from '@/config' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { LanguagesSupported } from '@/i18n/language' import I18n from '@/context/i18n' import { noop } from 'lodash-es' import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/plugin-type-switch' @@ -187,7 +187,7 @@ const PluginPage = ({ isExploringMarketplace && ( <>
    diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts index d4ea088571..509f44708d 100644 --- a/web/i18n/de-DE/common.ts +++ b/web/i18n/de-DE/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { 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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API-Erweiterung hinzufügen', selector: { title: 'API-Erweiterung', diff --git a/web/i18n/de-DE/dataset-creation.ts b/web/i18n/de-DE/dataset-creation.ts index 60102a2a7d..6e532794aa 100644 --- a/web/i18n/de-DE/dataset-creation.ts +++ b/web/i18n/de-DE/dataset-creation.ts @@ -69,7 +69,7 @@ const translation = { unknownError: 'Unbekannter Fehler', resetAll: 'Alles zurücksetzen', extractOnlyMainContent: 'Extrahieren Sie nur den Hauptinhalt (keine Kopf-, Navigations- und Fußzeilen usw.)', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + 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', 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', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index bf2bf83f68..0491ff57a8 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { 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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Add API Extension', selector: { title: 'API Extension', diff --git a/web/i18n/en-US/dataset-creation.ts b/web/i18n/en-US/dataset-creation.ts index fe48e076fa..cf2d454f06 100644 --- a/web/i18n/en-US/dataset-creation.ts +++ b/web/i18n/en-US/dataset-creation.ts @@ -80,10 +80,10 @@ const translation = { run: 'Run', firecrawlTitle: 'Extract web content with 🔥Firecrawl', firecrawlDoc: 'Firecrawl docs', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + 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', watercrawlDoc: 'Watercrawl docs', - watercrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + 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', jinaReaderDoc: 'Learn more about Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/es-ES/common.ts b/web/i18n/es-ES/common.ts index 5933105ffd..483949553e 100644 --- a/web/i18n/es-ES/common.ts +++ b/web/i18n/es-ES/common.ts @@ -459,7 +459,7 @@ const translation = { 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.', link: 'Aprende cómo desarrollar tu propia Extensión API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Agregar Extensión API', selector: { title: 'Extensión API', diff --git a/web/i18n/es-ES/dataset-creation.ts b/web/i18n/es-ES/dataset-creation.ts index 7047ff9e9a..4fcccb0633 100644 --- a/web/i18n/es-ES/dataset-creation.ts +++ b/web/i18n/es-ES/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Ejecutar', firecrawlTitle: 'Extraer contenido web con 🔥Firecrawl', firecrawlDoc: 'Documentación de Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Opciones', crawlSubPage: 'Rastrear subpáginas', limit: 'Límite', diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 44d6bb006b..5ca55da25b 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'افزونه‌های مبتنی بر API مدیریت متمرکز API را فراهم می‌کنند و پیکربندی را برای استفاده آسان در برنامه‌های Dify ساده می‌کنند.', link: 'نحوه توسعه افزونه API خود را بیاموزید.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'افزودن افزونه API', selector: { title: 'افزونه API', diff --git a/web/i18n/fa-IR/dataset-creation.ts b/web/i18n/fa-IR/dataset-creation.ts index 0ca51ef534..55eae67875 100644 --- a/web/i18n/fa-IR/dataset-creation.ts +++ b/web/i18n/fa-IR/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'اجرا', firecrawlTitle: 'استخراج محتوای وب با fireFirecrawl', firecrawlDoc: 'مستندات Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'گزینهها', crawlSubPage: 'خزش صفحات فرعی', limit: 'محدودیت', diff --git a/web/i18n/fr-FR/dataset-creation.ts b/web/i18n/fr-FR/dataset-creation.ts index e7357749c4..50d750aa44 100644 --- a/web/i18n/fr-FR/dataset-creation.ts +++ b/web/i18n/fr-FR/dataset-creation.ts @@ -61,7 +61,7 @@ const translation = { preview: 'Aperçu', crawlSubPage: 'Explorer les sous-pages', configure: 'Configurer', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', maxDepth: 'Profondeur maximale', fireCrawlNotConfigured: 'Firecrawl n’est pas configuré', firecrawlTitle: 'Extraire du contenu web avec 🔥Firecrawl', diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index c5cecc1052..dab3229fe2 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -476,7 +476,7 @@ const translation = { title: 'एपीआई एक्सटेंशन केंद्रीकृत एपीआई प्रबंधन प्रदान करते हैं, जो Dify के अनुप्रयोगों में आसान उपयोग के लिए कॉन्फ़िगरेशन को सरल बनाते हैं।', link: 'अपना खुद का एपीआई एक्सटेंशन कैसे विकसित करें, यह जानें।', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'एपीआई एक्सटेंशन जोड़ें', selector: { title: 'एपीआई एक्सटेंशन', diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts index cc8129625a..a413a9ef68 100644 --- a/web/i18n/it-IT/common.ts +++ b/web/i18n/it-IT/common.ts @@ -483,7 +483,7 @@ const translation = { title: '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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Aggiungi Estensione API', selector: { title: 'Estensione API', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 9f480c5af6..c6163ec5cd 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { title: 'API拡張機能は、Difyのアプリケーション全体での簡単な使用のための設定を簡素化し、集中的なAPI管理を提供します。', link: '独自のAPI拡張機能を開発する方法について学ぶ。', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API拡張機能を追加', selector: { title: 'API拡張機能', diff --git a/web/i18n/ja-JP/dataset-creation.ts b/web/i18n/ja-JP/dataset-creation.ts index 3de5b4c615..3cd856d134 100644 --- a/web/i18n/ja-JP/dataset-creation.ts +++ b/web/i18n/ja-JP/dataset-creation.ts @@ -72,7 +72,7 @@ const translation = { run: '実行', firecrawlTitle: '🔥Firecrawlを使っでウエブコンテンツを抽出', firecrawlDoc: 'Firecrawlドキュメント', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'サイト全体をMarkdownに変換する', jinaReaderDoc: 'Jina Readerの詳細', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/ko-KR/common.ts b/web/i18n/ko-KR/common.ts index 8068a76d8e..aff00be97a 100644 --- a/web/i18n/ko-KR/common.ts +++ b/web/i18n/ko-KR/common.ts @@ -451,7 +451,7 @@ const translation = { apiBasedExtension: { title: 'API 기반 확장은 Dify 애플리케이션 전체에서 간편한 사용을 위한 설정을 단순화하고 집중적인 API 관리를 제공합니다.', link: '사용자 정의 API 기반 확장을 개발하는 방법 배우기', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API 기반 확장 추가', selector: { title: 'API 기반 확장', diff --git a/web/i18n/ko-KR/dataset-creation.ts b/web/i18n/ko-KR/dataset-creation.ts index 33ca624307..10385bbb71 100644 --- a/web/i18n/ko-KR/dataset-creation.ts +++ b/web/i18n/ko-KR/dataset-creation.ts @@ -52,7 +52,7 @@ const translation = { failed: '생성에 실패했습니다', }, website: { - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', limit: '한계', options: '옵션', firecrawlDoc: 'Firecrawl 문서', diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts index c8b0b79257..b0ef026f5e 100644 --- a/web/i18n/pl-PL/common.ts +++ b/web/i18n/pl-PL/common.ts @@ -469,7 +469,7 @@ const translation = { title: '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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj rozszerzenie interfejsu API', selector: { title: 'Rozszerzenie interfejsu API', diff --git a/web/i18n/pl-PL/dataset-creation.ts b/web/i18n/pl-PL/dataset-creation.ts index 98c0613a30..4073fdd681 100644 --- a/web/i18n/pl-PL/dataset-creation.ts +++ b/web/i18n/pl-PL/dataset-creation.ts @@ -54,7 +54,7 @@ const translation = { }, website: { limit: 'Ograniczać', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', firecrawlDoc: 'Dokumentacja Firecrawl', unknownError: 'Nieznany błąd', fireCrawlNotConfiguredDescription: 'Skonfiguruj Firecrawl z kluczem API, aby z niego korzystać.', diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts index 180bcbb4da..eb92d9ab1d 100644 --- a/web/i18n/pt-BR/common.ts +++ b/web/i18n/pt-BR/common.ts @@ -455,7 +455,7 @@ const translation = { 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.', link: 'Saiba como desenvolver sua própria Extensão de API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adicionar Extensão de API', selector: { title: 'Extensão de API', diff --git a/web/i18n/pt-BR/dataset-creation.ts b/web/i18n/pt-BR/dataset-creation.ts index a3949f484b..d4315e6866 100644 --- a/web/i18n/pt-BR/dataset-creation.ts +++ b/web/i18n/pt-BR/dataset-creation.ts @@ -58,7 +58,7 @@ const translation = { crawlSubPage: 'Rastrear subpáginas', selectAll: 'Selecionar tudo', resetAll: 'Redefinir tudo', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', includeOnlyPaths: 'Incluir apenas caminhos', configure: 'Configurar', limit: 'Limite', diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index ad000e26c4..e755d59354 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -455,7 +455,7 @@ const translation = { 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.', link: 'Aflați cum să dezvoltați propria extensie bazată pe API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adăugați extensie API', selector: { title: 'Extensie API', diff --git a/web/i18n/ro-RO/dataset-creation.ts b/web/i18n/ro-RO/dataset-creation.ts index 3587070999..2c92d8417c 100644 --- a/web/i18n/ro-RO/dataset-creation.ts +++ b/web/i18n/ro-RO/dataset-creation.ts @@ -65,7 +65,7 @@ const translation = { firecrawlTitle: 'Extrageți conținut web cu 🔥Firecrawl', unknownError: 'Eroare necunoscută', scrapTimeInfo: 'Pagini răzuite {{total}} în total în {{timp}}s', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', excludePaths: 'Excluderea căilor', resetAll: 'Resetați toate', extractOnlyMainContent: 'Extrageți doar conținutul principal (fără anteturi, navigări, subsoluri etc.)', diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts index d419bcc97e..de925d1742 100644 --- a/web/i18n/ru-RU/common.ts +++ b/web/i18n/ru-RU/common.ts @@ -459,7 +459,7 @@ const translation = { apiBasedExtension: { title: 'API-расширения обеспечивают централизованное управление API, упрощая настройку для удобного использования в приложениях Dify.', link: 'Узнайте, как разработать собственное API-расширение.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Добавить API Extension', selector: { title: 'API Extension', diff --git a/web/i18n/ru-RU/dataset-creation.ts b/web/i18n/ru-RU/dataset-creation.ts index 765bb88497..0b7b203a28 100644 --- a/web/i18n/ru-RU/dataset-creation.ts +++ b/web/i18n/ru-RU/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Запустить', firecrawlTitle: 'Извлечь веб-контент с помощью 🔥Firecrawl', firecrawlDoc: 'Документация Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Опции', crawlSubPage: 'Сканировать подстраницы', limit: 'Лимит', diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts index 1167f33697..ce80c8a086 100644 --- a/web/i18n/sl-SI/common.ts +++ b/web/i18n/sl-SI/common.ts @@ -452,7 +452,7 @@ const translation = { apiBasedExtension: { 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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj API razširitev', selector: { title: 'API razširitev', @@ -681,7 +681,7 @@ const translation = { type: 'Vrsta', 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.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodajanje razširitve API-ja', }, about: { diff --git a/web/i18n/sl-SI/dataset-creation.ts b/web/i18n/sl-SI/dataset-creation.ts index e675c61813..8f3a67d7d1 100644 --- a/web/i18n/sl-SI/dataset-creation.ts +++ b/web/i18n/sl-SI/dataset-creation.ts @@ -71,7 +71,7 @@ const translation = { run: 'Zaženi', firecrawlTitle: 'Izvleci spletno vsebino z 🔥Firecrawl', firecrawlDoc: 'Firecrawl dokumentacija', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + 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', jinaReaderDoc: 'Več o Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index be1f62cdd7..fca1dc428c 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -454,7 +454,7 @@ const translation = { apiBasedExtension: { title: 'ส่วนขยาย API ให้การจัดการ API แบบรวมศูนย์ ทําให้การกําหนดค่าง่ายขึ้นเพื่อให้ใช้งานได้ง่ายในแอปพลิเคชันของ Dify', link: 'เรียนรู้วิธีพัฒนาส่วนขยาย API ของคุณเอง', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', add: 'เพิ่มส่วนขยาย API', selector: { title: 'ส่วนขยาย API', diff --git a/web/i18n/th-TH/dataset-creation.ts b/web/i18n/th-TH/dataset-creation.ts index 4be2250184..dd33e65e47 100644 --- a/web/i18n/th-TH/dataset-creation.ts +++ b/web/i18n/th-TH/dataset-creation.ts @@ -71,7 +71,7 @@ const translation = { run: 'วิ่ง', firecrawlTitle: 'แยกเนื้อหาเว็บด้วย 🔥Firecrawl', firecrawlDoc: 'เอกสาร Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'แปลงทั้งไซต์เป็น Markdown', jinaReaderDoc: 'เรียนรู้เพิ่มเติมเกี่ยวกับ Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts index 9dd2f2dd7e..d66c226aa6 100644 --- a/web/i18n/tr-TR/common.ts +++ b/web/i18n/tr-TR/common.ts @@ -459,7 +459,7 @@ const translation = { 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.', link: 'Kendi API Uzantınızı nasıl geliştireceğinizi öğrenin.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API Uzantısı Ekle', selector: { title: 'API Uzantısı', diff --git a/web/i18n/tr-TR/dataset-creation.ts b/web/i18n/tr-TR/dataset-creation.ts index 0394a4816a..e672433786 100644 --- a/web/i18n/tr-TR/dataset-creation.ts +++ b/web/i18n/tr-TR/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { run: 'Çalıştır', firecrawlTitle: '🔥Firecrawl ile web içeriğini çıkarın', firecrawlDoc: 'Firecrawl dokümanları', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Seçenekler', crawlSubPage: 'Alt sayfaları tarayın', limit: 'Sınır', diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts index dd6dab61cc..7416e30594 100644 --- a/web/i18n/uk-UA/common.ts +++ b/web/i18n/uk-UA/common.ts @@ -456,7 +456,7 @@ const translation = { apiBasedExtension: { title: 'API-розширення забезпечують централізоване керування API, спрощуючи конфігурацію для зручного використання в різних програмах Dify.', link: 'Дізнайтеся, як розробити власне розширення API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Додати розширення API', selector: { title: 'Розширення API', diff --git a/web/i18n/uk-UA/dataset-creation.ts b/web/i18n/uk-UA/dataset-creation.ts index 96af3bbd1e..9d924629e6 100644 --- a/web/i18n/uk-UA/dataset-creation.ts +++ b/web/i18n/uk-UA/dataset-creation.ts @@ -60,7 +60,7 @@ const translation = { unknownError: 'Невідома помилка', maxDepth: 'Максимальна глибина', crawlSubPage: 'Сканування підсторінок', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: 'Попередній перегляд', fireCrawlNotConfigured: 'Firecrawl не налаштовано', includeOnlyPaths: 'Включати лише контури', diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts index a047fb6c9f..2ce9c3c41a 100644 --- a/web/i18n/vi-VN/common.ts +++ b/web/i18n/vi-VN/common.ts @@ -455,7 +455,7 @@ const translation = { 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.', 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/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Thêm Phần mở rộng API', selector: { title: 'Phần mở rộng API', diff --git a/web/i18n/vi-VN/dataset-creation.ts b/web/i18n/vi-VN/dataset-creation.ts index 8acaf329b2..071c1e3d13 100644 --- a/web/i18n/vi-VN/dataset-creation.ts +++ b/web/i18n/vi-VN/dataset-creation.ts @@ -63,7 +63,7 @@ const translation = { 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.)', exceptionErrorTitle: 'Một ngoại lệ xảy ra trong khi chạy tác vụ Firecrawl:', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + 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ả', firecrawlTitle: 'Trích xuất nội dung web bằng 🔥Firecrawl', totalPageScraped: 'Tổng số trang được cạo:', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 8ed1e28fd8..327f41ea05 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -475,7 +475,7 @@ const translation = { apiBasedExtension: { title: 'API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify 上的各类应用中直接使用。', link: '了解如何开发您自己的 API 扩展。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/extension/api-based-extension', add: '新增 API 扩展', selector: { title: 'API 扩展', diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts index 6a91b1b996..e3ca478f40 100644 --- a/web/i18n/zh-Hans/dataset-creation.ts +++ b/web/i18n/zh-Hans/dataset-creation.ts @@ -79,7 +79,7 @@ const translation = { run: '运行', firecrawlTitle: '使用 🔥Firecrawl 提取网页内容', firecrawlDoc: 'Firecrawl 文档', - firecrawlDocLink: 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: '将整个站点内容转换为 Markdown 格式', jinaReaderDoc: '了解更多关于 Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 18f42a3b93..cf15a00d61 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -455,7 +455,7 @@ const translation = { apiBasedExtension: { title: 'API 擴充套件提供了一個集中式的 API 管理,在此統一新增 API 配置後,方便在 Dify 上的各類應用中直接使用。', link: '瞭解如何開發您自己的 API 擴充套件。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/tools/extensions/api-based/api-based-extension', add: '新增 API 擴充套件', selector: { title: 'API 擴充套件', diff --git a/web/i18n/zh-Hant/dataset-creation.ts b/web/i18n/zh-Hant/dataset-creation.ts index 9f5186d024..61737775dc 100644 --- a/web/i18n/zh-Hant/dataset-creation.ts +++ b/web/i18n/zh-Hant/dataset-creation.ts @@ -61,7 +61,7 @@ const translation = { fireCrawlNotConfiguredDescription: '使用 API 金鑰配置 Firecrawl 以使用它。', limit: '限制', crawlSubPage: '抓取子頁面', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: '預覽', configure: '配置', excludePaths: '排除路徑', From 8de24bc16e9deff497132760a88937ea4d9f19ba Mon Sep 17 00:00:00 2001 From: Rajhans Jadhao Date: Tue, 6 May 2025 14:32:40 +0530 Subject: [PATCH 57/89] chore: enhance dev script robustness by determining the script directory (#19209) --- dev/mypy-check | 3 +++ dev/pytest/pytest_all_tests.sh | 3 +++ dev/pytest/pytest_artifacts.sh | 3 +++ dev/pytest/pytest_model_runtime.sh | 3 +++ dev/pytest/pytest_tools.sh | 3 +++ dev/pytest/pytest_unit_tests.sh | 3 +++ dev/pytest/pytest_vdb.sh | 3 +++ dev/pytest/pytest_workflow.sh | 3 +++ dev/reformat | 3 +++ dev/sync-uv | 3 +++ 10 files changed, 30 insertions(+) diff --git a/dev/mypy-check b/dev/mypy-check index 24cfb06824..c043faffe6 100755 --- a/dev/mypy-check +++ b/dev/mypy-check @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run mypy checks uv run --directory api --dev --with pip \ python -m mypy --install-types --non-interactive --cache-fine-grained --sqlite-cache . diff --git a/dev/pytest/pytest_all_tests.sh b/dev/pytest/pytest_all_tests.sh index f0c8a78548..30898b4fcf 100755 --- a/dev/pytest/pytest_all_tests.sh +++ b/dev/pytest/pytest_all_tests.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # ModelRuntime dev/pytest/pytest_model_runtime.sh diff --git a/dev/pytest/pytest_artifacts.sh b/dev/pytest/pytest_artifacts.sh index d52acb2273..3086ef5cc4 100755 --- a/dev/pytest/pytest_artifacts.sh +++ b/dev/pytest/pytest_artifacts.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/artifact_tests/ diff --git a/dev/pytest/pytest_model_runtime.sh b/dev/pytest/pytest_model_runtime.sh index dc6c6ac627..2cbbbbfd81 100755 --- a/dev/pytest/pytest_model_runtime.sh +++ b/dev/pytest/pytest_model_runtime.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/model_runtime/anthropic \ api/tests/integration_tests/model_runtime/azure_openai \ api/tests/integration_tests/model_runtime/openai api/tests/integration_tests/model_runtime/chatglm \ diff --git a/dev/pytest/pytest_tools.sh b/dev/pytest/pytest_tools.sh index 0b1a8c9877..d10934626f 100755 --- a/dev/pytest/pytest_tools.sh +++ b/dev/pytest/pytest_tools.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/tools diff --git a/dev/pytest/pytest_unit_tests.sh b/dev/pytest/pytest_unit_tests.sh index 2075596b7f..1a1819ca28 100755 --- a/dev/pytest/pytest_unit_tests.sh +++ b/dev/pytest/pytest_unit_tests.sh @@ -1,5 +1,8 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # libs pytest api/tests/unit_tests diff --git a/dev/pytest/pytest_vdb.sh b/dev/pytest/pytest_vdb.sh index dd03ca3514..7f617a9c05 100755 --- a/dev/pytest/pytest_vdb.sh +++ b/dev/pytest/pytest_vdb.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/vdb/chroma \ api/tests/integration_tests/vdb/milvus \ api/tests/integration_tests/vdb/pgvecto_rs \ diff --git a/dev/pytest/pytest_workflow.sh b/dev/pytest/pytest_workflow.sh index db8fdb2fb9..b63d49069f 100755 --- a/dev/pytest/pytest_workflow.sh +++ b/dev/pytest/pytest_workflow.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/workflow diff --git a/dev/reformat b/dev/reformat index 53d7703fce..71cb6abb1e 100755 --- a/dev/reformat +++ b/dev/reformat @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run ruff linter uv run --directory api --dev ruff check --fix ./ diff --git a/dev/sync-uv b/dev/sync-uv index 7bc3bb22be..67a8133f5a 100755 --- a/dev/sync-uv +++ b/dev/sync-uv @@ -6,5 +6,8 @@ if ! command -v uv &> /dev/null; then pip install uv fi +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # check uv.lock in sync with pyproject.toml uv lock --project api From 9565fe9b1b4246a3b0d49ed8f4cd2f6298adc406 Mon Sep 17 00:00:00 2001 From: QuantumGhost Date: Tue, 6 May 2025 18:05:19 +0800 Subject: [PATCH 58/89] fix(api): fix alembic offline mode (#19285) Alembic's offline mode generates SQL from SQLAlchemy migration operations, providing developers with a clear view of database schema changes without requiring an active database connection. However, some migration versions (specifically bbadea11becb and d7999dfa4aae) were performing database schema introspection, which fails in offline mode since it requires an actual database connection. This commit: - Adds offline mode support by detecting context.is_offline_mode() - Skips introspection steps when in offline mode - Adds warning messages in SQL output to inform users that assumptions were made - Prompts users to review the generated SQL for accuracy These changes ensure migrations work consistently in both online and offline modes. Close #19284. --- ...a11becb_add_name_and_size_to_tool_files.py | 56 ++++++++++++------- ..._remove_workflow_node_executions_retry_.py | 32 +++++++---- api/models/tools.py | 36 ++---------- 3 files changed, 61 insertions(+), 63 deletions(-) diff --git a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py index 5b5656e7ed..00f2b15802 100644 --- a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py +++ b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py @@ -5,45 +5,61 @@ Revises: 33f5fac87f29 Create Date: 2024-10-10 05:16:14.764268 """ -from alembic import op -import models as models + import sqlalchemy as sa -from sqlalchemy.dialects import postgresql +from alembic import op, context # revision identifiers, used by Alembic. -revision = 'bbadea11becb' -down_revision = 'd8e744d88ed6' +revision = "bbadea11becb" +down_revision = "d8e744d88ed6" branch_labels = None depends_on = None def upgrade(): + def _has_name_or_size_column() -> bool: + # We cannot access the database in offline mode, so assume + # the "name" and "size" columns do not exist. + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + "-- Executing in offline mode, assuming the name and size columns do not exist.\n" + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + + return False + # Use SQLAlchemy inspector to get the columns of the 'tool_files' table + inspector = sa.inspect(conn) + columns = [col["name"] for col in inspector.get_columns("tool_files")] + + # If 'name' or 'size' columns already exist, exit the upgrade function + if "name" in columns or "size" in columns: + return True + return False + # ### commands auto generated by Alembic - please adjust! ### # Get the database connection conn = op.get_bind() - # Use SQLAlchemy inspector to get the columns of the 'tool_files' table - inspector = sa.inspect(conn) - columns = [col['name'] for col in inspector.get_columns('tool_files')] - - # If 'name' or 'size' columns already exist, exit the upgrade function - if 'name' in columns or 'size' in columns: + if _has_name_or_size_column(): return - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.add_column(sa.Column('name', sa.String(), nullable=True)) - batch_op.add_column(sa.Column('size', sa.Integer(), nullable=True)) + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.add_column(sa.Column("name", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("size", sa.Integer(), nullable=True)) op.execute("UPDATE tool_files SET name = '' WHERE name IS NULL") op.execute("UPDATE tool_files SET size = -1 WHERE size IS NULL") - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.alter_column('name', existing_type=sa.String(), nullable=False) - batch_op.alter_column('size', existing_type=sa.Integer(), nullable=False) + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.alter_column("name", existing_type=sa.String(), nullable=False) + batch_op.alter_column("size", existing_type=sa.Integer(), nullable=False) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.drop_column('size') - batch_op.drop_column('name') + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.drop_column("size") + batch_op.drop_column("name") # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py index 07454b0917..adf6421e57 100644 --- a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py +++ b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py @@ -5,28 +5,38 @@ Revises: e1944c35e15e Create Date: 2024-12-23 11:54:15.344543 """ -from alembic import op -import models as models -import sqlalchemy as sa + +from alembic import op, context from sqlalchemy import inspect # revision identifiers, used by Alembic. -revision = 'd7999dfa4aae' -down_revision = 'e1944c35e15e' +revision = "d7999dfa4aae" +down_revision = "e1944c35e15e" branch_labels = None depends_on = None def upgrade(): - # Check if column exists before attempting to remove it - conn = op.get_bind() - inspector = inspect(conn) - has_column = 'retry_index' in [col['name'] for col in inspector.get_columns('workflow_node_executions')] + def _has_retry_index_column() -> bool: + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + '-- Executing in offline mode: assuming the "retry_index" column does not exist.\n' + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + return False + conn = op.get_bind() + inspector = inspect(conn) + return "retry_index" in [col["name"] for col in inspector.get_columns("workflow_node_executions")] + + has_column = _has_retry_index_column() if has_column: - with op.batch_alter_table('workflow_node_executions', schema=None) as batch_op: - batch_op.drop_column('retry_index') + with op.batch_alter_table("workflow_node_executions", schema=None) as batch_op: + batch_op.drop_column("retry_index") def downgrade(): diff --git a/api/models/tools.py b/api/models/tools.py index 05604b9330..e027475e38 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Any, Optional, cast +from typing import Any, cast import sqlalchemy as sa from deprecated import deprecated @@ -304,8 +304,11 @@ class DeprecatedPublishedAppTool(Base): db.UniqueConstraint("app_id", "user_id", name="unique_published_app_tool"), ) + id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) # id of the app app_id = db.Column(StringUUID, ForeignKey("apps.id"), nullable=False) + + user_id: Mapped[str] = db.Column(StringUUID, nullable=False) # who published this tool description = db.Column(db.Text, nullable=False) # llm_description of the tool, for LLM @@ -325,34 +328,3 @@ class DeprecatedPublishedAppTool(Base): @property def description_i18n(self) -> I18nObject: return I18nObject(**json.loads(self.description)) - - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - user_id: Mapped[str] = db.Column(StringUUID, nullable=False) - tenant_id: Mapped[str] = db.Column(StringUUID, nullable=False) - conversation_id: Mapped[Optional[str]] = db.Column(StringUUID, nullable=True) - file_key: Mapped[str] = db.Column(db.String(255), nullable=False) - mimetype: Mapped[str] = db.Column(db.String(255), nullable=False) - original_url: Mapped[Optional[str]] = db.Column(db.String(2048), nullable=True) - name: Mapped[str] = mapped_column(default="") - size: Mapped[int] = mapped_column(default=-1) - - def __init__( - self, - *, - user_id: str, - tenant_id: str, - conversation_id: Optional[str] = None, - file_key: str, - mimetype: str, - original_url: Optional[str] = None, - name: str, - size: int, - ): - self.user_id = user_id - self.tenant_id = tenant_id - self.conversation_id = conversation_id - self.file_key = file_key - self.mimetype = mimetype - self.original_url = original_url - self.name = name - self.size = size From a6827493f0e9a4a2f8470c717e7f13f7b15c6f67 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 6 May 2025 18:24:10 +0800 Subject: [PATCH 59/89] chore: slice workflow refresh draft hook (#19292) --- .../workflow-app/components/workflow-main.tsx | 4 +++ .../components/workflow-app/hooks/index.ts | 1 + .../hooks/use-nodes-sync-draft.ts | 4 +-- .../hooks/use-workflow-refresh-draft.ts | 36 +++++++++++++++++++ .../components/workflow/hooks-store/store.ts | 3 ++ web/app/components/workflow/hooks/index.ts | 1 + .../hooks/use-workflow-interactions.ts | 25 ------------- .../hooks/use-workflow-refresh-draft.ts | 9 +++++ web/app/components/workflow/index.tsx | 4 +-- 9 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts create mode 100644 web/app/components/workflow/hooks/use-workflow-refresh-draft.ts diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 4ff1f4c624..2f2295cb59 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -8,6 +8,7 @@ import type { WorkflowProps } from '@/app/components/workflow' import WorkflowChildren from './workflow-children' import { useNodesSyncDraft, + useWorkflowRefreshDraft, useWorkflowRun, useWorkflowStartRun, } from '../hooks' @@ -32,6 +33,7 @@ const WorkflowMain = ({ doSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, } = useNodesSyncDraft() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const { handleBackupDraft, handleLoadBackupDraft, @@ -49,6 +51,7 @@ const WorkflowMain = ({ return { syncWorkflowDraftWhenPageClose, doSyncWorkflowDraft, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, @@ -61,6 +64,7 @@ const WorkflowMain = ({ }, [ syncWorkflowDraftWhenPageClose, doSyncWorkflowDraft, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index 1517eb9a16..6373a8591c 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -4,3 +4,4 @@ export * from './use-nodes-sync-draft' export * from './use-workflow-run' export * from './use-workflow-start-run' export * from './use-is-chat-mode' +export * from './use-workflow-refresh-draft' diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 7c6eb6a5be..db21cfb05e 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -6,20 +6,20 @@ import { useWorkflowStore, } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' -import { useWorkflowUpdate } from '@/app/components/workflow/hooks' import { useNodesReadOnly, } from '@/app/components/workflow/hooks/use-workflow' import { syncWorkflowDraft } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { API_PREFIX } from '@/config' +import { useWorkflowRefreshDraft } from '.' export const useNodesSyncDraft = () => { const store = useStoreApi() const workflowStore = useWorkflowStore() const featuresStore = useFeaturesStore() const { getNodesReadOnly } = useNodesReadOnly() - const { handleRefreshWorkflowDraft } = useWorkflowUpdate() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const params = useParams() const getPostParams = useCallback(() => { diff --git a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts new file mode 100644 index 0000000000..c944e10c4c --- /dev/null +++ b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts @@ -0,0 +1,36 @@ +import { useCallback } from 'react' +import { useWorkflowStore } from '@/app/components/workflow/store' +import { fetchWorkflowDraft } from '@/service/workflow' +import type { WorkflowDataUpdater } from '@/app/components/workflow/types' +import { useWorkflowUpdate } from '@/app/components/workflow/hooks' + +export const useWorkflowRefreshDraft = () => { + const workflowStore = useWorkflowStore() + const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() + + const handleRefreshWorkflowDraft = useCallback(() => { + const { + appId, + setSyncWorkflowDraftHash, + setIsSyncingWorkflowDraft, + setEnvironmentVariables, + setEnvSecrets, + setConversationVariables, + } = workflowStore.getState() + setIsSyncingWorkflowDraft(true) + fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { + handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) + setSyncWorkflowDraftHash(response.hash) + setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { + acc[env.id] = env.value + return acc + }, {} as Record)) + setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) + setConversationVariables(response.conversation_variables || []) + }).finally(() => setIsSyncingWorkflowDraft(false)) + }, [handleUpdateWorkflowCanvas, workflowStore]) + + return { + handleRefreshWorkflowDraft, + } +} diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 2e40cbfbc9..9f5e1a6650 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -18,6 +18,7 @@ type CommonHooksFnMap = { } ) => Promise syncWorkflowDraftWhenPageClose: () => void + handleRefreshWorkflowDraft: () => void handleBackupDraft: () => void handleLoadBackupDraft: () => void handleRestoreFromPublishedWorkflow: (...args: any[]) => void @@ -35,6 +36,7 @@ export type Shape = { export const createHooksStore = ({ doSyncWorkflowDraft = async () => noop(), syncWorkflowDraftWhenPageClose = noop, + handleRefreshWorkflowDraft = noop, handleBackupDraft = noop, handleLoadBackupDraft = noop, handleRestoreFromPublishedWorkflow = noop, @@ -48,6 +50,7 @@ export const createHooksStore = ({ refreshAll: props => set(state => ({ ...state, ...props })), doSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index 20a34c69e3..fda0f50aa6 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -16,3 +16,4 @@ export * from './use-shortcuts' export * from './use-workflow-interactions' export * from './use-workflow-mode' export * from './use-format-time-from-now' +export * from './use-workflow-refresh-draft' diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index 740868c594..636d3b94f9 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -313,7 +313,6 @@ export const useWorkflowZoom = () => { export const useWorkflowUpdate = () => { const reactflow = useReactFlow() - const workflowStore = useWorkflowStore() const { eventEmitter } = useEventEmitterContextContext() const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdater) => { @@ -333,32 +332,8 @@ export const useWorkflowUpdate = () => { setViewport(viewport) }, [eventEmitter, reactflow]) - const handleRefreshWorkflowDraft = useCallback(() => { - const { - appId, - setSyncWorkflowDraftHash, - setIsSyncingWorkflowDraft, - setEnvironmentVariables, - setEnvSecrets, - setConversationVariables, - } = workflowStore.getState() - setIsSyncingWorkflowDraft(true) - fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { - handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) - setSyncWorkflowDraftHash(response.hash) - setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value - return acc - }, {} as Record)) - setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) - // #TODO chatVar sync# - setConversationVariables(response.conversation_variables || []) - }).finally(() => setIsSyncingWorkflowDraft(false)) - }, [handleUpdateWorkflowCanvas, workflowStore]) - return { handleUpdateWorkflowCanvas, - handleRefreshWorkflowDraft, } } diff --git a/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts new file mode 100644 index 0000000000..1948bd471d --- /dev/null +++ b/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts @@ -0,0 +1,9 @@ +import { useHooksStore } from '@/app/components/workflow/hooks-store' + +export const useWorkflowRefreshDraft = () => { + const handleRefreshWorkflowDraft = useHooksStore(s => s.handleRefreshWorkflowDraft) + + return { + handleRefreshWorkflowDraft, + } +} diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 3d7692ed7b..549117faf7 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -44,7 +44,7 @@ import { useShortcuts, useWorkflow, useWorkflowReadOnly, - useWorkflowUpdate, + useWorkflowRefreshDraft, } from './hooks' import CustomNode from './nodes' import CustomNoteNode from './note-node' @@ -160,7 +160,7 @@ export const Workflow: FC = memo(({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const { handleRefreshWorkflowDraft } = useWorkflowUpdate() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { if (document.visibilityState === 'hidden') syncWorkflowDraftWhenPageClose() From f23cf983173b5768185035f72f7119b5cf11a473 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 6 May 2025 21:14:51 +0800 Subject: [PATCH 60/89] refactor: Remove RepositoryFactory (#19176) Signed-off-by: -LAN- --- api/app_factory.py | 2 - .../app/apps/advanced_chat/app_generator.py | 32 +- .../advanced_chat/generate_task_pipeline.py | 6 +- api/core/app/apps/workflow/app_generator.py | 34 +- .../easy_ui_based_generate_task_pipeline.py | 2 +- api/core/base/__init__.py | 1 + api/core/base/tts/__init__.py | 6 + .../tts}/app_generator_tts_publisher.py | 0 api/core/ops/langfuse_trace/langfuse_trace.py | 6 +- .../ops/langsmith_trace/langsmith_trace.py | 10 +- api/core/ops/opik_trace/opik_trace.py | 10 +- api/core/repositories/__init__.py | 6 + api/core/repositories/repository_registry.py | 87 ----- ...emy_workflow_node_execution_repository.py} | 4 +- .../workflow_node_execution/__init__.py | 9 - api/core/workflow/repository/__init__.py | 5 +- .../workflow/repository/repository_factory.py | 97 ----- .../workflow_app_generate_task_pipeline.py} | 6 +- .../workflow_cycle_manager.py} | 2 +- api/extensions/ext_repositories.py | 18 - api/services/workflow_run_service.py | 10 +- api/services/workflow_service.py | 10 +- api/tasks/remove_app_and_related_data_task.py | 10 +- .../workflow/test_workflow_cycle_manager.py | 348 ++++++++++++++++++ .../test_sqlalchemy_repository.py | 10 +- 25 files changed, 423 insertions(+), 308 deletions(-) create mode 100644 api/core/base/__init__.py create mode 100644 api/core/base/tts/__init__.py rename api/core/{app/apps/advanced_chat => base/tts}/app_generator_tts_publisher.py (100%) delete mode 100644 api/core/repositories/repository_registry.py rename api/core/repositories/{workflow_node_execution/sqlalchemy_repository.py => sqlalchemy_workflow_node_execution_repository.py} (98%) delete mode 100644 api/core/repositories/workflow_node_execution/__init__.py delete mode 100644 api/core/workflow/repository/repository_factory.py rename api/core/{app/apps/workflow/generate_task_pipeline.py => workflow/workflow_app_generate_task_pipeline.py} (99%) rename api/core/{app/task_pipeline/workflow_cycle_manage.py => workflow/workflow_cycle_manager.py} (99%) delete mode 100644 api/extensions/ext_repositories.py create mode 100644 api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py diff --git a/api/app_factory.py b/api/app_factory.py index 586f2ded9e..1c886ac5c7 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -54,7 +54,6 @@ def initialize_extensions(app: DifyApp): ext_otel, ext_proxy_fix, ext_redis, - ext_repositories, ext_sentry, ext_set_secretkey, ext_storage, @@ -75,7 +74,6 @@ def initialize_extensions(app: DifyApp): ext_migrate, ext_redis, ext_storage, - ext_repositories, ext_celery, ext_login, ext_mail, diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index fd0d7fafbd..4b0e64130b 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -25,7 +25,7 @@ from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotA from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager from core.prompt.utils.get_thread_messages_length import get_thread_messages_length -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository from extensions.ext_database import db from factories import file_factory @@ -163,12 +163,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -231,12 +229,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -297,12 +293,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 1f4db54a9c..f71c49d112 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -9,7 +9,6 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AdvancedChatAppGenerateEntity, @@ -58,7 +57,7 @@ from core.app.entities.task_entities import ( ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.message_cycle_manage import MessageCycleManage -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.utils.encoders import jsonable_encoder from core.ops.ops_trace_manager import TraceQueueManager @@ -66,6 +65,7 @@ from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes import NodeType from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager from events.message_event import message_was_created from extensions.ext_database import db from models import Conversation, EndUser, Message, MessageFile @@ -113,7 +113,7 @@ class AdvancedChatAppGenerateTaskPipeline: else: raise NotImplementedError(f"User type not supported: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.QUERY: message.query, diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 9c3d78a338..1d67671974 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -18,13 +18,13 @@ from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.app.apps.workflow.app_queue_manager import WorkflowAppQueueManager from core.app.apps.workflow.app_runner import WorkflowAppRunner from core.app.apps.workflow.generate_response_converter import WorkflowAppGenerateResponseConverter -from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_app_generate_task_pipeline import WorkflowAppGenerateTaskPipeline from extensions.ext_database import db from factories import file_factory from models import Account, App, EndUser, Workflow @@ -138,12 +138,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -264,12 +262,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( @@ -329,12 +325,10 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + tenant_id=application_generate_entity.app_config.tenant_id, + app_id=application_generate_entity.app_config.app_id, ) return self._generate( diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index 8c9c26d36e..a98a42f5df 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -9,7 +9,6 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AgentChatAppGenerateEntity, @@ -45,6 +44,7 @@ from core.app.entities.task_entities import ( ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.message_cycle_manage import MessageCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_manager import ModelInstance from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from core.model_runtime.entities.message_entities import ( diff --git a/api/core/base/__init__.py b/api/core/base/__init__.py new file mode 100644 index 0000000000..3f4bd3b771 --- /dev/null +++ b/api/core/base/__init__.py @@ -0,0 +1 @@ +# Core base package diff --git a/api/core/base/tts/__init__.py b/api/core/base/tts/__init__.py new file mode 100644 index 0000000000..37b6eeebb0 --- /dev/null +++ b/api/core/base/tts/__init__.py @@ -0,0 +1,6 @@ +from core.base.tts.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk + +__all__ = [ + "AppGeneratorTTSPublisher", + "AudioTrunk", +] diff --git a/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py b/api/core/base/tts/app_generator_tts_publisher.py similarity index 100% rename from api/core/app/apps/advanced_chat/app_generator_tts_publisher.py rename to api/core/base/tts/app_generator_tts_publisher.py diff --git a/api/core/ops/langfuse_trace/langfuse_trace.py b/api/core/ops/langfuse_trace/langfuse_trace.py index b229d244f7..c74617e558 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -29,7 +29,7 @@ from core.ops.langfuse_trace.entities.langfuse_trace_entity import ( UnitEnum, ) from core.ops.utils import filter_none_values -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser @@ -113,8 +113,8 @@ class LangFuseDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={"tenant_id": trace_info.tenant_id, "session_factory": session_factory}, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id ) # Get all executions for this workflow run diff --git a/api/core/ops/langsmith_trace/langsmith_trace.py b/api/core/ops/langsmith_trace/langsmith_trace.py index 78a51ff36e..d1e16d3152 100644 --- a/api/core/ops/langsmith_trace/langsmith_trace.py +++ b/api/core/ops/langsmith_trace/langsmith_trace.py @@ -28,7 +28,7 @@ from core.ops.langsmith_trace.entities.langsmith_trace_entity import ( LangSmithRunUpdateModel, ) from core.ops.utils import filter_none_values, generate_dotted_order -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser, MessageFile @@ -137,12 +137,8 @@ class LangSmithDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id, app_id=trace_info.metadata.get("app_id") ) # Get all executions for this workflow run diff --git a/api/core/ops/opik_trace/opik_trace.py b/api/core/ops/opik_trace/opik_trace.py index a14b5afb8e..1484041447 100644 --- a/api/core/ops/opik_trace/opik_trace.py +++ b/api/core/ops/opik_trace/opik_trace.py @@ -22,7 +22,7 @@ from core.ops.entities.trace_entity import ( TraceTaskName, WorkflowTraceInfo, ) -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.model import EndUser, MessageFile @@ -150,12 +150,8 @@ class OpikDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, tenant_id=trace_info.tenant_id, app_id=trace_info.metadata.get("app_id") ) # Get all executions for this workflow run diff --git a/api/core/repositories/__init__.py b/api/core/repositories/__init__.py index 5c70d50cde..6452317120 100644 --- a/api/core/repositories/__init__.py +++ b/api/core/repositories/__init__.py @@ -4,3 +4,9 @@ Repository implementations for data access. This package contains concrete implementations of the repository interfaces defined in the core.workflow.repository package. """ + +from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository + +__all__ = [ + "SQLAlchemyWorkflowNodeExecutionRepository", +] diff --git a/api/core/repositories/repository_registry.py b/api/core/repositories/repository_registry.py deleted file mode 100644 index b66f3ba8e6..0000000000 --- a/api/core/repositories/repository_registry.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Registry for repository implementations. - -This module is responsible for registering factory functions with the repository factory. -""" - -import logging -from collections.abc import Mapping -from typing import Any - -from sqlalchemy.orm import sessionmaker - -from configs import dify_config -from core.repositories.workflow_node_execution import SQLAlchemyWorkflowNodeExecutionRepository -from core.workflow.repository.repository_factory import RepositoryFactory -from extensions.ext_database import db - -logger = logging.getLogger(__name__) - -# Storage type constants -STORAGE_TYPE_RDBMS = "rdbms" -STORAGE_TYPE_HYBRID = "hybrid" - - -def register_repositories() -> None: - """ - Register repository factory functions with the RepositoryFactory. - - This function reads configuration settings to determine which repository - implementations to register. - """ - # Configure WorkflowNodeExecutionRepository factory based on configuration - workflow_node_execution_storage = dify_config.WORKFLOW_NODE_EXECUTION_STORAGE - - # Check storage type and register appropriate implementation - if workflow_node_execution_storage == STORAGE_TYPE_RDBMS: - # Register SQLAlchemy implementation for RDBMS storage - logger.info("Registering WorkflowNodeExecution repository with RDBMS storage") - RepositoryFactory.register_workflow_node_execution_factory(create_workflow_node_execution_repository) - elif workflow_node_execution_storage == STORAGE_TYPE_HYBRID: - # Hybrid storage is not yet implemented - raise NotImplementedError("Hybrid storage for WorkflowNodeExecution repository is not yet implemented") - else: - # Unknown storage type - raise ValueError( - f"Unknown storage type '{workflow_node_execution_storage}' for WorkflowNodeExecution repository. " - f"Supported types: {STORAGE_TYPE_RDBMS}" - ) - - -def create_workflow_node_execution_repository(params: Mapping[str, Any]) -> SQLAlchemyWorkflowNodeExecutionRepository: - """ - Create a WorkflowNodeExecutionRepository instance using SQLAlchemy implementation. - - This factory function creates a repository for the RDBMS storage type. - - Args: - params: Parameters for creating the repository, including: - - tenant_id: Required. The tenant ID for multi-tenancy. - - app_id: Optional. The application ID for filtering. - - session_factory: Optional. A SQLAlchemy sessionmaker instance. If not provided, - a new sessionmaker will be created using the global database engine. - - Returns: - A WorkflowNodeExecutionRepository instance - - Raises: - ValueError: If required parameters are missing - """ - # Extract required parameters - tenant_id = params.get("tenant_id") - if tenant_id is None: - raise ValueError("tenant_id is required for WorkflowNodeExecution repository with RDBMS storage") - - # Extract optional parameters - app_id = params.get("app_id") - - # Use the session_factory from params if provided, otherwise create one using the global db engine - session_factory = params.get("session_factory") - if session_factory is None: - # Create a sessionmaker using the same engine as the global db session - session_factory = sessionmaker(bind=db.engine) - - # Create and return the repository - return SQLAlchemyWorkflowNodeExecutionRepository( - session_factory=session_factory, tenant_id=tenant_id, app_id=app_id - ) diff --git a/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py similarity index 98% rename from api/core/repositories/workflow_node_execution/sqlalchemy_repository.py rename to api/core/repositories/sqlalchemy_workflow_node_execution_repository.py index b1d37163a4..8bf2ab8761 100644 --- a/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py +++ b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py @@ -10,13 +10,13 @@ from sqlalchemy import UnaryExpression, asc, delete, desc, select from sqlalchemy.engine import Engine from sqlalchemy.orm import sessionmaker -from core.workflow.repository.workflow_node_execution_repository import OrderConfig +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionStatus, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) -class SQLAlchemyWorkflowNodeExecutionRepository: +class SQLAlchemyWorkflowNodeExecutionRepository(WorkflowNodeExecutionRepository): """ SQLAlchemy implementation of the WorkflowNodeExecutionRepository interface. diff --git a/api/core/repositories/workflow_node_execution/__init__.py b/api/core/repositories/workflow_node_execution/__init__.py deleted file mode 100644 index 76e8282b7d..0000000000 --- a/api/core/repositories/workflow_node_execution/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -WorkflowNodeExecution repository implementations. -""" - -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository - -__all__ = [ - "SQLAlchemyWorkflowNodeExecutionRepository", -] diff --git a/api/core/workflow/repository/__init__.py b/api/core/workflow/repository/__init__.py index d91506e72f..672abb6583 100644 --- a/api/core/workflow/repository/__init__.py +++ b/api/core/workflow/repository/__init__.py @@ -6,10 +6,9 @@ for accessing and manipulating data, regardless of the underlying storage mechanism. """ -from core.workflow.repository.repository_factory import RepositoryFactory -from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository __all__ = [ - "RepositoryFactory", + "OrderConfig", "WorkflowNodeExecutionRepository", ] diff --git a/api/core/workflow/repository/repository_factory.py b/api/core/workflow/repository/repository_factory.py deleted file mode 100644 index 45d6f5d842..0000000000 --- a/api/core/workflow/repository/repository_factory.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Repository factory for creating repository instances. - -This module provides a simple factory interface for creating repository instances. -It does not contain any implementation details or dependencies on specific repositories. -""" - -from collections.abc import Callable, Mapping -from typing import Any, Literal, Optional, cast - -from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository - -# Type for factory functions - takes a dict of parameters and returns any repository type -RepositoryFactoryFunc = Callable[[Mapping[str, Any]], Any] - -# Type for workflow node execution factory function -WorkflowNodeExecutionFactoryFunc = Callable[[Mapping[str, Any]], WorkflowNodeExecutionRepository] - -# Repository type literals -_RepositoryType = Literal["workflow_node_execution"] - - -class RepositoryFactory: - """ - Factory class for creating repository instances. - - This factory delegates the actual repository creation to implementation-specific - factory functions that are registered with the factory at runtime. - """ - - # Dictionary to store factory functions - _factory_functions: dict[str, RepositoryFactoryFunc] = {} - - @classmethod - def _register_factory(cls, repository_type: _RepositoryType, factory_func: RepositoryFactoryFunc) -> None: - """ - Register a factory function for a specific repository type. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository (e.g., 'workflow_node_execution') - factory_func: A function that takes parameters and returns a repository instance - """ - cls._factory_functions[repository_type] = factory_func - - @classmethod - def _create_repository(cls, repository_type: _RepositoryType, params: Optional[Mapping[str, Any]] = None) -> Any: - """ - Create a new repository instance with the provided parameters. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository to create - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the requested repository - - Raises: - ValueError: If no factory function is registered for the repository type - """ - if repository_type not in cls._factory_functions: - raise ValueError(f"No factory function registered for repository type '{repository_type}'") - - # Use empty dict if params is None - params = params or {} - - return cls._factory_functions[repository_type](params) - - @classmethod - def register_workflow_node_execution_factory(cls, factory_func: WorkflowNodeExecutionFactoryFunc) -> None: - """ - Register a factory function for the workflow node execution repository. - - Args: - factory_func: A function that takes parameters and returns a WorkflowNodeExecutionRepository instance - """ - cls._register_factory("workflow_node_execution", factory_func) - - @classmethod - def create_workflow_node_execution_repository( - cls, params: Optional[Mapping[str, Any]] = None - ) -> WorkflowNodeExecutionRepository: - """ - Create a new WorkflowNodeExecutionRepository instance with the provided parameters. - - Args: - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the WorkflowNodeExecutionRepository - - Raises: - ValueError: If no factory function is registered for the workflow_node_execution repository type - """ - # We can safely cast here because we've registered a WorkflowNodeExecutionFactoryFunc - return cast(WorkflowNodeExecutionRepository, cls._create_repository("workflow_node_execution", params)) diff --git a/api/core/app/apps/workflow/generate_task_pipeline.py b/api/core/workflow/workflow_app_generate_task_pipeline.py similarity index 99% rename from api/core/app/apps/workflow/generate_task_pipeline.py rename to api/core/workflow/workflow_app_generate_task_pipeline.py index 67cad9c998..10a2d8b38b 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/workflow/workflow_app_generate_task_pipeline.py @@ -6,7 +6,6 @@ from typing import Optional, Union from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import ( InvokeFrom, @@ -52,10 +51,11 @@ from core.app.entities.task_entities import ( WorkflowTaskState, ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.ops.ops_trace_manager import TraceQueueManager from core.workflow.enums import SystemVariableKey from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager from extensions.ext_database import db from models.account import Account from models.enums import CreatedByRole @@ -102,7 +102,7 @@ class WorkflowAppGenerateTaskPipeline: else: raise ValueError(f"Invalid user type: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.FILES: application_generate_entity.files, diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/workflow/workflow_cycle_manager.py similarity index 99% rename from api/core/app/task_pipeline/workflow_cycle_manage.py rename to api/core/workflow/workflow_cycle_manager.py index 09e2ee74e6..01d5db4303 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/workflow/workflow_cycle_manager.py @@ -69,7 +69,7 @@ from models.workflow import ( ) -class WorkflowCycleManage: +class WorkflowCycleManager: def __init__( self, *, diff --git a/api/extensions/ext_repositories.py b/api/extensions/ext_repositories.py deleted file mode 100644 index b8cfea121b..0000000000 --- a/api/extensions/ext_repositories.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Extension for initializing repositories. - -This extension registers repository implementations with the RepositoryFactory. -""" - -from core.repositories.repository_registry import register_repositories -from dify_app import DifyApp - - -def init_app(_app: DifyApp) -> None: - """ - Initialize repository implementations. - - Args: - _app: The Flask application instance (unused) - """ - register_repositories() diff --git a/api/services/workflow_run_service.py b/api/services/workflow_run_service.py index f7c4f500a8..6d5b737962 100644 --- a/api/services/workflow_run_service.py +++ b/api/services/workflow_run_service.py @@ -2,7 +2,7 @@ import threading from typing import Optional import contexts -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import OrderConfig from extensions.ext_database import db from libs.infinite_scroll_pagination import InfiniteScrollPagination @@ -129,12 +129,8 @@ class WorkflowRunService: return [] # Use the repository to get the node executions - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=app_model.tenant_id, app_id=app_model.id ) # Use the repository to get the node executions with ordering diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ebe65e5d5f..331dba8bf1 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -11,6 +11,7 @@ from sqlalchemy.orm import Session from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.model_runtime.utils.encoders import jsonable_encoder +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.variables import Variable from core.workflow.entities.node_entities import NodeRunResult from core.workflow.errors import WorkflowNodeRunFailedError @@ -21,7 +22,6 @@ from core.workflow.nodes.enums import ErrorStrategy from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.event.types import NodeEvent from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING -from core.workflow.repository import RepositoryFactory from core.workflow.workflow_entry import WorkflowEntry from events.app_event import app_draft_workflow_was_synced, app_published_workflow_was_updated from extensions.ext_database import db @@ -285,12 +285,8 @@ class WorkflowService: workflow_node_execution.workflow_id = draft_workflow.id # Use the repository to save the workflow node execution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=app_model.tenant_id, app_id=app_model.id ) repository.save(workflow_node_execution) diff --git a/api/tasks/remove_app_and_related_data_task.py b/api/tasks/remove_app_and_related_data_task.py index dedf1c5334..d5a783396a 100644 --- a/api/tasks/remove_app_and_related_data_task.py +++ b/api/tasks/remove_app_and_related_data_task.py @@ -7,7 +7,7 @@ from celery import shared_task # type: ignore from sqlalchemy import delete from sqlalchemy.exc import SQLAlchemyError -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db from models.dataset import AppDatasetJoin from models.model import ( @@ -189,12 +189,8 @@ def _delete_app_workflow_runs(tenant_id: str, app_id: str): def _delete_app_workflow_node_executions(tenant_id: str, app_id: str): # Create a repository instance for WorkflowNodeExecution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": tenant_id, - "app_id": app_id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, tenant_id=tenant_id, app_id=app_id ) # Use the clear method to delete all records for this tenant_id and app_id diff --git a/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py new file mode 100644 index 0000000000..6b00b203c4 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py @@ -0,0 +1,348 @@ +import json +import time +from datetime import UTC, datetime +from unittest.mock import MagicMock, patch + +import pytest +from sqlalchemy.orm import Session + +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom +from core.app.entities.queue_entities import ( + QueueNodeFailedEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, +) +from core.workflow.enums import SystemVariableKey +from core.workflow.nodes import NodeType +from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager +from models.enums import CreatedByRole +from models.workflow import ( + Workflow, + WorkflowNodeExecution, + WorkflowNodeExecutionStatus, + WorkflowRun, + WorkflowRunStatus, +) + + +@pytest.fixture +def mock_app_generate_entity(): + entity = MagicMock(spec=AdvancedChatAppGenerateEntity) + entity.inputs = {"query": "test query"} + entity.invoke_from = InvokeFrom.WEB_APP + # Create app_config as a separate mock + app_config = MagicMock() + app_config.tenant_id = "test-tenant-id" + app_config.app_id = "test-app-id" + entity.app_config = app_config + return entity + + +@pytest.fixture +def mock_workflow_system_variables(): + return { + SystemVariableKey.QUERY: "test query", + SystemVariableKey.CONVERSATION_ID: "test-conversation-id", + SystemVariableKey.USER_ID: "test-user-id", + SystemVariableKey.APP_ID: "test-app-id", + SystemVariableKey.WORKFLOW_ID: "test-workflow-id", + SystemVariableKey.WORKFLOW_RUN_ID: "test-workflow-run-id", + } + + +@pytest.fixture +def mock_node_execution_repository(): + repo = MagicMock(spec=WorkflowNodeExecutionRepository) + repo.get_by_node_execution_id.return_value = None + repo.get_running_executions.return_value = [] + return repo + + +@pytest.fixture +def workflow_cycle_manager(mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository): + return WorkflowCycleManager( + application_generate_entity=mock_app_generate_entity, + workflow_system_variables=mock_workflow_system_variables, + workflow_node_execution_repository=mock_node_execution_repository, + ) + + +@pytest.fixture +def mock_session(): + session = MagicMock(spec=Session) + return session + + +@pytest.fixture +def mock_workflow(): + workflow = MagicMock(spec=Workflow) + workflow.id = "test-workflow-id" + workflow.tenant_id = "test-tenant-id" + workflow.app_id = "test-app-id" + workflow.type = "chat" + workflow.version = "1.0" + workflow.graph = json.dumps({"nodes": [], "edges": []}) + return workflow + + +@pytest.fixture +def mock_workflow_run(): + workflow_run = MagicMock(spec=WorkflowRun) + workflow_run.id = "test-workflow-run-id" + workflow_run.tenant_id = "test-tenant-id" + workflow_run.app_id = "test-app-id" + workflow_run.workflow_id = "test-workflow-id" + workflow_run.status = WorkflowRunStatus.RUNNING + workflow_run.created_by_role = CreatedByRole.ACCOUNT + workflow_run.created_by = "test-user-id" + workflow_run.created_at = datetime.now(UTC).replace(tzinfo=None) + workflow_run.inputs_dict = {"query": "test query"} + workflow_run.outputs_dict = {"answer": "test answer"} + return workflow_run + + +def test_init( + workflow_cycle_manager, mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository +): + """Test initialization of WorkflowCycleManager""" + assert workflow_cycle_manager._workflow_run is None + assert workflow_cycle_manager._workflow_node_executions == {} + assert workflow_cycle_manager._application_generate_entity == mock_app_generate_entity + assert workflow_cycle_manager._workflow_system_variables == mock_workflow_system_variables + assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository + + +def test_handle_workflow_run_start(workflow_cycle_manager, mock_session, mock_workflow): + """Test _handle_workflow_run_start method""" + # Mock session.scalar to return the workflow and max sequence + mock_session.scalar.side_effect = [mock_workflow, 5] + + # Call the method + workflow_run = workflow_cycle_manager._handle_workflow_run_start( + session=mock_session, + workflow_id="test-workflow-id", + user_id="test-user-id", + created_by_role=CreatedByRole.ACCOUNT, + ) + + # Verify the result + assert workflow_run.tenant_id == mock_workflow.tenant_id + assert workflow_run.app_id == mock_workflow.app_id + assert workflow_run.workflow_id == mock_workflow.id + assert workflow_run.sequence_number == 6 # max_sequence + 1 + assert workflow_run.status == WorkflowRunStatus.RUNNING + assert workflow_run.created_by_role == CreatedByRole.ACCOUNT + assert workflow_run.created_by == "test-user-id" + + # Verify session.add was called + mock_session.add.assert_called_once_with(workflow_run) + + +def test_handle_workflow_run_success(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=100, + total_steps=5, + outputs={"answer": "test answer"}, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.SUCCEEDED + assert result.outputs == json.dumps({"answer": "test answer"}) + assert result.total_tokens == 100 + assert result.total_steps == 5 + assert result.finished_at is not None + + +def test_handle_workflow_run_failed(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_failed method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Mock get_running_executions to return an empty list + workflow_cycle_manager._workflow_node_execution_repository.get_running_executions.return_value = [] + + # Call the method + result = workflow_cycle_manager._handle_workflow_run_failed( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=50, + total_steps=3, + status=WorkflowRunStatus.FAILED, + error="Test error message", + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.FAILED.value + assert result.error == "Test error message" + assert result.total_tokens == 50 + assert result.total_steps == 3 + assert result.finished_at is not None + + +def test_handle_node_execution_start(workflow_cycle_manager, mock_workflow_run): + """Test _handle_node_execution_start method""" + # Create a mock event + event = MagicMock(spec=QueueNodeStartedEvent) + event.node_execution_id = "test-node-execution-id" + event.node_id = "test-node-id" + event.node_type = NodeType.LLM + + # Create node_data as a separate mock + node_data = MagicMock() + node_data.title = "Test Node" + event.node_data = node_data + + event.predecessor_node_id = "test-predecessor-node-id" + event.node_run_index = 1 + event.parallel_mode_run_id = "test-parallel-mode-run-id" + event.in_iteration_id = "test-iteration-id" + event.in_loop_id = "test-loop-id" + + # Call the method + result = workflow_cycle_manager._handle_node_execution_start( + workflow_run=mock_workflow_run, + event=event, + ) + + # Verify the result + assert result.tenant_id == mock_workflow_run.tenant_id + assert result.app_id == mock_workflow_run.app_id + assert result.workflow_id == mock_workflow_run.workflow_id + assert result.workflow_run_id == mock_workflow_run.id + assert result.node_execution_id == event.node_execution_id + assert result.node_id == event.node_id + assert result.node_type == event.node_type.value + assert result.title == event.node_data.title + assert result.status == WorkflowNodeExecutionStatus.RUNNING.value + assert result.created_by_role == mock_workflow_run.created_by_role + assert result.created_by == mock_workflow_run.created_by + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result) + + # Verify the node execution was added to the cache + assert workflow_cycle_manager._workflow_node_executions[event.node_execution_id] == result + + +def test_get_workflow_run(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _get_workflow_run method""" + # Mock session.scalar to return the workflow run + mock_session.scalar.return_value = mock_workflow_run + + # Call the method + result = workflow_cycle_manager._get_workflow_run( + session=mock_session, + workflow_run_id="test-workflow-run-id", + ) + + # Verify the result + assert result == mock_workflow_run + assert workflow_cycle_manager._workflow_run == mock_workflow_run + + +def test_handle_workflow_node_execution_success(workflow_cycle_manager): + """Test _handle_workflow_node_execution_success method""" + # Create a mock event + event = MagicMock(spec=QueueNodeSucceededEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + + # Create a mock workflow node execution + node_execution = MagicMock(spec=WorkflowNodeExecution) + node_execution.node_execution_id = "test-node-execution-id" + + # Mock _get_workflow_node_execution to return the mock node execution + with patch.object(workflow_cycle_manager, "_get_workflow_node_execution", return_value=node_execution): + # Call the method + result = workflow_cycle_manager._handle_workflow_node_execution_success( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED.value + assert result.inputs == json.dumps(event.inputs) + assert result.process_data == json.dumps(event.process_data) + assert result.outputs == json.dumps(event.outputs) + assert result.finished_at is not None + assert result.elapsed_time is not None + + # Verify update was called + workflow_cycle_manager._workflow_node_execution_repository.update.assert_called_once_with(node_execution) + + +def test_handle_workflow_run_partial_success(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_partial_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_partial_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=75, + total_steps=4, + outputs={"partial_answer": "test partial answer"}, + exceptions_count=2, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.PARTIAL_SUCCEEDED.value + assert result.outputs == json.dumps({"partial_answer": "test partial answer"}) + assert result.total_tokens == 75 + assert result.total_steps == 4 + assert result.exceptions_count == 2 + assert result.finished_at is not None + + +def test_handle_workflow_node_execution_failed(workflow_cycle_manager): + """Test _handle_workflow_node_execution_failed method""" + # Create a mock event + event = MagicMock(spec=QueueNodeFailedEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + event.error = "Test error message" + + # Create a mock workflow node execution + node_execution = MagicMock(spec=WorkflowNodeExecution) + node_execution.node_execution_id = "test-node-execution-id" + + # Mock _get_workflow_node_execution to return the mock node execution + with patch.object(workflow_cycle_manager, "_get_workflow_node_execution", return_value=node_execution): + # Call the method + result = workflow_cycle_manager._handle_workflow_node_execution_failed( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.FAILED.value + assert result.error == "Test error message" + assert result.inputs == json.dumps(event.inputs) + assert result.process_data == json.dumps(event.process_data) + assert result.outputs == json.dumps(event.outputs) + assert result.finished_at is not None + assert result.elapsed_time is not None + assert result.execution_metadata == json.dumps(event.execution_metadata) + + # Verify update was called + workflow_cycle_manager._workflow_node_execution_repository.update.assert_called_once_with(node_execution) diff --git a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py index c16b453cba..9cda873e90 100644 --- a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py +++ b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py @@ -8,7 +8,7 @@ import pytest from pytest_mock import MockerFixture from sqlalchemy.orm import Session, sessionmaker -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import OrderConfig from models.workflow import WorkflowNodeExecution @@ -80,7 +80,7 @@ def test_get_by_node_execution_id(repository, session, mocker: MockerFixture): """Test get_by_node_execution_id method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -99,7 +99,7 @@ def test_get_by_workflow_run(repository, session, mocker: MockerFixture): """Test get_by_workflow_run method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -120,7 +120,7 @@ def test_get_running_executions(repository, session, mocker: MockerFixture): """Test get_running_executions method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -158,7 +158,7 @@ def test_clear(repository, session, mocker: MockerFixture): """Test clear method.""" session_obj, _ = session # Set up mock - mock_delete = mocker.patch("core.repositories.workflow_node_execution.sqlalchemy_repository.delete") + mock_delete = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.delete") mock_stmt = mocker.MagicMock() mock_delete.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt From c4c20f6ed51e44ecf3de9427506bd2f52f2ec43c Mon Sep 17 00:00:00 2001 From: AichiB7A Date: Wed, 7 May 2025 09:17:26 +0800 Subject: [PATCH 61/89] [Observability] Update counter to include http method and target (#19297) Co-authored-by: QuantumGhost Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/extensions/ext_otel.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/extensions/ext_otel.py b/api/extensions/ext_otel.py index be47fdc6d6..3cbdc8560b 100644 --- a/api/extensions/ext_otel.py +++ b/api/extensions/ext_otel.py @@ -6,6 +6,7 @@ import socket import sys from typing import Union +import flask from celery.signals import worker_init # type: ignore from flask_login import user_loaded_from_request, user_logged_in # type: ignore @@ -27,6 +28,8 @@ def on_user_loaded(_sender, user): def init_app(app: DifyApp): + from opentelemetry.semconv.trace import SpanAttributes + def is_celery_worker(): return "celery" in sys.argv[0].lower() @@ -37,7 +40,9 @@ def init_app(app: DifyApp): def init_flask_instrumentor(app: DifyApp): meter = get_meter("http_metrics", version=dify_config.CURRENT_VERSION) _http_response_counter = meter.create_counter( - "http.server.response.count", description="Total number of HTTP responses by status code", unit="{response}" + "http.server.response.count", + description="Total number of HTTP responses by status code, method and target", + unit="{response}", ) def response_hook(span: Span, status: str, response_headers: list): @@ -50,7 +55,13 @@ def init_app(app: DifyApp): status = status.split(" ")[0] status_code = int(status) status_class = f"{status_code // 100}xx" - _http_response_counter.add(1, {"status_code": status_code, "status_class": status_class}) + attributes: dict[str, str | int] = {"status_code": status_code, "status_class": status_class} + request = flask.request + if request and request.url_rule: + attributes[SpanAttributes.HTTP_TARGET] = str(request.url_rule.rule) + if request and request.method: + attributes[SpanAttributes.HTTP_METHOD] = str(request.method) + _http_response_counter.add(1, attributes) instrumentor = FlaskInstrumentor() if dify_config.DEBUG: From c457e2b67a677af054f3e8b4f6f3243fd6c68953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E9=9C=B2=E5=85=88=E7=94=9F?= Date: Wed, 7 May 2025 09:25:35 +0800 Subject: [PATCH 62/89] clean docker compose env. (#19301) Signed-off-by: zhanluxianshen --- docker/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 7bff2975fb..f1ea72d8cc 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -395,7 +395,7 @@ SUPABASE_URL=your-server-url # ------------------------------ # The type of vector store to use. -# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `tidb_vector`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`. +# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`,`vastbase`,`tidb`,`tidb_on_qdrant`,`baidu`,`lindorm`,`huawei_cloud`,`upstash`. VECTOR_STORE=weaviate # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. @@ -865,7 +865,7 @@ CHROMA_IS_PERSISTENT=TRUE # ------------------------------ # Environment Variables for Oracle Service -# (only used when VECTOR_STORE is Oracle) +# (only used when VECTOR_STORE is oracle) # ------------------------------ ORACLE_PWD=Dify123456 ORACLE_CHARACTERSET=AL32UTF8 From d1c08a810b70155af2329d484d6728b48005ca8d Mon Sep 17 00:00:00 2001 From: Good Wood Date: Wed, 7 May 2025 14:49:28 +0800 Subject: [PATCH 63/89] feat: store mcp_config when switch agent strategy (#19291) --- web/app/components/workflow/nodes/agent/panel.tsx | 1 - web/app/components/workflow/nodes/agent/use-config.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 7fd6314781..f92e92dbcb 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -102,7 +102,6 @@ const AgentPanel: FC> = (props) => { agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, plugin_unique_identifier: strategy!.plugin_unique_identifier, - agent_parameters: {}, }) resetEditor(Date.now()) }} diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 32d7c6f9db..8196caa3f5 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -85,12 +85,13 @@ const useConfig = (id: string, payload: AgentNodeType) => { enabled: Boolean(pluginId), }) const formData = useMemo(() => { + const paramNameList = (currentStrategy?.parameters || []).map(item => item.name) return Object.fromEntries( - Object.entries(inputs.agent_parameters || {}).map(([key, value]) => { + Object.entries(inputs.agent_parameters || {}).filter(([name]) => paramNameList.includes(name)).map(([key, value]) => { return [key, value.value] }), ) - }, [inputs.agent_parameters]) + }, [inputs.agent_parameters, currentStrategy?.parameters]) const onFormChange = (value: Record) => { const res: ToolVarInputs = {} Object.entries(value).forEach(([key, val]) => { From bfa652f2d041ab1bbf4da25cc90f2cd11c46becf Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 7 May 2025 14:52:09 +0800 Subject: [PATCH 64/89] =?UTF-8?q?fix:=20metadata=20filtering=20condition?= =?UTF-8?q?=20variable=20unassigned;=20fix=20External=20K=E2=80=A6=20(#192?= =?UTF-8?q?08)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/console/datasets/external.py | 2 ++ api/core/agent/base_agent_runner.py | 2 ++ api/core/agent/cot_agent_runner.py | 7 ---- api/core/agent/fc_agent_runner.py | 7 ---- api/core/rag/datasource/retrieval_service.py | 18 ++++++++-- api/core/rag/retrieval/dataset_retrieval.py | 36 +++++++++++++------ .../dataset_retriever_tool.py | 36 +++++++++++++++---- .../tools/utils/dataset_retriever_tool.py | 4 +++ .../knowledge_retrieval_node.py | 27 +++++++++----- api/services/hit_testing_service.py | 2 ++ 10 files changed, 101 insertions(+), 40 deletions(-) diff --git a/api/controllers/console/datasets/external.py b/api/controllers/console/datasets/external.py index 30b7f63aab..cf9081e154 100644 --- a/api/controllers/console/datasets/external.py +++ b/api/controllers/console/datasets/external.py @@ -209,6 +209,7 @@ class ExternalKnowledgeHitTestingApi(Resource): parser = reqparse.RequestParser() parser.add_argument("query", type=str, location="json") parser.add_argument("external_retrieval_model", type=dict, required=False, location="json") + parser.add_argument("metadata_filtering_conditions", type=dict, required=False, location="json") args = parser.parse_args() HitTestingService.hit_testing_args_check(args) @@ -219,6 +220,7 @@ class ExternalKnowledgeHitTestingApi(Resource): query=args["query"], account=current_user, external_retrieval_model=args["external_retrieval_model"], + metadata_filtering_conditions=args["metadata_filtering_conditions"], ) return response diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index e648613605..6998e4d29a 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -91,6 +91,8 @@ class BaseAgentRunner(AppRunner): return_resource=app_config.additional_features.show_retrieve_source, invoke_from=application_generate_entity.invoke_from, hit_callback=hit_callback, + user_id=user_id, + inputs=cast(dict, application_generate_entity.inputs), ) # get how many agent thoughts have been created self.agent_thought_count = ( diff --git a/api/core/agent/cot_agent_runner.py b/api/core/agent/cot_agent_runner.py index de3b7e1ad7..feb8abf6ef 100644 --- a/api/core/agent/cot_agent_runner.py +++ b/api/core/agent/cot_agent_runner.py @@ -69,13 +69,6 @@ class CotAgentRunner(BaseAgentRunner, ABC): tool_instances, prompt_messages_tools = self._init_prompt_tools() self._prompt_messages_tools = prompt_messages_tools - # fix metadata filter not work - if app_config.dataset is not None: - metadata_filtering_conditions = app_config.dataset.retrieve_config.metadata_filtering_conditions - for key, dataset_retriever_tool in tool_instances.items(): - if hasattr(dataset_retriever_tool, "retrieval_tool"): - dataset_retriever_tool.retrieval_tool.metadata_filtering_conditions = metadata_filtering_conditions - function_call_state = True llm_usage: dict[str, Optional[LLMUsage]] = {"usage": None} final_answer = "" diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index 874bd6b93b..a1110e7709 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -45,13 +45,6 @@ class FunctionCallAgentRunner(BaseAgentRunner): # convert tools into ModelRuntime Tool format tool_instances, prompt_messages_tools = self._init_prompt_tools() - # fix metadata filter not work - if app_config.dataset is not None: - metadata_filtering_conditions = app_config.dataset.retrieve_config.metadata_filtering_conditions - for key, dataset_retriever_tool in tool_instances.items(): - if hasattr(dataset_retriever_tool, "retrieval_tool"): - dataset_retriever_tool.retrieval_tool.metadata_filtering_conditions = metadata_filtering_conditions - assert app_config.agent iteration_step = 1 diff --git a/api/core/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index 46a5330bdb..01f74b4a22 100644 --- a/api/core/rag/datasource/retrieval_service.py +++ b/api/core/rag/datasource/retrieval_service.py @@ -10,6 +10,7 @@ from core.rag.data_post_processor.data_post_processor import DataPostProcessor from core.rag.datasource.keyword.keyword_factory import Keyword from core.rag.datasource.vdb.vector_factory import Vector from core.rag.embedding.retrieval import RetrievalSegments +from core.rag.entities.metadata_entities import MetadataCondition from core.rag.index_processor.constant.index_type import IndexType from core.rag.models.document import Document from core.rag.rerank.rerank_type import RerankMode @@ -119,12 +120,25 @@ class RetrievalService: return all_documents @classmethod - def external_retrieve(cls, dataset_id: str, query: str, external_retrieval_model: Optional[dict] = None): + def external_retrieve( + cls, + dataset_id: str, + query: str, + external_retrieval_model: Optional[dict] = None, + metadata_filtering_conditions: Optional[dict] = None, + ): dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first() if not dataset: return [] + metadata_condition = ( + MetadataCondition(**metadata_filtering_conditions) if metadata_filtering_conditions else None + ) all_documents = ExternalDatasetService.fetch_external_knowledge_retrieval( - dataset.tenant_id, dataset_id, query, external_retrieval_model or {} + dataset.tenant_id, + dataset_id, + query, + external_retrieval_model or {}, + metadata_condition=metadata_condition, ) return all_documents diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index b1565f10f2..9216b31b8e 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -149,7 +149,7 @@ class DatasetRetrieval: else: inputs = {} available_datasets_ids = [dataset.id for dataset in available_datasets] - metadata_filter_document_ids, metadata_condition = self._get_metadata_filter_condition( + metadata_filter_document_ids, metadata_condition = self.get_metadata_filter_condition( available_datasets_ids, query, tenant_id, @@ -649,6 +649,8 @@ class DatasetRetrieval: return_resource: bool, invoke_from: InvokeFrom, hit_callback: DatasetIndexToolCallbackHandler, + user_id: str, + inputs: dict, ) -> Optional[list[DatasetRetrieverBaseTool]]: """ A dataset tool is a tool that can be used to retrieve information from a dataset @@ -706,6 +708,9 @@ class DatasetRetrieval: hit_callbacks=[hit_callback], return_resource=return_resource, retriever_from=invoke_from.to_source(), + retrieve_config=retrieve_config, + user_id=user_id, + inputs=inputs, ) tools.append(tool) @@ -826,7 +831,7 @@ class DatasetRetrieval: ) return filter_documents[:top_k] if top_k else filter_documents - def _get_metadata_filter_condition( + def get_metadata_filter_condition( self, dataset_ids: list, query: str, @@ -876,20 +881,31 @@ class DatasetRetrieval: ) elif metadata_filtering_mode == "manual": if metadata_filtering_conditions: - metadata_condition = MetadataCondition(**metadata_filtering_conditions.model_dump()) + conditions = [] for sequence, condition in enumerate(metadata_filtering_conditions.conditions): # type: ignore metadata_name = condition.name expected_value = condition.value - if expected_value is not None or condition.comparison_operator in ("empty", "not empty"): + if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): if isinstance(expected_value, str): expected_value = self._replace_metadata_filter_value(expected_value, inputs) - filters = self._process_metadata_filter_func( - sequence, - condition.comparison_operator, - metadata_name, - expected_value, - filters, + conditions.append( + Condition( + name=metadata_name, + comparison_operator=condition.comparison_operator, + value=expected_value, ) + ) + filters = self._process_metadata_filter_func( + sequence, + condition.comparison_operator, + metadata_name, + expected_value, + filters, + ) + metadata_condition = MetadataCondition( + logical_operator=metadata_filtering_conditions.logical_operator, + conditions=conditions, + ) else: raise ValueError("Invalid metadata filtering mode") if filters: diff --git a/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py b/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py index dcd3d080f3..ed97b44f95 100644 --- a/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py @@ -1,11 +1,12 @@ -from typing import Any +from typing import Any, Optional, cast from pydantic import BaseModel, Field +from core.app.app_config.entities import DatasetRetrieveConfigEntity, ModelConfig from core.rag.datasource.retrieval_service import RetrievalService from core.rag.entities.context_entities import DocumentContext -from core.rag.entities.metadata_entities import MetadataCondition from core.rag.models.document import Document as RetrievalDocument +from core.rag.retrieval.dataset_retrieval import DatasetRetrieval from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.tools.utils.dataset_retriever.dataset_retriever_base_tool import DatasetRetrieverBaseTool from extensions.ext_database import db @@ -34,7 +35,9 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): args_schema: type[BaseModel] = DatasetRetrieverToolInput description: str = "use this to retrieve a dataset. " dataset_id: str - metadata_filtering_conditions: MetadataCondition + user_id: Optional[str] = None + retrieve_config: DatasetRetrieveConfigEntity + inputs: dict @classmethod def from_dataset(cls, dataset: Dataset, **kwargs): @@ -48,7 +51,6 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): tenant_id=dataset.tenant_id, dataset_id=dataset.id, description=description, - metadata_filtering_conditions=MetadataCondition(), **kwargs, ) @@ -61,6 +63,21 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): return "" for hit_callback in self.hit_callbacks: hit_callback.on_query(query, dataset.id) + dataset_retrieval = DatasetRetrieval() + metadata_filter_document_ids, metadata_condition = dataset_retrieval.get_metadata_filter_condition( + [dataset.id], + query, + self.tenant_id, + self.user_id or "unknown", + cast(str, self.retrieve_config.metadata_filtering_mode), + cast(ModelConfig, self.retrieve_config.metadata_model_config), + self.retrieve_config.metadata_filtering_conditions, + self.inputs, + ) + if metadata_filter_document_ids: + document_ids_filter = metadata_filter_document_ids.get(dataset.id, []) + else: + document_ids_filter = None if dataset.provider == "external": results = [] external_documents = ExternalDatasetService.fetch_external_knowledge_retrieval( @@ -68,7 +85,7 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): dataset_id=dataset.id, query=query, external_retrieval_parameters=dataset.retrieval_model, - metadata_condition=self.metadata_filtering_conditions, + metadata_condition=metadata_condition, ) for external_document in external_documents: document = RetrievalDocument( @@ -104,12 +121,18 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): return str("\n".join([item.page_content for item in results])) else: + if metadata_condition and not document_ids_filter: + return "" # get retrieval model , if the model is not setting , using default retrieval_model: dict[str, Any] = dataset.retrieval_model or default_retrieval_model if dataset.indexing_technique == "economy": # use keyword table query documents = RetrievalService.retrieve( - retrieval_method="keyword_search", dataset_id=dataset.id, query=query, top_k=self.top_k + retrieval_method="keyword_search", + dataset_id=dataset.id, + query=query, + top_k=self.top_k, + document_ids_filter=document_ids_filter, ) return str("\n".join([document.page_content for document in documents])) else: @@ -128,6 +151,7 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): else None, reranking_mode=retrieval_model.get("reranking_mode") or "reranking_model", weights=retrieval_model.get("weights"), + document_ids_filter=document_ids_filter, ) else: documents = [] diff --git a/api/core/tools/utils/dataset_retriever_tool.py b/api/core/tools/utils/dataset_retriever_tool.py index b73dec4ebc..ec0575f6c3 100644 --- a/api/core/tools/utils/dataset_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever_tool.py @@ -34,6 +34,8 @@ class DatasetRetrieverTool(Tool): return_resource: bool, invoke_from: InvokeFrom, hit_callback: DatasetIndexToolCallbackHandler, + user_id: str, + inputs: dict, ) -> list["DatasetRetrieverTool"]: """ get dataset tool @@ -57,6 +59,8 @@ class DatasetRetrieverTool(Tool): return_resource=return_resource, invoke_from=invoke_from, hit_callback=hit_callback, + user_id=user_id, + inputs=inputs, ) if retrieval_tools is None or len(retrieval_tools) == 0: return [] diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 00dac1b7d7..5c4cac9719 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -356,12 +356,12 @@ class KnowledgeRetrievalNode(LLMNode): ) elif node_data.metadata_filtering_mode == "manual": if node_data.metadata_filtering_conditions: - metadata_condition = MetadataCondition(**node_data.metadata_filtering_conditions.model_dump()) + conditions = [] if node_data.metadata_filtering_conditions: for sequence, condition in enumerate(node_data.metadata_filtering_conditions.conditions): # type: ignore metadata_name = condition.name expected_value = condition.value - if expected_value is not None or condition.comparison_operator in ("empty", "not empty"): + if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): if isinstance(expected_value, str): expected_value = self.graph_runtime_state.variable_pool.convert_template( expected_value @@ -372,13 +372,24 @@ class KnowledgeRetrievalNode(LLMNode): expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore else: raise ValueError("Invalid expected metadata value type") - filters = self._process_metadata_filter_func( - sequence, - condition.comparison_operator, - metadata_name, - expected_value, - filters, + conditions.append( + Condition( + name=metadata_name, + comparison_operator=condition.comparison_operator, + value=expected_value, ) + ) + filters = self._process_metadata_filter_func( + sequence, + condition.comparison_operator, + metadata_name, + expected_value, + filters, + ) + metadata_condition = MetadataCondition( + logical_operator=node_data.metadata_filtering_conditions.logical_operator, + conditions=conditions, + ) else: raise ValueError("Invalid metadata filtering mode") if filters: diff --git a/api/services/hit_testing_service.py b/api/services/hit_testing_service.py index 0b98065f5d..56e06cc33e 100644 --- a/api/services/hit_testing_service.py +++ b/api/services/hit_testing_service.py @@ -69,6 +69,7 @@ class HitTestingService: query: str, account: Account, external_retrieval_model: dict, + metadata_filtering_conditions: dict, ) -> dict: if dataset.provider != "external": return { @@ -82,6 +83,7 @@ class HitTestingService: dataset_id=dataset.id, query=cls.escape_query_for_search(query), external_retrieval_model=external_retrieval_model, + metadata_filtering_conditions=metadata_filtering_conditions, ) end = time.perf_counter() From 838812640e1d4dcff415fdf017c4b0c8683cb589 Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 7 May 2025 14:58:45 +0800 Subject: [PATCH 65/89] fix: reopen switch to 'workflow orchestrate' menu in app detail page (#19274) --- web/app/components/app-sidebar/app-info.tsx | 56 +++++++++++++++++---- web/i18n/en-US/common.ts | 1 + web/i18n/ja-JP/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 18a9ac8bc8..5ec0e318a0 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -6,9 +6,11 @@ import { RiDeleteBinLine, RiEditLine, RiEqualizer2Line, + RiExchange2Line, RiFileCopy2Line, RiFileDownloadLine, RiFileUploadLine, + RiMoreLine, } from '@remixicon/react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' @@ -32,6 +34,7 @@ import { fetchWorkflowDraft } from '@/service/workflow' import ContentDialog from '@/app/components/base/content-dialog' import Button from '@/app/components/base/button' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../base/portal-to-follow-elem' export type IAppInfoProps = { expand: boolean @@ -179,6 +182,11 @@ const AppInfo = ({ expand }: IAppInfoProps) => { const { isCurrentWorkspaceEditor } = useAppContext() + const [showMore, setShowMore] = useState(false) + const handleTriggerMore = useCallback(() => { + setShowMore(true) + }, [setShowMore]) + if (!appDetail) return null @@ -276,22 +284,50 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {t('app.export')} - { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( + {appDetail.mode !== 'agent-chat' && + - ) - } + + +
    + { + (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') + &&
    { + setOpen(false) + setShowImportDSLModal(true) + }}> + + {t('workflow.common.importDSL')} +
    + } + { + (appDetail.mode === 'completion' || appDetail.mode === 'chat') + &&
    { + setOpen(false) + setShowSwitchModal(true) + }}> + + {t('app.switch')} +
    + } +
    +
    +
    }
    diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 0491ff57a8..8756095075 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -57,6 +57,7 @@ const translation = { submit: 'Submit', skip: 'Skip', format: 'Format', + more: 'More', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index c6163ec5cd..f2d7e0b4f0 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -57,6 +57,7 @@ const translation = { copied: 'コピーしました', in: '中', format: 'フォーマット', + more: 'もっと', }, errorMsg: { fieldRequired: '{{field}}は必要です', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 327f41ea05..22211a92aa 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -57,6 +57,7 @@ const translation = { submit: '提交', skip: '跳过', format: '格式化', + more: '更多', }, errorMsg: { fieldRequired: '{{field}} 为必填项', From 035885946790004af03e49cdc7d65686d05c92a3 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 7 May 2025 16:42:49 +0800 Subject: [PATCH 66/89] fix: llm_usage.total_tokens stat (#19177) --- api/core/agent/cot_agent_runner.py | 1 + api/core/agent/fc_agent_runner.py | 1 + 2 files changed, 2 insertions(+) diff --git a/api/core/agent/cot_agent_runner.py b/api/core/agent/cot_agent_runner.py index feb8abf6ef..5212d797d8 100644 --- a/api/core/agent/cot_agent_runner.py +++ b/api/core/agent/cot_agent_runner.py @@ -80,6 +80,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): llm_usage = final_llm_usage_dict["usage"] llm_usage.prompt_tokens += usage.prompt_tokens llm_usage.completion_tokens += usage.completion_tokens + llm_usage.total_tokens += usage.total_tokens llm_usage.prompt_price += usage.prompt_price llm_usage.completion_price += usage.completion_price llm_usage.total_price += usage.total_price diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index a1110e7709..611a55b30a 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -65,6 +65,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): llm_usage = final_llm_usage_dict["usage"] llm_usage.prompt_tokens += usage.prompt_tokens llm_usage.completion_tokens += usage.completion_tokens + llm_usage.total_tokens += usage.total_tokens llm_usage.prompt_price += usage.prompt_price llm_usage.completion_price += usage.completion_price llm_usage.total_price += usage.total_price From 623ac7ea6d0b33c40e4482573d4a2c0b9531604e Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 7 May 2025 16:46:02 +0800 Subject: [PATCH 67/89] feat: add optional hidden property to endpoint items and filter hidden endpoints in endpoint card (#19163) --- .../components/plugins/plugin-detail-panel/endpoint-card.tsx | 2 +- web/app/components/plugins/types.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx index 9ab0c5b9a3..cc3688aebc 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx @@ -149,7 +149,7 @@ const EndpointCard = ({
    - {data.declaration.endpoints.map((endpoint, index) => ( + {data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
    {endpoint.method}
    diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 5ed05d4523..f552d7c17a 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -36,6 +36,7 @@ export type PluginEndpointDeclaration = { export type EndpointItem = { path: string method: string + hidden?: boolean } export type EndpointListItem = { From 3258a91d5d70e7fa66ccbe57d000f4ae60e604d3 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 7 May 2025 17:28:38 +0800 Subject: [PATCH 68/89] Feat/add repo to plugin manifest (#19337) --- api/core/plugin/entities/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index 07ed94380a..c15f98c6ea 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -84,6 +84,7 @@ class PluginDeclaration(BaseModel): resource: PluginResourceRequirements plugins: Plugins tags: list[str] = Field(default_factory=list) + repo: Optional[str] = Field(default=None) verified: bool = Field(default=False) tool: Optional[ToolProviderEntity] = None model: Optional[ProviderEntity] = None From 163a76eb6eff249fd0560d046e95f81f6673ca34 Mon Sep 17 00:00:00 2001 From: hzhufa <46232940+hzhufa@users.noreply.github.com> Date: Thu, 8 May 2025 12:57:10 +0800 Subject: [PATCH 69/89] Bug fix: Invalid edge connection data causes the page to crash. (#19369) Co-authored-by: hzhufa --- .../hooks/use-workflow-run-event/use-workflow-node-started.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts index b537ccbb27..f1f184d4bb 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts @@ -67,6 +67,9 @@ export const useWorkflowNodeStarted = () => { incomeEdges.forEach((edge) => { const incomeNode = nodes.find(node => node.id === edge.source)! + if (!incomeNode || !('data' in incomeNode)) + return + if ( (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') || (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId) From 736a064bac81420d4e5f02dcdf499261696145c7 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Thu, 8 May 2025 16:20:51 +0800 Subject: [PATCH 70/89] fix(web): Add unique instanceId & key for AgentStrategy component (#18053) (#19386) --- .../workflow/nodes/_base/components/agent-strategy.tsx | 3 +++ 1 file changed, 3 insertions(+) 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 1e9612b7c7..de23602e34 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -66,6 +66,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { case FormTypeEnum.textInput: { const def = schema as CredentialFormSchemaTextInput const value = props.value[schema.variable] || schema.default + const instanceId = schema.variable const onChange = (value: string) => { props.onChange({ ...props.value, [schema.variable]: value }) } @@ -77,6 +78,8 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { value={value} onChange={onChange} onGenerated={handleGenerated} + instanceId={instanceId} + key={instanceId} title={renderI18nObject(schema.label)} headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase' containerBackgroundClassName='bg-transparent' From 58d9d3551551812181af0999d586cceadf546c08 Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 8 May 2025 16:33:28 +0800 Subject: [PATCH 71/89] fix: inconsistent metadata definitions (#19343) --- .../nodes/knowledge_retrieval/knowledge_retrieval_node.py | 2 +- api/core/workflow/nodes/llm/node.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 5c4cac9719..c84a1897de 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -289,7 +289,7 @@ class KnowledgeRetrievalNode(LLMNode): "dataset_name": dataset.name, "document_id": document.id, "document_name": document.name, - "document_data_source_type": document.data_source_type, + "data_source_type": document.data_source_type, "segment_id": segment.id, "retriever_from": "workflow", "score": record.score or 0.0, diff --git a/api/core/workflow/nodes/llm/node.py b/api/core/workflow/nodes/llm/node.py index f42bc6784d..eeb44601ec 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -506,7 +506,7 @@ class LLMNode(BaseNode[LLMNodeData]): "dataset_name": metadata.get("dataset_name"), "document_id": metadata.get("document_id"), "document_name": metadata.get("document_name"), - "data_source_type": metadata.get("document_data_source_type"), + "data_source_type": metadata.get("data_source_type"), "segment_id": metadata.get("segment_id"), "retriever_from": metadata.get("retriever_from"), "score": metadata.get("score"), From cbc8ebd8f5437f6b2859872e3422d0d2b53b0af9 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Thu, 8 May 2025 17:39:51 +0800 Subject: [PATCH 72/89] chore: bump pydantic to 2.11 and pydantic-settings to 2.9 (#15049) --- api/configs/feature/__init__.py | 6 +- api/configs/middleware/__init__.py | 2 +- api/core/plugin/entities/endpoint.py | 2 +- api/core/plugin/entities/plugin.py | 8 +- api/core/plugin/entities/request.py | 4 +- api/core/variables/variables.py | 2 +- .../workflow/graph_engine/entities/graph.py | 2 +- api/core/workflow/nodes/loop/entities.py | 2 +- api/core/workflow/utils/condition/entities.py | 2 +- api/pyproject.toml | 6 +- api/uv.lock | 125 ++++++++++-------- 11 files changed, 89 insertions(+), 72 deletions(-) diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 4890b5f746..a3da5c1b49 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -74,7 +74,7 @@ class CodeExecutionSandboxConfig(BaseSettings): CODE_EXECUTION_ENDPOINT: HttpUrl = Field( description="URL endpoint for the code execution service", - default="http://sandbox:8194", + default=HttpUrl("http://sandbox:8194"), ) CODE_EXECUTION_API_KEY: str = Field( @@ -145,7 +145,7 @@ class PluginConfig(BaseSettings): PLUGIN_DAEMON_URL: HttpUrl = Field( description="Plugin API URL", - default="http://localhost:5002", + default=HttpUrl("http://localhost:5002"), ) PLUGIN_DAEMON_KEY: str = Field( @@ -188,7 +188,7 @@ class MarketplaceConfig(BaseSettings): MARKETPLACE_API_URL: HttpUrl = Field( description="Marketplace API URL", - default="https://marketplace.dify.ai", + default=HttpUrl("https://marketplace.dify.ai"), ) diff --git a/api/configs/middleware/__init__.py b/api/configs/middleware/__init__.py index d285515998..9d30badb3d 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -173,7 +173,7 @@ class DatabaseConfig(BaseSettings): RETRIEVAL_SERVICE_EXECUTORS: NonNegativeInt = Field( description="Number of processes for the retrieval service, default to CPU cores.", - default=os.cpu_count(), + default=os.cpu_count() or 1, ) @computed_field diff --git a/api/core/plugin/entities/endpoint.py b/api/core/plugin/entities/endpoint.py index 6c6c8bf9bc..d7ba75bb4f 100644 --- a/api/core/plugin/entities/endpoint.py +++ b/api/core/plugin/entities/endpoint.py @@ -24,7 +24,7 @@ class EndpointProviderDeclaration(BaseModel): """ settings: list[ProviderConfig] = Field(default_factory=list) - endpoints: Optional[list[EndpointDeclaration]] = Field(default_factory=list) + endpoints: Optional[list[EndpointDeclaration]] = Field(default_factory=list[EndpointDeclaration]) class EndpointEntity(BasePluginEntity): diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index c15f98c6ea..bdf7d5ce1f 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -52,7 +52,7 @@ class PluginResourceRequirements(BaseModel): model: Optional[Model] = Field(default=None) node: Optional[Node] = Field(default=None) endpoint: Optional[Endpoint] = Field(default=None) - storage: Storage = Field(default=None) + storage: Optional[Storage] = Field(default=None) permission: Optional[Permission] = Field(default=None) @@ -66,9 +66,9 @@ class PluginCategory(enum.StrEnum): class PluginDeclaration(BaseModel): class Plugins(BaseModel): - tools: Optional[list[str]] = Field(default_factory=list) - models: Optional[list[str]] = Field(default_factory=list) - endpoints: Optional[list[str]] = Field(default_factory=list) + tools: Optional[list[str]] = Field(default_factory=list[str]) + models: Optional[list[str]] = Field(default_factory=list[str]) + endpoints: Optional[list[str]] = Field(default_factory=list[str]) class Meta(BaseModel): minimum_dify_version: Optional[str] = Field(default=None, pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$") diff --git a/api/core/plugin/entities/request.py b/api/core/plugin/entities/request.py index 6c0c7f2868..1692020ec8 100644 --- a/api/core/plugin/entities/request.py +++ b/api/core/plugin/entities/request.py @@ -55,8 +55,8 @@ class RequestInvokeLLM(BaseRequestInvokeModel): mode: str completion_params: dict[str, Any] = Field(default_factory=dict) prompt_messages: list[PromptMessage] = Field(default_factory=list) - tools: Optional[list[PromptMessageTool]] = Field(default_factory=list) - stop: Optional[list[str]] = Field(default_factory=list) + tools: Optional[list[PromptMessageTool]] = Field(default_factory=list[PromptMessageTool]) + stop: Optional[list[str]] = Field(default_factory=list[str]) stream: Optional[bool] = False model_config = ConfigDict(protected_namespaces=()) diff --git a/api/core/variables/variables.py b/api/core/variables/variables.py index c32815b24d..b650b1682e 100644 --- a/api/core/variables/variables.py +++ b/api/core/variables/variables.py @@ -30,7 +30,7 @@ class Variable(Segment): """ id: str = Field( - default=lambda _: str(uuid4()), + default_factory=lambda: str(uuid4()), description="Unique identity for variable.", ) name: str diff --git a/api/core/workflow/graph_engine/entities/graph.py b/api/core/workflow/graph_engine/entities/graph.py index 5c672c985b..8e5b1e7142 100644 --- a/api/core/workflow/graph_engine/entities/graph.py +++ b/api/core/workflow/graph_engine/entities/graph.py @@ -36,7 +36,7 @@ class Graph(BaseModel): root_node_id: str = Field(..., description="root node id of the graph") node_ids: list[str] = Field(default_factory=list, description="graph node ids") node_id_config_mapping: dict[str, dict] = Field( - default_factory=list, description="node configs mapping (node id: node config)" + default_factory=dict, description="node configs mapping (node id: node config)" ) edge_mapping: dict[str, list[GraphEdge]] = Field( default_factory=dict, description="graph edge mapping (source node id: edges)" diff --git a/api/core/workflow/nodes/loop/entities.py b/api/core/workflow/nodes/loop/entities.py index 16802311dc..3f4a5edab9 100644 --- a/api/core/workflow/nodes/loop/entities.py +++ b/api/core/workflow/nodes/loop/entities.py @@ -26,7 +26,7 @@ class LoopNodeData(BaseLoopNodeData): loop_count: int # Maximum number of loops break_conditions: list[Condition] # Conditions to break the loop logical_operator: Literal["and", "or"] - loop_variables: Optional[list[LoopVariableData]] = Field(default_factory=list) + loop_variables: Optional[list[LoopVariableData]] = Field(default_factory=list[LoopVariableData]) outputs: Optional[Mapping[str, Any]] = None diff --git a/api/core/workflow/utils/condition/entities.py b/api/core/workflow/utils/condition/entities.py index 799c735f54..56871a15d8 100644 --- a/api/core/workflow/utils/condition/entities.py +++ b/api/core/workflow/utils/condition/entities.py @@ -39,7 +39,7 @@ class SubCondition(BaseModel): class SubVariableCondition(BaseModel): logical_operator: Literal["and", "or"] - conditions: list[SubCondition] = Field(default=list) + conditions: list[SubCondition] = Field(default_factory=list) class Condition(BaseModel): diff --git a/api/pyproject.toml b/api/pyproject.toml index 65315e9be7..4cdae173dd 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -63,9 +63,9 @@ dependencies = [ "psycogreen~=1.0.2", "psycopg2-binary~=2.9.6", "pycryptodome==3.19.1", - "pydantic~=2.9.2", - "pydantic-extra-types~=2.9.0", - "pydantic-settings~=2.6.0", + "pydantic~=2.11.4", + "pydantic-extra-types~=2.10.3", + "pydantic-settings~=2.9.1", "pyjwt~=2.8.0", "pypdfium2~=4.30.0", "python-docx~=1.1.0", diff --git a/api/uv.lock b/api/uv.lock index 6fbb865d63..698cd310bc 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1384,9 +1384,9 @@ requires-dist = [ { name = "psycogreen", specifier = "~=1.0.2" }, { name = "psycopg2-binary", specifier = "~=2.9.6" }, { name = "pycryptodome", specifier = "==3.19.1" }, - { name = "pydantic", specifier = "~=2.9.2" }, - { name = "pydantic-extra-types", specifier = "~=2.9.0" }, - { name = "pydantic-settings", specifier = "~=2.6.0" }, + { name = "pydantic", specifier = "~=2.11.4" }, + { name = "pydantic-extra-types", specifier = "~=2.10.3" }, + { name = "pydantic-settings", specifier = "~=2.9.1" }, { name = "pyjwt", specifier = "~=2.8.0" }, { name = "pypdfium2", specifier = "~=4.30.0" }, { name = "python-docx", specifier = "~=1.1.0" }, @@ -2621,15 +2621,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/5c/abd7495c934d9af5c263c2245ae30cfaa716c3c0cf027b2b8fa686ee7bd4/json_repair-0.41.1-py3-none-any.whl", hash = "sha256:0e181fd43a696887881fe19fed23422a54b3e4c558b6ff27a86a8c3ddde9ae79", size = 21578, upload-time = "2025-04-14T07:01:46.815Z" }, ] -[[package]] -name = "jsonpath-python" -version = "1.0.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/49/e582e50b0c54c1b47e714241c4a4767bf28758bf90212248aea8e1ce8516/jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666", size = 18121, upload-time = "2022-03-14T02:35:01.877Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552, upload-time = "2022-03-14T02:34:59.754Z" }, -] - [[package]] name = "jsonschema" version = "4.23.0" @@ -4184,76 +4175,92 @@ wheels = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.11.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917, upload-time = "2024-09-17T15:59:54.273Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928, upload-time = "2024-09-17T15:59:51.827Z" }, + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, ] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156, upload-time = "2024-09-16T16:06:44.786Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160, upload-time = "2024-09-16T16:04:18.628Z" }, - { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777, upload-time = "2024-09-16T16:04:20.038Z" }, - { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244, upload-time = "2024-09-16T16:04:21.799Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307, upload-time = "2024-09-16T16:04:23.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663, upload-time = "2024-09-16T16:04:25.203Z" }, - { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941, upload-time = "2024-09-16T16:04:27.211Z" }, - { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105, upload-time = "2024-09-16T16:04:28.611Z" }, - { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967, upload-time = "2024-09-16T16:04:30.045Z" }, - { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291, upload-time = "2024-09-16T16:04:32.376Z" }, - { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666, upload-time = "2024-09-16T16:04:33.923Z" }, - { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940, upload-time = "2024-09-16T16:04:35.467Z" }, - { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804, upload-time = "2024-09-16T16:04:37.06Z" }, - { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459, upload-time = "2024-09-16T16:04:38.438Z" }, - { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007, upload-time = "2024-09-16T16:04:40.229Z" }, - { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245, upload-time = "2024-09-16T16:04:41.794Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260, upload-time = "2024-09-16T16:04:43.991Z" }, - { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872, upload-time = "2024-09-16T16:04:45.593Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617, upload-time = "2024-09-16T16:04:47.3Z" }, - { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831, upload-time = "2024-09-16T16:04:48.893Z" }, - { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453, upload-time = "2024-09-16T16:04:51.099Z" }, - { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793, upload-time = "2024-09-16T16:04:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872, upload-time = "2024-09-16T16:04:54.41Z" }, - { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535, upload-time = "2024-09-16T16:04:55.828Z" }, - { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992, upload-time = "2024-09-16T16:04:57.395Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] [[package]] name = "pydantic-extra-types" -version = "2.9.0" +version = "2.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/95/d61dcadd933cb34461adc271c13bbe14a7080b9922b9e0dc3c1d18b421cb/pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b", size = 39578, upload-time = "2024-07-03T17:19:47.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/33/0cde418479949cd6aa1ac669deffcd1c37d8d9cead99ddb48f344e75f2e3/pydantic_extra_types-2.10.4.tar.gz", hash = "sha256:bf8236a63d061eb3ecb1b2afa78ba0f97e3f67aa11dbbff56ec90491e8772edc", size = 95269, upload-time = "2025-04-28T08:18:34.869Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/2f/efc4877d1a1536ec76ca0114c3e9dee7d0a10a262c53d384d50163f5684c/pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0", size = 30544, upload-time = "2024-07-03T17:19:46.208Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/bee195ee49256385fad460ce420aeb42703a648dba487c20b6fd107e42ea/pydantic_extra_types-2.10.4-py3-none-any.whl", hash = "sha256:ce064595af3cab05e39ae062752432dcd0362ff80f7e695b61a3493a4d842db7", size = 37276, upload-time = "2025-04-28T08:18:31.617Z" }, ] [[package]] name = "pydantic-settings" -version = "2.6.1" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646, upload-time = "2024-11-01T11:00:05.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595, upload-time = "2024-11-01T11:00:02.64Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] [[package]] @@ -5885,6 +5892,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -5973,24 +5992,22 @@ pptx = [ [[package]] name = "unstructured-client" -version = "0.28.1" +version = "0.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, { name = "cryptography" }, { name = "eval-type-backport" }, { name = "httpx" }, - { name = "jsonpath-python" }, { name = "nest-asyncio" }, { name = "pydantic" }, { name = "pypdf" }, - { name = "python-dateutil" }, { name = "requests-toolbelt" }, - { name = "typing-inspect" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/a1/8b0bf11e8c092aeb704b579f5855b5cfb0d5278a6c542312cddad5a8097e/unstructured_client-0.28.1.tar.gz", hash = "sha256:aac11fe5dd6b8dfdbc15aad3205fe791a3834dac29bb9f499fd515643554f709", size = 48607, upload-time = "2024-11-26T21:26:29.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/32/9e819deaa5a59b57d97055b6c2cb9a83494e2f9c0fb07f56b3030bd1490f/unstructured_client-0.34.0.tar.gz", hash = "sha256:bc1c34edc622545993f1061127996da2576fc602fefd23e5cd8454e04c421e1f", size = 81006, upload-time = "2025-04-22T21:00:10.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/69/38e51f6ce07f2a454f53a364f6ef9acdbfc73841fe736d9c7cd152525048/unstructured_client-0.28.1-py3-none-any.whl", hash = "sha256:0112688908f544681a67abf314e0d2023dfa120c8e5d9fa6d31390b914a06d72", size = 62865, upload-time = "2024-11-26T21:26:27.811Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e3/d1c2d02d953555d2830af3013d5ce76351507441f148f81469ae751bec7c/unstructured_client-0.34.0-py3-none-any.whl", hash = "sha256:3180d2030695fe6279e7f6f3a1fb92b4038f26c5706e6f9dfe063f816893b734", size = 189417, upload-time = "2025-04-22T21:00:08.679Z" }, ] [[package]] From 135b8bd4f5ed02acc9446065427f567530c52bd1 Mon Sep 17 00:00:00 2001 From: LeanDeR <34670582+auxpd@users.noreply.github.com> Date: Thu, 8 May 2025 22:49:40 +0800 Subject: [PATCH 73/89] fix(workflow): Fix the expand/collapse animation effect (#19398) --- .../workflow/run/iteration-log/iteration-result-panel.tsx | 6 ++++-- .../components/workflow/run/loop-log/loop-result-panel.tsx | 6 ++++-- web/app/components/workflow/run/loop-result-panel.tsx | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx index ac80d7e39e..bba39bf4d6 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx @@ -109,8 +109,10 @@ const IterationResultPanel: FC = ({ className="h-px grow bg-divider-subtle" >
    }
    = ({ className="h-px grow bg-divider-subtle" >
    }
    { loopVariableMap?.[index] && ( diff --git a/web/app/components/workflow/run/loop-result-panel.tsx b/web/app/components/workflow/run/loop-result-panel.tsx index c66e8cb4b5..7176a4a2b2 100644 --- a/web/app/components/workflow/run/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-result-panel.tsx @@ -82,8 +82,10 @@ const LoopResultPanel: FC = ({ className="h-px grow bg-divider-subtle" >
    }
    Date: Thu, 8 May 2025 22:50:55 +0800 Subject: [PATCH 74/89] fix: support text wrapping in buttons for long content (#19390) --- web/app/components/base/markdown-blocks/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/base/markdown-blocks/button.tsx b/web/app/components/base/markdown-blocks/button.tsx index e1bd234f59..81a3f30660 100644 --- a/web/app/components/base/markdown-blocks/button.tsx +++ b/web/app/components/base/markdown-blocks/button.tsx @@ -22,7 +22,7 @@ const MarkdownButton = ({ node }: any) => { return