mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
fix(canvas): stabilize node resizing by hiding toolbar during resize to prevent rendering issues
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
+ [修复] MCP 初始化期间禁用 Agent 按钮与回车发送,避免首条消息提前清除服务加载状态。
|
+ [修复] MCP 初始化期间禁用 Agent 按钮与回车发送,避免首条消息提前清除服务加载状态。
|
||||||
+ [修复] Agent 对话代码块关闭行号后多行内容被拼接为一行的问题。
|
+ [修复] Agent 对话代码块关闭行号后多行内容被拼接为一行的问题。
|
||||||
+ [修复] 统一 Agent 实时事件与线程历史的消息归属,避免多窗口或刷新后出现过程重复等问题。
|
+ [修复] 统一 Agent 实时事件与线程历史的消息归属,避免多窗口或刷新后出现过程重复等问题。
|
||||||
|
+ [修复] 节点缩放期间暂停渲染跟随节点的浮层工具条高频更新导致页面崩溃。
|
||||||
|
|
||||||
## v0.12.1 - 2026-07-31
|
## v0.12.1 - 2026-07-31
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description: 当前版本已实现但仍需人工验证的变更项
|
|||||||
|
|
||||||
# 待测试
|
# 待测试
|
||||||
|
|
||||||
|
- 画布节点缩放稳定性:选中任意节点并反复拖动四角缩放时,节点工具条应在缩放期间隐藏、松开后恢复,页面不应再出现 `Maximum update depth exceeded`。
|
||||||
- Agent MCP 初始化状态:首次进入空白对话或点击「新对话」后,无需先发送消息,对话区应立即显示 Codex 会话准备,并同时列出 `codex_apps`、`infinite-canvas`、`node_repl` 等全部 MCP 服务各自的启动、就绪或失败状态;初始化期间可输入并保留草稿,但发送按钮、上传和回车提交应禁用,全部完成后显示服务数量、恢复发送并复用同一个预热线程;右侧日志应记录同样的真实状态,`starting` 与 `ready` 归入信息且就绪显示绿色成功图标,只有 `failed` 归入错误、`cancelled` 归入警告,模型开始处理后不应残留初始化文案。
|
- Agent MCP 初始化状态:首次进入空白对话或点击「新对话」后,无需先发送消息,对话区应立即显示 Codex 会话准备,并同时列出 `codex_apps`、`infinite-canvas`、`node_repl` 等全部 MCP 服务各自的启动、就绪或失败状态;初始化期间可输入并保留草稿,但发送按钮、上传和回车提交应禁用,全部完成后显示服务数量、恢复发送并复用同一个预热线程;右侧日志应记录同样的真实状态,`starting` 与 `ready` 归入信息且就绪显示绿色成功图标,只有 `failed` 归入错误、`cancelled` 归入警告,模型开始处理后不应残留初始化文案。
|
||||||
- Agent 模型设置:连接 Canvas Agent 后,输入框左下方应显示当前 Codex 模型与推理强度;模型列表应来自当前账号实际可用模型且不显示内部审查模型或重复项,切换模型后强度选项随模型能力更新且无空白选项,刷新页面后保留选择,发送任务时实际使用所选模型和强度;本地控制台与右侧「日志」应记录本轮使用的模型和推理强度。
|
- Agent 模型设置:连接 Canvas Agent 后,输入框左下方应显示当前 Codex 模型与推理强度;模型列表应来自当前账号实际可用模型且不显示内部审查模型或重复项,切换模型后强度选项随模型能力更新且无空白选项,刷新页面后保留选择,发送任务时实际使用所选模型和强度;本地控制台与右侧「日志」应记录本轮使用的模型和推理强度。
|
||||||
- Agent 新对话响应:一轮对话完成后点击「新对话」,聊天内容应立即清空并进入空白对话,不出现等待或新建按钮卡顿;后台应立即创建线程并预热 MCP,多个标签页应同步进入空白对话,点击后立即发送也不得误发到上一条会话或重复创建线程。
|
- Agent 新对话响应:一轮对话完成后点击「新对话」,聊天内容应立即清空并进入空白对话,不出现等待或新建按钮卡顿;后台应立即创建线程并预热 MCP,多个标签页应同步进入空白对话,点击后立即发送也不得误发到上一条会话或重复创建线程。
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ type CanvasNodeProps = {
|
|||||||
onHoverStart: (nodeId: string) => void;
|
onHoverStart: (nodeId: string) => void;
|
||||||
onHoverEnd: (nodeId: string) => void;
|
onHoverEnd: (nodeId: string) => void;
|
||||||
onConnectStart: (event: React.MouseEvent, nodeId: string, handleType: "source" | "target") => void;
|
onConnectStart: (event: React.MouseEvent, nodeId: string, handleType: "source" | "target") => void;
|
||||||
|
onResizeStart: (nodeId: string) => void;
|
||||||
onResize: (nodeId: string, width: number, height: number, position?: Position) => void;
|
onResize: (nodeId: string, width: number, height: number, position?: Position) => void;
|
||||||
|
onResizeEnd: (nodeId: string) => void;
|
||||||
onContentChange: (nodeId: string, content: string) => void;
|
onContentChange: (nodeId: string, content: string) => void;
|
||||||
onTitleChange: (nodeId: string, title: string) => void;
|
onTitleChange: (nodeId: string, title: string) => void;
|
||||||
onToggleBatch?: (nodeId: string) => void;
|
onToggleBatch?: (nodeId: string) => void;
|
||||||
@@ -105,7 +107,9 @@ export const CanvasNode = React.memo(function CanvasNode({
|
|||||||
onHoverStart,
|
onHoverStart,
|
||||||
onHoverEnd,
|
onHoverEnd,
|
||||||
onConnectStart,
|
onConnectStart,
|
||||||
|
onResizeStart,
|
||||||
onResize,
|
onResize,
|
||||||
|
onResizeEnd,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
onTitleChange,
|
onTitleChange,
|
||||||
onToggleBatch,
|
onToggleBatch,
|
||||||
@@ -261,11 +265,13 @@ export const CanvasNode = React.memo(function CanvasNode({
|
|||||||
resizeRef.current.isResizing = false;
|
resizeRef.current.isResizing = false;
|
||||||
window.removeEventListener("mousemove", handleResizeMove);
|
window.removeEventListener("mousemove", handleResizeMove);
|
||||||
window.removeEventListener("mouseup", handleResizeUp);
|
window.removeEventListener("mouseup", handleResizeUp);
|
||||||
}, [handleResizeMove]);
|
onResizeEnd(data.id);
|
||||||
|
}, [data.id, handleResizeMove, onResizeEnd]);
|
||||||
|
|
||||||
const handleResizeMouseDown = (event: React.MouseEvent, corner: ResizeCorner) => {
|
const handleResizeMouseDown = (event: React.MouseEvent, corner: ResizeCorner) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
onResizeStart(data.id);
|
||||||
resizeRef.current = {
|
resizeRef.current = {
|
||||||
isResizing: true,
|
isResizing: true,
|
||||||
corner,
|
corner,
|
||||||
@@ -286,8 +292,9 @@ export const CanvasNode = React.memo(function CanvasNode({
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("mousemove", handleResizeMove);
|
window.removeEventListener("mousemove", handleResizeMove);
|
||||||
window.removeEventListener("mouseup", handleResizeUp);
|
window.removeEventListener("mouseup", handleResizeUp);
|
||||||
|
if (resizeRef.current.isResizing) onResizeEnd(data.id);
|
||||||
};
|
};
|
||||||
}, [handleResizeMove, handleResizeUp]);
|
}, [data.id, handleResizeMove, handleResizeUp, onResizeEnd]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ function InfiniteCanvasPage() {
|
|||||||
const [collapsingBatchIds, setCollapsingBatchIds] = useState<Set<string>>(new Set());
|
const [collapsingBatchIds, setCollapsingBatchIds] = useState<Set<string>>(new Set());
|
||||||
const [openingBatchIds, setOpeningBatchIds] = useState<Set<string>>(new Set());
|
const [openingBatchIds, setOpeningBatchIds] = useState<Set<string>>(new Set());
|
||||||
const [isNodeDragging, setIsNodeDragging] = useState(false);
|
const [isNodeDragging, setIsNodeDragging] = useState(false);
|
||||||
|
const [isNodeResizing, setIsNodeResizing] = useState(false);
|
||||||
const [dropTargetGroupId, setDropTargetGroupId] = useState<string | null>(null);
|
const [dropTargetGroupId, setDropTargetGroupId] = useState<string | null>(null);
|
||||||
|
|
||||||
const nodesRef = useRef(nodes);
|
const nodesRef = useRef(nodes);
|
||||||
@@ -1463,6 +1464,9 @@ function InfiniteCanvasPage() {
|
|||||||
setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, width, height, position: position || node.position } : node)));
|
setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, width, height, position: position || node.position } : node)));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleNodeResizeStart = useCallback(() => setIsNodeResizing(true), []);
|
||||||
|
const handleNodeResizeEnd = useCallback(() => setIsNodeResizing(false), []);
|
||||||
|
|
||||||
const toggleNodeFreeResize = useCallback((nodeId: string) => {
|
const toggleNodeFreeResize = useCallback((nodeId: string) => {
|
||||||
setNodes((prev) =>
|
setNodes((prev) =>
|
||||||
prev.map((node) => {
|
prev.map((node) => {
|
||||||
@@ -2871,7 +2875,9 @@ function InfiniteCanvasPage() {
|
|||||||
onHoverStart={handleNodeHoverStart}
|
onHoverStart={handleNodeHoverStart}
|
||||||
onHoverEnd={handleNodeHoverEnd}
|
onHoverEnd={handleNodeHoverEnd}
|
||||||
onConnectStart={handleConnectStart}
|
onConnectStart={handleConnectStart}
|
||||||
|
onResizeStart={handleNodeResizeStart}
|
||||||
onResize={handleNodeResize}
|
onResize={handleNodeResize}
|
||||||
|
onResizeEnd={handleNodeResizeEnd}
|
||||||
onContentChange={handleNodeContentChange}
|
onContentChange={handleNodeContentChange}
|
||||||
onTitleChange={handleNodeTitleChange}
|
onTitleChange={handleNodeTitleChange}
|
||||||
onToggleBatch={toggleBatchExpanded}
|
onToggleBatch={toggleBatchExpanded}
|
||||||
@@ -2910,7 +2916,7 @@ function InfiniteCanvasPage() {
|
|||||||
</InfiniteCanvas>
|
</InfiniteCanvas>
|
||||||
|
|
||||||
<CanvasNodeHoverToolbar
|
<CanvasNodeHoverToolbar
|
||||||
node={isNodeDragging || nodeImageSettingsOpen ? null : toolbarNode}
|
node={isNodeDragging || isNodeResizing || nodeImageSettingsOpen ? null : toolbarNode}
|
||||||
viewport={viewport}
|
viewport={viewport}
|
||||||
extraTools={toolbarNode ? buildNodeToolbarItems(toolbarNode) : undefined}
|
extraTools={toolbarNode ? buildNodeToolbarItems(toolbarNode) : undefined}
|
||||||
onKeep={keepNodeToolbar}
|
onKeep={keepNodeToolbar}
|
||||||
|
|||||||
Reference in New Issue
Block a user