Merge branch 'refs/heads/develop' into fix/memory_bug_fix
This commit is contained in:
@@ -11,15 +11,16 @@ from app.core.response_utils import success
|
||||
from app.db import get_db
|
||||
from app.dependencies import get_current_user, cur_workspace_access_guard
|
||||
from app.models import User
|
||||
from app.models.app_model import AppType, App
|
||||
from app.models.app_model import AppType
|
||||
from app.repositories import knowledge_repository
|
||||
from app.repositories.end_user_repository import EndUserRepository
|
||||
from app.schemas import app_schema
|
||||
from app.schemas.response_schema import PageData, PageMeta
|
||||
from app.schemas.workflow_schema import WorkflowConfig as WorkflowConfigSchema
|
||||
from app.schemas.workflow_schema import WorkflowConfigUpdate
|
||||
from app.services import app_service, workspace_service
|
||||
from app.services.agent_config_helper import enrich_agent_config
|
||||
from app.services.app_service import AppService
|
||||
from app.schemas.workflow_schema import WorkflowConfig as WorkflowConfigSchema
|
||||
from app.services.workflow_service import WorkflowService, get_workflow_service
|
||||
|
||||
router = APIRouter(prefix="/apps", tags=["Apps"])
|
||||
@@ -405,6 +406,15 @@ async def draft_run(
|
||||
# 只读操作,允许访问共享应用
|
||||
service._validate_app_accessible(app, workspace_id)
|
||||
|
||||
if payload.user_id is None:
|
||||
end_user_repo = EndUserRepository(db)
|
||||
new_end_user = end_user_repo.get_or_create_end_user(
|
||||
app_id=app_id,
|
||||
other_id=str(current_user.id),
|
||||
original_user_id=str(current_user.id) # Save original user_id to other_id
|
||||
)
|
||||
payload.user_id = str(new_end_user.id)
|
||||
|
||||
# 处理会话ID(创建或验证)
|
||||
conversation_id = await draft_service._ensure_conversation(
|
||||
conversation_id=payload.conversation_id,
|
||||
|
||||
@@ -96,10 +96,7 @@ class SimpleMCPClient:
|
||||
"""初始化 SSE MCP 会话 - 参考 Dify 实现"""
|
||||
try:
|
||||
# 建立 SSE 连接
|
||||
response = await self._session.get(
|
||||
self.server_url,
|
||||
headers={"Accept": "text/event-stream"}
|
||||
)
|
||||
response = await self._session.get(self.server_url)
|
||||
|
||||
if response.status != 200:
|
||||
error_text = await response.text()
|
||||
|
||||
@@ -516,8 +516,16 @@ class ConversationService:
|
||||
|
||||
conversation_messages = self.get_conversation_history(
|
||||
conversation_id=conversation_id,
|
||||
max_history=30
|
||||
max_history=20
|
||||
)
|
||||
if len(conversation_messages) == 0:
|
||||
return ConversationOut(
|
||||
theme="",
|
||||
question=[],
|
||||
summary="",
|
||||
takeaways=[],
|
||||
info_score=0,
|
||||
)
|
||||
|
||||
with open('app/services/prompt/conversation_summary_system.jinja2', 'r', encoding='utf-8') as f:
|
||||
system_prompt = f.read()
|
||||
@@ -536,6 +544,7 @@ class ConversationService:
|
||||
]
|
||||
logger.info(f"Invoking LLM for conversation_id={conversation_id}")
|
||||
model_resp = await llm.ainvoke(messages)
|
||||
|
||||
try:
|
||||
if isinstance(model_resp.content, str):
|
||||
result = json_repair.repair_json(model_resp.content, return_objects=True)
|
||||
|
||||
@@ -245,7 +245,8 @@ class DraftRunService:
|
||||
storage_type: Optional[str] = None,
|
||||
user_rag_memory_id: Optional[str] = None,
|
||||
web_search: bool = True,
|
||||
memory: bool = True
|
||||
memory: bool = True,
|
||||
sub_agent: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""执行试运行(使用 LangChain Agent)
|
||||
|
||||
@@ -435,7 +436,7 @@ class DraftRunService:
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
# 8. 保存会话消息
|
||||
if agent_config.memory and agent_config.memory.get("enabled"):
|
||||
if not sub_agent and agent_config.memory and agent_config.memory.get("enabled"):
|
||||
await self._save_conversation_message(
|
||||
conversation_id=conversation_id,
|
||||
user_message=message,
|
||||
|
||||
@@ -267,14 +267,14 @@ class MemoryForgetService:
|
||||
elif node_type_label == 'memorysummary':
|
||||
node_type_label = 'summary'
|
||||
|
||||
# 将 Neo4j DateTime 对象转换为时间戳
|
||||
# 将 Neo4j DateTime 对象转换为时间戳(毫秒)
|
||||
last_access_time = result['last_access_time']
|
||||
last_access_dt = convert_neo4j_datetime_to_python(last_access_time)
|
||||
# 确保 datetime 带有时区信息(假定为 UTC),避免 naive datetime 导致的时区偏差
|
||||
if last_access_dt:
|
||||
if last_access_dt.tzinfo is None:
|
||||
last_access_dt = last_access_dt.replace(tzinfo=timezone.utc)
|
||||
last_access_timestamp = int(last_access_dt.timestamp())
|
||||
last_access_timestamp = int(last_access_dt.timestamp() * 1000)
|
||||
else:
|
||||
last_access_timestamp = 0
|
||||
|
||||
@@ -520,7 +520,7 @@ class MemoryForgetService:
|
||||
'average_activation_value': result['average_activation'],
|
||||
'low_activation_nodes': result['low_activation_nodes'] or 0,
|
||||
'forgetting_threshold': forgetting_threshold,
|
||||
'timestamp': int(datetime.now().timestamp())
|
||||
'timestamp': int(datetime.now().timestamp() * 1000)
|
||||
}
|
||||
else:
|
||||
activation_metrics = {
|
||||
@@ -530,7 +530,7 @@ class MemoryForgetService:
|
||||
'average_activation_value': None,
|
||||
'low_activation_nodes': 0,
|
||||
'forgetting_threshold': forgetting_threshold,
|
||||
'timestamp': int(datetime.now().timestamp())
|
||||
'timestamp': int(datetime.now().timestamp() * 1000)
|
||||
}
|
||||
|
||||
# 收集节点类型分布
|
||||
@@ -620,7 +620,7 @@ class MemoryForgetService:
|
||||
'merged_count': record.merged_count,
|
||||
'average_activation': record.average_activation_value,
|
||||
'total_nodes': record.total_nodes,
|
||||
'execution_time': int(record.execution_time.timestamp())
|
||||
'execution_time': int(record.execution_time.timestamp() * 1000)
|
||||
})
|
||||
|
||||
api_logger.info(f"成功获取最近 {len(recent_trends)} 个日期的历史趋势数据")
|
||||
@@ -661,7 +661,7 @@ class MemoryForgetService:
|
||||
'node_distribution': node_distribution,
|
||||
'recent_trends': recent_trends,
|
||||
'pending_nodes': pending_nodes,
|
||||
'timestamp': int(datetime.now().timestamp())
|
||||
'timestamp': int(datetime.now().timestamp() * 1000)
|
||||
}
|
||||
|
||||
api_logger.info(
|
||||
|
||||
@@ -1327,7 +1327,8 @@ class MultiAgentOrchestrator:
|
||||
web_search=web_search,
|
||||
memory=memory,
|
||||
storage_type=storage_type,
|
||||
user_rag_memory_id=user_rag_memory_id
|
||||
user_rag_memory_id=user_rag_memory_id,
|
||||
sub_agent=True
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@@ -13,11 +13,10 @@ from sqlalchemy.orm import Session
|
||||
from app.core.error_codes import BizCode
|
||||
from app.core.exceptions import BusinessException
|
||||
from app.core.workflow.validator import validate_workflow_config
|
||||
from app.db import get_db, get_db_context
|
||||
from app.db import get_db
|
||||
from app.models.conversation_model import Message
|
||||
from app.models.workflow_model import WorkflowConfig, WorkflowExecution
|
||||
from app.repositories.conversation_repository import MessageRepository
|
||||
from app.models.conversation_model import Message
|
||||
from app.repositories.end_user_repository import EndUserRepository
|
||||
from app.repositories.workflow_repository import (
|
||||
WorkflowConfigRepository,
|
||||
WorkflowExecutionRepository,
|
||||
@@ -483,14 +482,6 @@ class WorkflowService:
|
||||
try:
|
||||
# 更新状态为运行中
|
||||
self.update_execution_status(execution.execution_id, "running")
|
||||
with get_db_context() as db:
|
||||
end_user_repo = EndUserRepository(db)
|
||||
new_end_user = end_user_repo.get_or_create_end_user(
|
||||
app_id=app_id,
|
||||
other_id=payload.user_id,
|
||||
original_user_id=payload.user_id # Save original user_id to other_id
|
||||
)
|
||||
end_user_id = str(new_end_user.id)
|
||||
|
||||
executions = self.execution_repo.get_by_conversation_id(conversation_id=conversation_id_uuid)
|
||||
|
||||
@@ -511,7 +502,7 @@ class WorkflowService:
|
||||
input_data=input_data,
|
||||
execution_id=execution.execution_id,
|
||||
workspace_id=str(workspace_id),
|
||||
user_id=end_user_id
|
||||
user_id=payload.user_id
|
||||
)
|
||||
|
||||
# 更新执行结果
|
||||
@@ -638,14 +629,6 @@ class WorkflowService:
|
||||
try:
|
||||
# 更新状态为运行中
|
||||
self.update_execution_status(execution.execution_id, "running")
|
||||
with get_db_context() as db:
|
||||
end_user_repo = EndUserRepository(db)
|
||||
new_end_user = end_user_repo.get_or_create_end_user(
|
||||
app_id=app_id,
|
||||
other_id=payload.user_id,
|
||||
original_user_id=payload.user_id # Save original user_id to other_id
|
||||
)
|
||||
end_user_id = str(new_end_user.id)
|
||||
executions = self.execution_repo.get_by_conversation_id(conversation_id=conversation_id_uuid)
|
||||
|
||||
for exec_res in executions:
|
||||
@@ -665,7 +648,7 @@ class WorkflowService:
|
||||
input_data=input_data,
|
||||
execution_id=execution.execution_id,
|
||||
workspace_id=str(workspace_id),
|
||||
user_id=end_user_id
|
||||
user_id=payload.user_id
|
||||
):
|
||||
if event.get("event") == "workflow_end":
|
||||
|
||||
|
||||
@@ -30,15 +30,12 @@ import 'dayjs/plugin/utc'
|
||||
import { cookieUtils } from './utils/request';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { locale, language, timeZone } = useI18n()
|
||||
useEffect(() => {
|
||||
const authToken = cookieUtils.get('authToken')
|
||||
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||
if (!authToken && !window.location.hash.includes('#/login') && !window.location.hash.includes('#/conversation/')) {
|
||||
window.location.href = `/#/login`;
|
||||
}
|
||||
}, [])
|
||||
|
||||
14
web/src/assets/images/menu/helpCenter.svg
Normal file
14
web/src/assets/images/menu/helpCenter.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>使用帮助备份</title>
|
||||
<g id="v0.2.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-51, -358)" stroke="#5F6266">
|
||||
<g id="使用帮助备份" transform="translate(51, 358)">
|
||||
<g id="编组-35" transform="translate(2, 1.5)">
|
||||
<path d="M6.13163525,1.97938144 L10.3064533,1.97938144 C11.2417733,1.97938144 12,2.70634106 12,3.6030912 L12,10.3762902 C12,11.2730404 11.2417733,12 10.3064533,12 L1.69354673,12 C0.758226699,12 0,11.2730404 0,10.3762902 L0,3.6030912 C0,2.70634106 0.758226699,1.97938144 1.69354673,1.97938144 L2.02448435,1.97938144 L2.02448435,1.97938144" id="路径"></path>
|
||||
<path d="M3.52033177,0.78470905 L6.09032258,1.97938144 L6.09032258,1.97938144 L6.09032258,11.8762887 L2.51918436,10.2162282 C2.10022604,10.0214734 1.83225806,9.6014016 1.83225806,9.13938916 L1.83225806,1.86154804 C1.83225806,1.2057099 2.36391992,0.674048044 3.01975806,0.674048044 C3.19268295,0.674048044 3.36352144,0.711815028 3.52033177,0.78470905 Z" id="矩形" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
14
web/src/assets/images/menu/helpCenter_active.svg
Normal file
14
web/src/assets/images/menu/helpCenter_active.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>使用帮助</title>
|
||||
<g id="v0.2.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-24, -358)" stroke="#212332">
|
||||
<g id="使用帮助" transform="translate(24, 358)">
|
||||
<g id="编组-35" transform="translate(2, 1.5)">
|
||||
<path d="M6.13163525,1.97938144 L10.3064533,1.97938144 C11.2417733,1.97938144 12,2.70634106 12,3.6030912 L12,10.3762902 C12,11.2730404 11.2417733,12 10.3064533,12 L1.69354673,12 C0.758226699,12 0,11.2730404 0,10.3762902 L0,3.6030912 C0,2.70634106 0.758226699,1.97938144 1.69354673,1.97938144 L2.02448435,1.97938144 L2.02448435,1.97938144" id="路径"></path>
|
||||
<path d="M3.52033177,0.78470905 L6.09032258,1.97938144 L6.09032258,1.97938144 L6.09032258,11.8762887 L2.51918436,10.2162282 C2.10022604,10.0214734 1.83225806,9.6014016 1.83225806,9.13938916 L1.83225806,1.86154804 C1.83225806,1.2057099 2.36391992,0.674048044 3.01975806,0.674048044 C3.19268295,0.674048044 3.36352144,0.711815028 3.52033177,0.78470905 Z" id="矩形" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
14
web/src/components/Layout/NoAuthLayout.tsx
Normal file
14
web/src/components/Layout/NoAuthLayout.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { type FC } from 'react';
|
||||
|
||||
// 基础布局组件,用于展示内容并保留用户信息获取功能
|
||||
const NoAuthLayout: FC = () => {
|
||||
|
||||
return (
|
||||
<div className="rb:relative rb:h-full rb:w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default NoAuthLayout;
|
||||
@@ -81,7 +81,7 @@ const Code: FC<ICodeProps> = (props) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <span className="rb:bg-[#F0F3F8] rb:px-1 rb:py-0.5 rb:rounded rb:text-sm rb:font-mono">{children}</span>
|
||||
return <code className="rb:bg-[#F0F3F8] rb:px-1 rb:py-0.5 rb:rounded rb:text-sm rb:font-mono rb:whitespace-break-spaces">{children}</code>
|
||||
}
|
||||
|
||||
export default Code
|
||||
|
||||
@@ -91,6 +91,7 @@ export const en = {
|
||||
memberManagement: 'Member Management',
|
||||
memorySummary: 'Memory Summary',
|
||||
memoryConversation: 'Memory Validation',
|
||||
helpCenter: 'Help Center',
|
||||
memorySummaryHandlers: 'Memory Summary Handlers',
|
||||
createMemorySummary: 'Create Memory Summary',
|
||||
memoryManagement: 'Memory Management',
|
||||
@@ -190,7 +191,8 @@ export const en = {
|
||||
|
||||
memoryConversation: 'Memory Conversation',
|
||||
memoryConversationDesc: 'Memory Conversation',
|
||||
|
||||
helpCenter: 'Help Center',
|
||||
helpCenterDesc: 'Help Center',
|
||||
memorySummary: 'View Memory Summary',
|
||||
memorySummaryDesc: 'View Memory Summary Report',
|
||||
|
||||
@@ -616,6 +618,7 @@ export const en = {
|
||||
retrieve:'Retrieve',
|
||||
processing: 'Processing',
|
||||
processingMode: 'Processing Mode',
|
||||
processMsg: 'Processing Message',
|
||||
dataSize: 'Data Size',
|
||||
createUpdateTime: 'Create/Update Time',
|
||||
operation: 'Operation',
|
||||
@@ -1960,6 +1963,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
addMessage: 'Add Message',
|
||||
answerDesc: 'Reply',
|
||||
addNode: 'Add Node',
|
||||
arrange: 'Arrange',
|
||||
redo: 'Redo',
|
||||
undo: 'Undo',
|
||||
},
|
||||
emotionEngine: {
|
||||
emotionEngineConfig: 'Emotion Engine Configuration',
|
||||
|
||||
@@ -789,7 +789,8 @@ export const zh = {
|
||||
|
||||
memoryConversation: '记忆对话',
|
||||
memoryConversationDesc: '记忆对话',
|
||||
|
||||
helpCenter: '帮助中心',
|
||||
helpCenterDesc: '帮助中心',
|
||||
memorySummary: '查看记忆摘要',
|
||||
memorySummaryDesc: '查看记忆摘要报告',
|
||||
|
||||
@@ -2057,6 +2058,9 @@ export const zh = {
|
||||
addMessage: '添加消息',
|
||||
answerDesc: '回复',
|
||||
addNode: '添加节点',
|
||||
arrange: '整理',
|
||||
redo: '重做',
|
||||
undo: '撤销',
|
||||
},
|
||||
emotionEngine: {
|
||||
emotionEngineConfig: '情感引擎配置',
|
||||
|
||||
@@ -34,6 +34,7 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
||||
AuthSpaceLayout: lazy(() => import('@/components/Layout/AuthSpaceLayout')),
|
||||
BasicLayout: lazy(() => import('@/components/Layout/BasicLayout')),
|
||||
LoginLayout: lazy(() => import('@/components/Layout/LoginLayout')),
|
||||
NoAuthLayout: lazy(() => import('@/components/Layout/NoAuthLayout')),
|
||||
// 视图组件
|
||||
Index: lazy(() => import('@/views/Index')),
|
||||
Home: lazy(() => import('@/views/Home')),
|
||||
|
||||
@@ -42,12 +42,17 @@
|
||||
"element": "BasicLayout",
|
||||
"children": [
|
||||
{ "path": "/application/config/:id", "element": "ApplicationConfig" },
|
||||
{ "path": "/conversation/:token", "element": "Conversation" },
|
||||
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" },
|
||||
{ "path": "/statement/:id", "element": "StatementDetail" },
|
||||
{ "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"element": "NoAuthLayout",
|
||||
"children": [
|
||||
{ "path": "/conversation/:token", "element": "Conversation" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"element": "LoginLayout",
|
||||
"children": [
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Version: 0.0.1
|
||||
* @Author: yujiangping
|
||||
* @Date: 2026-01-05 17:22:23
|
||||
* @LastEditors: yujiangping
|
||||
* @LastEditTime: 2026-01-15 14:55:51
|
||||
*/
|
||||
import { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -5,33 +13,49 @@ import Card from './Card';
|
||||
import applicationIcon from '@/assets/images/menu/application_active.svg';
|
||||
import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg';
|
||||
import memoryConversationIcon from '@/assets/images/menu/memoryConversation_active.svg';
|
||||
import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg'
|
||||
import arrowTopRight from '@/assets/images/home/arrow_top_right.svg';
|
||||
|
||||
const quickOperations = [
|
||||
{ key: 'createNewApplication', url: '/application' },
|
||||
{ key: 'createNewKnowledge', url: '/knowledge-base' },
|
||||
{ key: 'memoryConversation', url: '/memory-conversation' },
|
||||
{ key: 'helpCenter', url: '' },
|
||||
]
|
||||
|
||||
const quickOperationIcons: {[key: string]: string | undefined} = {
|
||||
createNewApplication: applicationIcon,
|
||||
createNewKnowledge: knowledgeIcon,
|
||||
memoryConversation: memoryConversationIcon,
|
||||
helpCenter: helpCenterIcon
|
||||
}
|
||||
const QuickOperation:FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { t, i18n } = useTranslation()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleJump = (url: string | null) => {
|
||||
if (url) {
|
||||
navigate(url)
|
||||
}else{
|
||||
const currentLang = i18n.language;
|
||||
const lang = currentLang === 'zh' ? 'zh' : 'en';
|
||||
const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`;
|
||||
|
||||
// 创建隐藏的 a 标签来避免弹窗拦截
|
||||
const link = document.createElement('a');
|
||||
link.href = helpUrl;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
title={t('dashboard.quickOperation')}
|
||||
>
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-[16px]">
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
||||
{quickOperations.map(item => (
|
||||
<div key={item.key} className="rb:rounded-[8px] rb:p-[20px_16px] rb:border-1 rb:border-[#DFE4ED] rb:cursor-pointer rb:hover:border-[#155EEF]" onClick={() => handleJump(item.url)}>
|
||||
<div className="rb:flex rb:justify-between">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useEffect, useState, useRef, useCallback, type FC } from 'react';
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Switch, Button, Dropdown, Space, Modal, message, Radio } from 'antd';
|
||||
import { Switch, Button, Dropdown, Space, Modal, message, Radio, Tooltip } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import Table, { type TableRef } from '@/components/Table'
|
||||
@@ -564,6 +564,37 @@ const Private: FC = () => {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},{
|
||||
title: t('knowledgeBase.processMsg'),
|
||||
dataIndex: 'progress_msg',
|
||||
key: 'progress_msg',
|
||||
width: 320,
|
||||
render: (value: string) => {
|
||||
if (!value) return '-';
|
||||
|
||||
// 解析日志格式,将 \n 转换为换行
|
||||
const formattedText = value.replace(/\\n/g, '\n');
|
||||
|
||||
return (
|
||||
<Tooltip title={<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{formattedText}</pre>} placement="topLeft">
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '320px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
lineHeight: '1.5',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
}}
|
||||
>
|
||||
{formattedText}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('knowledgeBase.processingMode'),
|
||||
|
||||
@@ -41,7 +41,9 @@ export default function UserMemory() {
|
||||
navigate(`/user-memory/${id}`)
|
||||
}
|
||||
}
|
||||
const handleViewMemoryConfig = () => {
|
||||
const handleViewMemoryConfig = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate(`/memory`)
|
||||
}
|
||||
|
||||
@@ -84,8 +86,9 @@ export default function UserMemory() {
|
||||
title={name || '-'}
|
||||
extra={<div
|
||||
className="rb:w-7 rb:h-7 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/goto.svg')]"
|
||||
onClick={() => handleViewDetail(end_user.id)}
|
||||
></div>}
|
||||
className="rb:cursor-pointer"
|
||||
onClick={() => handleViewDetail(end_user.id)}
|
||||
>
|
||||
<div className="rb:flex rb:justify-between rb:items-center">
|
||||
<div>{t('userMemory.capacity')}</div>
|
||||
@@ -96,7 +99,7 @@ export default function UserMemory() {
|
||||
<div>{t(`userMemory.${item.type || 'person'}`)}</div>
|
||||
</div>
|
||||
|
||||
<div className="rb:mt-3 rb:bg-[#F6F8FC] rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:py-2 rb:px-3" onClick={handleViewMemoryConfig}>
|
||||
<div className="rb:relative rb:z-2 rb:mt-3 rb:bg-[#F6F8FC] rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:py-2 rb:px-3" onClick={handleViewMemoryConfig}>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:flex rb:justify-between rb:items-center">
|
||||
{t('userMemory.memory_config_name')}
|
||||
<div
|
||||
|
||||
@@ -81,12 +81,14 @@ const RelationshipNetwork:FC = () => {
|
||||
name: displayName,
|
||||
category: categoryIndex >= 0 ? categoryIndex : 0,
|
||||
symbolSize: symbolSize, // 根据连接数调整节点大小
|
||||
itemStyle: {
|
||||
color: colors[categoryIndex % 8]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 创建节点ID到标签的映射
|
||||
const nodeIdToLabel: Record<string, string> = {}
|
||||
nodes.forEach(node => {
|
||||
nodeIdToLabel[node.id] = node.label
|
||||
})
|
||||
// 处理边数据
|
||||
edges.forEach(edge => {
|
||||
curEdges.push({
|
||||
|
||||
@@ -40,7 +40,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
||||
const curVariables = startNodes[0].config.variables?.defaultValue
|
||||
|
||||
curVariables.forEach((vo: StartVariableItem) => {
|
||||
if (vo.default) {
|
||||
if (typeof vo.default !== 'undefined') {
|
||||
vo.value = vo.default
|
||||
}
|
||||
const lastVo = variables.find(item => item.name === vo.name)
|
||||
@@ -55,6 +55,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
||||
setOpen(false)
|
||||
setChatList([])
|
||||
setVariables([])
|
||||
setConversationId(null)
|
||||
}
|
||||
const handleEditVariables = () => {
|
||||
variableConfigModalRef.current?.handleOpen(variables)
|
||||
|
||||
@@ -13,11 +13,12 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
const handleNodeSelect = (selectedNodeType: any) => {
|
||||
const parentBBox = node.getBBox();
|
||||
const cycleId = data.cycle;
|
||||
const horizontalSpacing = 20;
|
||||
|
||||
const id = `${selectedNodeType.type.replace(/-/g, '_') }_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
const newNode = graph.addNode({
|
||||
...(graphNodeLibrary[selectedNodeType.type] || graphNodeLibrary.default),
|
||||
x: parentBBox.x,
|
||||
x: parentBBox.x + horizontalSpacing,
|
||||
y: parentBBox.y,
|
||||
id,
|
||||
data: {
|
||||
@@ -47,7 +48,6 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() },
|
||||
target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' },
|
||||
attrs: edge.getAttrs(),
|
||||
zIndex: 3
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,6 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' },
|
||||
target: { cell: edge.getTargetCellId(), port: targetPortId },
|
||||
attrs: edge.getAttrs(),
|
||||
zIndex: 3
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,10 +11,14 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
initNodes()
|
||||
// 检查是否需要添加add-node
|
||||
checkAndAddAddNode()
|
||||
}, [])
|
||||
// 使用setTimeout确保在所有节点都添加完成后再创建连线
|
||||
const timer = setTimeout(() => {
|
||||
initNodes()
|
||||
checkAndAddAddNode()
|
||||
}, 50)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [graph])
|
||||
|
||||
const checkAndAddAddNode = () => {
|
||||
if (!graph) return;
|
||||
@@ -29,7 +33,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
|
||||
const addNode = graph.addNode({
|
||||
...graphNodeLibrary.addStart,
|
||||
x: cycleStartBBox.x + 64,
|
||||
x: cycleStartBBox.x + 84,
|
||||
y: cycleStartBBox.y,
|
||||
data: {
|
||||
type: 'add-node',
|
||||
@@ -47,7 +51,8 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
const targetPorts = addNode.getPorts();
|
||||
const sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||
const targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||
|
||||
|
||||
// 然后创建连线
|
||||
graph.addEdge({
|
||||
source: { cell: cycleStartNode.id, port: sourcePort },
|
||||
target: { cell: addNode.id, port: targetPort },
|
||||
@@ -61,7 +66,6 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
zIndex: 10
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -93,7 +97,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
});
|
||||
const addNode = graph.addNode({
|
||||
...graphNodeLibrary.addStart,
|
||||
x: centerX + 64,
|
||||
x: centerX + 84,
|
||||
y: centerY,
|
||||
data: {
|
||||
type: 'add-node',
|
||||
@@ -124,13 +128,11 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
strokeWidth: 1,
|
||||
targetMarker: {
|
||||
name: 'block',
|
||||
size: 8,
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
zIndex: 10
|
||||
}
|
||||
|
||||
graph.addEdge(edgeConfig)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,77 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
if (!sourceNode || !graph) return;
|
||||
|
||||
const sourceNodeData = sourceNode.getData();
|
||||
const sourceNodeType = sourceNodeData?.type;
|
||||
|
||||
// 计算新节点位置(在源节点右侧)
|
||||
// 如果是cycle-start节点,需要处理add-node节点
|
||||
let addNodePosition = null;
|
||||
if (sourceNodeType === 'cycle-start' && sourceNodeData.cycle) {
|
||||
const cycleId = sourceNodeData.cycle;
|
||||
const addNodes = graph.getNodes().filter((n: any) =>
|
||||
n.getData()?.type === 'add-node' && n.getData()?.cycle === cycleId
|
||||
);
|
||||
|
||||
if (addNodes.length > 0) {
|
||||
const addNode = addNodes[0];
|
||||
addNodePosition = addNode.getBBox();
|
||||
addNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算新节点位置,避免重叠
|
||||
const sourceBBox = sourceNode.getBBox();
|
||||
const newX = sourceBBox.x + sourceBBox.width + 50;
|
||||
const newY = sourceBBox.y;
|
||||
const nodeWidth = graphNodeLibrary[selectedNodeType.type]?.width || 120;
|
||||
const nodeHeight = graphNodeLibrary[selectedNodeType.type]?.height || 88;
|
||||
const horizontalSpacing = sourceNodeType === 'cycle-start' ? 40 : 80;
|
||||
const verticalSpacing = 10;
|
||||
|
||||
// 获取源连接桩的group信息
|
||||
const sourcePortInfo = sourceNode.getPorts().find((p: any) => p.id === sourcePort);
|
||||
const sourcePortGroup = sourcePortInfo?.group || sourcePort;
|
||||
console.log('sourcePortGroup', sourcePortGroup, sourcePortInfo)
|
||||
|
||||
// 如果有add-node位置,使用该位置,否则计算新位置
|
||||
let newX, newY;
|
||||
if (addNodePosition) {
|
||||
newX = addNodePosition.x;
|
||||
newY = addNodePosition.y;
|
||||
} else {
|
||||
// 根据连接桩位置决定节点放置方向
|
||||
if (sourcePortGroup === 'left') {
|
||||
// 左侧连接桩,在左侧添加节点
|
||||
newX = sourceBBox.x - nodeWidth*2 - horizontalSpacing;
|
||||
newY = sourceBBox.y;
|
||||
} else {
|
||||
// 右侧连接桩,在右侧添加节点
|
||||
newX = sourceBBox.x + sourceBBox.width + horizontalSpacing;
|
||||
newY = sourceBBox.y;
|
||||
}
|
||||
|
||||
// 检查位置是否与现有节点重叠(只考虑与当前节点相连的节点)
|
||||
const checkOverlap = (x: number, y: number) => {
|
||||
// 获取与源节点相连的节点
|
||||
const connectedNodes = new Set();
|
||||
graph.getConnectedEdges(sourceNode).forEach((edge: any) => {
|
||||
const sourceId = edge.getSourceCellId();
|
||||
const targetId = edge.getTargetCellId();
|
||||
if (sourceId !== sourceNode.id) connectedNodes.add(sourceId);
|
||||
if (targetId !== sourceNode.id) connectedNodes.add(targetId);
|
||||
});
|
||||
|
||||
return graph.getNodes().some((node: any) => {
|
||||
if (node.id === sourceNode.id) return false;
|
||||
if (!connectedNodes.has(node.id)) return false; // 只考虑相连的节点
|
||||
const bbox = node.getBBox();
|
||||
return !(x + nodeWidth < bbox.x || x > bbox.x + bbox.width ||
|
||||
y + nodeHeight < bbox.y || y > bbox.y + bbox.height);
|
||||
});
|
||||
};
|
||||
|
||||
// 如果位置被占用,向下寻找空位
|
||||
while (checkOverlap(newX, newY)) {
|
||||
newY += nodeHeight + verticalSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新节点
|
||||
const id = `${selectedNodeType.type.replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
@@ -70,7 +136,15 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
// 创建连线
|
||||
setTimeout(() => {
|
||||
const targetPorts = newNode.getPorts();
|
||||
const targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||
let targetPort;
|
||||
|
||||
if (sourcePortGroup === 'left') {
|
||||
// 从左侧连接桩连出,连接到新节点的右侧
|
||||
targetPort = targetPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||
} else {
|
||||
// 从右侧连接桩连出,连接到新节点的左侧
|
||||
targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||
}
|
||||
|
||||
graph.addEdge({
|
||||
source: { cell: sourceNode.id, port: sourcePort },
|
||||
@@ -85,7 +159,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
zIndex: 0
|
||||
// zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0
|
||||
});
|
||||
|
||||
// 循环节点内子节点通过连接桩添加时,调整循环节点大小
|
||||
@@ -108,8 +182,9 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
}, { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });
|
||||
|
||||
const padding = 20;
|
||||
const bottomPadding = 50;
|
||||
const newWidth = Math.max(240, bounds.maxX - bounds.minX + padding * 2);
|
||||
const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding * 2);
|
||||
const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding + bottomPadding);
|
||||
|
||||
parentNode.prop('size', { width: newWidth, height: newHeight });
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ const CaseList: FC<CaseListProps> = ({
|
||||
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
||||
if (!selectedNode || !graphRef?.current) return;
|
||||
|
||||
// 获取当前端口数量来判断是添加还是删除操作
|
||||
const currentPorts = selectedNode.getPorts().filter((port: any) => port.group === 'right');
|
||||
const currentCaseCount = currentPorts.length - 1; // 减去ELSE端口
|
||||
const isAddingCase = removedCaseIndex === undefined && caseCount > currentCaseCount;
|
||||
|
||||
// 保存现有连线信息(包括左侧端口连线)
|
||||
const existingEdges = graphRef.current.getEdges().filter((edge: any) =>
|
||||
edge.getSourceCellId() === selectedNode.id || edge.getTargetCellId() === selectedNode.id
|
||||
@@ -83,14 +88,10 @@ const CaseList: FC<CaseListProps> = ({
|
||||
|
||||
selectedNode.prop('size', { width: 240, height: newHeight })
|
||||
|
||||
// 计算端口间距
|
||||
const dy = totalPorts;
|
||||
|
||||
// 添加 IF 端口
|
||||
selectedNode.addPort({
|
||||
id: 'CASE1',
|
||||
group: 'right',
|
||||
// args: { dy },
|
||||
attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }}
|
||||
});
|
||||
|
||||
@@ -99,7 +100,6 @@ const CaseList: FC<CaseListProps> = ({
|
||||
selectedNode.addPort({
|
||||
id: `CASE${i + 1}`,
|
||||
group: 'right',
|
||||
// args: { dy },
|
||||
attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }}
|
||||
});
|
||||
}
|
||||
@@ -108,22 +108,11 @@ const CaseList: FC<CaseListProps> = ({
|
||||
selectedNode.addPort({
|
||||
id: `CASE${caseCount + 1}`,
|
||||
group: 'right',
|
||||
// args: { dy },
|
||||
attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }}
|
||||
});
|
||||
|
||||
// 恢复仍然存在的端口连线
|
||||
// 恢复连线
|
||||
setTimeout(() => {
|
||||
// 计算删除前的总端口数来确定原ELSE端口编号
|
||||
const originalCaseCount = removedCaseIndex !== undefined ? caseCount + 1 : caseCount;
|
||||
const originalElsePortNumber = originalCaseCount + 1;
|
||||
|
||||
// 检查ELSE端口是否有连线
|
||||
const elseHasConnection = edgeConnections.some(({ sourcePortId, isIncoming }: any) => {
|
||||
const caseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0');
|
||||
return !isIncoming && caseNumber === originalElsePortNumber;
|
||||
});
|
||||
|
||||
edgeConnections.forEach(({ edge, sourcePortId, targetCellId, targetPortId, sourceCellId, isIncoming }: any) => {
|
||||
// 如果是进入连线(左侧端口),直接恢复
|
||||
if (isIncoming) {
|
||||
@@ -151,7 +140,7 @@ const CaseList: FC<CaseListProps> = ({
|
||||
// 处理右侧端口连线
|
||||
const originalCaseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0');
|
||||
|
||||
// 如果是被删除的端口,只删除该端口的连线
|
||||
// 如果是删除操作且是被删除的端口,删除连线
|
||||
if (removedCaseIndex !== undefined && originalCaseNumber === removedCaseIndex + 1) {
|
||||
graphRef.current?.removeCell(edge);
|
||||
return;
|
||||
@@ -159,12 +148,22 @@ const CaseList: FC<CaseListProps> = ({
|
||||
|
||||
let newPortId = sourcePortId;
|
||||
|
||||
// 如果是原来的ELSE端口且有连线,重新映射到新的ELSE端口
|
||||
if (originalCaseNumber === originalElsePortNumber && elseHasConnection) {
|
||||
newPortId = `CASE${caseCount + 1}`; // 新的ELSE端口
|
||||
} else if (removedCaseIndex !== undefined && originalCaseNumber > removedCaseIndex + 1) {
|
||||
// 如果是被删除端口之后的端口,编号向前移动
|
||||
newPortId = `CASE${originalCaseNumber - 1}`;
|
||||
// 如果是删除操作,需要重新映射端口ID
|
||||
if (removedCaseIndex !== undefined) {
|
||||
if (originalCaseNumber > removedCaseIndex + 1) {
|
||||
// 被删除端口之后的端口,编号向前移动
|
||||
newPortId = `CASE${originalCaseNumber - 1}`;
|
||||
}
|
||||
// ELSE端口始终映射到新的ELSE端口位置
|
||||
else if (originalCaseNumber === currentCaseCount + 1) {
|
||||
newPortId = `CASE${caseCount + 1}`;
|
||||
}
|
||||
} else if (isAddingCase) {
|
||||
// 如果是添加操作,ELSE端口需要重新映射
|
||||
if (originalCaseNumber === currentCaseCount + 1) {
|
||||
newPortId = `CASE${caseCount + 1}`; // 新的ELSE端口
|
||||
}
|
||||
// 新添加的端口不恢复任何连线
|
||||
}
|
||||
|
||||
const newPorts = selectedNode.getPorts();
|
||||
|
||||
@@ -110,6 +110,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
||||
value: key,
|
||||
label: t(`workflow.config.start.${key}`),
|
||||
}))}
|
||||
onChange={() => form.setFieldValue('default', undefined)}
|
||||
labelRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
||||
optionRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { Graph, Node } from '@antv/x6';
|
||||
import { Form, Input, Button, Select, InputNumber, Slider, Space, Divider, App, Switch } from 'antd'
|
||||
|
||||
import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef } from '../../types'
|
||||
import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef, ChatVariable } from '../../types'
|
||||
import Empty from '@/components/Empty';
|
||||
import emptyIcon from '@/assets/images/workflow/empty.png'
|
||||
import CustomSelect from "@/components/CustomSelect";
|
||||
@@ -34,11 +34,12 @@ interface PropertiesProps {
|
||||
copyEvent: () => void;
|
||||
parseEvent: () => void;
|
||||
config?: any;
|
||||
chatVariables: ChatVariable[];
|
||||
}
|
||||
const Properties: FC<PropertiesProps> = ({
|
||||
selectedNode,
|
||||
graphRef,
|
||||
config: workflowConfig,
|
||||
chatVariables
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { modal } = App.useApp()
|
||||
@@ -47,6 +48,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
const values = Form.useWatch([], form);
|
||||
const variableModalRef = useRef<VariableEditModalRef>(null)
|
||||
const [editIndex, setEditIndex] = useState<number | null>(null)
|
||||
const [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0)
|
||||
const prevMappingNamesRef = useRef<string[]>([])
|
||||
const prevTemplateVarsRef = useRef<string[]>([])
|
||||
const syncTimeoutRef = useRef<number | null>(null)
|
||||
@@ -242,6 +244,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
}, [values, selectedNode, form])
|
||||
|
||||
const handleAddVariable = () => {
|
||||
setEditIndex(null)
|
||||
variableModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleEditVariable = (index: number, vo: StartVariableItem) => {
|
||||
@@ -250,6 +253,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
}
|
||||
const handleRefreshVariable = (value: StartVariableItem) => {
|
||||
if (!selectedNode) return
|
||||
|
||||
if (editIndex !== null) {
|
||||
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
|
||||
defaultValue[editIndex] = value
|
||||
@@ -260,7 +264,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
}
|
||||
selectedNode?.setData({ ...selectedNode.data})
|
||||
|
||||
setConfigs({ ...selectedNode.data.config})
|
||||
setConfigs({ ...selectedNode.data.config })
|
||||
}
|
||||
const handleDeleteVariable = (index: number, vo: StartVariableItem) => {
|
||||
if (!selectedNode) return
|
||||
@@ -347,11 +351,9 @@ const Properties: FC<PropertiesProps> = ({
|
||||
const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id);
|
||||
allRelevantNodeIds.push(...parentPreviousNodeIds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add conversation variables from global config
|
||||
const conversationVariables = workflowConfig?.variables || [];
|
||||
const conversationVariables = chatVariables || [];
|
||||
|
||||
conversationVariables.forEach((variable: any) => {
|
||||
const key = `CONVERSATION_${variable.name}`;
|
||||
@@ -761,7 +763,36 @@ const Properties: FC<PropertiesProps> = ({
|
||||
}
|
||||
|
||||
return variableList;
|
||||
}, [selectedNode, graphRef, workflowConfig?.variables]);
|
||||
}, [selectedNode, graphRef, graphUpdateTrigger, chatVariables]);
|
||||
|
||||
// Trigger variableList update when graph edges or nodes change
|
||||
useEffect(() => {
|
||||
if (!graphRef?.current) return;
|
||||
|
||||
const graph = graphRef.current;
|
||||
const handleGraphChange = () => {
|
||||
console.log('handleGraphChange')
|
||||
// Force variableList recalculation by updating trigger
|
||||
setGraphUpdateTrigger(prev => prev + 1);
|
||||
};
|
||||
|
||||
// Listen to graph changes
|
||||
graph.on('edge:added', handleGraphChange);
|
||||
graph.on('edge:removed', handleGraphChange);
|
||||
graph.on('edge:changed', handleGraphChange);
|
||||
graph.on('node:added', handleGraphChange);
|
||||
graph.on('node:removed', handleGraphChange);
|
||||
graph.on('node:change:data', handleGraphChange);
|
||||
|
||||
return () => {
|
||||
graph.off('edge:added', handleGraphChange);
|
||||
graph.off('edge:removed', handleGraphChange);
|
||||
graph.off('edge:changed', handleGraphChange);
|
||||
graph.off('node:added', handleGraphChange);
|
||||
graph.off('node:removed', handleGraphChange);
|
||||
graph.off('node:change:data', handleGraphChange);
|
||||
};
|
||||
}, [graphRef]);
|
||||
|
||||
// Filter out boolean type variables for loop and llm nodes
|
||||
const getFilteredVariableList = (nodeType?: string, key?: string) => {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const useWorkflowGraph = ({
|
||||
const historyRef = useRef<{ undoStack: string[], redoStack: string[] }>({ undoStack: [], redoStack: [] });
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
const [isHandMode, setIsHandMode] = useState(false);
|
||||
const [isHandMode, setIsHandMode] = useState(true);
|
||||
const [config, setConfig] = useState<WorkflowConfig | null>(null);
|
||||
const [chatVariables, setChatVariables] = useState<ChatVariable[]>([])
|
||||
|
||||
@@ -275,6 +275,11 @@ export const useWorkflowGraph = ({
|
||||
}, 100)
|
||||
}
|
||||
if (edges.length) {
|
||||
// 计算loop和iteration类型节点的数量
|
||||
const loopIterationCount = nodes.filter(node =>
|
||||
node.type === 'loop' || node.type === 'iteration'
|
||||
).length;
|
||||
|
||||
// 去重处理:对于if-else和question-classifier节点,不同连接桩允许连接到相同节点
|
||||
const uniqueEdges = edges.filter((edge, index, arr) => {
|
||||
return arr.findIndex(e => {
|
||||
@@ -349,7 +354,7 @@ export const useWorkflowGraph = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
zIndex: targetCell.getData()?.cycle ? 3 : 0
|
||||
// zIndex: loopIterationCount
|
||||
}
|
||||
|
||||
return edgeConfig
|
||||
@@ -689,10 +694,9 @@ export const useWorkflowGraph = ({
|
||||
thickness: 1, // 网点大小
|
||||
}
|
||||
},
|
||||
panning: false,
|
||||
panning: isHandMode,
|
||||
mousewheel: {
|
||||
enabled: true,
|
||||
modifiers: ['ctrl', 'meta'],
|
||||
},
|
||||
connecting: {
|
||||
// router: 'orth',
|
||||
@@ -725,7 +729,6 @@ export const useWorkflowGraph = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
zIndex: 0,
|
||||
});
|
||||
},
|
||||
validateConnection({ sourceCell, targetCell, targetMagnet }) {
|
||||
@@ -762,9 +765,8 @@ export const useWorkflowGraph = ({
|
||||
},
|
||||
embedding: {
|
||||
enabled: true,
|
||||
validate (this, { parent }) {
|
||||
const parentData = parent.getData()
|
||||
return parentData.type === 'iteration' || parentData.type === 'loop'
|
||||
validate (this) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
translating: {
|
||||
|
||||
@@ -107,6 +107,7 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
|
||||
copyEvent={copyEvent}
|
||||
parseEvent={parseEvent}
|
||||
config={config}
|
||||
chatVariables={chatVariables}
|
||||
/>
|
||||
<Chat
|
||||
ref={chatRef}
|
||||
|
||||
Reference in New Issue
Block a user