fix(web): remove pet button from chat composer

The Pets entry in the composer toolbar is no longer needed here.
Users can still manage pets via /pet slash commands and Settings.
This commit is contained in:
qiongyu1999 2026-05-30 14:03:55 +08:00
parent 7b00e1de87
commit 75c17f2422

View file

@ -283,9 +283,6 @@ export const ChatComposer = forwardRef<ChatComposerHandle, Props>(
const toolsMenuRef = useRef<HTMLDivElement | null>(null); const toolsMenuRef = useRef<HTMLDivElement | null>(null);
const toolsTriggerRef = useRef<HTMLButtonElement | null>(null); const toolsTriggerRef = useRef<HTMLButtonElement | null>(null);
const petEnabled = Boolean(onAdoptPet && onTogglePet); const petEnabled = Boolean(onAdoptPet && onTogglePet);
const [petMenuOpen, setPetMenuOpen] = useState(false);
const petWrapRef = useRef<HTMLDivElement | null>(null);
const [petMenuStyle, setPetMenuStyle] = useState<React.CSSProperties>({});
const linkedDirs = projectMetadata?.linkedDirs ?? []; const linkedDirs = projectMetadata?.linkedDirs ?? [];
// initialDraft is only honored on the first non-empty value the parent // initialDraft is only honored on the first non-empty value the parent
// hands us. After we seed once, the composer is fully under user control // hands us. After we seed once, the composer is fully under user control
@ -329,52 +326,6 @@ export const ChatComposer = forwardRef<ChatComposerHandle, Props>(
}; };
}, [toolsOpen]); }, [toolsOpen]);
useEffect(() => {
if (!petMenuOpen) return;
function onPointer(e: MouseEvent) {
const target = e.target as Node;
if (petWrapRef.current?.contains(target)) return;
setPetMenuOpen(false);
}
function onKey(e: KeyboardEvent) {
if (e.key === 'Escape') setPetMenuOpen(false);
}
document.addEventListener('mousedown', onPointer);
document.addEventListener('keydown', onKey);
return () => {
document.removeEventListener('mousedown', onPointer);
document.removeEventListener('keydown', onKey);
};
}, [petMenuOpen]);
// Viewport-aware pet menu positioning — flips the popover to stay
// within screen bounds instead of clipping at the edge.
useEffect(() => {
if (!petMenuOpen) return;
const wrap = petWrapRef.current;
if (!wrap) return;
const rect = wrap.getBoundingClientRect();
const menuW = 260;
const menuH = 200;
const gap = 6;
const viewW = window.innerWidth;
const viewH = window.innerHeight;
// Prefer opening upward (bottom of menu above the button).
// Flip downward when there isn't enough room above.
// When neither direction fits, clamp to viewport bounds.
let top: number;
if (rect.top >= menuH + gap) {
top = rect.top - menuH - gap;
} else if (rect.bottom + menuH + gap <= viewH) {
top = rect.bottom + gap;
} else {
top = Math.max(gap, viewH - menuH - gap);
}
// Right-align by default (menu right edge ≈ button right edge).
// Shift left when the menu would spill past the viewport left edge.
const left = Math.max(8, Math.min(viewW - menuW - 8, rect.right - menuW));
setPetMenuStyle({ position: 'fixed', top, left });
}, [petMenuOpen]);
// Lazy-fetch the user's external MCP servers list once on mount so the // Lazy-fetch the user's external MCP servers list once on mount so the
// `/mcp …` slash palette and the composer's MCP button popover have // `/mcp …` slash palette and the composer's MCP button popover have
@ -1756,70 +1707,6 @@ export const ChatComposer = forwardRef<ChatComposerHandle, Props>(
</div> </div>
) : null} ) : null}
</div> </div>
{petEnabled ? (
<div className="composer-pet-wrap" ref={petWrapRef}>
<button
type="button"
className={`composer-pet${petConfig?.adopted ? ' adopted' : ''}`}
onClick={() => {
if (petConfig?.adopted) {
if (!petConfig.enabled) setPetMenuOpen(true);
else setPetMenuOpen((v) => !v);
} else {
setPetMenuOpen((v) => !v);
}
}}
title={t('pet.composerTitle')}
aria-haspopup="menu"
aria-expanded={petMenuOpen}
aria-label={t('pet.composerTitle')}
>
<span className="composer-pet-glyph">
{petConfig?.adopted ? (petConfig?.custom?.glyph || '🐾') : '🐾'}
</span>
<span className="composer-pet-label">
{petConfig?.adopted ? (petConfig?.custom?.name || 'Buddy') : t('pet.composerMenuTitle')}
</span>
</button>
{petMenuOpen ? (
<div
className="composer-pet-menu"
style={petMenuStyle}
>
<div className="composer-pet-menu-head">
<strong>{t('pet.composerMenuTitle')}</strong>
<span>{t('pet.composerMenuHint')}</span>
</div>
<button
type="button"
className="composer-pet-menu-row toggle"
onClick={() => {
if (petConfig?.adopted) {
onTogglePet?.();
} else {
onOpenPetSettings?.();
}
setPetMenuOpen(false);
}}
>
<Icon name={petConfig?.enabled ? 'eye-off' : 'eye'} size={12} />
<span>{petConfig?.enabled ? t('pet.tuck') : t('pet.wake')}</span>
</button>
<button
type="button"
className="composer-pet-menu-row settings"
onClick={() => {
onOpenPetSettings?.();
setPetMenuOpen(false);
}}
>
<Icon name="settings" size={12} />
<span>{t('pet.composerOpenSettings')}</span>
</button>
</div>
) : null}
</div>
) : null}
<button <button
className="icon-btn" className="icon-btn"
data-testid="chat-attach" data-testid="chat-attach"