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:
@@ -853,15 +853,17 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
tabIndex={-1}
|
||||
aria-hidden="true"
|
||||
enterKeyHint="send"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
zIndex: 1,
|
||||
// Android IME needs visible, larger dimensions to properly attach
|
||||
width: 30,
|
||||
height: 30,
|
||||
// Must be slightly visible for Android IME - clip to hide visually
|
||||
opacity: 0.011,
|
||||
zIndex: 10,
|
||||
background: 'transparent',
|
||||
color: 'transparent',
|
||||
caretColor: 'transparent',
|
||||
@@ -872,15 +874,32 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
outline: 'none',
|
||||
// Prevent iOS zoom on focus - must be 16px+
|
||||
fontSize: 16,
|
||||
// Ensure pointer events work
|
||||
pointerEvents: 'auto',
|
||||
// Android needs these for IME to work properly
|
||||
WebkitUserSelect: 'text',
|
||||
userSelect: 'text',
|
||||
// Transform off-screen but keep focusable
|
||||
transform: 'translateX(-9999px)',
|
||||
transformOrigin: 'left top',
|
||||
}}
|
||||
onFocus={(event) => {
|
||||
// Move back on screen when focused (for Android IME)
|
||||
event.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
// Move off screen when blurred
|
||||
event.currentTarget.style.transform = 'translateX(-9999px)';
|
||||
}}
|
||||
onBeforeInput={(event) => {
|
||||
const nativeEvent = event.nativeEvent as unknown as InputEvent | undefined;
|
||||
const inputType = nativeEvent?.inputType ?? '';
|
||||
const data = typeof nativeEvent?.data === 'string' ? nativeEvent.data : '';
|
||||
|
||||
// Android Chrome sometimes never commits text into the DOM value
|
||||
// for invisible inputs. Use beforeinput as the primary path.
|
||||
if (inputType === 'insertText' && data) {
|
||||
// Handle insertText (iOS) and insertCompositionText (Android Gboard)
|
||||
if ((inputType === 'insertText' || inputType === 'insertCompositionText') && data) {
|
||||
event.preventDefault();
|
||||
inputHandlerRef.current(data);
|
||||
return;
|
||||
@@ -898,17 +917,25 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
}
|
||||
}}
|
||||
onInput={(event) => {
|
||||
// Fallback: capture any text that makes it through to the input value
|
||||
const raw = String(event.currentTarget.value || '');
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some mobile keyboards (iOS esp.) insert `\n` for Enter; PTY expects CR.
|
||||
// Some mobile keyboards insert `\n` for Enter; PTY expects CR.
|
||||
const value = raw.replace(/\r\n|\r|\n/g, '\r');
|
||||
inputHandlerRef.current(value);
|
||||
event.currentTarget.value = '';
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
// Handle Enter key explicitly for Android
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
inputHandlerRef.current('\r');
|
||||
event.currentTarget.value = '';
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Backspace') {
|
||||
// If there's nothing in the input buffer, emulate DEL.
|
||||
if (!event.currentTarget.value) {
|
||||
@@ -916,6 +943,14 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
}
|
||||
}
|
||||
}}
|
||||
onCompositionEnd={(event) => {
|
||||
// Android IME sends final text via composition events
|
||||
const data = event.data;
|
||||
if (data) {
|
||||
inputHandlerRef.current(data);
|
||||
event.currentTarget.value = '';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{viewportRef.current && !enableTouchScroll ? (
|
||||
|
||||
Reference in New Issue
Block a user