feat(agent): unify scroll-to-latest controls

This commit is contained in:
yu
2026-07-31 13:23:21 +08:00
parent 29c5dbbc8c
commit ee5804e586
3 changed files with 9 additions and 15 deletions
+3
View File
@@ -2,6 +2,9 @@
## Unreleased
+ [优化] Agent 排查日志改为筛选栏固定的结构化滚动列表,支持筛选、展开详情并折叠连续重复事件。
+ [优化] Agent 对话与排查日志统一使用居中的回到底部入口,向上浏览时暂停跟随并可快速返回最新内容。
## v0.12.1 - 2026-07-31
+ [新增] 画布右侧Agent支持按当前账号可用范围选择Codex 模型与推理强度。
+3 -1
View File
@@ -18,6 +18,8 @@ description: 当前版本已实现但仍需人工验证的变更项
- Canvas Agent Debug:普通启动应通过公共 logger 输出 Info、Warn 和 Error 日志;使用 `npx -y @basketikun/canvas-agent --debug` 启动后还应输出 Debug 日志并显示日志文件路径,终端统一采用“`YYYY-MM-DD HH:mm:ss` 级别 消息 详情”的纯文本单行格式,`~/.infinite-canvas/logs/` 下应按启动日期生成相同格式的 `canvas-agent-YYYY-MM-DD.log`,同一天多次启动应追加到同一文件;日志中不应出现连接 token 或图片 Data URL 原文。
- Canvas Agent Codex 日志:Codex app-server 输出带颜色或样式控制符及 UTC 时间的 stderr 时,本地日志和网页诊断日志应只显示一份 `YYYY-MM-DD HH:mm:ss` 本地时间和干净文本,不应出现重复 ISO 时间或 ``、`` 等 ANSI 转义内容。
- Agent HTTP 诊断日志:网页发送一条普通消息后,本地 Debug 日志不应重复输出 `/health`、`/canvas/state`、`/canvas/activate` 成功请求、流式增量或完整会话响应,只保留 HTTP 请求与 Codex 生命周期摘要;右侧「日志」应以单行时间线展示发送、开始、回复、工具、完成用量和错误,不再输出 userMessage started/completed、流式摘要、重复 threadId 或大段原始 JSON。
- Agent 排查日志:右侧「日志」中的排查视图应显示连接摘要,并可按错误、警告和信息筛选;向下查看日志时仅列表内部滚动,标题、连接状态、筛选数量和操作按钮保持固定;连续相同事件应折叠计数,单条日志可展开查看完整详情;原始 JSON、复制全部、复制最近错误和清空功能应继续可用,浅色与深色主题下均应清晰。
- Agent 排查日志顺序与跟随:日志应按时间从旧到新排列,进入日志或切换筛选时定位到最新事件;停留底部时自动跟随新日志,展开最后一条后应继续停留在完整详情底部,向上浏览后暂停跟随并显示与对话区视觉高度一致的居中圆形向下箭头,新日志数量只在悬浮提示中显示;列表底部不应出现额外留白,点击按钮后回到底部并恢复跟随。
- Agent 对话统计:用户消息应右对齐并使用透明无气泡的简洁排版,用户和 Codex 两侧均不显示人物头像,消息下方均不显示时间或 Token 信息;输入框上方应居中展示最新一次模型调用的输入、缓存、输出 Token 用量,不显示会话累计值,数值更新时应从旧值平滑滚动到新值而非突然跳变,新建、切换或删除当前会话后应清空旧统计。
- Agent 回复实时显示:在右侧 Agent 发送消息后,用户消息下方应立即出现“正在思考...”,任务运行期间不应闪退;工具完成后应显示 Codex 正在继续处理及已等待时长,等待超过 30 秒时提示可继续等待或停止本轮;Codex 的回复应在当前对话中持续显示,实时事件缺失时也应在任务完成后自动同步完整内容,无需切换到历史或日志再返回对话。模型繁忙等任务失败时应立即结束等待状态,在对话中显示中文错误原因和重试建议,诊断日志不应再把失败轮次记为“处理完成”,刷新历史后错误仍应保留。新建尚未发送首条消息的空会话不应反复出现历史读取失败。
- Agent 流式交互性能:发送长回复时文字应连续平滑出现,输入框、滚动和画布操作不应随回复变长而明显卡顿;长历史会话中只有当前流式消息持续更新,屏幕外消息不应造成明显布局压力;任务完成后应通过 SSE 自动同步完整历史,不再持续请求 `/health` 轮询状态。
@@ -50,5 +52,5 @@ description: 当前版本已实现但仍需人工验证的变更项
- 本地 Agent 多标签页会话同步:所有标签页共享同一个站点级 Codex 活跃线程;任一页面发送消息、新建、恢复或删除会话后,其他页面应同步活跃线程和聊天记录;Agent 输出仅显示在事件所属线程,运行中不能新建、恢复、删除或再次发送任务。
- 本地 Agent 运行状态同步:在一个标签页运行较长 Codex 任务,等待某张工具卡显示「工具完成」后再打开或刷新第二个标签页;第二个标签页应立即显示 Codex 正在运行并禁用发送,整轮结束后两个标签页同时恢复;工具卡只显示「工具完成」,整轮结束由「本轮完成」表示。
- 本地 Agent 图片附件落画布:在右侧 Agent 上传参考图并要求基于商品信息创建生图流程,附件应创建为保持原比例的真实图片节点,分析提示词应创建为文本节点,二者都应连接到生成配置节点;刷新页面后参考图仍可显示并参与生成。任务中途切换到其他标签页时,附件只能写入发起任务的标签页;若发起标签页关闭,附件读取应失败且不能落入其他画布。
- Agent 对话滚动:从历史或日志切回对话、恢复其他会话时应自动定位到最新消息;手动向上浏览后应显示向下箭头新消息不强制打断阅读,点击箭头后平滑回到底部并继续跟随新消息。
- Agent 对话滚动:从历史或日志切回对话、恢复其他会话时应自动定位到最新消息;手动向上浏览后应显示与日志完全相同尺寸、位置和样式的居中圆形向下箭头新消息不强制打断阅读,对话底部不应出现额外留白,点击按钮后平滑回到底部并继续跟随新消息。
- Agent 消息区分:用户消息应在右侧使用透明无气泡排版,AI 回复应在左侧使用无头像的开放式 Markdown 排版;长文本、多张附件、错误消息及浅色/深色主题下均应清晰且不溢出。
+3 -14
View File
@@ -1,6 +1,4 @@
import { memo, useCallback, useEffect, useRef, useState } from "react";
import { Button, Tooltip } from "antd";
import { ChevronDown } from "lucide-react";
import { motion, useSpring, useTransform } from "motion/react";
import { canvasThemes } from "@/lib/canvas-theme";
@@ -8,6 +6,7 @@ import { summarizeCanvasAgentOps } from "@/lib/canvas/canvas-agent-ops";
import { useAgentStore, type AgentChatItem, type AgentPendingApproval, type AgentPendingToolCall, type AgentTokenUsage } from "@/stores/use-agent-store";
import { AgentApprovalCard, AgentChatMessage, AgentPendingToolCard, AgentToolCard, AgentWorkingMessage } from "./agent-chat-message";
import { agentMessageToChatMessage, currentPlanMessage, isPlanMessage, latestPlanMessage, toolCallDetail, toolName, workingActivity } from "./agent-event-formatters";
import { AgentScrollToBottom } from "./agent-scroll-to-bottom";
const SCROLL_BOTTOM_THRESHOLD = 48;
const historyMessageStyle = { contentVisibility: "auto", containIntrinsicSize: "0 80px" } as const;
@@ -57,7 +56,7 @@ export function AgentChatTimeline({
}, [messages, pendingApprovals, pendingTool, scrollToBottom, updateScrollState, waiting]);
return (
<div className="relative min-h-0 flex-1">
<div ref={listRef} className="thin-scrollbar h-full select-text space-y-4 overflow-y-auto px-4 pb-12 pt-4" onScroll={updateScrollState}>
<div ref={listRef} className="thin-scrollbar h-full select-text space-y-4 overflow-y-auto px-4 pt-4" onScroll={updateScrollState}>
{messages.map((item) => (
isPlanMessage(item) ? null : <AgentChatMessageRow key={item.id} item={item} theme={theme} />
))}
@@ -74,17 +73,7 @@ export function AgentChatTimeline({
{(sending || waiting) && !streaming && !pendingTool && !pendingApprovals.length ? <AgentWorkingMessage text={working.text} activityKey={working.key} theme={theme} /> : null}
</div>
{showScrollToBottom ? (
<Tooltip title="滚动到底部" placement="left">
<Button
type="text"
shape="circle"
aria-label="滚动到底部"
className="!absolute bottom-3 left-1/2 z-10 !h-8 !w-8 !min-w-8 -translate-x-1/2 backdrop-blur transition hover:-translate-y-0.5"
style={{ background: theme.toolbar.panel, border: `1px solid ${theme.node.stroke}`, color: theme.node.text }}
icon={<ChevronDown className="size-4" />}
onClick={() => scrollToBottom()}
/>
</Tooltip>
<AgentScrollToBottom theme={theme} title="查看最新消息" onClick={() => scrollToBottom()} />
) : null}
</div>
);