Feat/mobile quick actions (#282)

* feat(mobile): add quick actions button and improve mobile UX

Add mobile-optimized quick action buttons and improve autocomplete UX.

- Add quick command button (/) in mobile chat footer
- Add tabbed autocomplete interface for Commands/Agents/Files
- Fix agent selector conflict with model selector
- Improve TerminalViewport Android IME support
- Add reusable Slider component for settings
- Update StatusChip styling for mobile layout

Files changed:
- ChatInput.tsx: Mobile quick actions, command menu, autocomplete tabs
- AgentMentionAutocomplete.tsx: Tab support for mobile
- CommandAutocomplete.tsx: Tab support and touch handling
- FileMentionAutocomplete.tsx: Tab support for mobile
- ModelControls.tsx: Agent selector fix
- StatusChip.tsx: Mobile layout improvements
- TerminalViewport.tsx: Android IME improvements
- ui/slider.tsx: New reusable slider component

* feat(chat): remove unused BrowserVoiceButton component from ChatInput

* fix: Resolve linter errors
This commit is contained in:
gsxdsm
2026-02-04 11:14:10 +02:00
committed by GitHub
parent 66da4ad8f4
commit ea0deec14d
8 changed files with 656 additions and 196 deletions
@@ -16,10 +16,15 @@ export interface AgentMentionAutocompleteHandle {
handleKeyDown: (key: string) => void;
}
type AutocompleteTab = 'commands' | 'agents' | 'files';
interface AgentMentionAutocompleteProps {
searchQuery: string;
onAgentSelect: (agentName: string) => void;
onClose: () => void;
showTabs?: boolean;
activeTab?: AutocompleteTab;
onTabSelect?: (tab: AutocompleteTab) => void;
}
const isMentionable = (mode?: string | null): boolean => {
@@ -33,11 +38,15 @@ export const AgentMentionAutocomplete = React.forwardRef<AgentMentionAutocomplet
searchQuery,
onAgentSelect,
onClose,
showTabs,
activeTab = 'agents',
onTabSelect,
}, ref) => {
const containerRef = React.useRef<HTMLDivElement | null>(null);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const [agents, setAgents] = React.useState<AgentInfo[]>([]);
const itemRefs = React.useRef<(HTMLDivElement | null)[]>([]);
const ignoreTabClickRef = React.useRef(false);
const { getVisibleAgents } = useConfigStore();
const { agents: agentsWithMetadata, loadAgents } = useAgentsStore();
@@ -177,7 +186,47 @@ export const AgentMentionAutocomplete = React.forwardRef<AgentMentionAutocomplet
ref={containerRef}
className="absolute z-[100] min-w-0 w-full max-w-[360px] max-h-60 bg-background border-2 border-border/60 rounded-xl shadow-md bottom-full mb-2 left-0 flex flex-col"
>
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="px-0 pb-2" fillContainer={false}>
{showTabs ? (
<div className="px-2 pt-2 pb-1 border-b border-border/60">
<div className="flex items-center gap-1 rounded-lg bg-[var(--surface-elevated)] p-1">
{([
{ id: 'commands' as const, label: 'Commands' },
{ id: 'agents' as const, label: 'Agents' },
{ id: 'files' as const, label: 'Files' },
]).map((tab) => (
<button
key={tab.id}
type="button"
className={cn(
'flex-1 px-2.5 py-1 rounded-md typography-meta font-semibold transition-none',
activeTab === tab.id
? 'bg-interactive-selection text-interactive-selection-foreground shadow-sm'
: 'text-muted-foreground hover:bg-interactive-hover/50'
)}
onPointerDown={(event) => {
if (event.pointerType !== 'touch') {
return;
}
event.preventDefault();
event.stopPropagation();
ignoreTabClickRef.current = true;
onTabSelect?.(tab.id);
}}
onClick={() => {
if (ignoreTabClickRef.current) {
ignoreTabClickRef.current = false;
return;
}
onTabSelect?.(tab.id);
}}
>
{tab.label}
</button>
))}
</div>
</div>
) : null}
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="px-0 pb-2">
{agents.length ? (
<div>
{agents.map((agent, index) => renderAgent(agent, index))}