Merge pull request #517 from SuanmoSuanyangTechnology/release/v0.2.6

Release/v0.2.6
This commit is contained in:
Ke Sun
2026-03-09 10:56:39 +08:00
committed by GitHub
9 changed files with 35 additions and 12 deletions

View File

@@ -185,6 +185,9 @@ class DifyConverter(BaseConverter):
"not empty": ComparisonOperator.NOT_EMPTY, "not empty": ComparisonOperator.NOT_EMPTY,
"start with": ComparisonOperator.START_WITH, "start with": ComparisonOperator.START_WITH,
"end with": ComparisonOperator.END_WITH, "end with": ComparisonOperator.END_WITH,
"not contains": ComparisonOperator.NOT_CONTAINS,
"exists": ComparisonOperator.NOT_EMPTY,
"not exists": ComparisonOperator.EMPTY
} }
return operator_map.get(operator, operator) return operator_map.get(operator, operator)
@@ -364,7 +367,7 @@ class DifyConverter(BaseConverter):
node_data = node["data"] node_data = node["data"]
cases = [] cases = []
for case in node_data["cases"]: for case in node_data["cases"]:
case_id = case["id"] case_id = case.get("id") or case.get("case_id")
logical_operator = case["logical_operator"] logical_operator = case["logical_operator"]
conditions = [] conditions = []
for condition in case["conditions"]: for condition in case["conditions"]:
@@ -540,7 +543,8 @@ class DifyConverter(BaseConverter):
] = self.trans_variable_format(content["value"]) ] = self.trans_variable_format(content["value"])
else: else:
if node_data["body"]["data"]: if node_data["body"]["data"]:
body_content = node_data["body"]["data"][0]["value"] body_content = (node_data["body"]["data"][0].get("value") or
self._process_list_variable_litearl(node_data["body"]["data"][0].get("file")))
else: else:
body_content = "" body_content = ""

View File

