perf(app): keep warm workspace switching responsive (#3447)
* perf(app): reduce warm workspace switch work Retained workspaces avoided mounts but pane activity still rerendered the full composer, message input, and agent-control presentation. Keep the activity contract intact while moving focus-sensitive behavior behind small context consumers, and add a Playwright benchmark that measures rapid retained workspace switching on one browser clock. * perf(app): accelerate retained workspace switching Avoid repeated retained-stream layout reconciliation and unrelated workspace structure recomputation. Use the lightweight route transition while the host is already focused, while preserving targeted root POP_TO for returns from app-wide routes where it prevents duplicate hidden decks. * perf(app): defer composer focus during workspace switches * perf(app): retain recently used workspaces longer Keep up to ten workspace screens warm so normal multi-workspace navigation does not repeatedly rebuild recently used timelines. Inactive screens expire after ten minutes, while the active screen remains protected and capacity eviction stays least-recently-used. * fix(app): reconcile retained timeline geometry * fix(app): refresh retained reading position
This commit is contained in:
@@ -64,7 +64,11 @@ import { readMeasuredWidth } from "@/hooks/use-container-width";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { toErrorMessage } from "@/utils/error-messages";
|
||||
import { showProviderNoticeToast } from "@/utils/provider-notice-toast";
|
||||
import { useAgentControlCommandCenterActions } from "@/command-center/agent-control-registration";
|
||||
import {
|
||||
useAgentControlCommandCenterActions,
|
||||
type AgentControlCommandCenterSource,
|
||||
} from "@/command-center/agent-control-registration";
|
||||
import { useComposerKeyboardScope } from "@/composer/keyboard-scope";
|
||||
import { isNative } from "@/constants/platform";
|
||||
import {
|
||||
resolveComposerControlDensity,
|
||||
@@ -154,11 +158,28 @@ export interface DraftAgentControlsProps {
|
||||
interface AgentControlsProps {
|
||||
agentId: string;
|
||||
serverId: string;
|
||||
isPaneFocused: boolean;
|
||||
onDropdownClose?: () => void;
|
||||
isCompactLayout?: boolean;
|
||||
}
|
||||
|
||||
function AgentControlCommandCenterRegistration({
|
||||
sourceId,
|
||||
enabled,
|
||||
controls,
|
||||
}: {
|
||||
sourceId: string;
|
||||
enabled: boolean;
|
||||
controls: AgentControlCommandCenterSource;
|
||||
}) {
|
||||
const { isActiveComposer } = useComposerKeyboardScope();
|
||||
useAgentControlCommandCenterActions({
|
||||
sourceId,
|
||||
enabled: enabled && isActiveComposer,
|
||||
controls,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
function findOptionLabel(
|
||||
options: AgentControlOption[] | undefined,
|
||||
selectedId: string | undefined,
|
||||
@@ -1477,7 +1498,6 @@ function ThinkingComboboxOption({
|
||||
export const AgentControls = memo(function AgentControls({
|
||||
agentId,
|
||||
serverId,
|
||||
isPaneFocused,
|
||||
onDropdownClose,
|
||||
isCompactLayout,
|
||||
}: AgentControlsProps) {
|
||||
@@ -1649,10 +1669,8 @@ export const AgentControls = memo(function AgentControls({
|
||||
[agentId, agentProvider, client, toast, updatePreferences],
|
||||
);
|
||||
|
||||
useAgentControlCommandCenterActions({
|
||||
sourceId: `agent:${serverId}:${agentId}`,
|
||||
enabled: isPaneFocused && Boolean(client),
|
||||
controls: {
|
||||
const commandCenterControls = useMemo<AgentControlCommandCenterSource>(
|
||||
() => ({
|
||||
serverId,
|
||||
ownerKey: agentId,
|
||||
provider: agentProvider,
|
||||
@@ -1673,8 +1691,31 @@ export const AgentControls = memo(function AgentControls({
|
||||
list: agent?.features,
|
||||
set: handleSetFeature,
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
[
|
||||
activeModelId,
|
||||
agent?.features,
|
||||
agentId,
|
||||
agentModelSelectorProviders,
|
||||
agentProvider,
|
||||
commandCenterModes,
|
||||
handleSelectCommandCenterModel,
|
||||
handleSelectThinkingOption,
|
||||
handleSetFeature,
|
||||
modeProviderDefinitions,
|
||||
modelSelection.selectedThinkingId,
|
||||
modelSelection.thinkingOptions,
|
||||
serverId,
|
||||
],
|
||||
);
|
||||
|
||||
const commandCenterRegistration = (
|
||||
<AgentControlCommandCenterRegistration
|
||||
sourceId={`agent:${serverId}:${agentId}`}
|
||||
enabled={Boolean(client)}
|
||||
controls={commandCenterControls}
|
||||
/>
|
||||
);
|
||||
|
||||
const handleModelSelectorOpen = useCallback(() => {
|
||||
refetchSnapshotIfStale(agentProvider);
|
||||
@@ -1692,30 +1733,33 @@ export const AgentControls = memo(function AgentControls({
|
||||
}
|
||||
|
||||
return (
|
||||
<ControlledAgentControls
|
||||
provider={agent.provider}
|
||||
modelSelectorProviders={agentModelSelectorProviders}
|
||||
modelOptions={modelOptions}
|
||||
selectedModelId={modelSelection.activeModelId ?? undefined}
|
||||
onSelectModel={handleSelectModel}
|
||||
agentProfiles={agentProfiles}
|
||||
onApplyAgentProfile={agentProfiles?.applyProfile}
|
||||
onEditAgentProfiles={handleEditAgentProfiles}
|
||||
thinkingOptions={thinkingOptions.length > 1 ? thinkingOptions : undefined}
|
||||
selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined}
|
||||
onSelectThinkingOption={handleSelectThinkingOption}
|
||||
features={agent.features}
|
||||
onSetFeature={handleSetFeature}
|
||||
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
|
||||
onModelSelectorOpen={handleModelSelectorOpen}
|
||||
onRetryModelProvider={handleRetryModelProvider}
|
||||
isRetryingModelProvider={snapshotIsRefreshing}
|
||||
onDropdownClose={onDropdownClose}
|
||||
disabled={!client}
|
||||
modeControl={modeControl}
|
||||
modelSelectorServerId={serverId}
|
||||
isCompactLayout={isCompactLayout}
|
||||
/>
|
||||
<>
|
||||
{commandCenterRegistration}
|
||||
<ControlledAgentControls
|
||||
provider={agent.provider}
|
||||
modelSelectorProviders={agentModelSelectorProviders}
|
||||
modelOptions={modelOptions}
|
||||
selectedModelId={modelSelection.activeModelId ?? undefined}
|
||||
onSelectModel={handleSelectModel}
|
||||
agentProfiles={agentProfiles}
|
||||
onApplyAgentProfile={agentProfiles?.applyProfile}
|
||||
onEditAgentProfiles={handleEditAgentProfiles}
|
||||
thinkingOptions={thinkingOptions.length > 1 ? thinkingOptions : undefined}
|
||||
selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined}
|
||||
onSelectThinkingOption={handleSelectThinkingOption}
|
||||
features={agent.features}
|
||||
onSetFeature={handleSetFeature}
|
||||
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
|
||||
onModelSelectorOpen={handleModelSelectorOpen}
|
||||
onRetryModelProvider={handleRetryModelProvider}
|
||||
isRetryingModelProvider={snapshotIsRefreshing}
|
||||
onDropdownClose={onDropdownClose}
|
||||
disabled={!client}
|
||||
modeControl={modeControl}
|
||||
modelSelectorServerId={serverId}
|
||||
isCompactLayout={isCompactLayout}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ import type { KeyboardActionDefinition } from "@/keyboard/keyboard-action-dispat
|
||||
import type { MessageInputKeyboardActionKind } from "@/keyboard/actions";
|
||||
import { submitAgentInput } from "@/composer/submit";
|
||||
import { createMessageSubmissionWriter } from "@/composer/submission/writer";
|
||||
import { ComposerKeyboardScopeProvider } from "@/composer/keyboard-scope";
|
||||
import { ComposerKeyboardScopeProvider, useComposerKeyboardScope } from "@/composer/keyboard-scope";
|
||||
import { useAppSettings } from "@/hooks/use-settings";
|
||||
import { isWeb, isNative } from "@/constants/platform";
|
||||
import type { ForgeSearchItem } from "@getpaseo/protocol/messages";
|
||||
@@ -296,12 +296,11 @@ interface RenderLeftContentArgs {
|
||||
serverId: string;
|
||||
focusInput: () => void;
|
||||
isCompactLayout: boolean;
|
||||
isPaneFocused: boolean;
|
||||
showAgentControls: boolean;
|
||||
}
|
||||
|
||||
function renderLeftContent(args: RenderLeftContentArgs): ReactElement | null {
|
||||
const { agentControls, agentId, serverId, focusInput, isCompactLayout, isPaneFocused } = args;
|
||||
const { agentControls, agentId, serverId, focusInput, isCompactLayout } = args;
|
||||
if (!args.showAgentControls) return null;
|
||||
if (resolveAgentControlsMode(agentControls) === "draft" && agentControls) {
|
||||
return <DraftAgentControls {...agentControls} isCompactLayout={isCompactLayout} />;
|
||||
@@ -310,7 +309,6 @@ function renderLeftContent(args: RenderLeftContentArgs): ReactElement | null {
|
||||
<AgentControls
|
||||
agentId={agentId}
|
||||
serverId={serverId}
|
||||
isPaneFocused={isPaneFocused}
|
||||
onDropdownClose={focusInput}
|
||||
isCompactLayout={isCompactLayout}
|
||||
/>
|
||||
@@ -561,6 +559,68 @@ function dispatchComposerKeyboardAction(args: DispatchComposerKeyboardActionArgs
|
||||
return true;
|
||||
}
|
||||
|
||||
function ComposerKeyboardRegistration({
|
||||
messageInputRef,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
handleCancelAgent,
|
||||
focusMessageInputForKeyboardAction,
|
||||
isMessageInputFocused,
|
||||
handlerId,
|
||||
}: Omit<DispatchComposerKeyboardActionArgs, "action" | "isPaneFocused"> & {
|
||||
isMessageInputFocused: boolean;
|
||||
handlerId: string;
|
||||
}) {
|
||||
const { isActiveComposer } = useComposerKeyboardScope();
|
||||
const handleKeyboardAction = useCallback(
|
||||
(action: KeyboardActionDefinition): boolean =>
|
||||
dispatchComposerKeyboardAction({
|
||||
action,
|
||||
isPaneFocused: isActiveComposer,
|
||||
messageInputRef,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
handleCancelAgent,
|
||||
focusMessageInputForKeyboardAction,
|
||||
}),
|
||||
[
|
||||
focusMessageInputForKeyboardAction,
|
||||
handleCancelAgent,
|
||||
isActiveComposer,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
messageInputRef,
|
||||
],
|
||||
);
|
||||
|
||||
useKeyboardActionHandler({
|
||||
handlerId,
|
||||
actions: [
|
||||
"agent.interrupt",
|
||||
"message-input.focus",
|
||||
"message-input.send",
|
||||
"message-input.dictation-toggle",
|
||||
"message-input.dictation-cancel",
|
||||
"message-input.dictation-confirm",
|
||||
"message-input.voice-toggle",
|
||||
"message-input.voice-mute-toggle",
|
||||
],
|
||||
enabled: isActiveComposer,
|
||||
priority: resolveKeyboardPriority(isMessageInputFocused),
|
||||
isActive: () => isActiveComposer,
|
||||
handle: handleKeyboardAction,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
function ComposerAutocomplete(props: React.ComponentProps<typeof AutocompletePopover>) {
|
||||
const { isActiveComposer } = useComposerKeyboardScope();
|
||||
return <AutocompletePopover {...props} visible={isActiveComposer && props.visible} />;
|
||||
}
|
||||
|
||||
function resolveMessageInputPassthroughAction(
|
||||
actionId: string,
|
||||
): MessageInputKeyboardActionKind | null {
|
||||
@@ -1052,12 +1112,23 @@ function ComposerVoiceModeButton({
|
||||
);
|
||||
}
|
||||
|
||||
export function Composer({ isPaneFocused, ...props }: ComposerProps) {
|
||||
return (
|
||||
<ComposerKeyboardScopeProvider isActiveComposer={isPaneFocused}>
|
||||
<ComposerContent {...props} />
|
||||
</ComposerKeyboardScopeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
type ComposerContentProps = Omit<ComposerProps, "isPaneFocused">;
|
||||
|
||||
const ComposerContent = memo(ComposerContentImpl);
|
||||
|
||||
// oxlint-disable-next-line complexity
|
||||
export function Composer({
|
||||
function ComposerContentImpl({
|
||||
agentId,
|
||||
serverId,
|
||||
workspaceId,
|
||||
isPaneFocused,
|
||||
onSubmitMessage,
|
||||
onClientSlashCommand,
|
||||
hasExternalContent = false,
|
||||
@@ -1096,7 +1167,7 @@ export function Composer({
|
||||
readOnly = false,
|
||||
submitLabel,
|
||||
placeholder,
|
||||
}: ComposerProps) {
|
||||
}: ComposerContentProps) {
|
||||
const mode = resolveComposerInputMode(inputMode);
|
||||
const { t } = useTranslation();
|
||||
const buttonIconSize = resolveComposerButtonIconSize();
|
||||
@@ -1682,46 +1753,6 @@ export function Composer({
|
||||
focusMessageInputWithPlatformStrategy(messageInputRef);
|
||||
}, []);
|
||||
|
||||
const handleKeyboardAction = useCallback(
|
||||
(action: KeyboardActionDefinition): boolean =>
|
||||
dispatchComposerKeyboardAction({
|
||||
action,
|
||||
isPaneFocused,
|
||||
messageInputRef,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
handleCancelAgent,
|
||||
focusMessageInputForKeyboardAction,
|
||||
}),
|
||||
[
|
||||
focusMessageInputForKeyboardAction,
|
||||
handleCancelAgent,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
isPaneFocused,
|
||||
],
|
||||
);
|
||||
|
||||
useKeyboardActionHandler({
|
||||
handlerId: keyboardHandlerIdRef.current,
|
||||
actions: [
|
||||
"agent.interrupt",
|
||||
"message-input.focus",
|
||||
"message-input.send",
|
||||
"message-input.dictation-toggle",
|
||||
"message-input.dictation-cancel",
|
||||
"message-input.dictation-confirm",
|
||||
"message-input.voice-toggle",
|
||||
"message-input.voice-mute-toggle",
|
||||
],
|
||||
enabled: isPaneFocused,
|
||||
priority: resolveKeyboardPriority(isMessageInputFocused),
|
||||
isActive: () => isPaneFocused,
|
||||
handle: handleKeyboardAction,
|
||||
});
|
||||
|
||||
const { style: keyboardAnimatedStyle } = useKeyboardShiftStyle({
|
||||
mode: "translate",
|
||||
enabled: !externalKeyboardShift,
|
||||
@@ -2032,18 +2063,9 @@ export function Composer({
|
||||
serverId,
|
||||
focusInput,
|
||||
isCompactLayout,
|
||||
isPaneFocused,
|
||||
showAgentControls: mode.showAgentControls,
|
||||
}),
|
||||
[
|
||||
agentControls,
|
||||
agentId,
|
||||
focusInput,
|
||||
isCompactLayout,
|
||||
isPaneFocused,
|
||||
mode.showAgentControls,
|
||||
serverId,
|
||||
],
|
||||
[agentControls, agentId, focusInput, isCompactLayout, mode.showAgentControls, serverId],
|
||||
);
|
||||
|
||||
const handleAttachButtonRef = useCallback((node: View | null) => {
|
||||
@@ -2169,10 +2191,20 @@ export function Composer({
|
||||
const githubEmptyText = githubSearchResultsQuery.isFetching
|
||||
? t("composer.github.searching")
|
||||
: t("composer.github.noResults");
|
||||
const autocompleteVisible = autocomplete.isVisible && isPaneFocused && mode.showAutocomplete;
|
||||
const autocompleteVisible = autocomplete.isVisible && mode.showAutocomplete;
|
||||
|
||||
return (
|
||||
<ComposerKeyboardScopeProvider isActiveComposer={isPaneFocused}>
|
||||
<>
|
||||
<ComposerKeyboardRegistration
|
||||
handlerId={keyboardHandlerIdRef.current}
|
||||
messageInputRef={messageInputRef}
|
||||
isAgentRunning={isAgentRunning}
|
||||
isCancellingAgent={isCancellingAgent}
|
||||
isConnected={isConnected}
|
||||
handleCancelAgent={handleCancelAgent}
|
||||
focusMessageInputForKeyboardAction={focusMessageInputForKeyboardAction}
|
||||
isMessageInputFocused={isMessageInputFocused}
|
||||
/>
|
||||
<Animated.View style={composerContainerStyle}>
|
||||
<AttachmentLightbox metadata={lightboxMetadata} onClose={handleLightboxClose} />
|
||||
{/* Input area */}
|
||||
@@ -2182,7 +2214,7 @@ export function Composer({
|
||||
{sendErrorNode}
|
||||
|
||||
<View ref={messageInputContainerRef} style={styles.messageInputContainer}>
|
||||
<AutocompletePopover
|
||||
<ComposerAutocomplete
|
||||
visible={autocompleteVisible}
|
||||
anchorRef={messageInputContainerRef}
|
||||
options={autocomplete.options}
|
||||
@@ -2220,7 +2252,6 @@ export function Composer({
|
||||
autoFocus={messageInputAutoFocus}
|
||||
autoFocusKey={`${serverId}:${agentId}:${autoFocusKey ?? ""}`}
|
||||
disabled={isSubmitLoading}
|
||||
isPaneFocused={isPaneFocused}
|
||||
leftContent={leftContent}
|
||||
beforeVoiceContent={beforeVoiceContent}
|
||||
rightContent={rightContent}
|
||||
@@ -2267,7 +2298,7 @@ export function Composer({
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</ComposerKeyboardScopeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import type { MessageInputKeyboardActionKind } from "@/keyboard/actions";
|
||||
import { isImeComposingKeyboardEvent } from "@/utils/keyboard-ime";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { useComposerKeyboardScope } from "@/composer/keyboard-scope";
|
||||
import { useComposerHeightMirror } from "./height-mirror";
|
||||
import { resolveComposerInputMode, type ComposerInputMode } from "@/composer/input-mode";
|
||||
import type { NativePastedFile } from "@/composer/native-pasted-image";
|
||||
@@ -116,8 +117,6 @@ export interface MessageInputProps {
|
||||
autoFocus?: boolean;
|
||||
autoFocusKey?: string;
|
||||
disabled?: boolean;
|
||||
/** True when this composer's pane is focused. Used to gate global hotkeys and stop dictation when hidden. */
|
||||
isPaneFocused?: boolean;
|
||||
/** Content to render on the left side of the composer toolbar (e.g., AgentControls) */
|
||||
leftContent?: React.ReactNode;
|
||||
/** Content to render on the right side before the voice button (e.g., context window meter) */
|
||||
@@ -496,11 +495,26 @@ function useAutoFocusOnWebEffect(
|
||||
const active = typeof document !== "undefined" ? document.activeElement : null;
|
||||
return Boolean(element) && active === element;
|
||||
},
|
||||
deferInitialAttempt: true,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [autoFocus, autoFocusKey]);
|
||||
}
|
||||
|
||||
function MessageInputAutoFocus({
|
||||
enabled,
|
||||
autoFocusKey,
|
||||
textInputRef,
|
||||
}: {
|
||||
enabled: boolean;
|
||||
autoFocusKey: string | undefined;
|
||||
textInputRef: React.MutableRefObject<ComposerTextInputHandle | null>;
|
||||
}) {
|
||||
const { isActiveComposer } = useComposerKeyboardScope();
|
||||
useAutoFocusOnWebEffect(textInputRef, enabled && isActiveComposer, autoFocusKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
function MessageInputOverlay({
|
||||
showDictationOverlay,
|
||||
showRealtimeOverlay,
|
||||
@@ -580,7 +594,8 @@ function FocusHint({
|
||||
focusInputKeys: ShortcutChord | null | undefined;
|
||||
label: string;
|
||||
}) {
|
||||
if (!visible || !focusInputKeys || !label.trim()) return null;
|
||||
const { isActiveComposer } = useComposerKeyboardScope();
|
||||
if (!isActiveComposer || !visible || !focusInputKeys || !label.trim()) return null;
|
||||
return (
|
||||
<Text style={styles.focusHintText} pointerEvents="none">
|
||||
{label}
|
||||
@@ -1030,7 +1045,6 @@ interface ResolvedMessageInputProps {
|
||||
autoFocus: boolean;
|
||||
autoFocusKey: string | undefined;
|
||||
disabled: boolean;
|
||||
isPaneFocused: boolean;
|
||||
leftContent: React.ReactNode;
|
||||
beforeVoiceContent: React.ReactNode;
|
||||
rightContent: React.ReactNode;
|
||||
@@ -1078,7 +1092,6 @@ function resolveMessageInputProps(props: MessageInputProps): ResolvedMessageInpu
|
||||
autoFocus: props.autoFocus ?? false,
|
||||
autoFocusKey: props.autoFocusKey,
|
||||
disabled: props.disabled ?? false,
|
||||
isPaneFocused: props.isPaneFocused ?? true,
|
||||
leftContent: props.leftContent,
|
||||
beforeVoiceContent: props.beforeVoiceContent,
|
||||
rightContent: props.rightContent,
|
||||
@@ -1134,7 +1147,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
autoFocus,
|
||||
autoFocusKey,
|
||||
disabled,
|
||||
isPaneFocused,
|
||||
leftContent,
|
||||
beforeVoiceContent,
|
||||
rightContent,
|
||||
@@ -1237,8 +1249,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
};
|
||||
}, [onFocusChange]);
|
||||
|
||||
useAutoFocusOnWebEffect(textInputRef, autoFocus, autoFocusKey);
|
||||
|
||||
const handleDictationTranscript = useCallback(
|
||||
(text: string, _meta: { requestId: string }) => {
|
||||
const autoSend = sendAfterTranscriptRef.current;
|
||||
@@ -1738,6 +1748,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
|
||||
return (
|
||||
<View ref={rootRef} style={styles.container} testID="message-input-root">
|
||||
<MessageInputAutoFocus
|
||||
enabled={autoFocus}
|
||||
autoFocusKey={autoFocusKey}
|
||||
textInputRef={textInputRef}
|
||||
/>
|
||||
{/* Regular input */}
|
||||
<View
|
||||
ref={inputWrapperRef}
|
||||
@@ -1759,13 +1774,13 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
onBlur={handleInputBlur}
|
||||
editable={!isDictating && !isRealtimeVoiceForCurrentAgent && !disabled}
|
||||
scrollEnabled={isWeb ? inputHeight >= maxInputHeight : true}
|
||||
autoFocus={isWeb && autoFocus}
|
||||
autoFocus={false}
|
||||
onContentSizeChange={handleContentSizeChange}
|
||||
onKeyPress={shouldHandleWebKeyPress ? handleDesktopKeyPress : undefined}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onPasteImages={onPasteImages}
|
||||
onPasteError={handlePasteError}
|
||||
focusHintVisible={isWeb && isPaneFocused && !isInputFocused && !value}
|
||||
focusHintVisible={isWeb && !isInputFocused && !value}
|
||||
focusInputKeys={focusInputKeys}
|
||||
focusHintLabel={t("composer.input.focusHint", {
|
||||
shortcut: focusInputKeys ? formatShortcut(focusInputKeys[0], getShortcutOs()) : "",
|
||||
|
||||
Reference in New Issue
Block a user