Refine Agent sidebar icons and manual expansion
This commit is contained in:
1 parent
a340c0fd94
commit
ad1d2f261d
7 files changed
+221
-20
No files matched your search
@@ -0,0 +1,81 @@
|
||||
# Task: Keep Agent sidebar collapsed until explicit expansion
|
||||
|
||||
## Identity
|
||||
|
||||
- Task ID: 20261012-agent-sidebar-manual-67c1a8e4
|
||||
- Mode: Feature
|
||||
- Branch: codex/20261012-agent-sidebar-manual-67c1a8e4-agent-sidebar-manual
|
||||
- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261012-agent-sidebar-manual-67c1a8e4
|
||||
- Base commit: a340c0fd94ff5711f00a08312e80db4b148ff101
|
||||
- Owner: codex
|
||||
- Status: Ready for Integration
|
||||
|
||||
## Scope
|
||||
|
||||
- Remove the advanced-settings resource policy helper sentence. Keep the Agent sidebar collapsed on pointer/focus arrival; explicit toggle remains the only expansion action. Refresh the “我的智能体” and “渠道” SVG icons in the existing visual language, preserving labels and routes.
|
||||
|
||||
## Intent And Constraints
|
||||
|
||||
- User confirmed the requested icons are the sidebar “我的智能体” and “渠道”, not the top return/collapse controls. Product-language communication; no temporary app update. Preserve return/collapse alignment, cards-first entry, model/resource choices and permission enforcement. Restrict changed hover policy to cloud_agents; Code and other modules keep existing peek. No real cloud saves/publish/model requests during checks; only isolated fixtures.
|
||||
|
||||
## Project Context Loaded
|
||||
|
||||
Task context:
|
||||
- Task ID: 20261012-agent-sidebar-manual-67c1a8e4; Feature; owner codex.
|
||||
- Branch: codex/20261012-agent-sidebar-manual-67c1a8e4-agent-sidebar-manual.
|
||||
- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261012-agent-sidebar-manual-67c1a8e4.
|
||||
- Base: a340c0fd94ff5711f00a08312e80db4b148ff101, completed conversation polish including cards-first navigation.
|
||||
- Bundled check/start/status passed and ownership/reservation exactly match. 37 peer records read-only: 36 concrete scopes, one old child-language record undefined. No blocking semantic conflict. Relevant overlaps: entry-nav same-row return/toggle and cards-first behavior, config-simplify model/resource controls and existing permission semantics, Code hover/resize behavior outside Agents.
|
||||
|
||||
Read:
|
||||
- AGENTS.md; 05-agent-entry read-before-planning, memory-index and planning-gate; own task record.
|
||||
- 00-brief positioning/success; 20-architecture community-collaboration; task-relevant current-state integrated snapshot, decision-index/system-overview and full personal-cloud-agents ADR.
|
||||
- Task-relevant domain/evidence/reflection/commitments indexes; scopes/constraints/promotion candidates of every peer.
|
||||
- Current MainLayout, Sidebar, TitleBar, ConfigurationFields and focused navigation/peek checks. Design skill search; adapt its friendly/icon consistency guidance to the existing cream/pastel/orange product language.
|
||||
|
||||
Relevant understanding:
|
||||
- Project serves students/creators with creation and AI, excludes courses in the client. Main/WS/Yuxi own authorization, resources, accounting and run state; removing text does not change policy.
|
||||
- Integrated snapshot predates current simplified Agents UI. Human-approved base is authoritative for this feature; no canonical docs are edited in Feature mode.
|
||||
- Existing collapse preview listens across toggle, titlebar, focus and sidebar zones. Agents must have no transient preview, including a preview left over from another route. Explicit toggle and keyboard activation remain accessible.
|
||||
- Navigation labels/routes, current-page indication, return control/native window safe spaces and non-Agent preview remain intact. Personal cloud resource selections/enforcement unchanged.
|
||||
- Evidence commitments distinguish fixture/compiled source from real account, paid provider or installed app acceptance. User expressly forbids trial update; no live app operation is needed.
|
||||
- Likely files: MainLayout hover policy, Sidebar scoped SVG presentation, ConfigurationFields one sentence, README and focused behavior checks.
|
||||
- Unknowns resolved: user clarified both sidebar icon positions; no prescribed replacement artwork, use consistent existing SVG set. Old undefined peer has no concrete dependency.
|
||||
|
||||
Gate result:
|
||||
- Passed.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Scope sidebar-preview suppression to Agents and clear stale transient state across route changes; preserve explicit mouse/keyboard toggle and other modules.
|
||||
2. Delete only the resource helper sentence and refresh the two sidebar icons without changing navigation or policy.
|
||||
3. Verify hover/toggle/route transitions, resource UI and relevant Electron fixture, typecheck/lint/build; update task-scoped evidence and save changes. Do not update the temporary app.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Removed only the advanced-settings resource-policy helper sentence. Model requirements, resource selection and underlying access enforcement are unchanged.
|
||||
- Agents, including channels and conversation/configuration routes, now disable transient sidebar preview. Hovering the toggle, titlebar or left edge and focusing the toggle keep a collapsed sidebar hidden. Clicking the toggle or activating it with the keyboard still changes the persisted collapsed preference.
|
||||
- Entering/leaving the Agents module resets preview sources and cancels pending close timers. A preview from Code cannot appear in Agents or revive after returning; Code preview and the fixed expanded Canvas sidebar remain available.
|
||||
- Replaced the two confirmed sidebar icons with a pale-purple conversation robot for “我的智能体” and a pale-blue connection-node icon for “渠道”. Labels, current-page indication and destinations are preserved. Retained the separate Bot icon used by the robot module.
|
||||
- README and focused checks reflect the final behavior. No temporary application, runtime pointer, manifest, user profile or cloud data was updated.
|
||||
|
||||
## Verification
|
||||
|
||||
- `typecheck`: passed after preserving the robot module's shared Bot import. The initial check caught that missing import; it was corrected before the final build and UI check.
|
||||
- Scoped ESLint for MainLayout, Sidebar, ConfigurationFields, the sidebar-preview unit check and the Electron check: passed. `git diff --check`: passed.
|
||||
- `build:vite`: passed on the final source. Build log: `/tmp/makelore-agent-sidebar-manual-build.log`.
|
||||
- `cloud-agents-page.test.tsx`: 31 checks passed. MainLayout sidebar-preview and module-gate checks: 18 passed. Covers both Agent routes, all four preview sources, explicit toggle, module transitions, pending timers, unchanged Code preview and Canvas expansion.
|
||||
- Production Electron isolated fixture (`tests/e2e/cloud-agents.spec.ts`): 1 scenario passed. Actual shell checks confirm hovering the toggle/titlebar/left edge and focusing do not open the sidebar; Enter and click do. The removed helper is absent; existing create/configure/resource/save/start-chat checks still pass.
|
||||
- Visually reviewed `cloud-agent-sidebar-icons.png` and `cloud-agent-advanced-model.png` from `.tmp/agent-sidebar-manual-electron/cloud-agents-personal-clou-c4180--published-chat-in-Electron/`. Both sidebar icons are visible with distinct pastel badges and unchanged readable labels.
|
||||
- Verification uses an isolated test profile and fixture-owned Host API responses. This is source/fixture evidence, not acceptance in the user's logged-in temporary app or a real cloud/model call. User expressly requested no temporary app update.
|
||||
- Read-only peer review found no actionable issues: route boundary covers Agent subpages, sources/timers are cleared, explicit toggle remains available, other modules and shared robot icon are preserved.
|
||||
- Bundled `check_doc_drift.py --task-id 20261012-agent-sidebar-manual-67c1a8e4`: passed; changes remain inside Feature ownership boundaries.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Preserve this task branch/worktree for integration. Include its recorded base and preceding Agent page changes when integrating; do not replace another active checkout.
|
||||
- Temporary app update remains deferred until the user requests it. No cloud service change is needed for this task.
|
||||
|
||||
## Promotion Candidates
|
||||
|
||||
- Target: integrated current state and Agent navigation/UI guidance. Proposal: Agents alone use explicit sidebar expansion; other modules retain their existing preview behavior. Record the removed resource-policy helper as a presentation change, with policy enforcement unchanged. Evidence: user's explicit request, confirmed icon locations, 18 MainLayout checks and production Electron scenario. Future impact: subsequent shared-sidebar changes should preserve this per-module interaction. Semantic conflicts: older generic hover-preview descriptions need the Agents exception during serialized integration. Human confirmation: the behavior is already authorized by this conversation; canonical edits still require Integration mode.
|
||||
@@ -12,7 +12,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智
|
||||
|
||||
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
|
||||
会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
|
||||
- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回或展开,macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务、子智能体和知识库纵向呈现,不再设内部分类切换;不展示 Tool/Skill 解释、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:空对话展示当前智能体头像与名称,示例只填入草稿;用户消息和智能体回复区分呈现,长 Markdown、表格和代码在消息区内滚动,底部输入框、附件、停止和历史入口保留;短窗口中,配置完全生效且没有待处理问题时收起重复的已保存/已应用提示和禁用按钮,修改或待确认时恢复;“调整智能体”与客户端/微信来源同排,窄窗口会换行,未发送内容的离开保护不变。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。
|
||||
- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务、子智能体和知识库纵向呈现,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:空对话展示当前智能体头像与名称,示例只填入草稿;用户消息和智能体回复区分呈现,长 Markdown、表格和代码在消息区内滚动,底部输入框、附件、停止和历史入口保留;短窗口中,配置完全生效且没有待处理问题时收起重复的已保存/已应用提示和禁用按钮,修改或待确认时恢复;“调整智能体”与客户端/微信来源同排,窄窗口会换行,未发送内容的离开保护不变。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。
|
||||
|
||||
云知识库的 embedding 模型只由平台管理员在 Yuxi 后台维护:在「智能体管理 → 模型供应商」启用供应商,按供应商要求填写有效的服务地址和 API Key,添加并启用 embedding 模型并填写实际向量维度;embedding 向量化费用由平台承担,不计入用户用量、词元点数或智能体费用上限;one-api 不参与 embedding 配置,Works Square 只负责其他应计费模型调用的创建者词元点数账本。客户端区分目录加载中、加载失败与无可用模型,空状态提供管理员配置说明;「刷新知识库与模型」保留未提交名称,未确认的创建请求沿用原操作身份。
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export function MainLayout() {
|
||||
const isCloudAgents = activeModule === 'cloud_agents';
|
||||
|
||||
const handleSidebarPeekChange = useCallback((open: boolean, source: SidebarPeekSource) => {
|
||||
if (!sidebarCollapsed) return;
|
||||
if (!sidebarCollapsed || isCloudAgents) return;
|
||||
|
||||
const sources = sidebarPeekSourcesRef.current;
|
||||
if (sources[source] === open) return;
|
||||
@@ -55,19 +55,20 @@ export function MainLayout() {
|
||||
setSidebarPeekOpen(false);
|
||||
sidebarPeekCloseTimerRef.current = null;
|
||||
}, SIDEBAR_PEEK_CLOSE_DELAY_MS);
|
||||
}, [sidebarCollapsed]);
|
||||
}, [sidebarCollapsed, isCloudAgents]);
|
||||
|
||||
useEffect(() => {
|
||||
sidebarPeekSourcesRef.current = { toggle: false, focus: false, sidebar: false, titlebar: false };
|
||||
// This transient state mirrors the external persisted sidebar preference;
|
||||
// reset it when that preference changes so a stale preview cannot reopen.
|
||||
// reset it on preference changes and both edges of the Agents boundary so
|
||||
// a previous module's preview or timer cannot reopen the manual-only sidebar.
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setSidebarPeekOpen(false);
|
||||
if (sidebarPeekCloseTimerRef.current) {
|
||||
clearTimeout(sidebarPeekCloseTimerRef.current);
|
||||
sidebarPeekCloseTimerRef.current = null;
|
||||
}
|
||||
}, [sidebarCollapsed]);
|
||||
}, [sidebarCollapsed, isCloudAgents]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (sidebarPeekCloseTimerRef.current) clearTimeout(sidebarPeekCloseTimerRef.current);
|
||||
@@ -83,8 +84,8 @@ export function MainLayout() {
|
||||
workspaceLayout={isChatWorkspace}
|
||||
overlay={isPaintingModule}
|
||||
showSidebarControls={!isCanvasWorkspace}
|
||||
sidebarPeekOpen={isCanvasWorkspace ? false : sidebarPeekOpen}
|
||||
onSidebarPeekChange={isCanvasWorkspace ? undefined : handleSidebarPeekChange}
|
||||
sidebarPeekOpen={isCanvasWorkspace || isCloudAgents ? false : sidebarPeekOpen}
|
||||
onSidebarPeekChange={isCanvasWorkspace || isCloudAgents ? undefined : handleSidebarPeekChange}
|
||||
/>
|
||||
|
||||
{/* Below the title bar: sidebar + content */}
|
||||
@@ -93,8 +94,8 @@ export function MainLayout() {
|
||||
workspaceLayout={isChatWorkspace}
|
||||
hideOnCompact={isCanvasWorkspace}
|
||||
forceExpanded={isCanvasWorkspace}
|
||||
sidebarPeekOpen={isCanvasWorkspace ? false : sidebarPeekOpen}
|
||||
onSidebarPeekChange={isCanvasWorkspace ? undefined : handleSidebarPeekChange}
|
||||
sidebarPeekOpen={isCanvasWorkspace || isCloudAgents ? false : sidebarPeekOpen}
|
||||
onSidebarPeekChange={isCanvasWorkspace || isCloudAgents ? undefined : handleSidebarPeekChange}
|
||||
/>
|
||||
<main
|
||||
data-testid="main-content"
|
||||
|
||||
@@ -6,15 +6,16 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Bot,
|
||||
BotMessageSquare,
|
||||
Cpu,
|
||||
ChevronRight,
|
||||
FolderKanban,
|
||||
Gamepad2,
|
||||
LogOut,
|
||||
Plus,
|
||||
RadioTower,
|
||||
Settings as SettingsIcon,
|
||||
UserCircle2,
|
||||
Waypoints,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
@@ -505,13 +506,17 @@ export function Sidebar({
|
||||
<button type="button" aria-label="我的智能体" onClick={() => navigate('/cloud-agents?view=mine')}
|
||||
aria-current={location.pathname === '/cloud-agents' ? 'page' : undefined}
|
||||
className={cn('flex min-h-10 w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-sm font-semibold hover:bg-surface-tertiary', location.pathname === '/cloud-agents' && 'bg-brand-soft', sidebarCollapsed && 'justify-center px-0')}>
|
||||
<Bot className="h-4 w-4 shrink-0 text-brand" />
|
||||
<span aria-hidden="true" className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-[#e2d7ef] bg-[#f2ebfa] text-[#785b9b]">
|
||||
<BotMessageSquare className="h-5 w-5" strokeWidth={1.8} />
|
||||
</span>
|
||||
{!sidebarCollapsed && <span>我的智能体</span>}
|
||||
</button>
|
||||
<button type="button" aria-label="渠道" onClick={() => navigate('/cloud-agents/channels')}
|
||||
aria-current={location.pathname.startsWith('/cloud-agents/channels') ? 'page' : undefined}
|
||||
className={cn('mt-1 flex min-h-10 w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-sm font-semibold hover:bg-surface-tertiary', location.pathname.startsWith('/cloud-agents/channels') && 'bg-brand-soft', sidebarCollapsed && 'justify-center px-0')}>
|
||||
<RadioTower className="h-4 w-4 shrink-0 text-brand" />
|
||||
<span aria-hidden="true" className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-[#d7e4ec] bg-[#edf4fa] text-[#55788e]">
|
||||
<Waypoints className="h-5 w-5" strokeWidth={1.8} />
|
||||
</span>
|
||||
{!sidebarCollapsed && <span>渠道</span>}
|
||||
</button>
|
||||
</div> : <div data-testid="sidebar-robot-navigation" className="px-2 py-3">
|
||||
|
||||
@@ -76,6 +76,5 @@ export function ConfigurationFields({ slug, value, disabled, onChange, section =
|
||||
onCreated={kb => { change('knowledges', [...new Set([...value.knowledges, kb.kb_id])]); setRefresh(v => v + 1); }} /></div>}
|
||||
</fieldset>;
|
||||
})}
|
||||
<p className="text-xs leading-5 text-muted-foreground">未选择的资源不会开放给此智能体。通过微信或 API 使用时也遵循本配置。</p>
|
||||
</div>;
|
||||
}
|
||||
@@ -126,11 +126,28 @@ test('personal cloud Agent identity, configuration and separate published chat i
|
||||
expect(collapseBounds).not.toBeNull();
|
||||
expect(Math.abs(returnBounds!.y - collapseBounds!.y)).toBeLessThanOrEqual(1);
|
||||
expect(returnBounds!.x + returnBounds!.width).toBeLessThan(collapseBounds!.x);
|
||||
await page.screenshot({ path: testInfo.outputPath('cloud-agent-sidebar-icons.png') });
|
||||
await navigation.getByRole('button', { name: '折叠侧栏', exact: true }).click();
|
||||
await page.mouse.move(700, 400);
|
||||
const sidebar = page.getByTestId('sidebar');
|
||||
const sidebarToggle = page.getByTestId('titlebar-sidebar-toggle');
|
||||
await sidebarToggle.hover();
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||||
await expect(sidebarToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
await page.getByTestId('titlebar-sidebar-toggle-layer').hover();
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||||
await page.mouse.move(3, 120);
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||||
await sidebarToggle.focus();
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||||
await expect(navigation.getByRole('button', { name: '返回首页', exact: true })).toBeVisible();
|
||||
await navigation.getByRole('button', { name: /展开侧栏|固定侧栏/ }).click();
|
||||
await expect(navigation.getByRole('button', { name: '展开侧栏', exact: true })).toBeVisible();
|
||||
await sidebarToggle.press('Enter');
|
||||
await expect(page.getByTestId('sidebar-cloud-agents-navigation')).toBeVisible();
|
||||
await expect(sidebarToggle).toHaveAttribute('aria-expanded', 'true');
|
||||
await sidebarToggle.click();
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||||
await sidebarToggle.click();
|
||||
await expect(sidebar).toHaveAttribute('aria-hidden', 'false');
|
||||
const fixtureState = () => app.evaluate(() => Reflect.get(globalThis, '__cloudAgentUiFixture').snapshot());
|
||||
const messageOperations = async () => (await fixtureState()).operations.filter((operation: string) => operation === 'preview' || operation === 'submit');
|
||||
await page.getByRole('button', { name: '创建我的第一个智能体', exact: true }).click();
|
||||
@@ -192,6 +209,7 @@ test('personal cloud Agent identity, configuration and separate published chat i
|
||||
await expect(page.getByText('选它做事时能用的工具。', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('给它一本手册,照着方法做。', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('管理我的云端能力', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('未选择的资源不会开放给此智能体。通过微信或 API 使用时也遵循本配置。', { exact: true })).toHaveCount(0);
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MainLayout } from '@/components/layout/MainLayout';
|
||||
import { codingWorkspaceStore } from '@/stores/coding-workspace';
|
||||
@@ -8,6 +8,7 @@ import { useSettingsStore } from '@/stores/settings';
|
||||
type PeekProps = {
|
||||
sidebarPeekOpen?: boolean;
|
||||
onSidebarPeekChange?: (open: boolean, source: 'toggle' | 'focus' | 'sidebar' | 'titlebar') => void;
|
||||
forceExpanded?: boolean;
|
||||
};
|
||||
|
||||
vi.mock('@/components/layout/TitleBar', () => ({
|
||||
@@ -17,8 +18,12 @@ vi.mock('@/components/layout/TitleBar', () => ({
|
||||
type="button"
|
||||
data-testid="peek-trigger"
|
||||
data-peek-open={sidebarPeekOpen ? 'true' : 'false'}
|
||||
aria-label={useSettingsStore.getState().sidebarCollapsed ? '展开侧栏' : '折叠侧栏'}
|
||||
onClick={() => useSettingsStore.getState().setSidebarCollapsed(!useSettingsStore.getState().sidebarCollapsed)}
|
||||
onPointerEnter={() => onSidebarPeekChange?.(true, 'toggle')}
|
||||
onPointerLeave={() => onSidebarPeekChange?.(false, 'toggle')}
|
||||
onFocus={() => onSidebarPeekChange?.(true, 'focus')}
|
||||
onBlur={() => onSidebarPeekChange?.(false, 'focus')}
|
||||
/>
|
||||
<div
|
||||
data-testid="peek-titlebar"
|
||||
@@ -30,22 +35,33 @@ vi.mock('@/components/layout/TitleBar', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('@/components/layout/Sidebar', () => ({
|
||||
Sidebar: ({ sidebarPeekOpen, onSidebarPeekChange }: PeekProps) => (
|
||||
Sidebar: ({ sidebarPeekOpen, onSidebarPeekChange, forceExpanded }: PeekProps) => (
|
||||
<aside
|
||||
data-testid="peek-sidebar"
|
||||
data-peek-open={sidebarPeekOpen ? 'true' : 'false'}
|
||||
data-force-expanded={forceExpanded ? 'true' : 'false'}
|
||||
onPointerEnter={() => onSidebarPeekChange?.(true, 'sidebar')}
|
||||
onPointerLeave={() => onSidebarPeekChange?.(false, 'sidebar')}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
function renderLayout() {
|
||||
function NavigationProbe() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
return <>
|
||||
<output data-testid="layout-location">{location.pathname}</output>
|
||||
<button onClick={() => navigate('/cloud-agents')}>进入智能体</button>
|
||||
<button onClick={() => navigate('/chat')}>进入编程</button>
|
||||
</>;
|
||||
}
|
||||
|
||||
function renderLayout(path = '/chat') {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/chat']}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Routes>
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="*" element={null} />
|
||||
<Route path="*" element={<NavigationProbe />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
@@ -140,4 +156,85 @@ describe('MainLayout sidebar hover preview', () => {
|
||||
});
|
||||
expect(trigger).toHaveAttribute('data-peek-open', 'false');
|
||||
});
|
||||
|
||||
it.each(['/cloud-agents', '/cloud-agents/channels'])('ignores every pointer and focus preview source on %s', (path) => {
|
||||
renderLayout(path);
|
||||
const trigger = screen.getByTestId('peek-trigger');
|
||||
const sidebar = screen.getByTestId('peek-sidebar');
|
||||
const titlebar = screen.getByTestId('peek-titlebar');
|
||||
for (const target of [trigger, titlebar, sidebar]) {
|
||||
fireEvent.pointerEnter(target);
|
||||
expect(trigger).toHaveAttribute('data-peek-open', 'false');
|
||||
expect(sidebar).toHaveAttribute('data-peek-open', 'false');
|
||||
fireEvent.pointerLeave(target);
|
||||
}
|
||||
fireEvent.focus(trigger);
|
||||
expect(trigger).toHaveAttribute('data-peek-open', 'false');
|
||||
expect(sidebar).toHaveAttribute('data-peek-open', 'false');
|
||||
fireEvent.blur(trigger);
|
||||
act(() => vi.advanceTimersByTime(180));
|
||||
expect(sidebar).toHaveAttribute('data-peek-open', 'false');
|
||||
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
});
|
||||
|
||||
it.each(['/cloud-agents', '/cloud-agents/channels'])('allows explicit sidebar expansion and collapse on %s', (path) => {
|
||||
renderLayout(path);
|
||||
fireEvent.click(screen.getByRole('button', { name: '展开侧栏' }));
|
||||
expect(useSettingsStore.getState().sidebarCollapsed).toBe(false);
|
||||
expect(screen.getByRole('button', { name: '折叠侧栏' })).toHaveAttribute('data-peek-open', 'false');
|
||||
fireEvent.click(screen.getByRole('button', { name: '折叠侧栏' }));
|
||||
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-trigger'));
|
||||
fireEvent.focus(screen.getByTestId('peek-trigger'));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
});
|
||||
|
||||
it('clears active Code preview sources when entering Agents and does not restore them on return', () => {
|
||||
renderLayout();
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-trigger'));
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-titlebar'));
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-sidebar'));
|
||||
fireEvent.focus(screen.getByTestId('peek-trigger'));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'true');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '进入智能体' }));
|
||||
expect(screen.getByTestId('layout-location')).toHaveTextContent('/cloud-agents');
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
fireEvent.click(screen.getByRole('button', { name: '进入编程' }));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-trigger'));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'true');
|
||||
fireEvent.pointerLeave(screen.getByTestId('peek-trigger'));
|
||||
act(() => vi.advanceTimersByTime(180));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
});
|
||||
|
||||
it('cancels a stale Code close timer at the Agents boundary before Code starts a fresh preview', () => {
|
||||
renderLayout();
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-trigger'));
|
||||
fireEvent.pointerLeave(screen.getByTestId('peek-trigger'));
|
||||
expect(vi.getTimerCount()).toBe(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '进入智能体' }));
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
act(() => vi.advanceTimersByTime(180));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '进入编程' }));
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-titlebar'));
|
||||
act(() => vi.advanceTimersByTime(180));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'true');
|
||||
fireEvent.pointerLeave(screen.getByTestId('peek-titlebar'));
|
||||
act(() => vi.advanceTimersByTime(180));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
});
|
||||
|
||||
it('retains the fixed expanded Canvas workspace sidebar', () => {
|
||||
renderLayout('/image-canvas');
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-force-expanded', 'true');
|
||||
fireEvent.pointerEnter(screen.getByTestId('peek-trigger'));
|
||||
fireEvent.focus(screen.getByTestId('peek-trigger'));
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-peek-open', 'false');
|
||||
expect(screen.getByTestId('peek-sidebar')).toHaveAttribute('data-force-expanded', 'true');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user