Redesign the mobile composer model and agent buttons as borderless, full-bleed labels that hug their content, truncate with an ellipsis when space is tight, and show the provider logo inline before the model name. Tighten the footer action buttons (sessions / attach / auto-accept) so they sit close together, with a small left inset on the group. In the mobile model selection overlay, make the thinking-variant control text-only with a chevron, vertically center the variant and favorite controls in each row, and place the provider logo inline with the model name.
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import { useConfigStore } from '@/stores/useConfigStore';
|
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
|
import { useSelectionStore } from '@/sync/selection-store';
|
|
import { getAgentDisplayName } from './mobileControlsUtils';
|
|
import { getAgentColor } from '@/lib/agentColors';
|
|
|
|
interface MobileAgentButtonProps {
|
|
onCycleAgent: () => void;
|
|
onOpenAgentPanel: () => void;
|
|
className?: string;
|
|
}
|
|
|
|
const LONG_PRESS_MS = 500;
|
|
|
|
// NOTE: Use pointer events instead of onClick to keep soft keyboard open on mobile
|
|
export const MobileAgentButton: React.FC<MobileAgentButtonProps> = ({ onCycleAgent, onOpenAgentPanel, className }) => {
|
|
const currentAgentName = useConfigStore((state) => state.currentAgentName);
|
|
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
|
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
|
const sessionAgentName = useSelectionStore((state) =>
|
|
currentSessionId ? state.getSessionAgentSelection(currentSessionId) : null
|
|
);
|
|
|
|
const agents = getVisibleAgents();
|
|
const uiAgentName = currentSessionId ? (sessionAgentName || currentAgentName) : currentAgentName;
|
|
const agentLabel = getAgentDisplayName(agents, uiAgentName);
|
|
const agentColor = getAgentColor(uiAgentName);
|
|
|
|
const longPressTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
const isLongPressRef = React.useRef(false);
|
|
|
|
const handlePointerDown = () => {
|
|
isLongPressRef.current = false;
|
|
longPressTimerRef.current = setTimeout(() => {
|
|
isLongPressRef.current = true;
|
|
onOpenAgentPanel();
|
|
}, LONG_PRESS_MS);
|
|
};
|
|
|
|
// Use onPointerUp (not onClick) to prevent focus transfer that closes mobile keyboard
|
|
const handlePointerUp = () => {
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
longPressTimerRef.current = null;
|
|
}
|
|
if (!isLongPressRef.current) {
|
|
onCycleAgent();
|
|
}
|
|
};
|
|
|
|
const handlePointerLeave = () => {
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
longPressTimerRef.current = null;
|
|
}
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
return () => {
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onPointerDown={handlePointerDown}
|
|
onPointerUp={handlePointerUp} // Don't use onClick - it closes mobile keyboard
|
|
onPointerLeave={handlePointerLeave}
|
|
onContextMenu={(e) => e.preventDefault()}
|
|
className={cn(
|
|
'inline-flex min-w-0 items-stretch select-none',
|
|
'rounded-lg',
|
|
'typography-micro font-medium',
|
|
'focus:outline-none hover:bg-[var(--interactive-hover)]',
|
|
'touch-none',
|
|
className
|
|
)}
|
|
style={{
|
|
height: '26px',
|
|
maxHeight: '26px',
|
|
minHeight: '26px',
|
|
color: `var(${agentColor.var})`,
|
|
}}
|
|
title={agentLabel}
|
|
>
|
|
<span className="flex h-full w-full min-w-0 items-center">
|
|
<span className="truncate">{agentLabel}</span>
|
|
</span>
|
|
</button>
|
|
);
|
|
};
|