diff --git a/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md b/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md new file mode 100644 index 00000000..f4232069 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md @@ -0,0 +1,41 @@ +# Task: Correct TOOL SKILL labels and knowledge upload card placement + +## Identity + +- Task ID: 20261013-agent-controls-correction-6d42b9e1 +- Mode: Feature +- Branch: codex/20261013-agent-controls-correction-6d42b9e1-agent-controls-correction +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261013-agent-controls-correction-6d42b9e1 +- Base commit: 020b36673d9034fed53f7e896b7685aa56e4cecb +- Owner: codex +- Status: In Progress + +## Scope + +- Correct advanced configuration headers/buttons/search labels to TOOL/选择TOOL and SKILL/选择SKILL. Move knowledge upload action into the left 上传自己的文件 card beside 新建知识库; preserve existing selection widths, refresh button styling and upload/pending authority. Enable corrected Renderer in current temporary app. + +## Intent And Constraints + +- User explicitly corrects both labels and upload-button placement, superseding prior Chinese-label/right-card choices. No API/schema, cloud mutations, real upload, model call, WeChat actions, Main restart, profile/credential inspection or user-input discard is needed. Preserve active020b366 full client baseline and MainPID12660/Host58003. +- Concurrent Gate Passed: official check/start/status verified exact Identity task/mode/branch/absoluteworktree/base. Read AGENTS, entry/memory-index, active record, positioning, integrated state, decision-index/system-overview, personal-cloud ADR, community boundary, domain/success criteria and relevant indexes. Integrated shared docs are historical; active runtime current.json and source020b are delivery baseline.48 peer scopes read only; independent semantic audit finds prior label/layout decisions superseded by this user correction, with no unresolved conflict. Only owned task doc may change; no canonical/peer writes. +- Project Context Loaded: goal remains ordinary creative/client workflow with Main-owned auth/cloud API; current scope is Renderer presentation only. Configuration tools/skills/preload_skills and knowledge permissions/pending operation IDs remain authoritative. Relevant files ConfigurationFields.tsx, CloudKnowledge.tsx, agent-config.css and existing unit/geometry/Electron fixtures. Design skill recommends stable flex wrapping, focus/contrast and responsive checks; retain current established palette/components. Gate result: Passed before implementation plan. +- Plan: move the stable portal target into left action row without remounting documents; change display labels; update existing assertions and wide/narrow geometry expectations. Run typecheck, relevant units/lint, browser geometry and existing Electron fixture, default fullbuild. Prepare immutable Renderer overlay over unchanged Main artifacts, clean/idle native CUA reload and verify actual corrected controls before final metadata. + +## Outcome + +- Implemented exact TOOL/SKILL headings and generated selection/search labels, with SKILL preload copy consistent. Knowledge upload portal target moved into the left stable create-action row beside 新建知识库; row wraps at narrow widths. Document component/key, request intent, permissions and pending lock logic remain unchanged. Right management row contains select/refresh only; previous widths and selected-list alignment are retained. +- Current active Main/shared/resources/package/lock/build-config source delta is empty; only three Renderer product files change. Immutable overlay and native saved/idle reload remain to deliver. + +## Verification + +- Typecheck, two scoped ESLint runs, default full build:vite (NIANCODE_RENDERER_ONLY unset) and git diff --check passed. +-57 relevant unit cases passed:40 configuration/selector preservation +17 owned-knowledge permission/pending/cancel/retry cases.14 browser geometry/platform scenarios passed, with knowledge checks covering375/768/1024/1440 and long retry wording. Two focused screenshot retakes passed in an independent output directory; independent visual review PASS for all four widths and left/right card membership/alignment. +- Native Electron cloud-agents full fixture passed on quiet rerun (8.3s). Initial run had a simulated voice stop-button observation timeout after changed-label configuration checks already passed; voice source is unchanged and rerun passes the entire scenario. No real microphone/recording, upload, provider or cloud account mutation was used. + +## Follow-ups + +- None recorded. + +## Promotion Candidates + +- None recorded. diff --git a/README.md b/README.md index 04a64db9..1a6fc37b 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。 会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。 -- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务和知识库纵向呈现;移除子智能体选择模块,保存其他配置时保留历史子智能体绑定,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框沿用 Code 的浅蓝边框、圆形按钮及一至六行输入规则;Enter 发送、Shift+Enter 换行,保留中文输入法确认。麦克风复用应用级语音输入,识别只追加当前草稿,不自动发送;切换账号、对话或隐藏页面后丢弃过期识别。支持拖入文件、粘贴图片和多文件选择,上传与确认仍由 Main 持有认证并转发;失败重试保留同一批文件身份。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。 +- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、TOOL、SKILL、MCP 服务和知识库纵向呈现,资源选择按钮分别显示「选择TOOL」「选择SKILL」;移除子智能体选择模块,保存其他配置时保留历史子智能体绑定,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框沿用 Code 的浅蓝边框、圆形按钮及一至六行输入规则;Enter 发送、Shift+Enter 换行,保留中文输入法确认。麦克风复用应用级语音输入,识别只追加当前草稿,不自动发送;切换账号、对话或隐藏页面后丢弃过期识别。支持拖入文件、粘贴图片和多文件选择,上传与确认仍由 Main 持有认证并转发;失败重试保留同一批文件身份。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。 个人智能体聊天在每条提问与回答下方显示云端记录的消息时间;发送时间位于紫色消息气泡外下方并靠右,助手回复时间保持在回答下方:今天显示时分,跨日增加日期,悬停可查看完整日期与秒数;发送时间与回复时间沿用原消息记录,重开对话不改为当前时间。流式回答尚无可靠时间时显示「回复中…」,历史到达后展示原时间;缺失或无效时间显示「时间未记录」。正式对话、草稿试用和上次试用对比使用相同规则,不新增 Yuxi 接口。 历史待确认记录收起在智能体列表标题区的「创建记录/待处理记录」入口,只有点击后才打开记录窗口;配置、聊天和创建页不再置顶这些旧提醒。记录保留智能体名称和原请求身份;应用设置的待确认记录显示“网络可能不稳定,请再点一次「应用」。”,点击「应用」继续原来的提交,成功后显示“设置已应用。”。其他记录仍可明确「再试一次」或「不再提醒」,不会自动重试或清除。当前创建失败继续在创建表单内提示;关闭记录窗口保留已收到的结果和密钥,切换配置、聊天或创建页后可返回列表继续查看。密钥须明确关闭结果后,才可处理其他记录。 - 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。知识库区域上方左右分区,两张卡片在并排时保持等高:左侧「上传自己的文件」仅放「新建知识库」入口及展开的创建表单,右侧「查看我的知识库」通过下拉框查看本人知识库、从电脑追加资料和管理文件。宽窗口中新建按钮与右侧选择、刷新、上传控件共用居中的水平线,管理下拉框约为原宽度的一半,两种目录刷新均显示为带边框按钮;下方独立「选择知识库」,选择智能体使用的「MakeLore知识」和「我的知识库」,下拉框缩至原宽度的约2/3,并与已选知识库框左右对齐。小窗口按新建、查看、选用顺序纵向排列,控件允许换行并保持可读。创建成功后在右侧打开新库;新建和查看均不会自动加入智能体配置,选用上限不影响管理与新建。仅提供「从电脑上传」作为添加文件入口,未选择管理目标时上传按钮置灰;聊天文件导入入口已移除。上传后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。 + 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。知识库区域上方左右分区,两张卡片在并排时保持等高:左侧「上传自己的文件」将「新建知识库」和「从电脑上传」左右并排居中,下方保留展开的创建表单;右侧「查看我的知识库」通过下拉框选择本人知识库,保留刷新入口和文件管理。宽窗口中左侧两个按钮与右侧选择、刷新控件共用居中的水平线,管理下拉框约为原宽度的一半,两种目录刷新均显示为带边框按钮;下方独立「选择知识库」,选择智能体使用的「MakeLore知识」和「我的知识库」,下拉框缩至原宽度的约2/3,并与已选知识库框左右对齐。小窗口按新建、查看、选用顺序纵向排列,控件允许换行并保持可读。创建成功后在右侧打开新库;新建和查看均不会自动加入智能体配置,选用上限不影响管理与新建。仅提供「从电脑上传」作为添加文件入口,未选择管理目标时上传按钮置灰;聊天文件导入入口已移除。上传后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。 云知识库的 embedding 模型只由平台管理员在 Yuxi 后台维护:在「智能体管理 → 模型供应商」启用供应商,按供应商要求填写有效的服务地址和 API Key,添加并启用 embedding 模型并填写实际向量维度;embedding 向量化费用由平台承担,不计入用户用量、词元点数或智能体费用上限;one-api 不参与 embedding 配置,Works Square 只负责其他应计费模型调用的创建者词元点数账本。客户端区分目录加载中、加载失败与无可用模型,空状态提供管理员配置说明;「刷新知识库与模型」保留未提交名称,未确认的创建请求沿用原操作身份。 diff --git a/src/pages/CloudAgents/CloudKnowledge.tsx b/src/pages/CloudAgents/CloudKnowledge.tsx index c07d0b5e..d863a400 100644 --- a/src/pages/CloudAgents/CloudKnowledge.tsx +++ b/src/pages/CloudAgents/CloudKnowledge.tsx @@ -100,6 +100,9 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onCatalog, ref
+
+ {!selectedKnowledge && } +
{creationDetails} @@ -119,9 +122,6 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onCatalog, ref )} -
- {!selectedKnowledge && } -
{errorTarget === 'management' && error &&

{error}

} diff --git a/src/pages/CloudAgents/ConfigurationFields.tsx b/src/pages/CloudAgents/ConfigurationFields.tsx index 74667e34..5494ea19 100644 --- a/src/pages/CloudAgents/ConfigurationFields.tsx +++ b/src/pages/CloudAgents/ConfigurationFields.tsx @@ -8,8 +8,8 @@ import { AgentConfigHeading } from './AgentConfigHeading'; import type { CloudAgentConfiguration, CloudCatalog } from '../../../shared/cloud-agents'; const resourceSections = [ - { key: 'tools', label: '工具箱', title: '工具箱(Tool)', icon: Wrench, tone: 'blue' }, - { key: 'skills', label: '做事手册', title: '做事手册(Skill)', icon: BookOpen, tone: 'lilac' }, + { key: 'tools', label: 'TOOL', title: 'TOOL', icon: Wrench, tone: 'blue' }, + { key: 'skills', label: 'SKILL', title: 'SKILL', icon: BookOpen, tone: 'lilac' }, { key: 'mcps', label: 'MCP 服务', title: 'MCP 服务', icon: Plug, tone: 'neutral' }, { key: 'knowledges', label: '知识库', title: '知识库', icon: Library, tone: 'mint' }, ] as const; @@ -75,7 +75,7 @@ export function ConfigurationFields({ slug, value, disabled, onChange, section = } {key === 'skills' && value.skills.length > 0 && } + onChange={e => change('preload_skills', e.target.checked ? [...value.skills] : [])} />开始对话前,先读好已选SKILL的使用方法} ; })}
; diff --git a/src/pages/CloudAgents/agent-config.css b/src/pages/CloudAgents/agent-config.css index 41601706..e1f1256d 100644 --- a/src/pages/CloudAgents/agent-config.css +++ b/src/pages/CloudAgents/agent-config.css @@ -47,7 +47,7 @@ .agent-knowledge-upload, .agent-knowledge-management, .agent-knowledge-selection { min-width: 0; padding: 18px; border: 1px solid #dce5e0; border-radius: 17px; background: #fffdfb; overflow-wrap: anywhere; } .agent-knowledge-upload { border-color: #dce5f3; } .agent-knowledge-upload, .agent-knowledge-management { display: grid; grid-template-rows: subgrid; grid-row: span 3; } -.agent-knowledge-create-action { display: flex; align-items: center; justify-content: center; } +.agent-knowledge-create-action { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; min-width: 0; } .agent-knowledge-management-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; min-width: 0; } .agent-config-card .agent-knowledge-management-controls select { width: calc((100% - 144px - 8px) / 2); flex: none; padding-inline: 8px; font-size: 14px; } .agent-knowledge-upload-action { display: flex; min-width: 0; } diff --git a/tests/e2e/cloud-agent-knowledge.spec.ts b/tests/e2e/cloud-agent-knowledge.spec.ts index 5368ca7c..0a1c377a 100644 --- a/tests/e2e/cloud-agent-knowledge.spec.ts +++ b/tests/e2e/cloud-agent-knowledge.spec.ts @@ -186,9 +186,9 @@ async function expectKnowledgeLayout(page: Page) { const selection = knowledge.getByRole('region', { name: '选择知识库', exact: true }); await expect(creation.getByRole('button', { name: '新建知识库', exact: true })).toBeVisible(); await expect(creation.getByRole('combobox', { name: '管理的知识库', exact: true })).toHaveCount(0); - await expect(creation.getByRole('button', { name: '从电脑上传', exact: true })).toHaveCount(0); + await expect(creation.getByRole('button', { name: '从电脑上传', exact: true })).toBeVisible(); await expect(management.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeVisible(); - await expect(management.getByRole('button', { name: '从电脑上传', exact: true })).toBeVisible(); + await expect(management.getByRole('button', { name: '从电脑上传', exact: true })).toHaveCount(0); await expect(management.getByRole('button', { name: '新建知识库', exact: true })).toHaveCount(0); await expect(management.locator('summary').filter({ hasText: '使用说明与费用' })).toHaveCount(0); await expect(management.getByText('上传到知识库的文档可供获准使用此智能体的人检索。解析并建立索引后才会生效;embedding 向量化由平台承担,不计入用户用量、词元点数或智能体费用上限。知识处理如触发对话模型调用,仍按原模型实际用量计费。', { exact: true })).toHaveCount(0); @@ -212,8 +212,11 @@ async function expectKnowledgeLayout(page: Page) { const create = await creation.getByRole('button', { name: '新建知识库', exact: true }).boundingBox(); const manager = await management.getByRole('combobox', { name: '管理的知识库', exact: true }).boundingBox(); const refresh = await management.getByRole('button', { name: '刷新知识库与模型', exact: true }).boundingBox(); - const upload = await management.getByRole('button', { name: '从电脑上传', exact: true }).boundingBox(); + const upload = await creation.getByRole('button', { name: '从电脑上传', exact: true }).boundingBox(); const controls = await management.locator('.agent-knowledge-management-controls').boundingBox(); + expect(create!.x + create!.width).toBeLessThanOrEqual(upload!.x); + expect(upload!.x + upload!.width).toBeLessThanOrEqual(creationBounds!.x + creationBounds!.width); + expect(manager!.x + manager!.width).toBeLessThanOrEqual(refresh!.x); for (const control of [manager, refresh, upload]) { expect(Math.abs(control!.y + control!.height / 2 - create!.y - create!.height / 2)).toBeLessThanOrEqual(1); } @@ -305,7 +308,7 @@ for (const width of [375, 1440]) { const creation = knowledge.getByRole('region', { name: '上传自己的文件', exact: true }); const management = knowledge.getByRole('region', { name: '查看我的知识库', exact: true }); const manager = management.getByRole('combobox', { name: '管理的知识库', exact: true }); - const upload = management.getByRole('button', { name: '从电脑上传', exact: true }); + const upload = creation.getByRole('button', { name: '从电脑上传', exact: true }); const create = creation.getByRole('button', { name: '新建知识库', exact: true }); await expect(upload).toBeDisabled(); await expectKnowledgeLayout(page); @@ -382,15 +385,22 @@ test('cancelled and failed uploads preserve shared choices and the personal draf // An uncertain upload response keeps its retry attached to the same library. await expect(knowledge.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeDisabled(); await expect(knowledge.getByRole('button', { name: '新建知识库', exact: true })).toBeDisabled(); - await page.setViewportSize({ width: 1440, height: 850 }); + const creationCard = knowledge.getByRole('region', { name: '上传自己的文件', exact: true }); const managementCard = knowledge.getByRole('region', { name: '查看我的知识库', exact: true }); - const managementBounds = await managementCard.boundingBox(); - for (const control of await managementCard.locator('.agent-knowledge-management-controls').getByRole('button').all()) { - const bounds = await control.boundingBox(); - expect(bounds!.x).toBeGreaterThanOrEqual(managementBounds!.x); - expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(managementBounds!.x + managementBounds!.width); + await expect(creationCard.getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toBeEnabled(); + await expect(managementCard.getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toHaveCount(0); + for (const width of [375, 768, 1024, 1440]) { + await page.setViewportSize({ width, height: 850 }); + for (const [card, row] of [[creationCard, '.agent-knowledge-create-action'], [managementCard, '.agent-knowledge-management-controls']] as const) { + const cardBounds = await card.boundingBox(); + for (const control of await card.locator(row).getByRole('button').all()) { + const bounds = await control.boundingBox(); + expect(bounds!.x).toBeGreaterThanOrEqual(cardBounds!.x); + expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(cardBounds!.x + cardBounds!.width); + } + } + await expectNoClipping(page); } - await expectNoClipping(page); await page.setViewportSize({ width: 375, height: 850 }); const failedUpload = (await calls(page)).filter(call => call.path.endsWith('/knowledge/pick')).at(-1)!; await page.evaluate(() => { (window as unknown as FixtureWindow).__knowledgeFixture.upload = 'cancel'; }); diff --git a/tests/e2e/cloud-agent-platform-avatars.spec.ts b/tests/e2e/cloud-agent-platform-avatars.spec.ts index 54865fb6..ce5c5bd6 100644 --- a/tests/e2e/cloud-agent-platform-avatars.spec.ts +++ b/tests/e2e/cloud-agent-platform-avatars.spec.ts @@ -187,8 +187,9 @@ for (const width of [375, 768, 1024, 1440]) { await expect(tabs.getByRole('tab', { name: '高级设置', exact: true })).toHaveAttribute('aria-selected', 'true'); await expect(advanced.getByRole('navigation')).toHaveCount(0); await expect(advanced.getByRole('tablist')).toHaveCount(0); - const headings = ['模型', '工具箱(Tool)', '做事手册(Skill)', 'MCP 服务', '子智能体', '知识库']; + const headings = ['模型', 'TOOL', 'SKILL', 'MCP 服务', '知识库']; for (const name of headings) await expect(advanced.getByRole('heading', { name, exact: true })).toBeVisible(); + await expect(advanced.getByRole('heading', { name: '子智能体', exact: true })).toHaveCount(0); await expect(advanced.getByRole('heading').first()).toHaveText('模型'); await expect(advanced.getByLabel('模型', { exact: true })).toHaveAttribute('required', ''); for (const copy of ['选它做事时能用的工具。', '给它一本手册,照着方法做。', '管理我的云端能力']) { diff --git a/tests/e2e/cloud-agents.spec.ts b/tests/e2e/cloud-agents.spec.ts index fca06e41..ffeb28c8 100644 --- a/tests/e2e/cloud-agents.spec.ts +++ b/tests/e2e/cloud-agents.spec.ts @@ -290,11 +290,11 @@ test('personal cloud Agent identity, configuration and separate published chat i await expect(page.getByRole('group', { name: /^知识库/ })).toBeVisible(); await expect(page.getByRole('heading', { name: '费用上限', exact: true })).toHaveCount(0); await expect(page.getByRole('heading', { name: '运行限制', exact: true })).toHaveCount(0); - await page.getByRole('button', { name: '选择工具箱', exact: true }).click(); + await page.getByRole('button', { name: '选择TOOL', exact: true }).click(); await expect(page.getByRole('checkbox', { name: /上网查资料.*在网上查找相关信息/ })).toBeVisible(); await page.getByRole('checkbox', { name: '资料整理工具', exact: true }).check(); - await page.getByRole('button', { name: '选择做事手册', exact: true }).click(); - await page.getByLabel('搜索做事手册', { exact: true }).fill('出处'); + await page.getByRole('button', { name: '选择SKILL', exact: true }).click(); + await page.getByLabel('搜索SKILL', { exact: true }).fill('出处'); await expect(page.getByRole('checkbox', { name: /查资料,做研究.*出处/ })).toBeVisible(); await instructionsTab.click(); await expect(introduction).toHaveValue('一起梳理故事和文章'); diff --git a/tests/unit/cloud-agent-subagent-selector.test.tsx b/tests/unit/cloud-agent-subagent-selector.test.tsx index 22b5f085..256da0b9 100644 --- a/tests/unit/cloud-agent-subagent-selector.test.tsx +++ b/tests/unit/cloud-agent-subagent-selector.test.tsx @@ -39,7 +39,7 @@ it('removes subagent selection while other capability edits preserve historical expect(screen.queryByText(/missing-child/)).not.toBeInTheDocument(); expect(changed).not.toHaveBeenCalled(); fireEvent.change(screen.getByRole('combobox', { name: '模型' }), { target: { value: 'new-model' } }); - for (const [button, checkbox] of [['选择工具箱', '查询工具'], ['选择做事手册', '研究手册'], ['选择MCP 服务', '资料连接']]) { + for (const [button, checkbox] of [['选择TOOL', '查询工具'], ['选择SKILL', '研究手册'], ['选择MCP 服务', '资料连接']]) { fireEvent.click(screen.getByRole('button', { name: button, exact: true })); fireEvent.click(screen.getByRole('checkbox', { name: checkbox, exact: true })); } diff --git a/tests/unit/cloud-agents-page.test.tsx b/tests/unit/cloud-agents-page.test.tsx index 0ab04ff7..e35683ef 100644 --- a/tests/unit/cloud-agents-page.test.tsx +++ b/tests/unit/cloud-agents-page.test.tsx @@ -486,8 +486,8 @@ it('shows only model and capability settings together and preserves unfinished s fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false }); const model = screen.getByLabelText('模型'); - const toolbox = screen.getByRole('group', { name: /^工具箱(Tool)/ }); - const handbook = screen.getByRole('group', { name: /^做事手册(Skill)/ }); + const toolbox = screen.getByRole('group', { name: 'TOOL', exact: true }); + const handbook = screen.getByRole('group', { name: 'SKILL', exact: true }); expect(model).toBeVisible(); expect(model).toBeRequired(); expect(toolbox).toBeVisible(); @@ -504,10 +504,10 @@ it('shows only model and capability settings together and preserves unfinished s expect(screen.getAllByRole('tab').map(tab => tab.textContent)).toEqual(['介绍与提示词', '高级设置']); fireEvent.change(model, { target: { value: 'model-a' } }); - fireEvent.click(screen.getByRole('button', { name: '选择做事手册', exact: true })); + fireEvent.click(screen.getByRole('button', { name: '选择SKILL', exact: true })); fireEvent.click(screen.getByRole('checkbox', { name: '写作方法', exact: true })); - fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true })); - const search = screen.getByLabelText('搜索工具箱'); + fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true })); + const search = screen.getByLabelText('搜索TOOL'); fireEvent.change(search, { target: { value: '文章' } }); fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true })); @@ -520,7 +520,7 @@ it('shows only model and capability settings together and preserves unfinished s expect(screen.getByLabelText('模型')).toBe(model); expect(model).toHaveValue('model-a'); - expect(screen.getByLabelText('搜索工具箱')).toBe(search); + expect(screen.getByLabelText('搜索TOOL')).toBe(search); expect(search).toHaveValue('文章'); expect(screen.getByRole('checkbox', { name: '文章整理工具', exact: true })).toBeChecked(); expect(screen.getByRole('checkbox', { name: '写作方法', exact: true })).toBeChecked(); @@ -1003,7 +1003,7 @@ it('preserves hidden runtime policies while saving model, prompt and capability fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false }); await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled()); fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'another-model' } }); - fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true })); + fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true })); fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true })); expect(screen.queryByLabelText('最多执行步数')).not.toBeInTheDocument(); expect(screen.queryByLabelText('每日上限(词元点数)')).not.toBeInTheDocument(); @@ -1054,19 +1054,19 @@ it('searches capabilities while retaining existing selections outside the result fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true })); fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false }); expect(await screen.findByRole('checkbox', { name: /missing-tool/ })).toBeChecked(); - fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true })); - fireEvent.change(screen.getByLabelText('搜索工具箱'), { target: { value: '资料' } }); + fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true })); + fireEvent.change(screen.getByLabelText('搜索TOOL'), { target: { value: '资料' } }); fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false }); fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false }); - expect(screen.getByLabelText('搜索工具箱')).toHaveValue('资料'); + expect(screen.getByLabelText('搜索TOOL')).toHaveValue('资料'); expect(screen.queryByRole('checkbox', { name: '当前时间' })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('checkbox', { name: /上网查资料/ })); fireEvent.click(screen.getByRole('button', { name: '完成选择' })); expect(screen.getByRole('checkbox', { name: /missing-tool/ })).toBeChecked(); - fireEvent.click(screen.getByRole('button', { name: '选择做事手册', exact: true })); - fireEvent.change(screen.getByLabelText('搜索做事手册'), { target: { value: '出处' } }); + fireEvent.click(screen.getByRole('button', { name: '选择SKILL', exact: true })); + fireEvent.change(screen.getByLabelText('搜索SKILL'), { target: { value: '出处' } }); fireEvent.click(screen.getByRole('checkbox', { name: /查资料,做研究/ })); - fireEvent.click(screen.getByRole('checkbox', { name: '开始对话前,先读好已选做事手册的使用方法' })); + fireEvent.click(screen.getByRole('checkbox', { name: '开始对话前,先读好已选SKILL的使用方法' })); fireEvent.click(screen.getByRole('button', { name: '保存草稿' })); await waitFor(() => expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ configuration: expect.objectContaining({ tools: ['missing-tool', 'web_search'], skills: ['deep-research'], preload_skills: ['deep-research'] }), diff --git a/tests/unit/cloud-knowledge-picker.test.tsx b/tests/unit/cloud-knowledge-picker.test.tsx index 930f5abc..5ab3587e 100644 --- a/tests/unit/cloud-knowledge-picker.test.tsx +++ b/tests/unit/cloud-knowledge-picker.test.tsx @@ -50,9 +50,9 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries const selection = screen.getByRole('region', { name: '选择知识库', exact: true }); expect(within(creation).getByRole('button', { name: '新建知识库', exact: true })).toBeVisible(); expect(within(creation).queryByRole('combobox', { name: '管理的知识库' })).not.toBeInTheDocument(); - expect(within(creation).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument(); + expect(within(creation).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled(); expect(within(management).getByRole('combobox', { name: '管理的知识库' })).toBeVisible(); - expect(within(management).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled(); + expect(within(management).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument(); expect(within(management).queryByRole('button', { name: '新建知识库', exact: true })).not.toBeInTheDocument(); expect(within(selection).getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible(); expect(creation.compareDocumentPosition(management) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); @@ -150,10 +150,15 @@ it('limits bindings to 100 libraries without preventing browsing or creation', a it('browses an owned library without binding it and only explicit selection changes the draft', async () => { render(); await openOwn(); + const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true }); + const management = screen.getByRole('region', { name: '查看我的知识库', exact: true }); + const upload = within(creation).getByRole('button', { name: '从电脑上传', exact: true }); + expect(upload).toBeEnabled(); + expect(within(management).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument(); expect(changes).not.toHaveBeenCalled(); expect(screen.queryByRole('button', { name: '移除知识库 ' + own.name })).not.toBeInTheDocument(); api.uploadKnowledge.mockResolvedValue(null); - fireEvent.click(screen.getByRole('button', { name: '从电脑上传', exact: true })); + fireEvent.click(upload); await waitFor(() => expect(api.uploadKnowledge).toHaveBeenCalledTimes(1)); await waitFor(() => expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled()); expect(api.call.mock.calls.every(([operation]) => operation !== 'processKnowledge')).toBe(true); @@ -163,7 +168,7 @@ it('browses an owned library without binding it and only explicit selection chan expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] })); }); -it('keeps an upload failure in the management column while the creation form is open', async () => { +it('keeps document errors in management when the left-card upload is used beside an open creation form', async () => { api.uploadKnowledge.mockRejectedValue(new Error('上传结果未确认')); render(); await openOwn(); @@ -171,8 +176,10 @@ it('keeps an upload failure in the management column while the creation form is const management = screen.getByRole('region', { name: '查看我的知识库', exact: true }); fireEvent.click(within(creation).getByRole('button', { name: '新建知识库', exact: true })); fireEvent.change(within(creation).getByRole('textbox', { name: '知识库名称' }), { target: { value: '还没创建的资料' } }); - fireEvent.click(within(management).getByRole('button', { name: '从电脑上传', exact: true })); + fireEvent.click(within(creation).getByRole('button', { name: '从电脑上传', exact: true })); expect(await within(management).findByRole('alert')).toHaveTextContent('上传结果未确认'); + expect(within(creation).getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toBeEnabled(); + expect(within(management).queryByRole('button', { name: '重新选择同一文件并重试', exact: true })).not.toBeInTheDocument(); expect(within(creation).queryByRole('alert')).not.toBeInTheDocument(); expect(within(creation).getByRole('textbox', { name: '知识库名称' })).toHaveValue('还没创建的资料'); expect(changes).not.toHaveBeenCalled();