@@ -44,7 +44,8 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
"parameter-extractor": NodeType.PARAMETER_EXTRACTOR, "parameter-extractor": NodeType.PARAMETER_EXTRACTOR,
"question-classifier": NodeType.QUESTION_CLASSIFIER, "question-classifier": NodeType.QUESTION_CLASSIFIER,
"variable-aggregator": NodeType.VAR_AGGREGATOR, "variable-aggregator": NodeType.VAR_AGGREGATOR,
"tool": NodeType.TOOL "tool": NodeType.TOOL,
"": NodeType.NOTES
} }
def __init__(self, config: dict[str, Any]): def __init__(self, config: dict[str, Any]):
@@ -165,7 +166,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
return NodeDefinition( return NodeDefinition(
id=node["id"], id=node["id"],
type=self.map_node_type(node_data["type"]), type=self.map_node_type(node_data["type"]),
name=node_data.get("title"), name=node_data.get("title") or "notes",
cycle=node.get("parentId"), cycle=node.get("parentId"),
description=None, description=None,
config=self._convert_node_config(node), config=self._convert_node_config(node),
@@ -209,16 +210,15 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
source = edge["source"] source = edge["source"]
target = edge["target"] target = edge["target"]
edge_id = edge["id"]
label = None label = None
if source in self.branch_node_cache: if source in self.branch_node_cache:
case_id = "-".join(edge_id.split("-")[1:-2]) case_id = edge["sourceHandle"]
if case_id == "false": if case_id == "false":
label = f'CASE{len(self.branch_node_cache[source])+1}' label = f'CASE{len(self.branch_node_cache[source])+1}'
else: else:
label = f'CASE{self.branch_node_cache[source].index(case_id) + 1}' label = f'CASE{self.branch_node_cache[source].index(case_id) + 1}'
if source in self.error_branch_node_cache: if source in self.error_branch_node_cache:
case_id = "-".join(edge_id.split("-")[1:-2]) case_id = edge["sourceHandle"]
if case_id == "source": if case_id == "source":
label = "SUCCESS" label = "SUCCESS"
else: else:
@@ -243,6 +243,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
name=variable["name"], name=variable["name"],
default=variable["value"], default=variable["value"],
type=self.variable_type_map(variable["value_type"]), type=self.variable_type_map(variable["value_type"]),
description=variable.get("description")
) )
except Exception as e: except Exception as e:
self.errors.append(ExceptionDefineition( self.errors.append(ExceptionDefineition(

View File

@@ -292,6 +292,8 @@ class GraphBuilder:
""" """
for node in self.nodes: for node in self.nodes:
node_type = node.get("type") node_type = node.get("type")
if node_type == NodeType.NOTES:
continue
node_id = node.get("id") node_id = node.get("id")
cycle_node = node.get("cycle") cycle_node = node.get("cycle")
if cycle_node: if cycle_node:

View File

@@ -25,6 +25,7 @@ class NodeType(StrEnum):
MEMORY_WRITE = "memory-write" MEMORY_WRITE = "memory-write"
UNKNOWN = "unknown" UNKNOWN = "unknown"
NOTES = "notes"
BRANCH_NODES = [NodeType.IF_ELSE, NodeType.HTTP_REQUEST, NodeType.QUESTION_CLASSIFIER] BRANCH_NODES = [NodeType.IF_ELSE, NodeType.HTTP_REQUEST, NodeType.QUESTION_CLASSIFIER]

View File

@@ -180,6 +180,8 @@ class KnowledgeRetrievalNode(BaseNode):
RuntimeError: If no valid knowledge base is found or access is denied. RuntimeError: If no valid knowledge base is found or access is denied.
""" """
self.typed_config = KnowledgeRetrievalNodeConfig(**self.config) self.typed_config = KnowledgeRetrievalNodeConfig(**self.config)
if not self.typed_config.knowledge_bases:
return []
query = self._render_template(self.typed_config.query, variable_pool) query = self._render_template(self.typed_config.query, variable_pool)
with get_db_read() as db: with get_db_read() as db:
knowledge_bases = self.typed_config.knowledge_bases knowledge_bases = self.typed_config.knowledge_bases

View File

@@ -138,7 +138,7 @@ class WorkflowValidator:
errors.append("工作流必须至少有一个 end 节点") errors.append("工作流必须至少有一个 end 节点")
# 3. 验证节点 ID 唯一性 # 3. 验证节点 ID 唯一性
node_ids = [n.get("id") for n in nodes] node_ids = [n.get("id") for n in nodes if n.get("type") != NodeType.NOTES]
if len(node_ids) != len(set(node_ids)): if len(node_ids) != len(set(node_ids)):
duplicates = [nid for nid in node_ids if node_ids.count(nid) > 1] duplicates = [nid for nid in node_ids if node_ids.count(nid) > 1]
errors.append(f"节点 ID 必须唯一,重复的 ID: {set(duplicates)}") errors.append(f"节点 ID 必须唯一,重复的 ID: {set(duplicates)}")

View File

@@ -25,6 +25,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
const textContent = root.getTextContent(); const textContent = root.getTextContent();
if (textContent !== prevValueRef.current) { if (textContent !== prevValueRef.current) {
isUserInputRef.current = true; isUserInputRef.current = true;
prevValueRef.current = textContent;
} }
}); });
}); });
@@ -33,7 +34,13 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
}, [editor]); }, [editor]);
useEffect(() => { useEffect(() => {
if ((value !== prevValueRef.current || enableLineNumbers !== prevEnableLineNumbersRef.current) && !isUserInputRef.current) { if (value !== prevValueRef.current || enableLineNumbers !== prevEnableLineNumbersRef.current) {
// Skip reset if the change was triggered by user input (avoid cursor jump)
if (isUserInputRef.current && enableLineNumbers === prevEnableLineNumbersRef.current) {
prevValueRef.current = value;
isUserInputRef.current = false;
return;
}
queueMicrotask(() => { queueMicrotask(() => {
editor.update(() => { editor.update(() => {
const root = $getRoot(); const root = $getRoot();

View File

@@ -529,6 +529,10 @@ export const unknownNode = {
type: 'unknown', type: 'unknown',
icon: unknownIcon icon: unknownIcon
} }
export const noteNode = {
type: 'notes',
icon: unknownIcon
}
export const nodeWidth = 240; export const nodeWidth = 240;
/** /**

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:17:48 * @Date: 2026-02-03 15:17:48
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-28 17:59:34 * @Last Modified time: 2026-03-07 15:23:39
*/ */
import { useRef, useEffect, useState } from 'react'; import { useRef, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
@@ -12,7 +12,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
import { register } from '@antv/x6-react-shape'; import { register } from '@antv/x6-react-shape';
import type { PortMetadata } from '@antv/x6/lib/model/port'; import type { PortMetadata } from '@antv/x6/lib/model/port';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode } from '../constant'; import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, noteNode } from '../constant';
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types'; import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application' import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
@@ -128,7 +128,7 @@ export const useWorkflowGraph = ({
if (nodes.length) { if (nodes.length) {
const nodeList = nodes.map(node => { const nodeList = nodes.map(node => {
const { id, type, name, position, config = {} } = node const { id, type, name, position, config = {} } = node
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode] }] let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode, noteNode] }]
.flatMap(category => category.nodes) .flatMap(category => category.nodes)
.find(n => n.type === type) .find(n => n.type === type)
nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties
@@ -715,6 +715,8 @@ export const useWorkflowGraph = ({
panning: isHandMode, panning: isHandMode,
mousewheel: { mousewheel: {
enabled: true, enabled: true,
factor: 0.1,
modifiers: null,
}, },
connecting: { connecting: {
connector: { connector: {