From 9e0eb98f3d84ada328087820cf4fdb3d0f18633c Mon Sep 17 00:00:00 2001 From: xintaofei Date: Tue, 24 Mar 2026 23:25:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96agent=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/chat/agent-selector.tsx | 138 +++++++++++++++++++++---- 1 file changed, 116 insertions(+), 22 deletions(-) diff --git a/src/components/chat/agent-selector.tsx b/src/components/chat/agent-selector.tsx index 91c96fb..c9066e8 100644 --- a/src/components/chat/agent-selector.tsx +++ b/src/components/chat/agent-selector.tsx @@ -1,6 +1,6 @@ "use client" -import { useEffect, useRef, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { useTranslations } from "next-intl" import { acpListAgents } from "@/lib/tauri" import { disposeTauriListener } from "@/lib/tauri-listener" @@ -35,6 +35,59 @@ export function AgentSelector({ const onSelectRef = useRef(onSelect) const onAgentsLoadedRef = useRef(onAgentsLoaded) + // Sliding indicator state + const containerRef = useRef(null) + const itemRefs = useRef>(new Map()) + const [indicator, setIndicator] = useState<{ + left: number + width: number + } | null>(null) + + // Use ResizeObserver to track button size changes during CSS transitions + useEffect(() => { + const container = containerRef.current + if (!container) return + + const measure = () => { + if (!selected) { + setIndicator(null) + return + } + const btn = itemRefs.current.get(selected) + if (!btn || !container) { + setIndicator(null) + return + } + const containerRect = container.getBoundingClientRect() + const btnRect = btn.getBoundingClientRect() + setIndicator({ + left: btnRect.left - containerRect.left, + width: btnRect.width, + }) + } + + const ro = new ResizeObserver(() => { + measure() + }) + + // Observe all button elements so indicator updates as they resize + for (const btn of itemRefs.current.values()) { + ro.observe(btn) + } + ro.observe(container) + + // Initial measurement + measure() + + const onResize = () => measure() + window.addEventListener("resize", onResize) + + return () => { + ro.disconnect() + window.removeEventListener("resize", onResize) + } + }, [selected, agents]) + useEffect(() => { onSelectRef.current = onSelect }, [onSelect]) @@ -120,6 +173,17 @@ export function AgentSelector({ onSelect(agentType) } + const setItemRef = useCallback( + (agentType: AgentType) => (el: HTMLButtonElement | null) => { + if (el) { + itemRefs.current.set(agentType, el) + } else { + itemRefs.current.delete(agentType) + } + }, + [] + ) + if (agents.length === 0) { return (
@@ -138,27 +202,57 @@ export function AgentSelector({ } return ( -
- {agents.map((agent) => ( - - ))} +
+ {/* Sliding droplet indicator */} + {indicator && ( +
+ )} + {agents.map((agent) => { + const isSelected = selected === agent.agent_type + return ( + + ) + })}
) }