From 513911930711bb66521a85b4ef89e50069e94eca Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:55:05 +0800 Subject: [PATCH 01/11] chore: bump pnpm version (#26905) Signed-off-by: lyzno1 --- web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/package.json b/web/package.json index 6ce808291e..562938d5b3 100644 --- a/web/package.json +++ b/web/package.json @@ -2,7 +2,7 @@ "name": "dify-web", "version": "1.9.1", "private": true, - "packageManager": "pnpm@10.18.2", + "packageManager": "pnpm@10.18.3+sha512.bbd16e6d7286fd7e01f6b3c0b3c932cda2965c06a908328f74663f10a9aea51f1129eea615134bf992831b009eabe167ecb7008b597f40ff9bc75946aadfb08d", "engines": { "node": ">=v22.11.0" }, From f906e70f6b80457b0d41e18381b35cdfff5932e5 Mon Sep 17 00:00:00 2001 From: Novice Date: Wed, 15 Oct 2025 09:55:39 +0800 Subject: [PATCH 02/11] chore: remove redundant dependencies (#26907) --- api/pyproject.toml | 1 - api/uv.lock | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 1fb5747edc..62af88a1b2 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -86,7 +86,6 @@ dependencies = [ "sendgrid~=6.12.3", "flask-restx~=1.3.0", "packaging~=23.2", - "weaviate-client==4.17.0", ] # Before adding new dependency, consider place it in # alphabet order (a-z) and suitable group. diff --git a/api/uv.lock b/api/uv.lock index 7914cd6b65..96aee8a97b 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11, <3.13" resolution-markers = [ "python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", @@ -1372,7 +1372,6 @@ dependencies = [ { name = "transformers" }, { name = "unstructured", extra = ["docx", "epub", "md", "ppt", "pptx"] }, { name = "weave" }, - { name = "weaviate-client" }, { name = "webvtt-py" }, { name = "yarl" }, ] @@ -1562,7 +1561,6 @@ requires-dist = [ { name = "transformers", specifier = "~=4.56.1" }, { name = "unstructured", extras = ["docx", "epub", "md", "ppt", "pptx"], specifier = "~=0.16.1" }, { name = "weave", specifier = "~=0.51.0" }, - { name = "weaviate-client", specifier = "==4.17.0" }, { name = "webvtt-py", specifier = "~=0.5.1" }, { name = "yarl", specifier = "~=1.18.3" }, ] From c39dae06d45485a0a879ed5b270ff2fecc6dddaf Mon Sep 17 00:00:00 2001 From: kenwoodjw Date: Wed, 15 Oct 2025 10:39:51 +0800 Subject: [PATCH 03/11] fix: workflow token usage (#26723) Signed-off-by: kenwoodjw --- .../event_management/event_handlers.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/core/workflow/graph_engine/event_management/event_handlers.py b/api/core/workflow/graph_engine/event_management/event_handlers.py index 7247b17967..1cb5851ab1 100644 --- a/api/core/workflow/graph_engine/event_management/event_handlers.py +++ b/api/core/workflow/graph_engine/event_management/event_handlers.py @@ -7,6 +7,7 @@ from collections.abc import Mapping from functools import singledispatchmethod from typing import TYPE_CHECKING, final +from core.model_runtime.entities.llm_entities import LLMUsage from core.workflow.entities import GraphRuntimeState from core.workflow.enums import ErrorStrategy, NodeExecutionType from core.workflow.graph import Graph @@ -125,6 +126,7 @@ class EventHandler: node_execution = self._graph_execution.get_or_create_node_execution(event.node_id) is_initial_attempt = node_execution.retry_count == 0 node_execution.mark_started(event.id) + self._graph_runtime_state.increment_node_run_steps() # Track in response coordinator for stream ordering self._response_coordinator.track_node_execution(event.node_id, event.id) @@ -163,6 +165,8 @@ class EventHandler: node_execution = self._graph_execution.get_or_create_node_execution(event.node_id) node_execution.mark_taken() + self._accumulate_node_usage(event.node_run_result.llm_usage) + # Store outputs in variable pool self._store_node_outputs(event.node_id, event.node_run_result.outputs) @@ -212,6 +216,8 @@ class EventHandler: node_execution.mark_failed(event.error) self._graph_execution.record_node_failure() + self._accumulate_node_usage(event.node_run_result.llm_usage) + result = self._error_handler.handle_node_failure(event) if result: @@ -235,6 +241,8 @@ class EventHandler: node_execution = self._graph_execution.get_or_create_node_execution(event.node_id) node_execution.mark_taken() + self._accumulate_node_usage(event.node_run_result.llm_usage) + # Persist outputs produced by the exception strategy (e.g. default values) self._store_node_outputs(event.node_id, event.node_run_result.outputs) @@ -286,6 +294,19 @@ class EventHandler: self._state_manager.enqueue_node(event.node_id) self._state_manager.start_execution(event.node_id) + def _accumulate_node_usage(self, usage: LLMUsage) -> None: + """Accumulate token usage into the shared runtime state.""" + if usage.total_tokens <= 0: + return + + self._graph_runtime_state.add_tokens(usage.total_tokens) + + current_usage = self._graph_runtime_state.llm_usage + if current_usage.total_tokens == 0: + self._graph_runtime_state.llm_usage = usage + else: + self._graph_runtime_state.llm_usage = current_usage.plus(usage) + def _store_node_outputs(self, node_id: str, outputs: Mapping[str, object]) -> None: """ Store node outputs in the variable pool. From a16ef7e73c4f11fef98d59d9d87526e976139c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adem=C3=ADlson=20Tonato?= Date: Tue, 14 Oct 2025 23:48:54 -0300 Subject: [PATCH 04/11] refactor: Update Firecrawl to use v2 API (#24734) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../rag/extractor/firecrawl/firecrawl_app.py | 30 +++++++++++++++---- api/services/website_service.py | 9 ++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/api/core/rag/extractor/firecrawl/firecrawl_app.py b/api/core/rag/extractor/firecrawl/firecrawl_app.py index c20ecd2b89..789ac8557d 100644 --- a/api/core/rag/extractor/firecrawl/firecrawl_app.py +++ b/api/core/rag/extractor/firecrawl/firecrawl_app.py @@ -25,7 +25,7 @@ class FirecrawlApp: } if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v1/scrape", json_data, headers) + response = self._post_request(f"{self.base_url}/v2/scrape", json_data, headers) if response.status_code == 200: response_data = response.json() data = response_data["data"] @@ -42,7 +42,7 @@ class FirecrawlApp: json_data = {"url": url} if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v1/crawl", json_data, headers) + response = self._post_request(f"{self.base_url}/v2/crawl", json_data, headers) if response.status_code == 200: # There's also another two fields in the response: "success" (bool) and "url" (str) job_id = response.json().get("id") @@ -51,9 +51,25 @@ class FirecrawlApp: self._handle_error(response, "start crawl job") return "" # unreachable + def map(self, url: str, params: dict[str, Any] | None = None) -> dict[str, Any]: + # Documentation: https://docs.firecrawl.dev/api-reference/endpoint/map + headers = self._prepare_headers() + json_data: dict[str, Any] = {"url": url, "integration": "dify"} + if params: + # Pass through provided params, including optional "sitemap": "only" | "include" | "skip" + json_data.update(params) + response = self._post_request(f"{self.base_url}/v2/map", json_data, headers) + if response.status_code == 200: + return cast(dict[str, Any], response.json()) + elif response.status_code in {402, 409, 500, 429, 408}: + self._handle_error(response, "start map job") + return {} + else: + raise Exception(f"Failed to start map job. Status code: {response.status_code}") + def check_crawl_status(self, job_id) -> dict[str, Any]: headers = self._prepare_headers() - response = self._get_request(f"{self.base_url}/v1/crawl/{job_id}", headers) + response = self._get_request(f"{self.base_url}/v2/crawl/{job_id}", headers) if response.status_code == 200: crawl_status_response = response.json() if crawl_status_response.get("status") == "completed": @@ -135,12 +151,16 @@ class FirecrawlApp: "lang": "en", "country": "us", "timeout": 60000, - "ignoreInvalidURLs": False, + "ignoreInvalidURLs": True, "scrapeOptions": {}, + "sources": [ + {"type": "web"}, + ], + "integration": "dify", } if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v1/search", json_data, headers) + response = self._post_request(f"{self.base_url}/v2/search", json_data, headers) if response.status_code == 200: response_data = response.json() if not response_data.get("success"): diff --git a/api/services/website_service.py b/api/services/website_service.py index 37588d6ba5..a23f01ec71 100644 --- a/api/services/website_service.py +++ b/api/services/website_service.py @@ -23,6 +23,7 @@ class CrawlOptions: only_main_content: bool = False includes: str | None = None excludes: str | None = None + prompt: str | None = None max_depth: int | None = None use_sitemap: bool = True @@ -70,6 +71,7 @@ class WebsiteCrawlApiRequest: only_main_content=self.options.get("only_main_content", False), includes=self.options.get("includes"), excludes=self.options.get("excludes"), + prompt=self.options.get("prompt"), max_depth=self.options.get("max_depth"), use_sitemap=self.options.get("use_sitemap", True), ) @@ -174,6 +176,7 @@ class WebsiteService: def _crawl_with_firecrawl(cls, request: CrawlRequest, api_key: str, config: dict) -> dict[str, Any]: firecrawl_app = FirecrawlApp(api_key=api_key, base_url=config.get("base_url")) + params: dict[str, Any] if not request.options.crawl_sub_pages: params = { "includePaths": [], @@ -188,8 +191,10 @@ class WebsiteService: "limit": request.options.limit, "scrapeOptions": {"onlyMainContent": request.options.only_main_content}, } - if request.options.max_depth: - params["maxDepth"] = request.options.max_depth + + # Add optional prompt for Firecrawl v2 crawl-params compatibility + if request.options.prompt: + params["prompt"] = request.options.prompt job_id = firecrawl_app.crawl_url(request.url, params) website_crawl_time_cache_key = f"website_crawl_{job_id}" From 4d8b8f92105b5b719aa1b2ef74cdc187a10d7b48 Mon Sep 17 00:00:00 2001 From: znn Date: Wed, 15 Oct 2025 08:49:53 +0530 Subject: [PATCH 05/11] allow editing of hidden inputs in preview (#24370) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com> --- .../components/before-run-form/form-item.tsx | 16 ++++++++++++++-- .../workflow/panel/debug-and-preview/index.tsx | 2 +- .../panel/debug-and-preview/user-input.tsx | 3 ++- web/i18n/en-US/workflow.ts | 1 + web/i18n/zh-Hans/workflow.ts | 1 + 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index fd1cd01abf..d6f56b8c43 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -118,8 +118,20 @@ const FormItem: FC = ({
{!isArrayLikeType && !isBooleanType && (
-
{typeof payload.label === 'object' ? nodeKey : payload.label}
- {!payload.required && {t('workflow.panel.optional')}} +
+ {typeof payload.label === 'object' ? nodeKey : payload.label} +
+ {payload.hide === true ? ( + + {t('workflow.panel.optional_and_hidden')} + + ) : ( + !payload.required && ( + + {t('workflow.panel.optional')} + + ) + )}
)}
diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index baf4c21dcd..9c64849dc5 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -39,7 +39,7 @@ const DebugAndPreview = () => { const selectedNode = nodes.find(node => node.data.selected) const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const variables = startNode?.data.variables || [] - const visibleVariables = variables.filter(v => v.hide !== true) + const visibleVariables = variables const [showConversationVariableModal, setShowConversationVariableModal] = useState(false) diff --git a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx index 0670d9a52b..b7ed2a54fd 100644 --- a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx @@ -14,10 +14,11 @@ import cn from '@/utils/classnames' const UserInput = () => { const workflowStore = useWorkflowStore() const inputs = useStore(s => s.inputs) + const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel) const nodes = useNodes() const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const variables = startNode?.data.variables || [] - const visibleVariables = variables.filter(v => v.hide !== true) + const visibleVariables = showDebugAndPreviewPanel ? variables : variables.filter(v => v.hide !== true) const handleValueChange = (variable: string, v: string) => { const { diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index a5862bef86..01d17a4111 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -340,6 +340,7 @@ const translation = { optional: '(optional)', maximize: 'Maximize Canvas', minimize: 'Exit Full Screen', + optional_and_hidden: '(optional & hidden)', }, nodes: { common: { diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index a497bca56a..441c0a707e 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -340,6 +340,7 @@ const translation = { moveToThisNode: '定位至此节点', maximize: '最大化画布', minimize: '退出最大化', + optional_and_hidden: '(选填 & 隐藏)', }, nodes: { common: { From cff5de626b2c237c3272f371ecb94b908855bfae Mon Sep 17 00:00:00 2001 From: yangzheli <43645580+yangzheli@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:07:51 +0800 Subject: [PATCH 06/11] feat(agent): similar to the start node of workflow, agent variables also support drag-and-drop (#26899) --- .../app/configuration/config-var/index.tsx | 52 ++++++++++++++----- .../app/configuration/config-var/var-item.tsx | 12 ++++- .../_base/components/variable/var-list.tsx | 1 - .../nodes/start/components/var-list.tsx | 47 +++++++---------- 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 2ac68227e3..b9227c6846 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -1,10 +1,11 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { useContext } from 'use-context-selector' import produce from 'immer' +import { ReactSortable } from 'react-sortablejs' import Panel from '../base/feature-panel' import EditModal from './config-modal' import VarItem from './var-item' @@ -22,6 +23,7 @@ import { useModalContext } from '@/context/modal-context' import { useEventEmitterContextContext } from '@/context/event-emitter' import type { InputVar } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL' @@ -218,6 +220,16 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar showEditModal() } + + const promptVariablesWithIds = useMemo(() => promptVariables.map((item) => { + return { + id: item.key, + variable: { ...item }, + } + }), [promptVariables]) + + const canDrag = !readonly && promptVariables.length > 1 + return ( = ({ promptVariables, readonly, onPromptVar )} {hasVar && (
- {promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => ( - handleConfig({ type, key, index, name, config, icon, icon_background })} - onRemove={() => handleRemoveVar(index)} - /> - ))} + { onPromptVariablesChange?.(list.map(item => item.variable)) }} + handle='.handle' + ghostClass='opacity-50' + animation={150} + > + {promptVariablesWithIds.map((item, index) => { + const { key, name, type, required, config, icon, icon_background } = item.variable + return ( + handleConfig({ type, key, index, name, config, icon, icon_background })} + onRemove={() => handleRemoveVar(index)} + canDrag={canDrag} + /> + ) + })} +
)} diff --git a/web/app/components/app/configuration/config-var/var-item.tsx b/web/app/components/app/configuration/config-var/var-item.tsx index 78ed4b1031..88cd5d7843 100644 --- a/web/app/components/app/configuration/config-var/var-item.tsx +++ b/web/app/components/app/configuration/config-var/var-item.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react' import React, { useState } from 'react' import { RiDeleteBinLine, + RiDraggable, RiEditLine, } from '@remixicon/react' import type { IInputTypeIconProps } from './input-type-icon' @@ -12,6 +13,7 @@ import Badge from '@/app/components/base/badge' import cn from '@/utils/classnames' type ItemProps = { + className?: string readonly?: boolean name: string label: string @@ -19,9 +21,11 @@ type ItemProps = { type: string onEdit: () => void onRemove: () => void + canDrag?: boolean } const VarItem: FC = ({ + className, readonly, name, label, @@ -29,12 +33,16 @@ const VarItem: FC = ({ type, onEdit, onRemove, + canDrag, }) => { const [isDeleting, setIsDeleting] = useState(false) return ( -
- +
+ + {canDrag && ( + + )}
{name} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index 447dfe1f40..5e84a518cc 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -61,7 +61,6 @@ const VarList: FC = ({ return } if (list.some(item => item.variable?.trim() === newKey.trim())) { - console.log('new key', newKey.trim()) setToastHandle(Toast.notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), diff --git a/web/app/components/workflow/nodes/start/components/var-list.tsx b/web/app/components/workflow/nodes/start/components/var-list.tsx index bbfeed461a..36524b482f 100644 --- a/web/app/components/workflow/nodes/start/components/var-list.tsx +++ b/web/app/components/workflow/nodes/start/components/var-list.tsx @@ -5,7 +5,6 @@ import produce from 'immer' import { useTranslation } from 'react-i18next' import VarItem from './var-item' import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types' -import { v4 as uuid4 } from 'uuid' import { ReactSortable } from 'react-sortablejs' import { RiDraggable } from '@remixicon/react' import cn from '@/utils/classnames' @@ -71,9 +70,8 @@ const VarList: FC = ({ }, [list, onChange]) const listWithIds = useMemo(() => list.map((item) => { - const id = uuid4() return { - id, + id: item.variable, variable: { ...item }, } }), [list]) @@ -88,6 +86,8 @@ const VarList: FC = ({ ) } + const canDrag = !readonly && varCount > 1 + return ( = ({ ghostClass='opacity-50' animation={150} > - {list.map((item, index) => { - const canDrag = (() => { - if (readonly) - return false - return varCount > 1 - })() - return ( -
- item.variable)} - canDrag={canDrag} - /> - {canDrag &&
- ) - })} + {listWithIds.map((itemWithId, index) => ( +
+ item.variable)} + canDrag={canDrag} + /> + {canDrag &&
+ ))}
) } From be7ee380bca7f7ae960214b88f52e15984858ca8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:36:39 +0800 Subject: [PATCH 07/11] chore: translate i18n files and update type definitions (#26916) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- web/i18n/de-DE/workflow.ts | 1 + web/i18n/es-ES/workflow.ts | 1 + web/i18n/fa-IR/workflow.ts | 1 + web/i18n/fr-FR/workflow.ts | 1 + web/i18n/hi-IN/workflow.ts | 1 + web/i18n/id-ID/workflow.ts | 1 + web/i18n/it-IT/workflow.ts | 1 + web/i18n/ja-JP/workflow.ts | 1 + web/i18n/ko-KR/workflow.ts | 1 + web/i18n/pl-PL/workflow.ts | 1 + web/i18n/pt-BR/workflow.ts | 1 + web/i18n/ro-RO/workflow.ts | 1 + web/i18n/ru-RU/workflow.ts | 1 + web/i18n/sl-SI/workflow.ts | 1 + web/i18n/th-TH/workflow.ts | 1 + web/i18n/tr-TR/workflow.ts | 1 + web/i18n/uk-UA/workflow.ts | 1 + web/i18n/vi-VN/workflow.ts | 1 + web/i18n/zh-Hant/workflow.ts | 1 + 19 files changed, 19 insertions(+) diff --git a/web/i18n/de-DE/workflow.ts b/web/i18n/de-DE/workflow.ts index ff1b9d76f9..55bf4ddf72 100644 --- a/web/i18n/de-DE/workflow.ts +++ b/web/i18n/de-DE/workflow.ts @@ -328,6 +328,7 @@ const translation = { changeBlock: 'Knoten ändern', maximize: 'Maximiere die Leinwand', minimize: 'Vollbildmodus beenden', + optional_and_hidden: '(optional & hidden)', }, nodes: { common: { diff --git a/web/i18n/es-ES/workflow.ts b/web/i18n/es-ES/workflow.ts index cc1a19bfbf..b4250284aa 100644 --- a/web/i18n/es-ES/workflow.ts +++ b/web/i18n/es-ES/workflow.ts @@ -328,6 +328,7 @@ const translation = { selectNextStep: 'Seleccionar siguiente paso', maximize: 'Maximizar Canvas', minimize: 'Salir de pantalla completa', + optional_and_hidden: '(opcional y oculto)', }, nodes: { common: { diff --git a/web/i18n/fa-IR/workflow.ts b/web/i18n/fa-IR/workflow.ts index c6e002a5f2..94d966f2a9 100644 --- a/web/i18n/fa-IR/workflow.ts +++ b/web/i18n/fa-IR/workflow.ts @@ -328,6 +328,7 @@ const translation = { addNextStep: 'مرحله بعدی را به این فرآیند اضافه کنید', minimize: 'خروج از حالت تمام صفحه', maximize: 'بیشینه‌سازی بوم', + optional_and_hidden: '(اختیاری و پنهان)', }, nodes: { common: { diff --git a/web/i18n/fr-FR/workflow.ts b/web/i18n/fr-FR/workflow.ts index f5d76e7579..e7389b4b23 100644 --- a/web/i18n/fr-FR/workflow.ts +++ b/web/i18n/fr-FR/workflow.ts @@ -328,6 +328,7 @@ const translation = { changeBlock: 'Changer de nœud', maximize: 'Maximiser le Canvas', minimize: 'Sortir du mode plein écran', + optional_and_hidden: '(optionnel et caché)', }, nodes: { common: { diff --git a/web/i18n/hi-IN/workflow.ts b/web/i18n/hi-IN/workflow.ts index 382a570a17..bf4c6cd8d3 100644 --- a/web/i18n/hi-IN/workflow.ts +++ b/web/i18n/hi-IN/workflow.ts @@ -340,6 +340,7 @@ const translation = { organizeBlocks: 'नोड्स का आयोजन करें', minimize: 'पूर्ण स्क्रीन से बाहर निकलें', maximize: 'कैनवास का अधिकतम लाभ उठाएँ', + optional_and_hidden: '(वैकल्पिक और छिपा हुआ)', }, nodes: { common: { diff --git a/web/i18n/id-ID/workflow.ts b/web/i18n/id-ID/workflow.ts index 969b7bb8b0..2ec7ace93f 100644 --- a/web/i18n/id-ID/workflow.ts +++ b/web/i18n/id-ID/workflow.ts @@ -325,6 +325,7 @@ const translation = { changeBlock: 'Ubah Node', runThisStep: 'Jalankan langkah ini', maximize: 'Maksimalkan Kanvas', + optional_and_hidden: '(opsional & tersembunyi)', }, nodes: { common: { diff --git a/web/i18n/it-IT/workflow.ts b/web/i18n/it-IT/workflow.ts index b032f1a59b..a874c7a9b6 100644 --- a/web/i18n/it-IT/workflow.ts +++ b/web/i18n/it-IT/workflow.ts @@ -343,6 +343,7 @@ const translation = { addNextStep: 'Aggiungi il prossimo passo in questo flusso di lavoro', minimize: 'Esci dalla modalità schermo intero', maximize: 'Massimizza Canvas', + optional_and_hidden: '(opzionale e nascosto)', }, nodes: { common: { diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index a4ffc95f68..b3b61f974f 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -340,6 +340,7 @@ const translation = { moveToThisNode: 'このノードに移動する', maximize: 'キャンバスを最大化する', minimize: '全画面を終了する', + optional_and_hidden: '(オプションおよび非表示)', }, nodes: { common: { diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts index 75c607a004..2199054f0e 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -349,6 +349,7 @@ const translation = { addNextStep: '이 워크플로우에 다음 단계를 추가하세요.', minimize: '전체 화면 종료', maximize: '캔버스 전체 화면', + optional_and_hidden: '(선택 사항 및 숨김)', }, nodes: { common: { diff --git a/web/i18n/pl-PL/workflow.ts b/web/i18n/pl-PL/workflow.ts index 1d2a892941..7eac765449 100644 --- a/web/i18n/pl-PL/workflow.ts +++ b/web/i18n/pl-PL/workflow.ts @@ -328,6 +328,7 @@ const translation = { organizeBlocks: 'Organizuj węzły', minimize: 'Wyjdź z trybu pełnoekranowego', maximize: 'Maksymalizuj płótno', + optional_and_hidden: '(opcjonalne i ukryte)', }, nodes: { common: { diff --git a/web/i18n/pt-BR/workflow.ts b/web/i18n/pt-BR/workflow.ts index 5610cacc13..9dc7439286 100644 --- a/web/i18n/pt-BR/workflow.ts +++ b/web/i18n/pt-BR/workflow.ts @@ -328,6 +328,7 @@ const translation = { selectNextStep: 'Selecione o próximo passo', maximize: 'Maximize Canvas', minimize: 'Sair do Modo Tela Cheia', + optional_and_hidden: '(opcional & oculto)', }, nodes: { common: { diff --git a/web/i18n/ro-RO/workflow.ts b/web/i18n/ro-RO/workflow.ts index d2239e7979..b91cecac30 100644 --- a/web/i18n/ro-RO/workflow.ts +++ b/web/i18n/ro-RO/workflow.ts @@ -328,6 +328,7 @@ const translation = { selectNextStep: 'Selectați Pasul Următor', maximize: 'Maximize Canvas', minimize: 'Iesi din modul pe tot ecranul', + optional_and_hidden: '(opțional și ascuns)', }, nodes: { common: { diff --git a/web/i18n/ru-RU/workflow.ts b/web/i18n/ru-RU/workflow.ts index 2345f3447b..f3be73ed99 100644 --- a/web/i18n/ru-RU/workflow.ts +++ b/web/i18n/ru-RU/workflow.ts @@ -328,6 +328,7 @@ const translation = { changeBlock: 'Изменить узел', minimize: 'Выйти из полноэкранного режима', maximize: 'Максимизировать холст', + optional_and_hidden: '(необязательно и скрыто)', }, nodes: { common: { diff --git a/web/i18n/sl-SI/workflow.ts b/web/i18n/sl-SI/workflow.ts index 7a167c236f..0aa445e820 100644 --- a/web/i18n/sl-SI/workflow.ts +++ b/web/i18n/sl-SI/workflow.ts @@ -335,6 +335,7 @@ const translation = { minimize: 'Izhod iz celotnega zaslona', maximize: 'Maksimiziraj platno', optional: '(neobvezno)', + optional_and_hidden: '(neobvezno in skrito)', }, nodes: { common: { diff --git a/web/i18n/th-TH/workflow.ts b/web/i18n/th-TH/workflow.ts index 1cea01690a..3491814b73 100644 --- a/web/i18n/th-TH/workflow.ts +++ b/web/i18n/th-TH/workflow.ts @@ -328,6 +328,7 @@ const translation = { selectNextStep: 'เลือกขั้นตอนถัดไป', minimize: 'ออกจากโหมดเต็มหน้าจอ', maximize: 'เพิ่มประสิทธิภาพผ้าใบ', + optional_and_hidden: '(ตัวเลือก & ซ่อน)', }, nodes: { common: { diff --git a/web/i18n/tr-TR/workflow.ts b/web/i18n/tr-TR/workflow.ts index dfab5c2c0c..bd853917b2 100644 --- a/web/i18n/tr-TR/workflow.ts +++ b/web/i18n/tr-TR/workflow.ts @@ -328,6 +328,7 @@ const translation = { selectNextStep: 'Sonraki Adımı Seç', minimize: 'Tam Ekrandan Çık', maximize: 'Kanvası Maksimize Et', + optional_and_hidden: '(isteğe bağlı ve gizli)', }, nodes: { common: { diff --git a/web/i18n/uk-UA/workflow.ts b/web/i18n/uk-UA/workflow.ts index 09f2b71eea..069cbcf74c 100644 --- a/web/i18n/uk-UA/workflow.ts +++ b/web/i18n/uk-UA/workflow.ts @@ -328,6 +328,7 @@ const translation = { addNextStep: 'Додайте наступний крок у цей робочий процес', minimize: 'Вийти з повноекранного режиму', maximize: 'Максимізувати полотно', + optional_and_hidden: '(необов\'язково & приховано)', }, nodes: { common: { diff --git a/web/i18n/vi-VN/workflow.ts b/web/i18n/vi-VN/workflow.ts index 27d19a37f4..f7b62ee78d 100644 --- a/web/i18n/vi-VN/workflow.ts +++ b/web/i18n/vi-VN/workflow.ts @@ -328,6 +328,7 @@ const translation = { addNextStep: 'Thêm bước tiếp theo trong quy trình này', maximize: 'Tối đa hóa Canvas', minimize: 'Thoát chế độ toàn màn hình', + optional_and_hidden: '(tùy chọn & ẩn)', }, nodes: { common: { diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 6acace8e4a..83464e5a7c 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -328,6 +328,7 @@ const translation = { moveToThisNode: '定位至此節點', minimize: '退出全螢幕', maximize: '最大化畫布', + optional_and_hidden: '(可選且隱藏)', }, nodes: { common: { From 433dad7e1ad9d13293610e6b259196b69fa8cd5b Mon Sep 17 00:00:00 2001 From: GuanMu Date: Wed, 15 Oct 2025 16:37:46 +0800 Subject: [PATCH 08/11] chore: add type-check script to package.json for TypeScript validation (#26929) --- web/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/web/package.json b/web/package.json index 562938d5b3..7c2f30aa61 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ "lint:fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix", "lint:quiet": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", "lint:complexity": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --rule 'complexity: [error, {max: 15}]' --quiet", + "type-check": "tsc --noEmit", "prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky", "gen-icons": "node ./app/components/base/icons/script.mjs", "uglify-embed": "node ./bin/uglify-embed", From 3474c179e691a43fc6357e070092902faa986501 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:46:46 +0800 Subject: [PATCH 09/11] fix: enhance dataset menu and add service API translations (#26931) --- .../components/app-sidebar/dataset-info/menu.tsx | 14 +++++++++----- web/i18n/de-DE/dataset.ts | 11 +++++++++++ web/i18n/en-US/dataset.ts | 4 ++-- web/i18n/es-ES/dataset.ts | 11 +++++++++++ web/i18n/fa-IR/dataset.ts | 11 +++++++++++ web/i18n/fr-FR/dataset.ts | 11 +++++++++++ web/i18n/hi-IN/dataset.ts | 11 +++++++++++ web/i18n/id-ID/dataset.ts | 11 +++++++++++ web/i18n/it-IT/dataset.ts | 11 +++++++++++ web/i18n/ja-JP/dataset.ts | 4 ++-- web/i18n/ko-KR/dataset.ts | 11 +++++++++++ web/i18n/pl-PL/dataset.ts | 11 +++++++++++ web/i18n/pt-BR/dataset.ts | 11 +++++++++++ web/i18n/ro-RO/dataset.ts | 11 +++++++++++ web/i18n/ru-RU/dataset.ts | 11 +++++++++++ web/i18n/sl-SI/dataset.ts | 11 +++++++++++ web/i18n/th-TH/dataset.ts | 11 +++++++++++ web/i18n/tr-TR/dataset.ts | 11 +++++++++++ web/i18n/uk-UA/dataset.ts | 11 +++++++++++ web/i18n/vi-VN/dataset.ts | 11 +++++++++++ web/i18n/zh-Hans/dataset.ts | 4 ++-- web/i18n/zh-Hant/dataset.ts | 11 +++++++++++ 22 files changed, 213 insertions(+), 11 deletions(-) diff --git a/web/app/components/app-sidebar/dataset-info/menu.tsx b/web/app/components/app-sidebar/dataset-info/menu.tsx index fd560ce643..6f91c9c513 100644 --- a/web/app/components/app-sidebar/dataset-info/menu.tsx +++ b/web/app/components/app-sidebar/dataset-info/menu.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import MenuItem from './menu-item' import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react' import Divider from '../../base/divider' +import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' type MenuProps = { showDelete: boolean @@ -18,6 +19,7 @@ const Menu = ({ detectIsUsedByApp, }: MenuProps) => { const { t } = useTranslation() + const runtimeMode = useDatasetDetailContextWithSelector(state => state.dataset?.runtime_mode) return (
@@ -27,11 +29,13 @@ const Menu = ({ name={t('common.operation.edit')} handleClick={openRenameModal} /> - + {runtimeMode === 'rag_pipeline' && ( + + )}
{showDelete && ( <> diff --git a/web/i18n/de-DE/dataset.ts b/web/i18n/de-DE/dataset.ts index fbb52878f8..0b9d08a984 100644 --- a/web/i18n/de-DE/dataset.ts +++ b/web/i18n/de-DE/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Aktualisierte', externalKnowledgeBase: 'Externe Wissensdatenbank', createFromPipeline: 'Aus Wissenspipeline erstellen', + serviceApi: { + card: { + title: 'Backend-Dienst-API', + apiReference: 'API Referenz', + apiKey: 'API-Schlüssel', + endpoint: 'Service-API-Endpunkt', + }, + title: 'Service-API', + enabled: 'Im Dienst', + disabled: 'Behindert', + }, } export default translation diff --git a/web/i18n/en-US/dataset.ts b/web/i18n/en-US/dataset.ts index f0ad06121c..b89a1fbd34 100644 --- a/web/i18n/en-US/dataset.ts +++ b/web/i18n/en-US/dataset.ts @@ -90,8 +90,8 @@ const translation = { intro2: 'as a context', intro3: ',', intro4: 'or it ', - intro5: 'can be created', - intro6: ' as a standalone ChatGPT index plug-in to publish', + intro5: 'can be published', + intro6: ' as an independent service.', unavailable: 'Unavailable', unavailableTip: 'Embedding model is not available, the default embedding model needs to be configured', datasets: 'KNOWLEDGE', diff --git a/web/i18n/es-ES/dataset.ts b/web/i18n/es-ES/dataset.ts index 785e2c962b..4fbdae1239 100644 --- a/web/i18n/es-ES/dataset.ts +++ b/web/i18n/es-ES/dataset.ts @@ -226,6 +226,17 @@ const translation = { externalKnowledgeBase: 'Base de conocimientos externa', createFromPipeline: 'Crear desde Knowledge Pipeline', updated: 'Actualizado', + serviceApi: { + card: { + apiReference: 'Referencia de la API', + apiKey: 'Clave API', + endpoint: 'Punto de enlace de la API de servicio', + title: 'API del servicio de backend', + }, + enabled: 'En servicio', + title: 'API de servicios', + disabled: 'Discapacitado', + }, } export default translation diff --git a/web/i18n/fa-IR/dataset.ts b/web/i18n/fa-IR/dataset.ts index b4a6f1f97d..f0c1a69044 100644 --- a/web/i18n/fa-IR/dataset.ts +++ b/web/i18n/fa-IR/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'بروز رسانی', createFromPipeline: 'ایجاد از پایپ لاین دانش', externalKnowledgeBase: 'پایگاه دانش خارجی', + serviceApi: { + card: { + apiKey: 'کلید API', + title: 'رابط برنامه‌نویسی سرویس پشتیبان', + apiReference: 'مرجع API', + endpoint: 'نقطه انتهایی رابط برنامه‌نویسی سرویس', + }, + disabled: 'معلول', + enabled: 'در حال خدمت', + title: 'رابط برنامه‌نویسی سرویس', + }, } export default translation diff --git a/web/i18n/fr-FR/dataset.ts b/web/i18n/fr-FR/dataset.ts index d267356ce7..2a18ae9f6b 100644 --- a/web/i18n/fr-FR/dataset.ts +++ b/web/i18n/fr-FR/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Actualisé', createFromPipeline: 'Créer à partir du pipeline de connaissances', externalKnowledgeBase: 'Base de connaissances externe', + serviceApi: { + card: { + apiKey: 'Clé API', + apiReference: 'Référence API', + title: 'API du service backend', + endpoint: 'Point de terminaison de l\'API', + }, + enabled: 'En service', + title: 'API de service', + disabled: 'désactivé', + }, } export default translation diff --git a/web/i18n/hi-IN/dataset.ts b/web/i18n/hi-IN/dataset.ts index 360b57d21d..fa1948c497 100644 --- a/web/i18n/hi-IN/dataset.ts +++ b/web/i18n/hi-IN/dataset.ts @@ -233,6 +233,17 @@ const translation = { updated: 'अपडेट किया गया', externalKnowledgeBase: 'बाहरी ज्ञान आधार', createFromPipeline: 'ज्ञान पाइपलाइन से बनाएं', + serviceApi: { + card: { + apiReference: 'एपीआई संदर्भ', + apiKey: 'एपीआई कुंजी', + title: 'बैकएंड सेवा एपीआई', + endpoint: 'सेवा एपीआई एंडपॉइंट', + }, + enabled: 'सेवा में', + disabled: 'अक्षम', + title: 'सेवा एपीआई', + }, } export default translation diff --git a/web/i18n/id-ID/dataset.ts b/web/i18n/id-ID/dataset.ts index ac421c4d1a..4c41fb0942 100644 --- a/web/i18n/id-ID/dataset.ts +++ b/web/i18n/id-ID/dataset.ts @@ -219,6 +219,17 @@ const translation = { updated: 'Diperbarui', createFromPipeline: 'Membuat dari Knowledge Pipeline', externalKnowledgeBase: 'Basis Pengetahuan Eksternal', + serviceApi: { + card: { + apiKey: 'Kunci API', + apiReference: 'Referensi API', + title: 'API layanan backend', + endpoint: 'Titik Akhir API Layanan', + }, + title: 'API Layanan', + enabled: 'Sedang Beroperasi', + disabled: 'Dinonaktifkan', + }, } export default translation diff --git a/web/i18n/it-IT/dataset.ts b/web/i18n/it-IT/dataset.ts index 99a21f12e0..7489034e53 100644 --- a/web/i18n/it-IT/dataset.ts +++ b/web/i18n/it-IT/dataset.ts @@ -233,6 +233,17 @@ const translation = { updated: 'Aggiornato', externalKnowledgeBase: 'Base di conoscenza esterna', createFromPipeline: 'Creazione da pipeline di conoscenza', + serviceApi: { + card: { + endpoint: 'Endpoint dell\'API di servizio', + apiKey: 'Chiave API', + title: 'API del servizio backend', + apiReference: 'Riferimento API', + }, + disabled: 'Disabilitato', + title: 'API di servizio', + enabled: 'In servizio', + }, } export default translation diff --git a/web/i18n/ja-JP/dataset.ts b/web/i18n/ja-JP/dataset.ts index 1e96961751..02afcd453a 100644 --- a/web/i18n/ja-JP/dataset.ts +++ b/web/i18n/ja-JP/dataset.ts @@ -87,8 +87,8 @@ const translation = { intro2: 'コンテキストとして', intro3: '、', intro4: 'または', - intro5: '作成することができます', - intro6: '単体の ChatGPT インデックスプラグインとして公開するために', + intro5: '公開することができます', + intro6: '独立したサービスとして', unavailable: '利用不可', unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります', datasets: 'ナレッジベース', diff --git a/web/i18n/ko-KR/dataset.ts b/web/i18n/ko-KR/dataset.ts index 2bbed9a339..7f6153f968 100644 --- a/web/i18n/ko-KR/dataset.ts +++ b/web/i18n/ko-KR/dataset.ts @@ -225,6 +225,17 @@ const translation = { updated: '업데이트', externalKnowledgeBase: '외부 기술 자료', createFromPipeline: '지식 파이프라인에서 만들기', + serviceApi: { + card: { + apiReference: 'API 참고', + endpoint: '서비스 API 엔드포인트', + apiKey: 'API 키', + title: '백엔드 서비스 API', + }, + enabled: '서비스 중', + title: '서비스 API', + disabled: '장애인', + }, } export default translation diff --git a/web/i18n/pl-PL/dataset.ts b/web/i18n/pl-PL/dataset.ts index 4267736a35..5c1d3630e9 100644 --- a/web/i18n/pl-PL/dataset.ts +++ b/web/i18n/pl-PL/dataset.ts @@ -232,6 +232,17 @@ const translation = { updated: 'Aktualizowano', createFromPipeline: 'Tworzenie na podstawie potoku wiedzy', externalKnowledgeBase: 'Zewnętrzna baza wiedzy', + serviceApi: { + card: { + apiKey: 'Klucz API', + title: 'Usługa backendowa API', + apiReference: 'Dokumentacja API', + endpoint: 'Punkt końcowy API usługi', + }, + title: 'Interfejs API usługi', + disabled: 'Niepełnosprawny', + enabled: 'W serwisie', + }, } export default translation diff --git a/web/i18n/pt-BR/dataset.ts b/web/i18n/pt-BR/dataset.ts index ebbf0a1d0d..0983eddcf6 100644 --- a/web/i18n/pt-BR/dataset.ts +++ b/web/i18n/pt-BR/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Atualizado', externalKnowledgeBase: 'Base de conhecimento externa', createFromPipeline: 'Criar a partir do pipeline de conhecimento', + serviceApi: { + card: { + apiKey: 'Chave de API', + apiReference: 'Referência da API', + title: 'API de serviço de backend', + endpoint: 'Endpoint da API de Serviço', + }, + enabled: 'Em serviço', + title: 'API de Serviço', + disabled: 'Desativado', + }, } export default translation diff --git a/web/i18n/ro-RO/dataset.ts b/web/i18n/ro-RO/dataset.ts index dcdc081779..29efbd10fc 100644 --- a/web/i18n/ro-RO/dataset.ts +++ b/web/i18n/ro-RO/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Actualizat', externalKnowledgeBase: 'Baza de cunoștințe externă', createFromPipeline: 'Crearea din Knowledge Pipeline', + serviceApi: { + card: { + title: 'API pentru serviciul backend', + apiReference: 'Referință API', + endpoint: 'Punct final API de servicii', + apiKey: 'Cheie API', + }, + disabled: 'Dezactivat', + enabled: 'În serviciu', + title: 'API de servicii', + }, } export default translation diff --git a/web/i18n/ru-RU/dataset.ts b/web/i18n/ru-RU/dataset.ts index d1701b4be5..1b8c8d4c31 100644 --- a/web/i18n/ru-RU/dataset.ts +++ b/web/i18n/ru-RU/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Обновлено', externalKnowledgeBase: 'Внешняя база знаний', createFromPipeline: 'Создание из конвейера знаний', + serviceApi: { + card: { + apiReference: 'Справочник API', + title: 'API бэкенд-сервиса', + apiKey: 'API ключ', + endpoint: 'Конечная точка API сервиса', + }, + enabled: 'На службе', + title: 'Сервисный API', + disabled: 'Отключено', + }, } export default translation diff --git a/web/i18n/sl-SI/dataset.ts b/web/i18n/sl-SI/dataset.ts index fab7a11241..cc84adf851 100644 --- a/web/i18n/sl-SI/dataset.ts +++ b/web/i18n/sl-SI/dataset.ts @@ -226,6 +226,17 @@ const translation = { createFromPipeline: 'Ustvarjanje iz cevovoda znanja', updated: 'Posodobljene', externalKnowledgeBase: 'Zunanja baza znanja', + serviceApi: { + card: { + apiKey: 'API ključ', + endpoint: 'Vhodna točka API storitve', + title: 'API storitev za zaledje', + apiReference: 'API Referenca', + }, + title: 'Storitveni API', + disabled: 'Onemogočeno', + enabled: 'V storitvi', + }, } export default translation diff --git a/web/i18n/th-TH/dataset.ts b/web/i18n/th-TH/dataset.ts index 1a49ba1626..58ddf8ba8e 100644 --- a/web/i18n/th-TH/dataset.ts +++ b/web/i18n/th-TH/dataset.ts @@ -225,6 +225,17 @@ const translation = { updated: 'ปรับ ปรุง', externalKnowledgeBase: 'ฐานความรู้ภายนอก', createFromPipeline: 'สร้างจากไปป์ไลน์ความรู้', + serviceApi: { + card: { + title: 'บริการแบ็กเอนด์ API', + apiReference: 'เอกสารอ้างอิง API', + apiKey: 'กุญแจ API', + endpoint: 'จุดเชื่อมต่อ API บริการ', + }, + enabled: 'ให้บริการ', + disabled: 'ถูกปิดใช้งาน', + title: 'บริการ API', + }, } export default translation diff --git a/web/i18n/tr-TR/dataset.ts b/web/i18n/tr-TR/dataset.ts index fea5ab5bb0..e290dfe711 100644 --- a/web/i18n/tr-TR/dataset.ts +++ b/web/i18n/tr-TR/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Güncel -leştirilmiş', createFromPipeline: 'Bilgi İşlem Hattından Oluşturun', externalKnowledgeBase: 'Harici Bilgi Bankası', + serviceApi: { + card: { + apiReference: 'API Referansı', + title: 'Backend servis api', + apiKey: 'API Anahtarı', + endpoint: 'Hizmet API Uç Noktası', + }, + disabled: 'Engelli', + enabled: 'Hizmette', + title: 'Servis API\'si', + }, } export default translation diff --git a/web/i18n/uk-UA/dataset.ts b/web/i18n/uk-UA/dataset.ts index 5732d80718..61972ac565 100644 --- a/web/i18n/uk-UA/dataset.ts +++ b/web/i18n/uk-UA/dataset.ts @@ -227,6 +227,17 @@ const translation = { updated: 'Оновлено', createFromPipeline: 'Створюйте на основі Knowledge Pipeline', externalKnowledgeBase: 'Зовнішня база знань', + serviceApi: { + card: { + title: 'API бекенд-сервіс', + apiReference: 'Посилання на API', + apiKey: 'Ключ API', + endpoint: 'Кінцева точка API сервісу', + }, + disabled: 'Вимкнено', + enabled: 'У службі', + title: 'Сервісний API', + }, } export default translation diff --git a/web/i18n/vi-VN/dataset.ts b/web/i18n/vi-VN/dataset.ts index 695e97e6ea..e5ffd5b61b 100644 --- a/web/i18n/vi-VN/dataset.ts +++ b/web/i18n/vi-VN/dataset.ts @@ -226,6 +226,17 @@ const translation = { updated: 'Cập nhật', createFromPipeline: 'Tạo từ quy trình kiến thức', externalKnowledgeBase: 'Cơ sở kiến thức bên ngoài', + serviceApi: { + card: { + title: 'API dịch vụ backend', + endpoint: 'Điểm cuối API dịch vụ', + apiKey: 'Khóa API', + apiReference: 'Tham chiếu API', + }, + enabled: 'Đang phục vụ', + disabled: 'Vô hiệu hóa', + title: 'Giao diện lập trình dịch vụ', + }, } export default translation diff --git a/web/i18n/zh-Hans/dataset.ts b/web/i18n/zh-Hans/dataset.ts index 641925a425..69a92b5529 100644 --- a/web/i18n/zh-Hans/dataset.ts +++ b/web/i18n/zh-Hans/dataset.ts @@ -90,8 +90,8 @@ const translation = { intro2: '作为上下文', intro3: ',', intro4: '或可以', - intro5: '创建', - intro6: '为独立的 ChatGPT 插件发布使用', + intro5: '发布', + intro6: '为独立的服务', unavailable: '不可用', unavailableTip: '由于 embedding 模型不可用,需要配置默认 embedding 模型', datasets: '知识库', diff --git a/web/i18n/zh-Hant/dataset.ts b/web/i18n/zh-Hant/dataset.ts index fcc16963fa..f037ec7a70 100644 --- a/web/i18n/zh-Hant/dataset.ts +++ b/web/i18n/zh-Hant/dataset.ts @@ -226,6 +226,17 @@ const translation = { externalKnowledgeBase: '外部知識庫', createFromPipeline: '從知識管線建立', updated: '更新時間', + serviceApi: { + card: { + title: '後端服務 API', + apiReference: 'API 參考', + endpoint: '服務 API 端點', + apiKey: 'API 金鑰', + }, + enabled: '使用中', + title: '服務 API', + disabled: '已停用', + }, } export default translation From 1d8cca4fa225e2ebcdef7a053c0eab06e8c0b017 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Wed, 15 Oct 2025 16:47:43 +0800 Subject: [PATCH 10/11] Fix: check external commands after node completion (#26891) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- .../command_channels/redis_channel.py | 19 +++ .../graph_engine/orchestration/dispatcher.py | 22 +++- .../command_channels/test_redis_channel.py | 121 +++++++++++++----- .../workflow/graph_engine/test_dispatcher.py | 104 +++++++++++++++ .../test_redis_stop_integration.py | 37 ++++-- 5 files changed, 260 insertions(+), 43 deletions(-) create mode 100644 api/tests/unit_tests/core/workflow/graph_engine/test_dispatcher.py diff --git a/api/core/workflow/graph_engine/command_channels/redis_channel.py b/api/core/workflow/graph_engine/command_channels/redis_channel.py index c841459170..527647ae3b 100644 --- a/api/core/workflow/graph_engine/command_channels/redis_channel.py +++ b/api/core/workflow/graph_engine/command_channels/redis_channel.py @@ -41,6 +41,7 @@ class RedisChannel: self._redis = redis_client self._key = channel_key self._command_ttl = command_ttl + self._pending_key = f"{channel_key}:pending" def fetch_commands(self) -> list[GraphEngineCommand]: """ @@ -49,6 +50,9 @@ class RedisChannel: Returns: List of pending commands (drains the Redis list) """ + if not self._has_pending_commands(): + return [] + commands: list[GraphEngineCommand] = [] # Use pipeline for atomic operations @@ -85,6 +89,7 @@ class RedisChannel: with self._redis.pipeline() as pipe: pipe.rpush(self._key, command_json) pipe.expire(self._key, self._command_ttl) + pipe.set(self._pending_key, "1", ex=self._command_ttl) pipe.execute() def _deserialize_command(self, data: dict[str, Any]) -> GraphEngineCommand | None: @@ -112,3 +117,17 @@ class RedisChannel: except (ValueError, TypeError): return None + + def _has_pending_commands(self) -> bool: + """ + Check and consume the pending marker to avoid unnecessary list reads. + + Returns: + True if commands should be fetched from Redis. + """ + with self._redis.pipeline() as pipe: + pipe.get(self._pending_key) + pipe.delete(self._pending_key) + pending_value, _ = pipe.execute() + + return pending_value is not None diff --git a/api/core/workflow/graph_engine/orchestration/dispatcher.py b/api/core/workflow/graph_engine/orchestration/dispatcher.py index a7229ce4e8..8340c10b49 100644 --- a/api/core/workflow/graph_engine/orchestration/dispatcher.py +++ b/api/core/workflow/graph_engine/orchestration/dispatcher.py @@ -8,7 +8,12 @@ import threading import time from typing import TYPE_CHECKING, final -from core.workflow.graph_events.base import GraphNodeEventBase +from core.workflow.graph_events import ( + GraphNodeEventBase, + NodeRunExceptionEvent, + NodeRunFailedEvent, + NodeRunSucceededEvent, +) from ..event_management import EventManager from .execution_coordinator import ExecutionCoordinator @@ -72,13 +77,16 @@ class Dispatcher: if self._thread and self._thread.is_alive(): self._thread.join(timeout=10.0) + _COMMAND_TRIGGER_EVENTS = ( + NodeRunSucceededEvent, + NodeRunFailedEvent, + NodeRunExceptionEvent, + ) + def _dispatcher_loop(self) -> None: """Main dispatcher loop.""" try: while not self._stop_event.is_set(): - # Check for commands - self._execution_coordinator.check_commands() - # Check for scaling self._execution_coordinator.check_scaling() @@ -87,6 +95,8 @@ class Dispatcher: event = self._event_queue.get(timeout=0.1) # Route to the event handler self._event_handler.dispatch(event) + if self._should_check_commands(event): + self._execution_coordinator.check_commands() self._event_queue.task_done() except queue.Empty: # Check if execution is complete @@ -102,3 +112,7 @@ class Dispatcher: # Signal the event emitter that execution is complete if self._event_emitter: self._event_emitter.mark_complete() + + def _should_check_commands(self, event: GraphNodeEventBase) -> bool: + """Return True if the event represents a node completion.""" + return isinstance(event, self._COMMAND_TRIGGER_EVENTS) diff --git a/api/tests/unit_tests/core/workflow/graph_engine/command_channels/test_redis_channel.py b/api/tests/unit_tests/core/workflow/graph_engine/command_channels/test_redis_channel.py index 7ebccf83a7..8677325d4e 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/command_channels/test_redis_channel.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/command_channels/test_redis_channel.py @@ -35,11 +35,15 @@ class TestRedisChannel: """Test sending a command to Redis.""" mock_redis = MagicMock() mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + context = MagicMock() + context.__enter__.return_value = mock_pipe + context.__exit__.return_value = None + mock_redis.pipeline.return_value = context channel = RedisChannel(mock_redis, "test:key", 3600) + pending_key = "test:key:pending" + # Create a test command command = GraphEngineCommand(command_type=CommandType.ABORT) @@ -55,6 +59,7 @@ class TestRedisChannel: # Verify expire was set mock_pipe.expire.assert_called_once_with("test:key", 3600) + mock_pipe.set.assert_called_once_with(pending_key, "1", ex=3600) # Verify execute was called mock_pipe.execute.assert_called_once() @@ -62,33 +67,48 @@ class TestRedisChannel: def test_fetch_commands_empty(self): """Test fetching commands when Redis list is empty.""" mock_redis = MagicMock() - mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context] - # Simulate empty list - mock_pipe.execute.return_value = [[], 1] # Empty list, delete successful + # No pending marker + pending_pipe.execute.return_value = [None, 0] + mock_redis.llen.return_value = 0 channel = RedisChannel(mock_redis, "test:key") commands = channel.fetch_commands() assert commands == [] - mock_pipe.lrange.assert_called_once_with("test:key", 0, -1) - mock_pipe.delete.assert_called_once_with("test:key") + mock_redis.pipeline.assert_called_once() + fetch_pipe.lrange.assert_not_called() + fetch_pipe.delete.assert_not_called() def test_fetch_commands_with_abort_command(self): """Test fetching abort commands from Redis.""" mock_redis = MagicMock() - mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] # Create abort command data abort_command = AbortCommand() command_json = json.dumps(abort_command.model_dump()) # Simulate Redis returning one command - mock_pipe.execute.return_value = [[command_json.encode()], 1] + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [[command_json.encode()], 1] channel = RedisChannel(mock_redis, "test:key") commands = channel.fetch_commands() @@ -100,9 +120,15 @@ class TestRedisChannel: def test_fetch_commands_multiple(self): """Test fetching multiple commands from Redis.""" mock_redis = MagicMock() - mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] # Create multiple commands command1 = GraphEngineCommand(command_type=CommandType.ABORT) @@ -112,7 +138,8 @@ class TestRedisChannel: command2_json = json.dumps(command2.model_dump()) # Simulate Redis returning multiple commands - mock_pipe.execute.return_value = [[command1_json.encode(), command2_json.encode()], 1] + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [[command1_json.encode(), command2_json.encode()], 1] channel = RedisChannel(mock_redis, "test:key") commands = channel.fetch_commands() @@ -124,9 +151,15 @@ class TestRedisChannel: def test_fetch_commands_skips_invalid_json(self): """Test that invalid JSON commands are skipped.""" mock_redis = MagicMock() - mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] # Mix valid and invalid JSON valid_command = AbortCommand() @@ -134,7 +167,8 @@ class TestRedisChannel: invalid_json = b"invalid json {" # Simulate Redis returning mixed valid/invalid commands - mock_pipe.execute.return_value = [[invalid_json, valid_json.encode()], 1] + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [[invalid_json, valid_json.encode()], 1] channel = RedisChannel(mock_redis, "test:key") commands = channel.fetch_commands() @@ -187,13 +221,20 @@ class TestRedisChannel: def test_atomic_fetch_and_clear(self): """Test that fetch_commands atomically fetches and clears the list.""" mock_redis = MagicMock() - mock_pipe = MagicMock() - mock_redis.pipeline.return_value.__enter__ = MagicMock(return_value=mock_pipe) - mock_redis.pipeline.return_value.__exit__ = MagicMock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] command = AbortCommand() command_json = json.dumps(command.model_dump()) - mock_pipe.execute.return_value = [[command_json.encode()], 1] + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [[command_json.encode()], 1] channel = RedisChannel(mock_redis, "test:key") @@ -202,7 +243,29 @@ class TestRedisChannel: assert len(commands) == 1 # Verify both lrange and delete were called in the pipeline - assert mock_pipe.lrange.call_count == 1 - assert mock_pipe.delete.call_count == 1 - mock_pipe.lrange.assert_called_with("test:key", 0, -1) - mock_pipe.delete.assert_called_with("test:key") + assert fetch_pipe.lrange.call_count == 1 + assert fetch_pipe.delete.call_count == 1 + fetch_pipe.lrange.assert_called_with("test:key", 0, -1) + fetch_pipe.delete.assert_called_with("test:key") + + def test_fetch_commands_without_pending_marker_returns_empty(self): + """Ensure we avoid unnecessary list reads when pending flag is missing.""" + mock_redis = MagicMock() + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] + + # Pending flag absent + pending_pipe.execute.return_value = [None, 0] + channel = RedisChannel(mock_redis, "test:key") + commands = channel.fetch_commands() + + assert commands == [] + mock_redis.llen.assert_not_called() + assert mock_redis.pipeline.call_count == 1 diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_dispatcher.py b/api/tests/unit_tests/core/workflow/graph_engine/test_dispatcher.py new file mode 100644 index 0000000000..830fc0884d --- /dev/null +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_dispatcher.py @@ -0,0 +1,104 @@ +"""Tests for dispatcher command checking behavior.""" + +from __future__ import annotations + +import queue +from datetime import datetime + +from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus +from core.workflow.graph_engine.event_management.event_manager import EventManager +from core.workflow.graph_engine.orchestration.dispatcher import Dispatcher +from core.workflow.graph_events import NodeRunStartedEvent, NodeRunSucceededEvent +from core.workflow.node_events import NodeRunResult + + +class _StubExecutionCoordinator: + """Stub execution coordinator that tracks command checks.""" + + def __init__(self) -> None: + self.command_checks = 0 + self.scaling_checks = 0 + self._execution_complete = False + self.mark_complete_called = False + self.failed = False + + def check_commands(self) -> None: + self.command_checks += 1 + + def check_scaling(self) -> None: + self.scaling_checks += 1 + + def is_execution_complete(self) -> bool: + return self._execution_complete + + def mark_complete(self) -> None: + self.mark_complete_called = True + + def mark_failed(self, error: Exception) -> None: # pragma: no cover - defensive, not triggered in tests + self.failed = True + + def set_execution_complete(self) -> None: + self._execution_complete = True + + +class _StubEventHandler: + """Minimal event handler that marks execution complete after handling an event.""" + + def __init__(self, coordinator: _StubExecutionCoordinator) -> None: + self._coordinator = coordinator + self.events = [] + + def dispatch(self, event) -> None: + self.events.append(event) + self._coordinator.set_execution_complete() + + +def _run_dispatcher_for_event(event) -> int: + """Run the dispatcher loop for a single event and return command check count.""" + event_queue: queue.Queue = queue.Queue() + event_queue.put(event) + + coordinator = _StubExecutionCoordinator() + event_handler = _StubEventHandler(coordinator) + event_manager = EventManager() + + dispatcher = Dispatcher( + event_queue=event_queue, + event_handler=event_handler, + event_collector=event_manager, + execution_coordinator=coordinator, + ) + + dispatcher._dispatcher_loop() + + return coordinator.command_checks + + +def _make_started_event() -> NodeRunStartedEvent: + return NodeRunStartedEvent( + id="start-event", + node_id="node-1", + node_type=NodeType.CODE, + node_title="Test Node", + start_at=datetime.utcnow(), + ) + + +def _make_succeeded_event() -> NodeRunSucceededEvent: + return NodeRunSucceededEvent( + id="success-event", + node_id="node-1", + node_type=NodeType.CODE, + node_title="Test Node", + start_at=datetime.utcnow(), + node_run_result=NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED), + ) + + +def test_dispatcher_checks_commands_after_node_completion() -> None: + """Dispatcher should only check commands after node completion events.""" + started_checks = _run_dispatcher_for_event(_make_started_event()) + succeeded_checks = _run_dispatcher_for_event(_make_succeeded_event()) + + assert started_checks == 0 + assert succeeded_checks == 1 diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_redis_stop_integration.py b/api/tests/unit_tests/core/workflow/graph_engine/test_redis_stop_integration.py index bd41fdeee5..e191246bed 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_redis_stop_integration.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_redis_stop_integration.py @@ -132,15 +132,22 @@ class TestRedisStopIntegration: """Test RedisChannel correctly fetches and deserializes commands.""" # Setup mock_redis = MagicMock() - mock_pipeline = MagicMock() - mock_redis.pipeline.return_value.__enter__ = Mock(return_value=mock_pipeline) - mock_redis.pipeline.return_value.__exit__ = Mock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] # Mock command data abort_command_json = json.dumps({"command_type": CommandType.ABORT, "reason": "Test abort", "payload": None}) # Mock pipeline execute to return commands - mock_pipeline.execute.return_value = [ + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [ [abort_command_json.encode()], # lrange result True, # delete result ] @@ -158,19 +165,29 @@ class TestRedisStopIntegration: assert commands[0].reason == "Test abort" # Verify Redis operations - mock_pipeline.lrange.assert_called_once_with(channel_key, 0, -1) - mock_pipeline.delete.assert_called_once_with(channel_key) + pending_pipe.get.assert_called_once_with(f"{channel_key}:pending") + pending_pipe.delete.assert_called_once_with(f"{channel_key}:pending") + fetch_pipe.lrange.assert_called_once_with(channel_key, 0, -1) + fetch_pipe.delete.assert_called_once_with(channel_key) + assert mock_redis.pipeline.call_count == 2 def test_redis_channel_fetch_commands_handles_invalid_json(self): """Test RedisChannel gracefully handles invalid JSON in commands.""" # Setup mock_redis = MagicMock() - mock_pipeline = MagicMock() - mock_redis.pipeline.return_value.__enter__ = Mock(return_value=mock_pipeline) - mock_redis.pipeline.return_value.__exit__ = Mock(return_value=None) + pending_pipe = MagicMock() + fetch_pipe = MagicMock() + pending_context = MagicMock() + fetch_context = MagicMock() + pending_context.__enter__.return_value = pending_pipe + pending_context.__exit__.return_value = None + fetch_context.__enter__.return_value = fetch_pipe + fetch_context.__exit__.return_value = None + mock_redis.pipeline.side_effect = [pending_context, fetch_context] # Mock invalid command data - mock_pipeline.execute.return_value = [ + pending_pipe.execute.return_value = [b"1", 1] + fetch_pipe.execute.return_value = [ [b"invalid json", b'{"command_type": "invalid_type"}'], # lrange result True, # delete result ] From c0b50ef61d4ce3209085c0b1159b494201f7b182 Mon Sep 17 00:00:00 2001 From: GuanMu Date: Wed, 15 Oct 2025 16:48:02 +0800 Subject: [PATCH 11/11] =?UTF-8?q?chore:=20remove=20unused=20icon=20compone?= =?UTF-8?q?nts=20and=20related=20features=20from=20the=20co=E2=80=A6=20(#2?= =?UTF-8?q?6933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/configuration/base/icons/citation.tsx | 29 ----- .../base/icons/more-like-this-icon.tsx | 14 --- .../suggested-questions-after-answer-icon.tsx | 12 -- .../config/feature/use-feature.tsx | 96 ---------------- .../prompt-mode/advanced-mode-waring.tsx | 50 --------- .../base/auto-height-textarea/common.tsx | 54 --------- .../base/chat/chat/thought/panel.tsx | 28 ----- .../base/chat/chat/thought/tool.tsx | 106 ------------------ web/app/components/base/copy-btn/index.tsx | 54 --------- .../components/base/copy-btn/style.module.css | 15 --- web/app/components/base/custom-icon/index.tsx | 16 --- .../components/base/divider/with-label.tsx | 23 ---- .../base/float-popover-container/index.tsx | 37 ------ .../components/base/install-button/index.tsx | 27 ----- 14 files changed, 561 deletions(-) delete mode 100644 web/app/components/app/configuration/base/icons/citation.tsx delete mode 100644 web/app/components/app/configuration/base/icons/more-like-this-icon.tsx delete mode 100644 web/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon.tsx delete mode 100644 web/app/components/app/configuration/config/feature/use-feature.tsx delete mode 100644 web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx delete mode 100644 web/app/components/base/auto-height-textarea/common.tsx delete mode 100644 web/app/components/base/chat/chat/thought/panel.tsx delete mode 100644 web/app/components/base/chat/chat/thought/tool.tsx delete mode 100644 web/app/components/base/copy-btn/index.tsx delete mode 100644 web/app/components/base/copy-btn/style.module.css delete mode 100644 web/app/components/base/custom-icon/index.tsx delete mode 100644 web/app/components/base/divider/with-label.tsx delete mode 100644 web/app/components/base/float-popover-container/index.tsx delete mode 100644 web/app/components/base/install-button/index.tsx diff --git a/web/app/components/app/configuration/base/icons/citation.tsx b/web/app/components/app/configuration/base/icons/citation.tsx deleted file mode 100644 index 3aa6b0f0e1..0000000000 --- a/web/app/components/app/configuration/base/icons/citation.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { SVGProps } from 'react' - -const CitationIcon = (props: SVGProps) => ( - -) - -export default CitationIcon diff --git a/web/app/components/app/configuration/base/icons/more-like-this-icon.tsx b/web/app/components/app/configuration/base/icons/more-like-this-icon.tsx deleted file mode 100644 index 74c808eb39..0000000000 --- a/web/app/components/app/configuration/base/icons/more-like-this-icon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' - -const MoreLikeThisIcon: FC = () => { - return ( - - - - - - ) -} -export default React.memo(MoreLikeThisIcon) diff --git a/web/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon.tsx b/web/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon.tsx deleted file mode 100644 index cabc2e4d73..0000000000 --- a/web/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' - -const SuggestedQuestionsAfterAnswerIcon: FC = () => { - return ( - - - - ) -} -export default React.memo(SuggestedQuestionsAfterAnswerIcon) diff --git a/web/app/components/app/configuration/config/feature/use-feature.tsx b/web/app/components/app/configuration/config/feature/use-feature.tsx deleted file mode 100644 index acc08dd4a4..0000000000 --- a/web/app/components/app/configuration/config/feature/use-feature.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useEffect } from 'react' - -function useFeature({ - introduction, - setIntroduction, - moreLikeThis, - setMoreLikeThis, - suggestedQuestionsAfterAnswer, - setSuggestedQuestionsAfterAnswer, - speechToText, - setSpeechToText, - textToSpeech, - setTextToSpeech, - citation, - setCitation, - annotation, - setAnnotation, - moderation, - setModeration, -}: { - introduction: string - setIntroduction: (introduction: string) => void - moreLikeThis: boolean - setMoreLikeThis: (moreLikeThis: boolean) => void - suggestedQuestionsAfterAnswer: boolean - setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: boolean) => void - speechToText: boolean - setSpeechToText: (speechToText: boolean) => void - textToSpeech: boolean - setTextToSpeech: (textToSpeech: boolean) => void - citation: boolean - setCitation: (citation: boolean) => void - annotation: boolean - setAnnotation: (annotation: boolean) => void - moderation: boolean - setModeration: (moderation: boolean) => void -}) { - const [tempShowOpeningStatement, setTempShowOpeningStatement] = React.useState(!!introduction) - useEffect(() => { - // wait to api data back - if (introduction) - setTempShowOpeningStatement(true) - }, [introduction]) - - // const [tempMoreLikeThis, setTempMoreLikeThis] = React.useState(moreLikeThis) - // useEffect(() => { - // setTempMoreLikeThis(moreLikeThis) - // }, [moreLikeThis]) - - const featureConfig = { - openingStatement: tempShowOpeningStatement, - moreLikeThis, - suggestedQuestionsAfterAnswer, - speechToText, - textToSpeech, - citation, - annotation, - moderation, - } - const handleFeatureChange = (key: string, value: boolean) => { - switch (key) { - case 'openingStatement': - if (!value) - setIntroduction('') - - setTempShowOpeningStatement(value) - break - case 'moreLikeThis': - setMoreLikeThis(value) - break - case 'suggestedQuestionsAfterAnswer': - setSuggestedQuestionsAfterAnswer(value) - break - case 'speechToText': - setSpeechToText(value) - break - case 'textToSpeech': - setTextToSpeech(value) - break - case 'citation': - setCitation(value) - break - case 'annotation': - setAnnotation(value) - break - case 'moderation': - setModeration(value) - } - } - return { - featureConfig, - handleFeatureChange, - } -} - -export default useFeature diff --git a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx deleted file mode 100644 index f207cddd16..0000000000 --- a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { useDocLink } from '@/context/i18n' -type Props = { - onReturnToSimpleMode: () => void -} - -const AdvancedModeWarning: FC = ({ - onReturnToSimpleMode, -}) => { - const { t } = useTranslation() - const docLink = useDocLink() - const [show, setShow] = React.useState(true) - if (!show) - return null - return ( -
-
{t('appDebug.promptMode.advancedWarning.title')}
-
-
- {t('appDebug.promptMode.advancedWarning.description')} - - {t('appDebug.promptMode.advancedWarning.learnMore')} - -
- -
-
-
{t('appDebug.promptMode.switchBack')}
-
-
setShow(false)} - >{t('appDebug.promptMode.advancedWarning.ok')}
-
- -
-
- ) -} -export default React.memo(AdvancedModeWarning) diff --git a/web/app/components/base/auto-height-textarea/common.tsx b/web/app/components/base/auto-height-textarea/common.tsx deleted file mode 100644 index eb0275cfcd..0000000000 --- a/web/app/components/base/auto-height-textarea/common.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect, useRef } from 'react' -import cn from '@/utils/classnames' - -type AutoHeightTextareaProps - = & React.DetailedHTMLProps, HTMLTextAreaElement> - & { outerClassName?: string } - -const AutoHeightTextarea = ( - { - ref: outRef, - outerClassName, - value, - className, - placeholder, - autoFocus, - disabled, - ...rest - }: AutoHeightTextareaProps & { - ref: React.RefObject; - }, -) => { - const innerRef = useRef(null) - const ref = outRef || innerRef - - useEffect(() => { - if (autoFocus && !disabled && value) { - if (typeof ref !== 'function') { - ref.current?.setSelectionRange(`${value}`.length, `${value}`.length) - ref.current?.focus() - } - } - }, [autoFocus, disabled, ref]) - return ( - (
-
-
- {!value ? placeholder : `${value}`.replace(/\n$/, '\n ')} -
-