From ac315875d03ac14cba630ed5422950ae58b1ce3c Mon Sep 17 00:00:00 2001 From: Morieity <3467703106@qq.com> Date: Wed, 22 Apr 2026 00:25:44 +0800 Subject: [PATCH 1/8] =?UTF-8?q?fix;=20=E5=AF=B9=E8=AF=9D=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Backend/Application/UseCases/ChatUseCase.py | 92 ++++++++++++--------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/Backend/Application/UseCases/ChatUseCase.py b/Backend/Application/UseCases/ChatUseCase.py index b197bf7..db998db 100644 --- a/Backend/Application/UseCases/ChatUseCase.py +++ b/Backend/Application/UseCases/ChatUseCase.py @@ -318,28 +318,33 @@ def _generate_fault_tree_async() -> None: finally: async_done.set() - if is_direct_fault_tree: - # ── 精确匹配:直接生成,不做 LLM 分析 ── - thread = threading.Thread(target=_generate_fault_tree_async, daemon=True) - thread.start() - yield {"type": "token", "content": "正在生成故障树,请稍候..."} - full_answer = "正在生成故障树,请稍候..." - async_done.wait(timeout=self._fault_tree_wait_timeout_seconds) - - if fault_tree_container[0] is not None: - fault_tree_id = fault_tree_container[0].id - yield {"type": "fault_tree", "fault_tree": fault_tree_container[0].to_dict()} - else: - err_msg = "\n\n故障树生成失败,请重试。" - full_answer += err_msg - yield {"type": "token", "content": err_msg} + # ── 统一流程:LLM 自然语言分析 + 后台 function calling 并行 ── + # 不论精确匹配还是宽泛匹配,都保证回复同时包含「文字说明 + 故障树」。 + thread = threading.Thread(target=_generate_fault_tree_async, daemon=True) + thread.start() + + if update_match: + stream_task_desc = ( + "用户要求修改已有故障树,故障树将自动更新并附加在回复末尾。\n" + "你的任务是用自然语言说明本次修改的思路,请遵循以下要求:\n" + "1. 简要说明本次修改的目标与改动点(哪些节点/连接被增删改)。\n" + "2. 如果上下文文档中有相关信息,引用关键内容辅助说明。\n" + "3. 严禁输出 JSON、代码块或任何故障树数据结构。\n" + "4. 不必逐一罗列节点和连接,故障树图会自动展示。\n" + "5. 如果信息不够充分,在末尾提出补充问题引导用户。" + ) + elif is_direct_fault_tree: + stream_task_desc = ( + "用户要求生成故障树,故障树将自动生成并附加在回复末尾。\n" + "你的任务是用自然语言进行故障分析说明,请遵循以下要求:\n" + "1. 简要分析故障场景、可能的故障原因及其逻辑关系。\n" + "2. 如果上下文文档中有相关信息,引用关键内容辅助分析。\n" + "3. 严禁输出 JSON、代码块或任何故障树数据结构。\n" + "4. 不必描述节点和连接的具体结构,故障树图会自动展示。\n" + "5. 如果信息不够充分,在末尾提出补充问题引导用户。" + ) else: - # ── 宽泛匹配:LLM 分析 + 后台生成并行 ── - thread = threading.Thread(target=_generate_fault_tree_async, daemon=True) - thread.start() - - stream_system = ( - f"{system_content}\n\n" + stream_task_desc = ( "用户提到了故障树相关内容,故障树将自动生成并附加在回复末尾。\n" "你的任务是用自然语言进行分析说明,请遵循以下要求:\n" "1. 简要分析故障场景、可能的故障原因及其逻辑关系。\n" @@ -348,31 +353,36 @@ def _generate_fault_tree_async() -> None: "4. 不必描述节点和连接的具体结构,故障树图会自动展示。\n" "5. 如果信息不够充分,在末尾提出补充问题引导用户。" ) - stream_messages = messages.copy() - stream_messages[0] = {"role": "system", "content": stream_system} - try: - for token in self._llm.stream_chat(stream_messages): - full_answer += token - yield {"type": "token", "content": token} - except Exception: - logger.exception("LLM stream failed during fault tree request") - async_done.wait(timeout=self._fault_tree_wait_timeout_seconds) - if fault_tree_container[0] is not None: - fault_tree_id = fault_tree_container[0].id - yield {"type": "fault_tree", "fault_tree": fault_tree_container[0].to_dict()} - _persist_round() - yield {"type": "error", "message": "LLM service error"} - return - - # 等待故障树后台线程完成 - async_done.wait(timeout=self._fault_tree_wait_timeout_seconds) + stream_system = f"{system_content}\n\n{stream_task_desc}" + stream_messages = messages.copy() + stream_messages[0] = {"role": "system", "content": stream_system} + try: + for token in self._llm.stream_chat(stream_messages): + full_answer += token + yield {"type": "token", "content": token} + except Exception: + logger.exception("LLM stream failed during fault tree request") + async_done.wait(timeout=self._fault_tree_wait_timeout_seconds) if fault_tree_container[0] is not None: fault_tree_id = fault_tree_container[0].id yield {"type": "fault_tree", "fault_tree": fault_tree_container[0].to_dict()} - else: - logger.warning("Async fault tree generation produced no result for: %s", question) + _persist_round() + yield {"type": "error", "message": "LLM service error"} + return + + # 等待故障树后台线程完成 + async_done.wait(timeout=self._fault_tree_wait_timeout_seconds) + + if fault_tree_container[0] is not None: + fault_tree_id = fault_tree_container[0].id + yield {"type": "fault_tree", "fault_tree": fault_tree_container[0].to_dict()} + else: + err_msg = "\n\n(故障树生成失败,请重试。)" + full_answer += err_msg + yield {"type": "token", "content": err_msg} + logger.warning("Async fault tree generation produced no result for: %s", question) else: # 普通对话:直接流式调用 LLM(无 tool calling) From 3cef5042254f15d8526cc3c2c4030bd838600dfc Mon Sep 17 00:00:00 2001 From: luoyingzhiling Date: Wed, 22 Apr 2026 00:37:48 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=B8=BB?= =?UTF-8?q?=E9=A1=B5=E7=9A=84=E5=B8=83=E5=B1=80=E5=92=8C=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=93=8D=E5=BA=94=E5=BC=8F=E9=AB=98?= =?UTF-8?q?=E5=BA=A6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/tree/FaultTreeWorkspace.js | 2 +- frontend/src/features/home/Home.js | 182 ++++++++++-------- 2 files changed, 99 insertions(+), 85 deletions(-) diff --git a/frontend/src/features/flow/components/tree/FaultTreeWorkspace.js b/frontend/src/features/flow/components/tree/FaultTreeWorkspace.js index fc7814c..5fa5a16 100644 --- a/frontend/src/features/flow/components/tree/FaultTreeWorkspace.js +++ b/frontend/src/features/flow/components/tree/FaultTreeWorkspace.js @@ -308,7 +308,7 @@ function FaultTreeWorkspaceInner({ tree, onBack, onSendToChat }) { proOptions={{ hideAttribution: true }} > - + diff --git a/frontend/src/features/home/Home.js b/frontend/src/features/home/Home.js index 23b8eb1..365e27a 100644 --- a/frontend/src/features/home/Home.js +++ b/frontend/src/features/home/Home.js @@ -5,14 +5,21 @@ import { Button } from '../../ui/button'; import { Card } from '../../ui/card'; import { motion } from 'motion/react'; -const SECTION_LABELS = ['首页', '功能', '应用', '文档', '团队', '关于']; +const SECTION_LABELS = ['首页', '数据', '功能', '应用', '文档', '团队', '关于']; const SCROLL_COOLDOWN = 100; export default function Home() { const [currentSection, setCurrentSection] = useState(0); + const [viewH, setViewH] = useState(() => window.innerHeight); const isScrolling = useRef(false); const containerRef = useRef(null); + useEffect(() => { + const updateVh = () => setViewH(window.innerHeight); + window.addEventListener('resize', updateVh); + return () => window.removeEventListener('resize', updateVh); + }, []); + const totalSections = SECTION_LABELS.length; const scrollToSection = useCallback((index) => { @@ -104,7 +111,7 @@ export default function Home() { ]; return ( -
+
{/* Side Navigation Indicator */}