refactor: unify workspace tab management and add runtime metrics
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { View, Pressable, Text, ActivityIndicator, Platform } from 'react-native'
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { StyleSheet, useUnistyles } from 'react-native-unistyles'
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
|
||||
import { ArrowUp, Square, Pencil, AudioLines } from 'lucide-react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
@@ -119,6 +119,10 @@ export function AgentInputArea({
|
||||
const setAgentStreamHead = useSessionStore((state) => state.setAgentStreamHead)
|
||||
|
||||
const [internalInput, setInternalInput] = useState('')
|
||||
const isDesktopWebBreakpoint =
|
||||
Platform.OS === 'web' &&
|
||||
UnistylesRuntime.breakpoint !== 'xs' &&
|
||||
UnistylesRuntime.breakpoint !== 'sm'
|
||||
const userInput = value ?? internalInput
|
||||
const setUserInput = onChangeText ?? setInternalInput
|
||||
const [cursorIndex, setCursorIndex] = useState(0)
|
||||
@@ -775,7 +779,8 @@ export function AgentInputArea({
|
||||
client={client}
|
||||
isReadyForDictation={isDictationReady}
|
||||
placeholder="Message agent..."
|
||||
autoFocus={autoFocus}
|
||||
autoFocus={autoFocus && isDesktopWebBreakpoint}
|
||||
autoFocusKey={`${serverId}:${agentId}`}
|
||||
disabled={isSubmitLoading}
|
||||
isScreenFocused={isScreenFocused}
|
||||
leftContent={leftContent}
|
||||
|
||||
@@ -440,14 +440,14 @@ const styles = StyleSheet.create((theme) => ({
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: theme.colors.surface0,
|
||||
backgroundColor: theme.colors.surfaceSidebar,
|
||||
overflow: "hidden",
|
||||
},
|
||||
desktopSidebar: {
|
||||
position: "relative",
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface0,
|
||||
backgroundColor: theme.colors.surfaceSidebar,
|
||||
},
|
||||
resizeHandle: {
|
||||
position: "absolute",
|
||||
|
||||
@@ -948,7 +948,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd }: GitDiffPaneProps) {
|
||||
if (isStatusLoading) {
|
||||
bodyContent = (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
<Text style={styles.loadingText}>Checking repository...</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -967,8 +967,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd }: GitDiffPaneProps) {
|
||||
} else if (isDiffLoading) {
|
||||
bodyContent = (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
<Text style={styles.loadingText}>Loading changes...</Text>
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
</View>
|
||||
);
|
||||
} else if (diffErrorMessage) {
|
||||
@@ -1234,9 +1233,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd }: GitDiffPaneProps) {
|
||||
<Text style={styles.branchLabel} testID="changes-branch" numberOfLines={1}>
|
||||
{branchLabel}
|
||||
</Text>
|
||||
{isStatusFetching && (
|
||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||
)}
|
||||
</View>
|
||||
{isGit ? (
|
||||
<View style={styles.headerRight}>
|
||||
|
||||
@@ -560,7 +560,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: theme.colors.surface0,
|
||||
backgroundColor: theme.colors.surfaceSidebar,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
sidebarContent: {
|
||||
@@ -571,7 +571,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
desktopSidebar: {
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface0,
|
||||
backgroundColor: theme.colors.surfaceSidebar,
|
||||
},
|
||||
sidebarHeader: {
|
||||
height: {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from '@/utils/image-attachments-from-files'
|
||||
import type { AttachmentMetadata } from '@/attachments/types'
|
||||
import { useAttachmentPreviewUrl } from '@/attachments/use-attachment-preview-url'
|
||||
import { focusWithRetries } from '@/utils/web-focus'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Shortcut } from '@/components/ui/shortcut'
|
||||
import type { MessageInputKeyboardActionKind } from '@/keyboard/actions'
|
||||
@@ -62,6 +63,7 @@ export interface MessageInputProps {
|
||||
isReadyForDictation?: boolean
|
||||
placeholder?: string
|
||||
autoFocus?: boolean
|
||||
autoFocusKey?: string
|
||||
disabled?: boolean
|
||||
/** True when the containing screen is focused (React Navigation). Used to disable global hotkeys and cancel dictation when unfocused. */
|
||||
isScreenFocused?: boolean
|
||||
@@ -137,6 +139,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
isReadyForDictation,
|
||||
placeholder = 'Message...',
|
||||
autoFocus = false,
|
||||
autoFocusKey,
|
||||
disabled = false,
|
||||
isScreenFocused = true,
|
||||
leftContent,
|
||||
@@ -231,15 +234,20 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
valueRef.current = value
|
||||
}, [value])
|
||||
|
||||
// Autofocus on web when autoFocus prop is true
|
||||
// Autofocus on web when autoFocus is true, and re-run when focus key changes.
|
||||
useEffect(() => {
|
||||
if (!IS_WEB || !autoFocus) return
|
||||
// Use requestAnimationFrame to ensure DOM is ready
|
||||
const rafId = requestAnimationFrame(() => {
|
||||
textInputRef.current?.focus()
|
||||
return focusWithRetries({
|
||||
focus: () => textInputRef.current?.focus(),
|
||||
isFocused: () => {
|
||||
const current = textInputRef.current as (TextInput & { getNativeRef?: () => unknown }) | null
|
||||
const native = typeof current?.getNativeRef === 'function' ? current.getNativeRef() : current
|
||||
const element = native instanceof HTMLElement ? native : null
|
||||
const active = typeof document !== 'undefined' ? document.activeElement : null
|
||||
return Boolean(element) && active === element
|
||||
},
|
||||
})
|
||||
return () => cancelAnimationFrame(rafId)
|
||||
}, [autoFocus])
|
||||
}, [autoFocus, autoFocusKey])
|
||||
|
||||
const handleDictationTranscript = useCallback(
|
||||
(text: string, _meta: { requestId: string }) => {
|
||||
|
||||
@@ -1181,7 +1181,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
marginBottom: theme.spacing[1],
|
||||
},
|
||||
workspaceListContainer: {
|
||||
marginLeft: theme.spacing[4],
|
||||
marginLeft: theme.spacing[2],
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
|
||||
Reference in New Issue
Block a user