diff --git a/docs/develop-guides/changelog.md b/docs/develop-guides/changelog.md index b41926bd..2a17d0ce 100644 --- a/docs/develop-guides/changelog.md +++ b/docs/develop-guides/changelog.md @@ -33,6 +33,7 @@ ### 修复 +- 调整聊天首页的智能体切换入口:在无历史对话时,智能体数量 `<= 3` 且 `chat-main` 宽度不小于 `380px` 时继续使用横向 segmented;当智能体数量 `>= 4` 或内容区宽度小于 `380px` 时自动收敛为“当前智能体 + 下拉按钮”形式,避免多智能体或窄屏场景下入口被截断 - 发布前一致性修复:统一 0.6.0 版本号(backend/package/web)、更新 dev/prod 镜像标签语义(`0.6.0.dev` / `0.6.0`),并为 `/api/system/health` 补充 `version` 字段,提升部署可观测性与发版追溯能力 - 收敛“状态工作台”自动弹出规则:前端不再因为共享 `workspace` 或文件系统天然存在内容而默认展开,改为仅在 `/home/gem/user-data/uploads` 或 `/home/gem/user-data/outputs` 下检测到实际文件时自动弹出;手动打开、关闭、刷新和伸缩交互保持不变 - 调整智能体 todo 展示语义:待办状态不再作为 `capabilities` 前端开关,而是直接根据运行态 `agent_state.todos` 渲染;同时将 todo 入口从 Agent Panel 移到输入框内的轻量浮层,并让右侧“状态工作台”收敛为文件系统视图,输入框按钮文案同步由“状态”调整为“文件” diff --git a/docs/develop-guides/roadmap.md b/docs/develop-guides/roadmap.md index 858a8057..f7cbb9bf 100644 --- a/docs/develop-guides/roadmap.md +++ b/docs/develop-guides/roadmap.md @@ -52,6 +52,7 @@ - 重写界面设计规范:参考 `DESIGN.md` 写法补充视觉气质、颜色 token、组件状态、布局层级、响应式与 Agent Prompt Guide,并基于该规范收敛首页视觉表现,移除装饰性渐变、重阴影、hover 位移和入场动画。 - 修复对话摘要中间件的工具结果卸载链路:摘要触发时改为将大体积 `ToolMessage` 写入当前 agent 可见的 sandbox outputs 路径,修正 `summary_offload` 路径拼接错误、`messages` 触发条件下不会真正裁剪历史的问题,并避免将 system message 重复纳入摘要与最终消息列表;补充对应单元测试覆盖。 - 调整智能体对话中的工具调用展示:连续工具调用默认折叠为“调用了 N 个工具”的轻量摘要,展开后改为弱化时间线样式,减少工具结果卡片对正文阅读节奏的干扰。 +- 调整聊天首页的智能体切换入口:在无历史对话时,智能体数量 `<= 3` 且 `chat-main` 宽度不小于 `380px` 时继续使用横向 segmented;当智能体数量 `>= 4` 或内容区宽度小于 `380px` 时自动收敛为“当前智能体 + 下拉按钮”形式。 --- diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 44ec53fd..847829f5 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -153,6 +153,37 @@ /> +
+ + + + +
+ { if (!currentAgentId.value || !agents.value || !agents.value.length) return null return agents.value.find((a) => a.id === currentAgentId.value) || null }) +const startAgents = computed(() => agents.value || []) const chatsList = computed(() => threads.value || []) const currentChatId = computed(() => chatState.currentThreadId) const currentThread = computed(() => { @@ -409,6 +449,7 @@ const currentThreadAgentName = computed(() => { } return currentAgentName.value }) +const currentAgentIcon = computed(() => getAgentIconComponent(currentAgentId.value)) // 检查当前智能体是否支持文件上传 const supportsFileUpload = computed(() => { @@ -530,7 +571,7 @@ const getAgentIconComponent = (agentId) => { } const agentSegmentOptions = computed(() => { - return (agents.value || []).map((agent) => { + return startAgents.value.map((agent) => { const IconComponent = getAgentIconComponent(agent.id) return { label: () => @@ -543,8 +584,16 @@ const agentSegmentOptions = computed(() => { }) }) +const showStartAgentSelector = computed(() => { + return !props.singleMode && !conversations.value.length && startAgents.value.length > 1 +}) + +const showStartAgentDropdown = computed(() => { + return showStartAgentSelector.value && (startAgents.value.length >= 4 || localUIState.chatMainWidth < 380) +}) + const showStartAgentSegment = computed(() => { - return !props.singleMode && !conversations.value.length && agentSegmentOptions.value.length > 1 + return showStartAgentSelector.value && !showStartAgentDropdown.value }) const handleStartAgentChange = async (agentId) => { @@ -594,9 +643,11 @@ onMounted(() => { } if (window.ResizeObserver && chatMainRef.value) { + localUIState.chatMainWidth = chatMainRef.value.clientWidth || window.innerWidth chatMainResizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const width = entry.contentRect.width + localUIState.chatMainWidth = width const isTakingSpace = chatUIStore.isSidebarOpen && !isSidebarFloating.value if (isTakingSpace) { @@ -1969,6 +2020,80 @@ watch(currentChatId, (threadId, oldThreadId) => { } } +.agent-switcher-wrapper { + display: flex; + justify-content: center; + margin: 0 auto 18px; +} + +.agent-switcher-btn { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; + max-width: 100%; + padding: 4px 12px; + border: 1px solid var(--gray-150); + border-radius: 8px; + background: var(--gray-0); + color: var(--gray-900); + cursor: pointer; + transition: + background-color 0.2s ease, + border-color 0.2s ease, + color 0.2s ease; + + &:hover { + background: var(--gray-0); + border-color: var(--gray-200); + } +} + +.agent-switcher-icon, +.agent-switcher-chevron { + flex-shrink: 0; + color: var(--gray-600); +} + +.agent-switcher-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +:deep(.agent-switcher-menu) { + min-width: 220px; +} + +:deep(.agent-switcher-menu-item) { + display: flex; + align-items: center; + gap: 8px; +} + +:deep(.agent-switcher-menu-icon) { + flex-shrink: 0; + color: var(--gray-600); +} + +:deep(.agent-switcher-menu-text) { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +:deep(.agent-switcher-menu-badge) { + flex-shrink: 0; + padding: 1px 8px; + border-radius: 999px; + background: var(--main-30); + color: var(--main-700); + font-size: 12px; +} + .example-questions { margin-top: 16px; text-align: center; @@ -2184,6 +2309,15 @@ watch(currentChatId, (threadId, oldThreadId) => { } } + .agent-switcher-wrapper { + margin-bottom: 8px; + } + + .agent-switcher-btn { + width: 100%; + justify-content: center; + } + .chat-header { .header__left { .text {