Compare commits
9 Commits
hotfix/v0.
...
v0.2.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e397b83b6 | ||
|
|
4961e7df79 | ||
|
|
cae87de6ef | ||
|
|
9ff3a3d5f7 | ||
|
|
18703919a8 | ||
|
|
d1beb9e5d5 | ||
|
|
1aec7115a5 | ||
|
|
8b9eb81d36 | ||
|
|
daaad51357 |
@@ -23,7 +23,6 @@ from app.models.user_model import User
|
|||||||
from app.schemas import chunk_schema
|
from app.schemas import chunk_schema
|
||||||
from app.schemas.response_schema import ApiResponse
|
from app.schemas.response_schema import ApiResponse
|
||||||
from app.services import knowledge_service, document_service, file_service, knowledgeshare_service
|
from app.services import knowledge_service, document_service, file_service, knowledgeshare_service
|
||||||
from app.services.model_service import ModelApiKeyService
|
|
||||||
|
|
||||||
# Obtain a dedicated API logger
|
# Obtain a dedicated API logger
|
||||||
api_logger = get_api_logger()
|
api_logger = get_api_logger()
|
||||||
@@ -461,20 +460,18 @@ async def retrieve_chunks(
|
|||||||
if retrieve_data.retrieve_type == chunk_schema.RetrieveType.Graph:
|
if retrieve_data.retrieve_type == chunk_schema.RetrieveType.Graph:
|
||||||
kb_ids = [str(kb_id) for kb_id in private_kb_ids]
|
kb_ids = [str(kb_id) for kb_id in private_kb_ids]
|
||||||
workspace_ids = [str(workspace_id) for workspace_id in private_workspace_ids]
|
workspace_ids = [str(workspace_id) for workspace_id in private_workspace_ids]
|
||||||
llm_key = ModelApiKeyService.get_available_api_key(db, db_knowledge.llm_id)
|
|
||||||
emb_key = ModelApiKeyService.get_available_api_key(db, db_knowledge.embedding_id)
|
|
||||||
# Prepare to configure chat_mdl、embedding_model、vision_model information
|
# Prepare to configure chat_mdl、embedding_model、vision_model information
|
||||||
chat_model = Base(
|
chat_model = Base(
|
||||||
key=llm_key.api_key,
|
key=db_knowledge.llm.api_keys[0].api_key,
|
||||||
model_name=llm_key.model_name,
|
model_name=db_knowledge.llm.api_keys[0].model_name,
|
||||||
base_url=llm_key.api_base
|
base_url=db_knowledge.llm.api_keys[0].api_base
|
||||||
)
|
)
|
||||||
embedding_model = OpenAIEmbed(
|
embedding_model = OpenAIEmbed(
|
||||||
key=emb_key.api_key,
|
key=db_knowledge.embedding.api_keys[0].api_key,
|
||||||
model_name=emb_key.model_name,
|
model_name=db_knowledge.embedding.api_keys[0].model_name,
|
||||||
base_url=emb_key.api_base
|
base_url=db_knowledge.embedding.api_keys[0].api_base
|
||||||
)
|
)
|
||||||
doc = kg_retriever.retrieval(question=retrieve_data.query, workspace_ids=workspace_ids, kb_ids=kb_ids, emb_mdl=embedding_model, llm=chat_model)
|
doc = kg_retriever.retrieval(question=retrieve_data.query, workspace_ids=workspace_ids, kb_ids= kb_ids, emb_mdl=embedding_model, llm=chat_model)
|
||||||
if doc:
|
if doc:
|
||||||
rs.insert(0, doc)
|
rs.insert(0, doc)
|
||||||
return success(data=jsonable_encoder(rs), msg="retrieval successful")
|
return success(data=jsonable_encoder(rs), msg="retrieval successful")
|
||||||
@@ -292,10 +292,9 @@ class MinerUParser(RAGPdfParser):
|
|||||||
self.page_from = page_from
|
self.page_from = page_from
|
||||||
self.page_to = page_to
|
self.page_to = page_to
|
||||||
try:
|
try:
|
||||||
with sys.modules[LOCK_KEY_pdfplumber]: # ← 加这一行,获取全局锁
|
with pdfplumber.open(fnm) if isinstance(fnm, (str, PathLike)) else pdfplumber.open(BytesIO(fnm)) as pdf:
|
||||||
with pdfplumber.open(fnm) if isinstance(fnm, (str, PathLike)) else pdfplumber.open(BytesIO(fnm)) as pdf:
|
self.pdf = pdf
|
||||||
self.pdf = pdf
|
self.page_images = [p.to_image(resolution=72 * zoomin, antialias=True).original for _, p in enumerate(self.pdf.pages[page_from:page_to])]
|
||||||
self.page_images = [p.to_image(resolution=72 * zoomin, antialias=True).original for _, p in enumerate(self.pdf.pages[page_from:page_to])]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.page_images = None
|
self.page_images = None
|
||||||
self.total_page = 0
|
self.total_page = 0
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from app.core.rag.common.float_utils import get_float
|
|||||||
from app.core.rag.common.constants import PAGERANK_FLD, TAG_FLD
|
from app.core.rag.common.constants import PAGERANK_FLD, TAG_FLD
|
||||||
from app.core.rag.llm.chat_model import Base
|
from app.core.rag.llm.chat_model import Base
|
||||||
from app.core.rag.llm.embedding_model import OpenAIEmbed
|
from app.core.rag.llm.embedding_model import OpenAIEmbed
|
||||||
from app.services.model_service import ModelApiKeyService
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -115,8 +114,9 @@ def knowledge_retrieval(
|
|||||||
# Use the specified reranker for re-ranking
|
# Use the specified reranker for re-ranking
|
||||||
if reranker_id:
|
if reranker_id:
|
||||||
try:
|
try:
|
||||||
all_results = rerank(db=db, reranker_id=reranker_id, query=query, docs=all_results, top_k=reranker_top_k)
|
return rerank(db=db, reranker_id=reranker_id, query=query, docs=all_results, top_k=reranker_top_k)
|
||||||
except Exception as rerank_error:
|
except Exception as rerank_error:
|
||||||
|
# If reranker fails, log warning and continue with original results
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Reranker failed, falling back to original results",
|
"Reranker failed, falling back to original results",
|
||||||
extra={
|
extra={
|
||||||
@@ -132,10 +132,7 @@ def knowledge_retrieval(
|
|||||||
from app.core.rag.common.settings import kg_retriever
|
from app.core.rag.common.settings import kg_retriever
|
||||||
doc = kg_retriever.retrieval(question=query, workspace_ids=workspace_ids, kb_ids=kb_ids, emb_mdl=embedding_model, llm=chat_model)
|
doc = kg_retriever.retrieval(question=query, workspace_ids=workspace_ids, kb_ids=kb_ids, emb_mdl=embedding_model, llm=chat_model)
|
||||||
if doc:
|
if doc:
|
||||||
all_results.insert(0, DocumentChunk(
|
all_results.insert(0, doc)
|
||||||
page_content=doc.get("page_content", ""),
|
|
||||||
metadata=doc.get("metadata", {})
|
|
||||||
))
|
|
||||||
except Exception as graph_error:
|
except Exception as graph_error:
|
||||||
print(f"Failed to retrieve from knowledge graph: {str(graph_error)}")
|
print(f"Failed to retrieve from knowledge graph: {str(graph_error)}")
|
||||||
|
|
||||||
@@ -201,18 +198,16 @@ def _retrieve_for_knowledge(
|
|||||||
workspace_ids.append(str(db_knowledge.workspace_id))
|
workspace_ids.append(str(db_knowledge.workspace_id))
|
||||||
|
|
||||||
if not chat_model:
|
if not chat_model:
|
||||||
llm_key = ModelApiKeyService.get_available_api_key(db, db_knowledge.llm_id)
|
|
||||||
chat_model = Base(
|
chat_model = Base(
|
||||||
key=llm_key.api_key,
|
key=db_knowledge.llm.api_keys[0].api_key,
|
||||||
model_name=llm_key.model_name,
|
model_name=db_knowledge.llm.api_keys[0].model_name,
|
||||||
base_url=llm_key.api_base,
|
base_url=db_knowledge.llm.api_keys[0].api_base,
|
||||||
)
|
)
|
||||||
if not embedding_model:
|
if not embedding_model:
|
||||||
emb_key = ModelApiKeyService.get_available_api_key(db, db_knowledge.embedding_id)
|
|
||||||
embedding_model = OpenAIEmbed(
|
embedding_model = OpenAIEmbed(
|
||||||
key=emb_key.api_key,
|
key=db_knowledge.embedding.api_keys[0].api_key,
|
||||||
model_name=emb_key.model_name,
|
model_name=db_knowledge.embedding.api_keys[0].model_name,
|
||||||
base_url=emb_key.api_base,
|
base_url=db_knowledge.embedding.api_keys[0].api_base,
|
||||||
)
|
)
|
||||||
|
|
||||||
vector_service = ElasticSearchVectorFactory().init_vector(knowledge=db_knowledge)
|
vector_service = ElasticSearchVectorFactory().init_vector(knowledge=db_knowledge)
|
||||||
@@ -253,29 +248,6 @@ def _retrieve_for_knowledge(
|
|||||||
seen_ids.add(doc.metadata["doc_id"])
|
seen_ids.add(doc.metadata["doc_id"])
|
||||||
unique_rs.append(doc)
|
unique_rs.append(doc)
|
||||||
rs = unique_rs
|
rs = unique_rs
|
||||||
if unique_rs:
|
|
||||||
rs = vector_service.rerank(
|
|
||||||
query=kb_config["query"],
|
|
||||||
docs=unique_rs,
|
|
||||||
top_k=kb_config["top_k"]
|
|
||||||
)
|
|
||||||
if kb_config["retrieve_type"] == "graph":
|
|
||||||
try:
|
|
||||||
from app.core.rag.common.settings import kg_retriever
|
|
||||||
graph_doc = kg_retriever.retrieval(
|
|
||||||
question=kb_config["query"],
|
|
||||||
workspace_ids=[str(db_knowledge.workspace_id)],
|
|
||||||
kb_ids=[str(db_knowledge.id)],
|
|
||||||
emb_mdl=embedding_model,
|
|
||||||
llm=chat_model,
|
|
||||||
)
|
|
||||||
if graph_doc:
|
|
||||||
rs.insert(0, DocumentChunk(
|
|
||||||
page_content=graph_doc.get("page_content", ""),
|
|
||||||
metadata=graph_doc.get("metadata", {})
|
|
||||||
))
|
|
||||||
except Exception as graph_error:
|
|
||||||
logger.warning(f"Graph retrieval failed for kb {db_knowledge.id}: {graph_error}")
|
|
||||||
|
|
||||||
results.extend(rs)
|
results.extend(rs)
|
||||||
return results, chat_model, embedding_model
|
return results, chat_model, embedding_model
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ class DateTimeTool(BuiltinTool):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _datetime_to_timestamp(kwargs) -> dict:
|
def _datetime_to_timestamp(kwargs) -> dict:
|
||||||
"""日期时间转时间戳"""
|
"""日期时间转时间戳"""
|
||||||
input_value = kwargs.get("input_value").strip()
|
input_value = kwargs.get("input_value")
|
||||||
input_format = kwargs.get("input_format", "%Y-%m-%d %H:%M:%S")
|
input_format = kwargs.get("input_format", "%Y-%m-%d %H:%M:%S")
|
||||||
timezone_str = kwargs.get("from_timezone", "Asia/Shanghai")
|
timezone_str = kwargs.get("from_timezone", "Asia/Shanghai")
|
||||||
|
|
||||||
@@ -253,9 +253,9 @@ class DateTimeTool(BuiltinTool):
|
|||||||
return {
|
return {
|
||||||
"datetime": input_value,
|
"datetime": input_value,
|
||||||
"timezone": timezone_str,
|
"timezone": timezone_str,
|
||||||
"timestamp": int(dt.timestamp()) * 1000,
|
"timestamp": int(dt.timestamp()),
|
||||||
"iso_format": dt.isoformat(),
|
"iso_format": dt.isoformat(),
|
||||||
"result_data": int(dt.timestamp()) * 1000
|
"result_data": int(dt.timestamp())
|
||||||
}
|
}
|
||||||
|
|
||||||
def _calculate_datetime(self, kwargs) -> dict:
|
def _calculate_datetime(self, kwargs) -> dict:
|
||||||
|
|||||||
@@ -138,29 +138,6 @@ class OperationTool(BaseTool):
|
|||||||
default="Asia/Shanghai"
|
default="Asia/Shanghai"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
elif self.operation == "datetime_to_timestamp":
|
|
||||||
return [
|
|
||||||
ToolParameter(
|
|
||||||
name="input_value",
|
|
||||||
type=ParameterType.STRING,
|
|
||||||
description="输入值(时间字符串,如:2026-04-07 10:30:25)",
|
|
||||||
required=True
|
|
||||||
),
|
|
||||||
ToolParameter(
|
|
||||||
name="input_format",
|
|
||||||
type=ParameterType.STRING,
|
|
||||||
description="输入时间格式(如:%Y-%m-%d %H:%M:%S)",
|
|
||||||
required=False,
|
|
||||||
default="%Y-%m-%d %H:%M:%S"
|
|
||||||
),
|
|
||||||
ToolParameter(
|
|
||||||
name="from_timezone",
|
|
||||||
type=ParameterType.STRING,
|
|
||||||
description="源时区(如:UTC, Asia/Shanghai)",
|
|
||||||
required=False,
|
|
||||||
default="Asia/Shanghai"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# Example:
|
# Example:
|
||||||
# "Hello {{user.name}}!" ->
|
# "Hello {{user.name}}!" ->
|
||||||
# ["Hello ", "{{user.name}}", "!"]
|
# ["Hello ", "{{user.name}}", "!"]
|
||||||
_OUTPUT_PATTERN = re.compile(r'\{\{.*?}}|[^{]+|{')
|
_OUTPUT_PATTERN = re.compile(r'\{\{.*?}}|[^{}]+')
|
||||||
# Strict variable format: {{ node_id.field_name }}
|
# Strict variable format: {{ node_id.field_name }}
|
||||||
_VARIABLE_PATTERN = re.compile(r'\{\{\s*[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\s*}}')
|
_VARIABLE_PATTERN = re.compile(r'\{\{\s*[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\s*}}')
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ class CycleGraphNode(BaseNode):
|
|||||||
if config.output_type in [
|
if config.output_type in [
|
||||||
VariableType.ARRAY_FILE,
|
VariableType.ARRAY_FILE,
|
||||||
VariableType.ARRAY_STRING,
|
VariableType.ARRAY_STRING,
|
||||||
VariableType.ARRAY_NUMBER,
|
VariableType.NUMBER,
|
||||||
VariableType.ARRAY_OBJECT,
|
VariableType.ARRAY_OBJECT,
|
||||||
VariableType.ARRAY_BOOLEAN
|
VariableType.BOOLEAN
|
||||||
]:
|
]:
|
||||||
if config.flatten:
|
if config.flatten:
|
||||||
outputs['output'] = config.output_type
|
outputs['output'] = config.output_type
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ from langchain_core.documents import Document
|
|||||||
from app.core.error_codes import BizCode
|
from app.core.error_codes import BizCode
|
||||||
from app.core.exceptions import BusinessException
|
from app.core.exceptions import BusinessException
|
||||||
from app.core.models import RedBearRerank, RedBearModelConfig
|
from app.core.models import RedBearRerank, RedBearModelConfig
|
||||||
from app.core.rag.llm.chat_model import Base
|
|
||||||
from app.core.rag.llm.embedding_model import OpenAIEmbed
|
|
||||||
from app.core.rag.models.chunk import DocumentChunk
|
from app.core.rag.models.chunk import DocumentChunk
|
||||||
from app.core.rag.vdb.elasticsearch.elasticsearch_vector import ElasticSearchVectorFactory
|
from app.core.rag.vdb.elasticsearch.elasticsearch_vector import ElasticSearchVectorFactory
|
||||||
from app.core.workflow.engine.state_manager import WorkflowState
|
from app.core.workflow.engine.state_manager import WorkflowState
|
||||||
@@ -42,8 +40,7 @@ class KnowledgeRetrievalNode(BaseNode):
|
|||||||
return business_result["chunks"]
|
return business_result["chunks"]
|
||||||
return business_result
|
return business_result
|
||||||
|
|
||||||
@staticmethod
|
def _extract_citations(self, business_result: Any) -> list:
|
||||||
def _extract_citations(business_result: Any) -> list:
|
|
||||||
if isinstance(business_result, dict):
|
if isinstance(business_result, dict):
|
||||||
return business_result.get("citations", [])
|
return business_result.get("citations", [])
|
||||||
return []
|
return []
|
||||||
@@ -233,23 +230,23 @@ class KnowledgeRetrievalNode(BaseNode):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case retrieve_type if retrieve_type in (RetrieveType.HYBRID, RetrieveType.Graph):
|
case RetrieveType.HYBRID:
|
||||||
rs1_task = asyncio.to_thread(
|
rs1_task = asyncio.to_thread(
|
||||||
vector_service.search_by_vector, **{
|
vector_service.search_by_vector, **{
|
||||||
"query": query,
|
"query": query,
|
||||||
"top_k": kb_config.top_k,
|
"top_k": kb_config.top_k,
|
||||||
"indices": indices,
|
"indices": indices,
|
||||||
"score_threshold": kb_config.vector_similarity_weight
|
"score_threshold": kb_config.vector_similarity_weight
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rs2_task = asyncio.to_thread(
|
rs2_task = asyncio.to_thread(
|
||||||
vector_service.search_by_full_text, **{
|
vector_service.search_by_full_text, **{
|
||||||
"query": query,
|
"query": query,
|
||||||
"top_k": kb_config.top_k,
|
"top_k": kb_config.top_k,
|
||||||
"indices": indices,
|
"indices": indices,
|
||||||
"score_threshold": kb_config.similarity_threshold
|
"score_threshold": kb_config.similarity_threshold
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rs1, rs2 = await asyncio.gather(rs1_task, rs2_task)
|
rs1, rs2 = await asyncio.gather(rs1_task, rs2_task)
|
||||||
|
|
||||||
# Deduplicate hybrid retrieval results
|
# Deduplicate hybrid retrieval results
|
||||||
@@ -269,33 +266,6 @@ class KnowledgeRetrievalNode(BaseNode):
|
|||||||
key=lambda d: d.metadata.get("score", 0),
|
key=lambda d: d.metadata.get("score", 0),
|
||||||
reverse=True
|
reverse=True
|
||||||
)[:kb_config.top_k])
|
)[:kb_config.top_k])
|
||||||
if kb_config.retrieve_type == RetrieveType.Graph:
|
|
||||||
from app.core.rag.common.settings import kg_retriever
|
|
||||||
llm_key = self.model_balance(db_knowledge.llm)
|
|
||||||
emb_key = self.model_balance(db_knowledge.embedding)
|
|
||||||
chat_model = Base(
|
|
||||||
key=llm_key.api_key,
|
|
||||||
model_name=llm_key.model_name,
|
|
||||||
base_url=llm_key.api_base
|
|
||||||
)
|
|
||||||
embedding_model = OpenAIEmbed(
|
|
||||||
key=emb_key.api_key,
|
|
||||||
model_name=emb_key.model_name,
|
|
||||||
base_url=emb_key.api_base
|
|
||||||
)
|
|
||||||
doc = await asyncio.to_thread(
|
|
||||||
kg_retriever.retrieval,
|
|
||||||
question=query,
|
|
||||||
workspace_ids=[str(db_knowledge.workspace_id)],
|
|
||||||
kb_ids=[str(kb_config.kb_id)],
|
|
||||||
emb_mdl=embedding_model,
|
|
||||||
llm=chat_model
|
|
||||||
)
|
|
||||||
if doc:
|
|
||||||
rs.insert(0, DocumentChunk(
|
|
||||||
page_content=doc.get("page_content", ""),
|
|
||||||
metadata=doc.get("metadata", {})
|
|
||||||
))
|
|
||||||
case _:
|
case _:
|
||||||
raise RuntimeError("Unknown retrieval type")
|
raise RuntimeError("Unknown retrieval type")
|
||||||
return rs
|
return rs
|
||||||
|
|||||||
@@ -574,29 +574,6 @@ class ToolService:
|
|||||||
"default": "Asia/Shanghai"
|
"default": "Asia/Shanghai"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
elif operation == "datetime_to_timestamp":
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"name": "input_value",
|
|
||||||
"type": "string",
|
|
||||||
"description": "输入值(时间字符串,如:2026-04-07 10:30:25)",
|
|
||||||
"required": True
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "input_format",
|
|
||||||
"type": "string",
|
|
||||||
"description": "输入时间格式(如:%Y-%m-%d %H:%M:%S)",
|
|
||||||
"required": False,
|
|
||||||
"default": "%Y-%m-%d %H:%M:%S"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "from_timezone",
|
|
||||||
"type": "string",
|
|
||||||
"description": "源时区(如:UTC, Asia/Shanghai)",
|
|
||||||
"required": False,
|
|
||||||
"default": "Asia/Shanghai"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
# 默认返回所有参数(除了operation)
|
# 默认返回所有参数(除了operation)
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -229,11 +229,7 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
|
|||||||
...knowledgeRest,
|
...knowledgeRest,
|
||||||
knowledge_bases: knowledge_bases.map(item => ({
|
knowledge_bases: knowledge_bases.map(item => ({
|
||||||
kb_id: item.kb_id || item.id,
|
kb_id: item.kb_id || item.id,
|
||||||
retrieve_type: item.retrieve_type,
|
...(item.config || {})
|
||||||
top_k: item.top_k,
|
|
||||||
similarity_threshold: item.similarity_threshold,
|
|
||||||
vector_similarity_weight: item.vector_similarity_weight,
|
|
||||||
// ...(item.config || {})
|
|
||||||
}))
|
}))
|
||||||
} as KnowledgeConfig : null,
|
} as KnowledgeConfig : null,
|
||||||
tools: tools.map(vo => {
|
tools: tools.map(vo => {
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
|||||||
const list = [...knowledgeList]
|
const list = [...knowledgeList]
|
||||||
list[index] = {
|
list[index] = {
|
||||||
...list[index],
|
...list[index],
|
||||||
...values,
|
|
||||||
config: {...values as KnowledgeConfigForm}
|
config: {...values as KnowledgeConfigForm}
|
||||||
}
|
}
|
||||||
setKnowledgeList([...list])
|
setKnowledgeList([...list])
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ interface KnowledgeConfigModalProps {
|
|||||||
* Available retrieval types
|
* Available retrieval types
|
||||||
*/
|
*/
|
||||||
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid',
|
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid',
|
||||||
'graph'
|
// 'graph'
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
refresh(selectedRows.map(item => ({
|
refresh(selectedRows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
similarity_threshold: 0.7,
|
|
||||||
retrieve_type: "hybrid",
|
|
||||||
top_k: 3,
|
|
||||||
weight: 1,
|
|
||||||
config: {
|
config: {
|
||||||
similarity_threshold: 0.7,
|
similarity_threshold: 0.7,
|
||||||
retrieve_type: "hybrid",
|
retrieve_type: "hybrid",
|
||||||
|
|||||||
@@ -155,10 +155,12 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
{['model', 'chat'].includes(source) && <>
|
{['model', 'chat'].includes(source) && <>
|
||||||
<FormItem name="capability" hidden />
|
<FormItem name="capability" hidden />
|
||||||
|
{(values?.deep_thinking || values?.capability?.includes('thinking')) && (
|
||||||
|
<FormItem name="deep_thinking" valuePropName="checked">
|
||||||
|
<Checkbox>{t('application.deep_thinking')}</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
</>}
|
</>}
|
||||||
<FormItem name="deep_thinking" valuePropName="checked" hidden={!['model', 'chat'].includes(source) || !(values?.deep_thinking || values?.capability?.includes('thinking'))}>
|
|
||||||
<Checkbox>{t('application.deep_thinking')}</Checkbox>
|
|
||||||
</FormItem>
|
|
||||||
{source === 'chat' && <FormItem name="label" hidden />}
|
{source === 'chat' && <FormItem name="label" hidden />}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const CreateDataset = () => {
|
|||||||
const [processingMethod, setProcessingMethod] = useState<ProcessingMethod>('directBlock');
|
const [processingMethod, setProcessingMethod] = useState<ProcessingMethod>('directBlock');
|
||||||
const [parameterSettings, setParameterSettings] = useState<ParameterSettings>('defaultSettings');
|
const [parameterSettings, setParameterSettings] = useState<ParameterSettings>('defaultSettings');
|
||||||
const [pdfEnhancementEnabled, setPdfEnhancementEnabled] = useState<boolean>(true);
|
const [pdfEnhancementEnabled, setPdfEnhancementEnabled] = useState<boolean>(true);
|
||||||
const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState<string>('mineru');
|
const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState<string>('deepdoc');
|
||||||
const fileType = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'md', 'htm', 'html', 'json', 'ppt', 'pptx', 'txt','png','jpg','mp3','mp4','mov','wav']
|
const fileType = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'md', 'htm', 'html', 'json', 'ppt', 'pptx', 'txt','png','jpg','mp3','mp4','mov','wav']
|
||||||
const steps = useMemo(
|
const steps = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
|||||||
@@ -89,6 +89,14 @@ const WordCloud: FC = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
color: ['#155EEF'],
|
color: ['#155EEF'],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const dataIndex = params.dataIndex
|
||||||
|
const item = radarData[dataIndex]
|
||||||
|
return `${item.name}<br/>${item.percentage.toFixed(1)}%`
|
||||||
|
}
|
||||||
|
},
|
||||||
radar: {
|
radar: {
|
||||||
indicator: radarData.map(item => ({
|
indicator: radarData.map(item => ({
|
||||||
name: t(`statementDetail.${item.name}`),
|
name: t(`statementDetail.${item.name}`),
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ const Editor: FC<LexicalEditorProps> =({
|
|||||||
<HistoryPlugin />
|
<HistoryPlugin />
|
||||||
<CommandPlugin />
|
<CommandPlugin />
|
||||||
<AutocompletePlugin options={options} />
|
<AutocompletePlugin options={options} />
|
||||||
<CharacterCountPlugin setCount={setCount} />
|
<CharacterCountPlugin setCount={setCount} onChange={onChange} />
|
||||||
<InitialValuePlugin value={value} options={options} onChange={onChange} />
|
<InitialValuePlugin value={value} options={options} />
|
||||||
<BlurPlugin />
|
<BlurPlugin />
|
||||||
</div>
|
</div>
|
||||||
</LexicalComposer>
|
</LexicalComposer>
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { $getRoot, $isParagraphNode } from 'lexical';
|
import { $getRoot, $isParagraphNode } from 'lexical';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
const CharacterCountPlugin = ({ setCount }: { setCount: (count: number) => void }) => {
|
import { $isVariableNode } from '../nodes/VariableNode';
|
||||||
|
|
||||||
|
const CharacterCountPlugin = ({ setCount, onChange }: { setCount: (count: number) => void; onChange?: (value: string) => void }) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
const onChangeRef = useRef(onChange);
|
||||||
|
onChangeRef.current = onChange;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return editor.registerUpdateListener(({ editorState, tags }) => {
|
return editor.registerUpdateListener(({ editorState, tags }) => {
|
||||||
if (tags.has('programmatic')) return;
|
if (tags.has('programmatic')) return;
|
||||||
editorState.read(() => {
|
editorState.read(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
let serializedContent = '';
|
||||||
|
|
||||||
|
// Traverse all nodes and serialize properly
|
||||||
const paragraphs: string[] = [];
|
const paragraphs: string[] = [];
|
||||||
root.getChildren().forEach(child => {
|
root.getChildren().forEach(child => {
|
||||||
if ($isParagraphNode(child)) {
|
if ($isParagraphNode(child)) {
|
||||||
paragraphs.push(child.getChildren().map(n => n.getTextContent()).join(''));
|
let paragraphContent = '';
|
||||||
|
child.getChildren().forEach(node => {
|
||||||
|
if ($isVariableNode(node)) {
|
||||||
|
paragraphContent += node.getTextContent();
|
||||||
|
} else {
|
||||||
|
paragraphContent += node.getTextContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
paragraphs.push(paragraphContent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setCount(paragraphs.join('\n').length);
|
|
||||||
|
serializedContent = paragraphs.join('\n');
|
||||||
|
|
||||||
|
setCount(serializedContent.length);
|
||||||
|
onChangeRef.current?.(serializedContent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [editor, setCount]);
|
}, [editor, setCount]);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const CommandPlugin = () => {
|
|||||||
|
|
||||||
// Create and insert the variable node
|
// Create and insert the variable node
|
||||||
const tagNode = $createVariableNode(payload.data);
|
const tagNode = $createVariableNode(payload.data);
|
||||||
const spaceNode = $createTextNode('');
|
const spaceNode = $createTextNode(' ');
|
||||||
|
|
||||||
anchorNode.insertAfter(tagNode);
|
anchorNode.insertAfter(tagNode);
|
||||||
tagNode.insertAfter(spaceNode);
|
tagNode.insertAfter(spaceNode);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import { $getRoot, $createParagraphNode, $createTextNode, $isParagraphNode } from 'lexical';
|
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
||||||
|
|
||||||
import { $createVariableNode } from '../nodes/VariableNode';
|
import { $createVariableNode } from '../nodes/VariableNode';
|
||||||
import { type Suggestion } from '../plugin/AutocompletePlugin'
|
import { type Suggestion } from '../plugin/AutocompletePlugin'
|
||||||
@@ -14,34 +14,24 @@ import { type Suggestion } from '../plugin/AutocompletePlugin'
|
|||||||
interface InitialValuePluginProps {
|
interface InitialValuePluginProps {
|
||||||
value: string;
|
value: string;
|
||||||
options?: Suggestion[];
|
options?: Suggestion[];
|
||||||
onChange?: (value: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [], onChange }) => {
|
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [] }) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const prevValueRef = useRef<string>('');
|
const prevValueRef = useRef<string>('');
|
||||||
const isUserInputRef = useRef(false);
|
const isUserInputRef = useRef(false);
|
||||||
const optionsRef = useRef(options);
|
const optionsRef = useRef(options);
|
||||||
optionsRef.current = options;
|
optionsRef.current = options;
|
||||||
const onChangeRef = useRef(onChange);
|
|
||||||
onChangeRef.current = onChange;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return editor.registerUpdateListener(({ editorState, tags }) => {
|
return editor.registerUpdateListener(({ editorState, tags }) => {
|
||||||
if (tags.has('programmatic')) return;
|
if (tags.has('programmatic')) return;
|
||||||
editorState.read(() => {
|
editorState.read(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
const paragraphs: string[] = [];
|
const textContent = root.getTextContent();
|
||||||
root.getChildren().forEach(child => {
|
if (textContent !== prevValueRef.current) {
|
||||||
if ($isParagraphNode(child)) {
|
|
||||||
paragraphs.push(child.getChildren().map(n => n.getTextContent()).join(''));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const text = paragraphs.join('\n');
|
|
||||||
if (text !== prevValueRef.current) {
|
|
||||||
isUserInputRef.current = true;
|
isUserInputRef.current = true;
|
||||||
prevValueRef.current = text;
|
prevValueRef.current = textContent;
|
||||||
onChangeRef.current?.(text);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,24 +49,23 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
|
|
||||||
const incomingEdges = graph.getIncomingEdges(node);
|
const incomingEdges = graph.getIncomingEdges(node);
|
||||||
const outgoingEdges = graph.getOutgoingEdges(node);
|
const outgoingEdges = graph.getOutgoingEdges(node);
|
||||||
const addedEdges: any[] = [];
|
|
||||||
|
|
||||||
incomingEdges?.forEach((edge: any) => {
|
incomingEdges?.forEach(edge => {
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() },
|
source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() },
|
||||||
target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' },
|
target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
outgoingEdges?.forEach((edge: any) => {
|
outgoingEdges?.forEach(edge => {
|
||||||
const targetCell = graph.getCellById(edge.getTargetCellId()) as any;
|
const targetCell = graph.getCellById(edge.getTargetCellId()) as any;
|
||||||
const targetPortId = targetCell?.getPorts?.()?.find((port: any) => port.group === 'left')?.id || edge.getTargetPortId();
|
const targetPortId = targetCell?.getPorts?.()?.find((port: any) => port.group === 'left')?.id || edge.getTargetPortId();
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' },
|
source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' },
|
||||||
target: { cell: edge.getTargetCellId(), port: targetPortId },
|
target: { cell: edge.getTargetCellId(), port: targetPortId },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove all add-node type nodes
|
// Remove all add-node type nodes
|
||||||
@@ -76,15 +75,6 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
addedEdges.forEach(e => {
|
|
||||||
const src = graph.getCellById(e.getSourceCellId());
|
|
||||||
const tgt = graph.getCellById(e.getTargetCellId());
|
|
||||||
if (src?.isNode()) src.toFront();
|
|
||||||
if (tgt?.isNode()) tgt.toFront();
|
|
||||||
});
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
// Automatically adjust loop node size
|
// Automatically adjust loop node size
|
||||||
const loopNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
const loopNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
||||||
if (loopNode) {
|
if (loopNode) {
|
||||||
|
|||||||
@@ -59,9 +59,6 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
target: { cell: addNode.id, port: targetPort },
|
target: { cell: addNode.id, port: targetPort },
|
||||||
...edgeAttrs,
|
...edgeAttrs,
|
||||||
});
|
});
|
||||||
|
|
||||||
cycleStartNode.toFront()
|
|
||||||
addNode.toFront()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +117,6 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}
|
}
|
||||||
graph.addEdge(edgeConfig)
|
graph.addEdge(edgeConfig)
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
cycleStartNode.toFront()
|
|
||||||
addNode.toFront()
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,12 +34,9 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('port:click', handlePortClick as EventListener);
|
window.addEventListener('port:click', handlePortClick as EventListener);
|
||||||
const handleBlankClick = () => handlePopoverClose();
|
|
||||||
window.addEventListener('blank:click', handleBlankClick);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('port:click', handlePortClick as EventListener);
|
window.removeEventListener('port:click', handlePortClick as EventListener);
|
||||||
window.removeEventListener('blank:click', handleBlankClick);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -191,39 +188,38 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const newPorts = newNode.getPorts();
|
const newPorts = newNode.getPorts();
|
||||||
|
|
||||||
const addedEdges: any[] = [];
|
|
||||||
if (edgeInsertion) {
|
if (edgeInsertion) {
|
||||||
// Edge insertion: create source→new and new→target edges
|
// Edge insertion: create source→new and new→target edges
|
||||||
const { targetCell, targetPort: origTargetPort } = edgeInsertion;
|
const { targetCell, targetPort: origTargetPort } = edgeInsertion;
|
||||||
const newLeftPort = newPorts.find((p: any) => p.group === 'left')?.id || 'left';
|
const newLeftPort = newPorts.find((p: any) => p.group === 'left')?.id || 'left';
|
||||||
const newRightPort = newPorts.find((p: any) => p.group === 'right')?.id || 'right';
|
const newRightPort = newPorts.find((p: any) => p.group === 'right')?.id || 'right';
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: sourceNode.id, port: sourcePort },
|
source: { cell: sourceNode.id, port: sourcePort },
|
||||||
target: { cell: newNode.id, port: newLeftPort },
|
target: { cell: newNode.id, port: newLeftPort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: newNode.id, port: newRightPort },
|
source: { cell: newNode.id, port: newRightPort },
|
||||||
target: { cell: targetCell.id, port: origTargetPort },
|
target: { cell: targetCell.id, port: origTargetPort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
setEdgeInsertion(null);
|
setEdgeInsertion(null);
|
||||||
} else if (sourcePortGroup === 'left') {
|
} else if (sourcePortGroup === 'left') {
|
||||||
// Connect from left port to new node's right side
|
// Connect from left port to new node's right side
|
||||||
const targetPort = newPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
const targetPort = newPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: newNode.id, port: targetPort },
|
source: { cell: newNode.id, port: targetPort },
|
||||||
target: { cell: sourceNode.id, port: sourcePort },
|
target: { cell: sourceNode.id, port: sourcePort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
} else {
|
} else {
|
||||||
// Connect from right port to new node's left side
|
// Connect from right port to new node's left side
|
||||||
const targetPort = newPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
const targetPort = newPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||||
addedEdges.push(graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: sourceNode.id, port: sourcePort },
|
source: { cell: sourceNode.id, port: sourcePort },
|
||||||
target: { cell: newNode.id, port: targetPort },
|
target: { cell: newNode.id, port: targetPort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust loop node size when child node is added via port within loop node
|
// Adjust loop node size when child node is added via port within loop node
|
||||||
@@ -270,44 +266,6 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCycleContainer = (type: string) => type === 'loop' || type === 'iteration';
|
|
||||||
const newNodeType = selectedNodeType.type;
|
|
||||||
|
|
||||||
// Helper: bring all child nodes and their edges of a cycle container to front
|
|
||||||
const bringCycleChildrenToFront = (cycleContainerId: string) => {
|
|
||||||
|
|
||||||
graph.getEdges().forEach((e: any) => {
|
|
||||||
const src = graph.getCellById(e.getSourceCellId());
|
|
||||||
const tgt = graph.getCellById(e.getTargetCellId());
|
|
||||||
if (src?.getData()?.cycle === cycleContainerId || tgt?.getData()?.cycle === cycleContainerId) e.toFront();
|
|
||||||
});
|
|
||||||
graph.getNodes().forEach((n: any) => {
|
|
||||||
if (n.getData()?.cycle === cycleContainerId) n.toFront();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isCycleContainer(sourceNodeType)) {
|
|
||||||
console.log('isCycleContainer(sourceNodeType)')
|
|
||||||
// Case 4: source is a loop/iteration node — bring new node to front, then its children
|
|
||||||
newNode.toFront();
|
|
||||||
sourceNode.toFront();
|
|
||||||
bringCycleChildrenToFront(sourceNodeData.id);
|
|
||||||
} else if (isCycleContainer(newNodeType)) {
|
|
||||||
console.log('isCycleContainer(newNodeType)')
|
|
||||||
// Case 3: adding a loop/iteration node from a normal node — bring new node to front, then its children
|
|
||||||
newNode.toFront();
|
|
||||||
sourceNode.toFront()
|
|
||||||
bringCycleChildrenToFront(id);
|
|
||||||
} else {
|
|
||||||
// Case 2: normal node → normal node
|
|
||||||
addedEdges.forEach(e => {
|
|
||||||
const src = graph.getCellById(e.getSourceCellId());
|
|
||||||
const tgt = graph.getCellById(e.getTargetCellId());
|
|
||||||
if (src?.isNode()) src.toFront();
|
|
||||||
if (tgt?.isNode()) tgt.toFront();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
// Clean up temporary element
|
// Clean up temporary element
|
||||||
|
|||||||
@@ -61,20 +61,6 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
const bringLoopChildrenToFront = (cell: any) => {
|
|
||||||
const type = cell?.getData()?.type;
|
|
||||||
if ((type !== 'loop' && type !== 'iteration') || !graphRef?.current) return;
|
|
||||||
const cycleId = cell.getData().id;
|
|
||||||
graphRef.current.getEdges().forEach((edge: any) => {
|
|
||||||
const src = graphRef.current?.getCellById(edge.getSourceCellId());
|
|
||||||
const tgt = graphRef.current?.getCellById(edge.getTargetCellId());
|
|
||||||
if (src?.getData()?.cycle === cycleId || tgt?.getData()?.cycle === cycleId) edge.toFront();
|
|
||||||
});
|
|
||||||
graphRef.current.getNodes().forEach((n: any) => {
|
|
||||||
if (n.getData()?.cycle === cycleId) n.toFront();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Recalculate node height and port Y positions without rebuilding ports
|
// Recalculate node height and port Y positions without rebuilding ports
|
||||||
const updateNodeLayout = (cases: any[]) => {
|
const updateNodeLayout = (cases: any[]) => {
|
||||||
if (!selectedNode || !graphRef?.current) return;
|
if (!selectedNode || !graphRef?.current) return;
|
||||||
@@ -153,10 +139,6 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
...edgeAttrs,
|
...edgeAttrs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sourceCell.toFront()
|
|
||||||
selectedNode.toFront()
|
|
||||||
bringLoopChildrenToFront(sourceCell)
|
|
||||||
bringLoopChildrenToFront(selectedNode)
|
|
||||||
graphRef.current?.removeCell(edge);
|
graphRef.current?.removeCell(edge);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -201,10 +183,6 @@ const CaseList: FC<CaseListProps> = ({
|
|||||||
target: { cell: targetCellId, port: targetPortId },
|
target: { cell: targetCellId, port: targetPortId },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
});
|
});
|
||||||
selectedNode.toFront()
|
|
||||||
bringLoopChildrenToFront(selectedNode)
|
|
||||||
targetCell.toFront()
|
|
||||||
bringLoopChildrenToFront(targetCell)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,20 +25,6 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
|||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const formValues = Form.useWatch([parentName], form);
|
const formValues = Form.useWatch([parentName], form);
|
||||||
|
|
||||||
const bringLoopChildrenToFront = (cell: any) => {
|
|
||||||
const type = cell?.getData()?.type;
|
|
||||||
if ((type !== 'loop' && type !== 'iteration') || !graphRef?.current) return;
|
|
||||||
const cycleId = cell.getData().id;
|
|
||||||
graphRef.current.getEdges().forEach((edge: any) => {
|
|
||||||
const src = graphRef.current?.getCellById(edge.getSourceCellId());
|
|
||||||
const tgt = graphRef.current?.getCellById(edge.getTargetCellId());
|
|
||||||
if (src?.getData()?.cycle === cycleId || tgt?.getData()?.cycle === cycleId) edge.toFront();
|
|
||||||
});
|
|
||||||
graphRef.current.getNodes().forEach((n: any) => {
|
|
||||||
if (n.getData()?.cycle === cycleId) n.toFront();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update node ports based on category count changes (add/remove categories)
|
// Update node ports based on category count changes (add/remove categories)
|
||||||
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
||||||
if (!selectedNode || !graphRef?.current) return;
|
if (!selectedNode || !graphRef?.current) return;
|
||||||
@@ -102,10 +88,6 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
|||||||
target: { cell: selectedNode.id, port: targetPortId },
|
target: { cell: selectedNode.id, port: targetPortId },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
});
|
});
|
||||||
sourceCell.toFront()
|
|
||||||
bringLoopChildrenToFront(sourceCell)
|
|
||||||
selectedNode.toFront()
|
|
||||||
bringLoopChildrenToFront(selectedNode)
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -137,10 +119,6 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
|||||||
target: { cell: targetCellId, port: targetPortId },
|
target: { cell: targetCellId, port: targetPortId },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
});
|
});
|
||||||
selectedNode.toFront()
|
|
||||||
bringLoopChildrenToFront(selectedNode)
|
|
||||||
targetCell.toFront()
|
|
||||||
bringLoopChildrenToFront(targetCell)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface KnowledgeConfigModalProps {
|
|||||||
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
||||||
}
|
}
|
||||||
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid',
|
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid',
|
||||||
'graph'
|
// 'graph'
|
||||||
]
|
]
|
||||||
|
|
||||||
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
||||||
|
|||||||
@@ -730,7 +730,7 @@ const defaultPortGroup = {
|
|||||||
stroke: port_color,
|
stroke: port_color,
|
||||||
strokeWidth: edge_width,
|
strokeWidth: edge_width,
|
||||||
fill: port_color,
|
fill: port_color,
|
||||||
opacity: 1,
|
opacity: 0,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
text: '+',
|
text: '+',
|
||||||
@@ -741,7 +741,7 @@ const defaultPortGroup = {
|
|||||||
textVerticalAnchor: 'middle',
|
textVerticalAnchor: 'middle',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
y: '0.15em',
|
y: '0.15em',
|
||||||
opacity: 1,
|
opacity: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export const useWorkflowGraph = ({
|
|||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
data: { ...node, ...nodeLibraryConfig },
|
data: { ...node, ...nodeLibraryConfig},
|
||||||
...position,
|
...position,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,8 +294,7 @@ export const useWorkflowGraph = ({
|
|||||||
x: nodeWidth,
|
x: nodeWidth,
|
||||||
y: portItemArgsY + portItemArgsY,
|
y: portItemArgsY + portItemArgsY,
|
||||||
},
|
},
|
||||||
id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs } }
|
id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs }}}
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -445,17 +444,9 @@ export const useWorkflowGraph = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (graphRef.current) {
|
if (graphRef.current) {
|
||||||
graphRef.current.centerContent()
|
graphRef.current.centerContent()
|
||||||
graphRef.current.getNodes().forEach(node => {
|
// graphRef.current.getNodes().forEach(node => node.toFront());
|
||||||
if (!node.getData()?.cycle) node.toFront();
|
|
||||||
});
|
|
||||||
// Bring edges to front first, then child nodes above edges; parent nodes stay behind
|
// Bring edges to front first, then child nodes above edges; parent nodes stay behind
|
||||||
graphRef.current.getEdges().forEach(edge => {
|
graphRef.current.getEdges().forEach(edge => edge.toFront());
|
||||||
const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId());
|
|
||||||
const targetCell = graphRef.current?.getCellById(edge.getTargetCellId());
|
|
||||||
if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) {
|
|
||||||
edge.toFront();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
graphRef.current.getNodes().forEach(node => {
|
graphRef.current.getNodes().forEach(node => {
|
||||||
if (node.getData()?.cycle) node.toFront();
|
if (node.getData()?.cycle) node.toFront();
|
||||||
});
|
});
|
||||||
@@ -584,7 +575,6 @@ export const useWorkflowGraph = ({
|
|||||||
clearEdgeSelect();
|
clearEdgeSelect();
|
||||||
graphRef.current?.cleanSelection();
|
graphRef.current?.cleanSelection();
|
||||||
setSelectedNode(null);
|
setSelectedNode(null);
|
||||||
window.dispatchEvent(new CustomEvent('blank:click'));
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Handle canvas scale/zoom event
|
* Handle canvas scale/zoom event
|
||||||
@@ -979,6 +969,25 @@ export const useWorkflowGraph = ({
|
|||||||
graphRef.current.on('edge:click', edgeClick);
|
graphRef.current.on('edge:click', edgeClick);
|
||||||
// Listen to port click event
|
// Listen to port click event
|
||||||
graphRef.current.on('node:port:click', nodePortClickEvent);
|
graphRef.current.on('node:port:click', nodePortClickEvent);
|
||||||
|
// Port hover: show circle style on right ports
|
||||||
|
graphRef.current.on('node:port:mouseenter', ({ node, port }) => {
|
||||||
|
console.log('node:port:mouseenter', port)
|
||||||
|
if (!port) return;
|
||||||
|
const portData = node.getPort(port);
|
||||||
|
if (portData?.group !== 'right') return;
|
||||||
|
node.toFront();
|
||||||
|
node.setPortProp(port, 'attrs/body/opacity', 0);
|
||||||
|
node.setPortProp(port, 'attrs/hoverBody/opacity', 1);
|
||||||
|
node.setPortProp(port, 'attrs/label/opacity', 1);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:port:mouseleave', ({ node, port }) => {
|
||||||
|
if (!port) return;
|
||||||
|
const portData = node.getPort(port);
|
||||||
|
if (portData?.group !== 'right') return;
|
||||||
|
node.setPortProp(port, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(port, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(port, 'attrs/label/opacity', 0);
|
||||||
|
});
|
||||||
// Listen to canvas click event, cancel selection
|
// Listen to canvas click event, cancel selection
|
||||||
graphRef.current.on('blank:click', blankClick);
|
graphRef.current.on('blank:click', blankClick);
|
||||||
// Node hover: highlight connected edges
|
// Node hover: highlight connected edges
|
||||||
@@ -996,6 +1005,11 @@ export const useWorkflowGraph = ({
|
|||||||
edge.setData({ ...edge.getData(), isNodeHover: true });
|
edge.setData({ ...edge.getData(), isNodeHover: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
node.getPorts().filter(p => p.group === 'right').forEach(p => {
|
||||||
|
node.setPortProp(p.id!, 'attrs/body/opacity', 0);
|
||||||
|
node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 1);
|
||||||
|
node.setPortProp(p.id!, 'attrs/label/opacity', 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
graphRef.current.on('node:mouseleave', ({ node }) => {
|
graphRef.current.on('node:mouseleave', ({ node }) => {
|
||||||
graphRef.current?.getConnectedEdges(node).forEach(edge => {
|
graphRef.current?.getConnectedEdges(node).forEach(edge => {
|
||||||
@@ -1004,31 +1018,48 @@ export const useWorkflowGraph = ({
|
|||||||
edge.setData({ ...edge.getData(), isNodeHover: false });
|
edge.setData({ ...edge.getData(), isNodeHover: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
node.getPorts().filter(p => p.group === 'right').forEach(p => {
|
||||||
|
node.setPortProp(p.id!, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(p.id!, 'attrs/label/opacity', 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// Listen to zoom event
|
// Listen to zoom event
|
||||||
graphRef.current.on('scale', scaleEvent);
|
graphRef.current.on('scale', scaleEvent);
|
||||||
// Listen to node move event
|
// Listen to node move event
|
||||||
graphRef.current.on('node:moved', nodeMoved);
|
graphRef.current.on('node:moved', nodeMoved);
|
||||||
|
// When parent (isGroup) node position changes, move children with it
|
||||||
|
graphRef.current.on('node:change:position', ({ node, current, previous }: { node: Node; current: { x: number; y: number }; previous: { x: number; y: number } }) => {
|
||||||
|
|
||||||
|
if (!(node.getData()?.type === 'iteration' && node.getData()?.type === 'loop') || !current || !previous) return;
|
||||||
|
|
||||||
|
const dx = current.x - previous.x;
|
||||||
|
const dy = current.y - previous.y;
|
||||||
|
const parentId = node.getData()?.id || node.id;
|
||||||
|
graphRef.current?.getNodes().forEach(child => {
|
||||||
|
if (child.getData()?.cycle === parentId) {
|
||||||
|
const cp = child.getPosition();
|
||||||
|
child.setPosition(cp.x + dx, cp.y + dy, { silent: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
graphRef.current.on('node:removed', blankClick)
|
graphRef.current.on('node:removed', blankClick)
|
||||||
// When edge connected, bring connected nodes' ports to front
|
// When edge connected, bring connected nodes' ports to front
|
||||||
graphRef.current.on('edge:connected', ({ isNew }) => {
|
graphRef.current.on('edge:connected', ({ isNew, edge }) => {
|
||||||
// Bring edge to front first, then bring child nodes above edges
|
// Bring edge to front first, then bring child nodes above edges
|
||||||
// Parent (loop/iteration) nodes stay behind to avoid covering edges
|
// Parent (loop/iteration) nodes stay behind to avoid covering edges
|
||||||
|
edge.toFront();
|
||||||
|
graphRef.current?.getNodes().forEach(node => {
|
||||||
|
if (node.getData()?.cycle) node.toFront();
|
||||||
|
});
|
||||||
// Reset any port hover state left from dragging
|
// Reset any port hover state left from dragging
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
graphRef.current?.getNodes().forEach(node => {
|
graphRef.current?.getNodes().forEach(node => {
|
||||||
if (!node.getData()?.cycle) node.toFront();
|
node.getPorts().filter(p => p.group === 'right').forEach(p => {
|
||||||
});
|
node.setPortProp(p.id!, 'attrs/body/opacity', 1);
|
||||||
graphRef.current?.getEdges().forEach(edge => {
|
node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 0);
|
||||||
const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId());
|
node.setPortProp(p.id!, 'attrs/label/opacity', 0);
|
||||||
const targetCell = graphRef.current?.getCellById(edge.getTargetCellId());
|
});
|
||||||
if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) {
|
|
||||||
edge.toFront();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
graphRef.current?.getNodes().forEach(node => {
|
|
||||||
if (node.getData()?.cycle) node.toFront();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1058,9 +1089,34 @@ export const useWorkflowGraph = ({
|
|||||||
if (found) break;
|
if (found) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHoveredPort = found;
|
if (found?.node.id !== lastHoveredPort?.node.id || found?.portId !== lastHoveredPort?.portId) {
|
||||||
|
// Leave previous
|
||||||
|
if (lastHoveredPort) {
|
||||||
|
const { node, portId } = lastHoveredPort;
|
||||||
|
node.setPortProp(portId, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(portId, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(portId, 'attrs/label/opacity', 0);
|
||||||
|
}
|
||||||
|
// Enter new
|
||||||
|
if (found) {
|
||||||
|
const { node, portId } = found;
|
||||||
|
node.toFront();
|
||||||
|
node.setPortProp(portId, 'attrs/body/opacity', 0);
|
||||||
|
node.setPortProp(portId, 'attrs/hoverBody/opacity', 1);
|
||||||
|
node.setPortProp(portId, 'attrs/label/opacity', 1);
|
||||||
|
}
|
||||||
|
lastHoveredPort = found;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
graphRef.current.on('edge:mouseup', () => {
|
||||||
|
if (lastHoveredPort) {
|
||||||
|
const { node, portId } = lastHoveredPort;
|
||||||
|
node.setPortProp(portId, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(portId, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(portId, 'attrs/label/opacity', 0);
|
||||||
|
lastHoveredPort = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
graphRef.current.on('edge:mouseup', () => { lastHoveredPort = null; });
|
|
||||||
// Listen to copy keyboard event
|
// Listen to copy keyboard event
|
||||||
graphRef.current.bindKey(['ctrl+c', 'cmd+c'], copyEvent);
|
graphRef.current.bindKey(['ctrl+c', 'cmd+c'], copyEvent);
|
||||||
// Listen to paste keyboard event
|
// Listen to paste keyboard event
|
||||||
@@ -1271,7 +1327,7 @@ export const useWorkflowGraph = ({
|
|||||||
|
|
||||||
// Filter invalid edges: source or target node doesn't exist, or is add-node type
|
// Filter invalid edges: source or target node doesn't exist, or is add-node type
|
||||||
if (!sourceCell?.getData()?.id || !targetCell?.getData()?.id ||
|
if (!sourceCell?.getData()?.id || !targetCell?.getData()?.id ||
|
||||||
sourceCell?.getData()?.type === 'add-node' || targetCell?.getData()?.type === 'add-node') {
|
sourceCell?.getData()?.type === 'add-node' || targetCell?.getData()?.type === 'add-node') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,34 +1371,34 @@ export const useWorkflowGraph = ({
|
|||||||
target: targetCell?.getData().id,
|
target: targetCell?.getData().id,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(edge => edge !== null)
|
.filter(edge => edge !== null)
|
||||||
.filter((edge, index, arr) => {
|
.filter((edge, index, arr) => {
|
||||||
// Deduplication: For if-else and question-classifier nodes, different ports can connect to same node
|
// Deduplication: For if-else and question-classifier nodes, different ports can connect to same node
|
||||||
return arr.findIndex(e => {
|
return arr.findIndex(e => {
|
||||||
if (!e || !edge) return false;
|
if (!e || !edge) return false;
|
||||||
const sourceCell = graphRef.current?.getCellById(e.source);
|
const sourceCell = graphRef.current?.getCellById(e.source);
|
||||||
const sourceType = sourceCell?.getData()?.type;
|
const sourceType = sourceCell?.getData()?.type;
|
||||||
const isMultiPortNode = sourceType === 'question-classifier' || sourceType === 'if-else';
|
const isMultiPortNode = sourceType === 'question-classifier' || sourceType === 'if-else';
|
||||||
|
|
||||||
if (isMultiPortNode) {
|
if (isMultiPortNode) {
|
||||||
// Multi-port nodes need to compare source, target and label
|
// Multi-port nodes need to compare source, target and label
|
||||||
return e.source === edge.source && e.target === edge.target && e.label === edge.label;
|
return e.source === edge.source && e.target === edge.target && e.label === edge.label;
|
||||||
} else {
|
} else {
|
||||||
// Other nodes only compare source and target
|
// Other nodes only compare source and target
|
||||||
return e.source === edge.source && e.target === edge.target;
|
return e.source === edge.source && e.target === edge.target;
|
||||||
}
|
}
|
||||||
}) === index;
|
}) === index;
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
saveWorkflowConfig(config.app_id, params as WorkflowConfig)
|
saveWorkflowConfig(config.app_id, params as WorkflowConfig)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
message.success({ content: t('common.saveSuccess'), duration: 1 })
|
message.success({ content: t('common.saveSuccess'), duration: 1 })
|
||||||
}
|
}
|
||||||
resolve(res)
|
resolve(res)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user