diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a87255..a7a6b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ + [修复] 修复 Agent 发送后的运行提示闪退及回复需要切换标签页才显示的问题。 + [修复] Agent 在模型繁忙等任务失败时会立即结束等待状态,并在对话和历史中展示中文错误原因与重试建议。 + [优化] Agent 历史记录支持多选批量删除,点击记录可直接进入对话。 ++ [调整] 进入画布并连接 Agent 时默认开启空白新对话,不再自动加载上一次会话,历史对话改为由用户主动选择恢复。 + [优化] Canvas Agent 改为从独立指令文件初始化当前工作目录的 AGENTS.md,不再为每轮消息重复拼接前置提示词。 + [优化] 重构 Canvas Agent 源码目录,按 Agent、画布、服务与通用工具拆分模块职责,提升代码可维护性。 diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 53053a5..ab2eef2 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -13,6 +13,7 @@ description: 当前版本已实现但仍需人工验证的变更项 - Agent 流式交互性能:发送长回复时文字应连续平滑出现,输入框、滚动和画布操作不应随回复变长而明显卡顿;长历史会话中只有当前流式消息持续更新,屏幕外消息不应造成明显布局压力;任务完成后应通过 SSE 自动同步完整历史,不再持续请求 `/health` 轮询状态。 - Agent 过程时间线:Codex 运行时应在对话中依次显示中文的思考摘要、执行计划、命令执行、网页搜索、文件修改和画布工具活动;结构化任务进度不应混在对话时间线中,而应独立放在对话区下方、Token 统计上方,支持展开和折叠,并逐项实时更新「待处理」「进行中」「已完成」状态;新任务生成时默认展开,任务结束后保留最新结果,同一计划更新不应生成重复内容;命令详情只展示工作目录、耗时、退出状态和运行输出,文件详情展示文件路径与新增/修改/删除动作,工具详情不应出现请求 ID、英文工具名或原始 JSON,刷新历史会话后仍保持相同展示。 - Agent 历史记录:点击记录卡片应直接进入对应对话,不再显示「进入」按钮;可勾选单条或全选多条记录并批量删除,删除当前对话后聊天内容应清空。 +- Agent 默认新对话:每次进入任一画布并连接 Agent 后,对话区应保持空白且不自动恢复上一次会话;第一次发送消息时才创建新线程,不应产生未发送消息的空历史记录;需要继续旧对话时可在「历史」中主动选择恢复。 - Agent 工作目录指令:`canvas-agent/agent-instructions.md` 应作为独立维护源;重启 Canvas Agent 后,当前工作目录应自动生成 `AGENTS.md`;新建对话发送消息时,Codex 日志中的用户消息只包含本轮请求和必要的附件上下文,不再重复整段 Infinite Canvas 前置提示词,画布及工作台工具仍可正常调用。 - 画布文本设置:文本节点和生成配置节点切换到文本模式后应显示推理强度设置,可选择自动、低、中、高、极高;选择自动时默认 OpenAI Responses 请求不应携带 `reasoning`,选择其他档位时应携带所选强度,刷新画布后节点设置应保留;文本模型自定义调用脚本应能读取 `reasoningEffort`,OpenAI 模板应按自动或指定档位正确组装请求。 - 生图工作台参考图:将一张或多张图片拖入参考图区域后应直接上传并显示缩略图;拖入非图片文件应忽略,拖动过程中区域应显示高亮提示,放下文件不应导致浏览器打开或替换当前页面。 diff --git a/web/src/components/agent/agent-api.ts b/web/src/components/agent/agent-api.ts new file mode 100644 index 0000000..aa2f5fa --- /dev/null +++ b/web/src/components/agent/agent-api.ts @@ -0,0 +1,43 @@ +import type { CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; + +type AgentConfigResponse = { ok?: boolean; url?: string; token?: string; hasToken?: boolean }; + +export async function postState(endpoint: string, token: string, clientId: string, snapshot: CanvasAgentSnapshot | null) { + try { + await fetch(`${endpoint}/canvas/state?token=${encodeURIComponent(token)}&clientId=${encodeURIComponent(clientId)}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(snapshot ? { ...snapshot, hasCanvas: true } : { hasCanvas: false }), + }); + } catch {} +} + +export async function activateAgentClient(endpoint: string, token: string, clientId: string) { + try { + await fetch(`${endpoint}/canvas/activate?token=${encodeURIComponent(token)}&clientId=${encodeURIComponent(clientId)}`, { method: "POST" }); + } catch {} +} + +export async function postToolResult(endpoint: string, token: string, clientId: string, body: { requestId: string; result?: unknown; error?: string }) { + await fetch(`${endpoint}/canvas/result?token=${encodeURIComponent(token)}&clientId=${encodeURIComponent(clientId)}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }); +} + +export async function fetchAgentJson(endpoint: string, token: string, path: string, init?: RequestInit) { + const url = `${endpoint}${path}${path.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}`; + const res = await fetch(url, init); + const data = (await res.json().catch(() => ({}))) as T & { error?: string; msg?: string }; + if (!res.ok) throw new Error(data.error || data.msg || "本地 Agent 请求失败"); + return data; +} + +export async function discoverAgentConfig(endpoint: string) { + try { + const res = await fetch(`${endpoint}/config`); + if (!res.ok) return null; + const data = (await res.json()) as AgentConfigResponse; + return data.ok ? data : null; + } catch { + return null; + } +} + diff --git a/web/src/components/agent/agent-chat-composer.tsx b/web/src/components/agent/agent-chat-composer.tsx new file mode 100644 index 0000000..a153b77 --- /dev/null +++ b/web/src/components/agent/agent-chat-composer.tsx @@ -0,0 +1,100 @@ +import { useRef, type ReactNode } from "react"; +import { Button, Tooltip } from "antd"; +import { ArrowUp, ImagePlus, LoaderCircle, Square, X } from "lucide-react"; + +import { canvasThemes } from "@/lib/canvas-theme"; +import { isPlainEnterKey } from "@/lib/keyboard-event"; +import type { AgentChatAttachment } from "./agent-chat-message"; + +export function AgentChatComposer({ + prompt, + attachments = [], + disabled, + sending, + placeholder, + theme, + onPromptChange, + onSubmit, + onStop, + onAddFiles, + onRemoveAttachment, + left, +}: { + prompt: string; + attachments?: AgentChatAttachment[]; + disabled?: boolean; + sending?: boolean; + placeholder: string; + theme: (typeof canvasThemes)[keyof typeof canvasThemes]; + onPromptChange: (value: string) => void; + onSubmit: () => void; + onStop?: () => void; + onAddFiles?: (files: FileList | File[] | null) => void | Promise; + onRemoveAttachment?: (id: string) => void; + left?: ReactNode; +}) { + const fileInputRef = useRef(null); + const canSubmit = !disabled && !sending && Boolean(prompt.trim() || attachments.length); + return ( +
event.stopPropagation()}> +
+ {attachments.length ? ( +
+ {attachments.map((item) => ( +
+ {item.name} + {onRemoveAttachment ? ( + + ) : null} +
+ ))} +
+ ) : null} +