From 429b04d64a760fa2661c5dd29bfb56c40a389f6c Mon Sep 17 00:00:00 2001 From: Blizzard Date: Thu, 25 Jun 2026 12:57:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(desktop):=20=E5=BA=95=E9=83=A8=E6=8A=BD?= =?UTF-8?q?=E5=B1=89=E5=8F=AF=E6=8B=96=E6=8B=BD=E8=B0=83=E9=AB=98/?= =?UTF-8?q?=E6=9C=80=E5=A4=A7=E5=8C=96=20+=20=E8=BE=93=E5=87=BA=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=20Markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决两个真实体验问题(编排页底部输出区): - 抽屉太矮(176px)长输出放不下:改为可拖拽顶边调高(140px~85vh) + 一键最大化(82vh)/还原。 - 输出原样显示 markdown 未渲染:接入现有轻量 Markdown 组件(标题/加粗/斜/码/列表/引用/分隔/双链, 随主题),替换
;含 ```chart 块时文本段渲 Markdown、图表段渲 SVG,保持顺序。
  复用现成组件,未引重依赖(react-markdown 试装后回退),bundle 不涨。

tsc + 生产构建 + vitest 48 全过;桌面端已 HMR。

Co-Authored-By: Claude Opus 4.8 (1M context) 
---
 .../frontend/src/shell/BottomDrawer.tsx       | 68 ++++++++++++++-----
 1 file changed, 50 insertions(+), 18 deletions(-)

diff --git a/sundynix-desktop/frontend/src/shell/BottomDrawer.tsx b/sundynix-desktop/frontend/src/shell/BottomDrawer.tsx
index be91343..d998fc2 100644
--- a/sundynix-desktop/frontend/src/shell/BottomDrawer.tsx
+++ b/sundynix-desktop/frontend/src/shell/BottomDrawer.tsx
@@ -1,8 +1,9 @@
-import { useState } from "react";
-import { ChevronDown, ChevronUp, Wrench, ShieldCheck, Check, X } from "lucide-react";
+import { useCallback, useRef, useState } from "react";
+import { ChevronDown, ChevronUp, Wrench, ShieldCheck, Check, X, Maximize2, Minimize2 } from "lucide-react";
 import { deriveNodes, pendingApproval, type RunState } from "../lib/run";
 import { ExecTrace } from "../components/ExecTrace";
 import { ChartView } from "../components/ChartView";
+import { Markdown } from "../components/Markdown";
 import { extractChartBlocks, hasChart } from "../lib/chartspec";
 import { approveTask } from "../lib/api";
 import { Tabs, Badge, cn, type TabDef } from "../ui";
@@ -13,6 +14,35 @@ type Tab = "output" | "trace" | "tools" | "cite" | "eval";
 export function BottomDrawer({ run }: { run: RunState }) {
   const [open, setOpen] = useState(true);
   const [tab, setTab] = useState("output");
+  const [height, setHeight] = useState(240); // 可拖拽调节的抽屉高度
+  const prevH = useRef(240);
+
+  // 拖拽顶边调高:监听 window,高度 = 视口高 - 鼠标 Y,夹在 [140, 85vh]。
+  const startResize = useCallback(() => {
+    const onMove = (e: MouseEvent) => {
+      const h = Math.min(window.innerHeight * 0.85, Math.max(140, window.innerHeight - e.clientY));
+      setOpen(true);
+      setHeight(h);
+    };
+    const onUp = () => {
+      window.removeEventListener("mousemove", onMove);
+      window.removeEventListener("mouseup", onUp);
+      document.body.style.userSelect = "";
+    };
+    document.body.style.userSelect = "none";
+    window.addEventListener("mousemove", onMove);
+    window.addEventListener("mouseup", onUp);
+  }, []);
+
+  const maximized = height >= window.innerHeight * 0.8;
+  const toggleMax = () => {
+    if (maximized) setHeight(prevH.current || 240);
+    else {
+      prevH.current = height;
+      setHeight(Math.round(window.innerHeight * 0.82));
+    }
+    setOpen(true);
+  };
 
   const nodes = deriveNodes(run.exec);
   const toolCount = nodes.filter((n) => n.kind === "tool").length;
@@ -38,6 +68,8 @@ export function BottomDrawer({ run }: { run: RunState }) {
   return (
     
{approval && run.taskId && } + {/* 拖拽调高手柄(横跨顶边) */} + {open &&
}
{statusText} - +
+ {open && ( + + )} + +
{open && ( -
+
{tab === "output" && } {tab === "trace" && } {tab === "tools" && } @@ -123,26 +162,19 @@ function ApprovalBar({ taskId, node, title, summary }: { taskId: string; node: s // OutputView:渲染模型输出。含 ```chart 块时分段渲染(文本 + SVG 图表),否则纯文本。 function OutputView({ output }: { output: string }) { if (!output) { - return ( -
-        在编排页搭图 → 运行,模型注入画像与历史后流式作答,token 在此呈现。
-      
- ); + return
在编排页搭图 → 运行,模型注入画像与历史后流式作答,结果在此渲染(支持 Markdown 与图表)。
; } if (!hasChart(output)) { - return
{output}
; + return ; } + // 含 ```chart 块:文本段渲染 Markdown,图表段渲染 SVG,保持原顺序。 return (
{extractChartBlocks(output).map((seg, i) => seg.kind === "chart" ? ( ) : ( - seg.text.trim() && ( -
-              {seg.text}
-            
- ) + seg.text.trim() && ), )}