Let composer controls adapt to narrow panes (#1270)
This commit is contained in:
@@ -97,6 +97,7 @@ interface ControlledAgentControlsProps {
|
|||||||
/** Extra elements rendered inline with the agent controls (desktop only). */
|
/** Extra elements rendered inline with the agent controls (desktop only). */
|
||||||
desktopExtras?: ReactNode;
|
desktopExtras?: ReactNode;
|
||||||
modelSelectorServerId?: string | null;
|
modelSelectorServerId?: string | null;
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DraftAgentControlsProps {
|
export interface DraftAgentControlsProps {
|
||||||
@@ -124,12 +125,14 @@ export interface DraftAgentControlsProps {
|
|||||||
isRetryingModelProvider?: boolean;
|
isRetryingModelProvider?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
modelSelectorServerId?: string | null;
|
modelSelectorServerId?: string | null;
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AgentControlsProps {
|
interface AgentControlsProps {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
onDropdownClose?: () => void;
|
onDropdownClose?: () => void;
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOptionLabel(
|
function findOptionLabel(
|
||||||
@@ -409,9 +412,11 @@ function ControlledAgentControls({
|
|||||||
isRetryingModelProvider = false,
|
isRetryingModelProvider = false,
|
||||||
desktopExtras,
|
desktopExtras,
|
||||||
modelSelectorServerId = null,
|
modelSelectorServerId = null,
|
||||||
|
isCompactLayout,
|
||||||
}: ControlledAgentControlsProps) {
|
}: ControlledAgentControlsProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const isCompact = isCompactLayout ?? isCompactFormFactor;
|
||||||
const [activeSheet, setActiveSheet] = useState<ActiveSheet>(null);
|
const [activeSheet, setActiveSheet] = useState<ActiveSheet>(null);
|
||||||
const [openSelector, setOpenSelector] = useState<AgentControlSelector | null>(null);
|
const [openSelector, setOpenSelector] = useState<AgentControlSelector | null>(null);
|
||||||
|
|
||||||
@@ -1345,6 +1350,7 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
agentId,
|
agentId,
|
||||||
serverId,
|
serverId,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
|
isCompactLayout,
|
||||||
}: AgentControlsProps) {
|
}: AgentControlsProps) {
|
||||||
const { preferences, updatePreferences } = useFormPreferences();
|
const { preferences, updatePreferences } = useFormPreferences();
|
||||||
const agent = useSessionStore(
|
const agent = useSessionStore(
|
||||||
@@ -1518,8 +1524,15 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const modeChip = useMemo(
|
const modeChip = useMemo(
|
||||||
() => <AgentModeControl serverId={serverId} agentId={agentId} placement="toolbar" />,
|
() => (
|
||||||
[serverId, agentId],
|
<AgentModeControl
|
||||||
|
serverId={serverId}
|
||||||
|
agentId={agentId}
|
||||||
|
placement="toolbar"
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[serverId, agentId, isCompactLayout],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
@@ -1548,6 +1561,7 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
disabled={!client}
|
disabled={!client}
|
||||||
desktopExtras={modeChip}
|
desktopExtras={modeChip}
|
||||||
modelSelectorServerId={serverId}
|
modelSelectorServerId={serverId}
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1577,9 +1591,11 @@ export function DraftAgentControls({
|
|||||||
isRetryingModelProvider = false,
|
isRetryingModelProvider = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
modelSelectorServerId = null,
|
modelSelectorServerId = null,
|
||||||
|
isCompactLayout,
|
||||||
}: DraftAgentControlsProps) {
|
}: DraftAgentControlsProps) {
|
||||||
const { preferences, updatePreferences } = useFormPreferences();
|
const { preferences, updatePreferences } = useFormPreferences();
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const isCompact = isCompactLayout ?? isCompactFormFactor;
|
||||||
|
|
||||||
const mappedThinkingOptions = useMemo<AgentControlOption[]>(() => {
|
const mappedThinkingOptions = useMemo<AgentControlOption[]>(() => {
|
||||||
return toThinkingControlOptions(thinkingOptions);
|
return toThinkingControlOptions(thinkingOptions);
|
||||||
@@ -1625,9 +1641,18 @@ export function DraftAgentControls({
|
|||||||
selectedMode={selectedMode}
|
selectedMode={selectedMode}
|
||||||
onSelectMode={onSelectMode}
|
onSelectMode={onSelectMode}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[selectedProvider, providerDefinitions, modeOptions, selectedMode, onSelectMode, disabled],
|
[
|
||||||
|
selectedProvider,
|
||||||
|
providerDefinitions,
|
||||||
|
modeOptions,
|
||||||
|
selectedMode,
|
||||||
|
onSelectMode,
|
||||||
|
disabled,
|
||||||
|
isCompactLayout,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isCompact) {
|
if (!isCompact) {
|
||||||
@@ -1661,6 +1686,7 @@ export function DraftAgentControls({
|
|||||||
isRetryingModelProvider={isRetryingModelProvider}
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
desktopExtras={draftModeChip}
|
desktopExtras={draftModeChip}
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
@@ -1688,6 +1714,7 @@ export function DraftAgentControls({
|
|||||||
isRetryingModelProvider={isRetryingModelProvider}
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
modelSelectorServerId={modelSelectorServerId}
|
modelSelectorServerId={modelSelectorServerId}
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,14 +235,17 @@ interface AgentModeControlProps {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
placement: AgentModeControlPlacement;
|
placement: AgentModeControlPlacement;
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgentModeControl = memo(function AgentModeControl({
|
export const AgentModeControl = memo(function AgentModeControl({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
agentId,
|
||||||
placement,
|
placement,
|
||||||
|
isCompactLayout,
|
||||||
}: AgentModeControlProps) {
|
}: AgentModeControlProps) {
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const isCompact = isCompactLayout ?? isCompactFormFactor;
|
||||||
const slice = useSessionStore(
|
const slice = useSessionStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
const agent = state.sessions[serverId]?.agents?.get(agentId);
|
const agent = state.sessions[serverId]?.agents?.get(agentId);
|
||||||
@@ -303,6 +306,7 @@ export interface DraftAgentModeControlProps {
|
|||||||
onSelectMode: (modeId: string) => void;
|
onSelectMode: (modeId: string) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
placement: AgentModeControlPlacement;
|
placement: AgentModeControlPlacement;
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DraftAgentModeControl({
|
export function DraftAgentModeControl({
|
||||||
@@ -313,8 +317,10 @@ export function DraftAgentModeControl({
|
|||||||
onSelectMode,
|
onSelectMode,
|
||||||
disabled,
|
disabled,
|
||||||
placement,
|
placement,
|
||||||
|
isCompactLayout,
|
||||||
}: DraftAgentModeControlProps) {
|
}: DraftAgentModeControlProps) {
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const isCompact = isCompactLayout ?? isCompactFormFactor;
|
||||||
if (!selectedProvider || modeOptions.length === 0) return null;
|
if (!selectedProvider || modeOptions.length === 0) return null;
|
||||||
if (!shouldRenderForPlacement(placement, isCompact)) return null;
|
if (!shouldRenderForPlacement(placement, isCompact)) return null;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ReanimatedAnimated from "react-native-reanimated";
|
|||||||
import { StyleSheet } from "react-native-unistyles";
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||||
|
import { useContainerWidthBelow } from "@/hooks/use-container-width";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
import { Composer } from "@/composer";
|
import { Composer } from "@/composer";
|
||||||
import { DraftAgentModeControl } from "@/composer/agent-controls/mode-control";
|
import { DraftAgentModeControl } from "@/composer/agent-controls/mode-control";
|
||||||
@@ -36,7 +37,11 @@ import {
|
|||||||
useWorkspaceAttachmentScopeKey,
|
useWorkspaceAttachmentScopeKey,
|
||||||
} from "@/attachments/workspace-attachments-store";
|
} from "@/attachments/workspace-attachments-store";
|
||||||
import type { UserMessageImageAttachment } from "@/types/stream";
|
import type { UserMessageImageAttachment } from "@/types/stream";
|
||||||
import { MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
|
import {
|
||||||
|
COMPACT_FORM_FACTOR_WIDTH,
|
||||||
|
MAX_CONTENT_WIDTH,
|
||||||
|
useIsCompactFormFactor,
|
||||||
|
} from "@/constants/layout";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
import type { WorkspaceDraftTabSetup } from "@/stores/workspace-tabs-store";
|
import type { WorkspaceDraftTabSetup } from "@/stores/workspace-tabs-store";
|
||||||
|
|
||||||
@@ -380,7 +385,11 @@ export function WorkspaceDraftAgentTab({
|
|||||||
};
|
};
|
||||||
}, [pendingAutoSubmit, pendingCreateAttempt]);
|
}, [pendingAutoSubmit, pendingCreateAttempt]);
|
||||||
const allowsEmptyAutoSubmit = pendingAutoSubmit?.allowEmptyText === true;
|
const allowsEmptyAutoSubmit = pendingAutoSubmit?.allowEmptyText === true;
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const { onLayout: onInputAreaLayout, isBelow: isCompactComposerLayout } = useContainerWidthBelow(
|
||||||
|
COMPACT_FORM_FACTOR_WIDTH,
|
||||||
|
{ initialIsBelow: isCompactFormFactor },
|
||||||
|
);
|
||||||
const workspaceAttachmentScopeKey = useWorkspaceAttachmentScopeKey({
|
const workspaceAttachmentScopeKey = useWorkspaceAttachmentScopeKey({
|
||||||
serverId,
|
serverId,
|
||||||
cwd: composerState.workingDir,
|
cwd: composerState.workingDir,
|
||||||
@@ -401,14 +410,14 @@ export function WorkspaceDraftAgentTab({
|
|||||||
};
|
};
|
||||||
openFileExplorerForCheckout({
|
openFileExplorerForCheckout({
|
||||||
checkout,
|
checkout,
|
||||||
isCompact,
|
isCompact: isCompactFormFactor,
|
||||||
});
|
});
|
||||||
setExplorerTabForCheckout({
|
setExplorerTabForCheckout({
|
||||||
...checkout,
|
...checkout,
|
||||||
tab: "changes",
|
tab: "changes",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isCompact, openFileExplorerForCheckout, serverId, setExplorerTabForCheckout],
|
[isCompactFormFactor, openFileExplorerForCheckout, serverId, setExplorerTabForCheckout],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -625,10 +634,14 @@ export function WorkspaceDraftAgentTab({
|
|||||||
);
|
);
|
||||||
const composerFooter = useMemo(
|
const composerFooter = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isCompact ? (
|
isCompactComposerLayout ? (
|
||||||
<DraftAgentModeControl placement="footer" {...composerAgentControls} />
|
<DraftAgentModeControl
|
||||||
|
placement="footer"
|
||||||
|
{...composerAgentControls}
|
||||||
|
isCompactLayout={isCompactComposerLayout}
|
||||||
|
/>
|
||||||
) : undefined,
|
) : undefined,
|
||||||
[isCompact, composerAgentControls],
|
[isCompactComposerLayout, composerAgentControls],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -662,7 +675,7 @@ export function WorkspaceDraftAgentTab({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ReanimatedAnimated.View style={inputAreaWrapperStyle}>
|
<ReanimatedAnimated.View style={inputAreaWrapperStyle} onLayout={onInputAreaLayout}>
|
||||||
{importPillPress ? (
|
{importPillPress ? (
|
||||||
<View style={styles.importPillRow}>
|
<View style={styles.importPillRow}>
|
||||||
<View style={styles.importPillContent}>
|
<View style={styles.importPillContent}>
|
||||||
@@ -692,6 +705,7 @@ export function WorkspaceDraftAgentTab({
|
|||||||
commandDraftConfig={composerState.commandDraftConfig}
|
commandDraftConfig={composerState.commandDraftConfig}
|
||||||
agentControls={composerAgentControls}
|
agentControls={composerAgentControls}
|
||||||
footer={composerFooter}
|
footer={composerFooter}
|
||||||
|
isCompactLayout={isCompactComposerLayout}
|
||||||
/>
|
/>
|
||||||
</ReanimatedAnimated.View>
|
</ReanimatedAnimated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ function resolveIsDesktopWebBreakpoint(isMobile: boolean): boolean {
|
|||||||
return isWeb && !isMobile;
|
return isWeb && !isMobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveCompactLayout(override: boolean | undefined, formFactor: boolean): boolean {
|
||||||
|
return override ?? formFactor;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveMessagePlaceholder(isDesktopWebBreakpoint: boolean): string {
|
function resolveMessagePlaceholder(isDesktopWebBreakpoint: boolean): string {
|
||||||
return isDesktopWebBreakpoint ? DESKTOP_MESSAGE_PLACEHOLDER : MOBILE_MESSAGE_PLACEHOLDER;
|
return isDesktopWebBreakpoint ? DESKTOP_MESSAGE_PLACEHOLDER : MOBILE_MESSAGE_PLACEHOLDER;
|
||||||
}
|
}
|
||||||
@@ -223,14 +227,22 @@ interface RenderLeftContentArgs {
|
|||||||
agentId: string;
|
agentId: string;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
focusInput: () => void;
|
focusInput: () => void;
|
||||||
|
isCompactLayout: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLeftContent(args: RenderLeftContentArgs): ReactElement {
|
function renderLeftContent(args: RenderLeftContentArgs): ReactElement {
|
||||||
const { agentControls, agentId, serverId, focusInput } = args;
|
const { agentControls, agentId, serverId, focusInput, isCompactLayout } = args;
|
||||||
if (resolveAgentControlsMode(agentControls) === "draft" && agentControls) {
|
if (resolveAgentControlsMode(agentControls) === "draft" && agentControls) {
|
||||||
return <DraftAgentControls {...agentControls} />;
|
return <DraftAgentControls {...agentControls} isCompactLayout={isCompactLayout} />;
|
||||||
}
|
}
|
||||||
return <AgentControls agentId={agentId} serverId={serverId} onDropdownClose={focusInput} />;
|
return (
|
||||||
|
<AgentControls
|
||||||
|
agentId={agentId}
|
||||||
|
serverId={serverId}
|
||||||
|
onDropdownClose={focusInput}
|
||||||
|
isCompactLayout={isCompactLayout}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RenderAttachmentTrayArgs {
|
interface RenderAttachmentTrayArgs {
|
||||||
@@ -674,6 +686,8 @@ interface ComposerProps {
|
|||||||
footer?: ReactNode;
|
footer?: ReactNode;
|
||||||
/** When true, a parent wrapper owns the keyboard shift, so the composer skips its own. */
|
/** When true, a parent wrapper owns the keyboard shift, so the composer skips its own. */
|
||||||
externalKeyboardShift?: boolean;
|
externalKeyboardShift?: boolean;
|
||||||
|
/** Optional panel/container layout breakpoint. Defaults to the screen breakpoint. */
|
||||||
|
isCompactLayout?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_ARRAY: readonly QueuedMessage[] = [];
|
const EMPTY_ARRAY: readonly QueuedMessage[] = [];
|
||||||
@@ -869,6 +883,7 @@ export function Composer({
|
|||||||
inputWrapperStyle,
|
inputWrapperStyle,
|
||||||
footer,
|
footer,
|
||||||
externalKeyboardShift,
|
externalKeyboardShift,
|
||||||
|
isCompactLayout: isCompactLayoutOverride,
|
||||||
}: ComposerProps) {
|
}: ComposerProps) {
|
||||||
const buttonIconSize = resolveComposerButtonIconSize();
|
const buttonIconSize = resolveComposerButtonIconSize();
|
||||||
const client = useHostRuntimeClient(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
@@ -899,9 +914,11 @@ export function Composer({
|
|||||||
const setAgentStreamTail = useSessionStore((state) => state.setAgentStreamTail);
|
const setAgentStreamTail = useSessionStore((state) => state.setAgentStreamTail);
|
||||||
const setAgentStreamHead = useSessionStore((state) => state.setAgentStreamHead);
|
const setAgentStreamHead = useSessionStore((state) => state.setAgentStreamHead);
|
||||||
|
|
||||||
const isMobile = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
const isDesktopWebBreakpoint = resolveIsDesktopWebBreakpoint(isMobile);
|
const isCompactLayout = resolveCompactLayout(isCompactLayoutOverride, isCompactFormFactor);
|
||||||
const messagePlaceholder = resolveMessagePlaceholder(isDesktopWebBreakpoint);
|
const isDesktopWebBreakpoint = resolveIsDesktopWebBreakpoint(isCompactFormFactor);
|
||||||
|
const isDesktopLayout = resolveIsDesktopWebBreakpoint(isCompactLayout);
|
||||||
|
const messagePlaceholder = resolveMessagePlaceholder(isDesktopLayout);
|
||||||
const userInput = value;
|
const userInput = value;
|
||||||
const setUserInput = onChangeText;
|
const setUserInput = onChangeText;
|
||||||
const {
|
const {
|
||||||
@@ -1437,7 +1454,7 @@ export function Composer({
|
|||||||
isAgentRunning={isAgentRunning}
|
isAgentRunning={isAgentRunning}
|
||||||
hasSendableContent={hasSendableContent}
|
hasSendableContent={hasSendableContent}
|
||||||
isProcessing={isProcessing}
|
isProcessing={isProcessing}
|
||||||
isCompact={isMobile}
|
isCompact={isCompactLayout}
|
||||||
buttonIconSize={buttonIconSize}
|
buttonIconSize={buttonIconSize}
|
||||||
handleToggleRealtimeVoice={handleToggleRealtimeVoice}
|
handleToggleRealtimeVoice={handleToggleRealtimeVoice}
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
@@ -1455,7 +1472,7 @@ export function Composer({
|
|||||||
hasSendableContent,
|
hasSendableContent,
|
||||||
isAgentRunning,
|
isAgentRunning,
|
||||||
isConnected,
|
isConnected,
|
||||||
isMobile,
|
isCompactLayout,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
isVoiceModeForAgent,
|
isVoiceModeForAgent,
|
||||||
isVoiceSwitching,
|
isVoiceSwitching,
|
||||||
@@ -1475,13 +1492,13 @@ export function Composer({
|
|||||||
contextWindowMaxTokens,
|
contextWindowMaxTokens,
|
||||||
contextWindowUsedTokens,
|
contextWindowUsedTokens,
|
||||||
agentState.totalCostUsd,
|
agentState.totalCostUsd,
|
||||||
isMobile,
|
isCompactLayout,
|
||||||
),
|
),
|
||||||
[contextWindowMaxTokens, contextWindowUsedTokens, agentState.totalCostUsd, isMobile],
|
[contextWindowMaxTokens, contextWindowUsedTokens, agentState.totalCostUsd, isCompactLayout],
|
||||||
);
|
);
|
||||||
const { beforeVoiceContent, footerInlineContent } = useMemo(
|
const { beforeVoiceContent, footerInlineContent } = useMemo(
|
||||||
() => resolveContextWindowPlacement(contextWindowMeter, isMobile),
|
() => resolveContextWindowPlacement(contextWindowMeter, isCompactLayout),
|
||||||
[contextWindowMeter, isMobile],
|
[contextWindowMeter, isCompactLayout],
|
||||||
);
|
);
|
||||||
|
|
||||||
const githubSearchQueryTrimmed = githubSearchQuery.trim();
|
const githubSearchQueryTrimmed = githubSearchQuery.trim();
|
||||||
@@ -1548,8 +1565,8 @@ export function Composer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const leftContent = useMemo(
|
const leftContent = useMemo(
|
||||||
() => renderLeftContent({ agentControls, agentId, serverId, focusInput }),
|
() => renderLeftContent({ agentControls, agentId, serverId, focusInput, isCompactLayout }),
|
||||||
[agentId, focusInput, serverId, agentControls],
|
[agentId, focusInput, serverId, agentControls, isCompactLayout],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAttachButtonRef = useCallback((node: View | null) => {
|
const handleAttachButtonRef = useCallback((node: View | null) => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const HEADER_TOP_PADDING_MOBILE = 8;
|
|||||||
|
|
||||||
// Max width for chat content (stream view, input area, new agent form)
|
// Max width for chat content (stream view, input area, new agent form)
|
||||||
export const MAX_CONTENT_WIDTH = 820;
|
export const MAX_CONTENT_WIDTH = 820;
|
||||||
|
export const COMPACT_FORM_FACTOR_WIDTH = 768;
|
||||||
|
|
||||||
// Desktop app constants for macOS traffic light buttons
|
// Desktop app constants for macOS traffic light buttons
|
||||||
// These buttons (close/minimize/maximize) overlay the top-left corner
|
// These buttons (close/minimize/maximize) overlay the top-left corner
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ export function useContainerWidth(): {
|
|||||||
/**
|
/**
|
||||||
* Tracks only whether a container is narrower than a threshold.
|
* Tracks only whether a container is narrower than a threshold.
|
||||||
*/
|
*/
|
||||||
export function useContainerWidthBelow(threshold: number): {
|
export function useContainerWidthBelow(
|
||||||
|
threshold: number,
|
||||||
|
options?: { initialIsBelow?: boolean },
|
||||||
|
): {
|
||||||
onLayout: (e: LayoutChangeEvent) => void;
|
onLayout: (e: LayoutChangeEvent) => void;
|
||||||
isBelow: boolean;
|
isBelow: boolean;
|
||||||
} {
|
} {
|
||||||
const [isBelow, setIsBelow] = useState(true);
|
const [isBelow, setIsBelow] = useState(options?.initialIsBelow ?? true);
|
||||||
return {
|
return {
|
||||||
onLayout: useCallback(
|
onLayout: useCallback(
|
||||||
(e: LayoutChangeEvent) => {
|
(e: LayoutChangeEvent) => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
useWorkspaceAttachments,
|
useWorkspaceAttachments,
|
||||||
useWorkspaceAttachmentScopeKey,
|
useWorkspaceAttachmentScopeKey,
|
||||||
} from "@/attachments/workspace-attachments-store";
|
} from "@/attachments/workspace-attachments-store";
|
||||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
import { COMPACT_FORM_FACTOR_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
|
||||||
import { isNative, isWeb } from "@/constants/platform";
|
import { isNative, isWeb } from "@/constants/platform";
|
||||||
import { useAgentAttentionClear } from "@/hooks/use-agent-attention-clear";
|
import { useAgentAttentionClear } from "@/hooks/use-agent-attention-clear";
|
||||||
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
} from "@/hooks/use-agent-screen-state-machine";
|
} from "@/hooks/use-agent-screen-state-machine";
|
||||||
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||||
|
import { useContainerWidthBelow } from "@/hooks/use-container-width";
|
||||||
import { usePaneContext, usePaneFocus } from "@/panels/pane-context";
|
import { usePaneContext, usePaneFocus } from "@/panels/pane-context";
|
||||||
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
||||||
import { RenderProfile } from "@/utils/render-profiler";
|
import { RenderProfile } from "@/utils/render-profiler";
|
||||||
@@ -1317,7 +1318,11 @@ function ActiveAgentComposer({
|
|||||||
onMessageSent: () => void;
|
onMessageSent: () => void;
|
||||||
}) {
|
}) {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const isCompact = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
|
const { onLayout: onInputAreaLayout, isBelow: isCompactComposerLayout } = useContainerWidthBelow(
|
||||||
|
COMPACT_FORM_FACTOR_WIDTH,
|
||||||
|
{ initialIsBelow: isCompactFormFactor },
|
||||||
|
);
|
||||||
const paneContext = usePaneContext();
|
const paneContext = usePaneContext();
|
||||||
const { workspaceId, tabId, retargetCurrentTab } = paneContext;
|
const { workspaceId, tabId, retargetCurrentTab } = paneContext;
|
||||||
const { archiveAgent } = useArchiveAgent();
|
const { archiveAgent } = useArchiveAgent();
|
||||||
@@ -1355,14 +1360,14 @@ function ActiveAgentComposer({
|
|||||||
};
|
};
|
||||||
openFileExplorerForCheckout({
|
openFileExplorerForCheckout({
|
||||||
checkout,
|
checkout,
|
||||||
isCompact,
|
isCompact: isCompactFormFactor,
|
||||||
});
|
});
|
||||||
setExplorerTabForCheckout({
|
setExplorerTabForCheckout({
|
||||||
...checkout,
|
...checkout,
|
||||||
tab: "changes",
|
tab: "changes",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isCompact, openFileExplorerForCheckout, serverId, setExplorerTabForCheckout],
|
[isCompactFormFactor, openFileExplorerForCheckout, serverId, setExplorerTabForCheckout],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClientSlashCommand = useCallback(
|
const handleClientSlashCommand = useCallback(
|
||||||
@@ -1414,14 +1419,19 @@ function ActiveAgentComposer({
|
|||||||
|
|
||||||
const composerFooter = useMemo(
|
const composerFooter = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isCompact ? (
|
isCompactComposerLayout ? (
|
||||||
<AgentModeControl serverId={serverId} agentId={agentId} placement="footer" />
|
<AgentModeControl
|
||||||
|
serverId={serverId}
|
||||||
|
agentId={agentId}
|
||||||
|
placement="footer"
|
||||||
|
isCompactLayout={isCompactComposerLayout}
|
||||||
|
/>
|
||||||
) : undefined,
|
) : undefined,
|
||||||
[isCompact, serverId, agentId],
|
[isCompactComposerLayout, serverId, agentId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReanimatedAnimated.View style={inputAreaStyle}>
|
<ReanimatedAnimated.View style={inputAreaStyle} onLayout={onInputAreaLayout}>
|
||||||
<SubagentsTrack
|
<SubagentsTrack
|
||||||
rows={subagentRows}
|
rows={subagentRows}
|
||||||
onOpenSubagent={handleOpenSubagent}
|
onOpenSubagent={handleOpenSubagent}
|
||||||
@@ -1449,6 +1459,7 @@ function ActiveAgentComposer({
|
|||||||
onMessageSent={onMessageSent}
|
onMessageSent={onMessageSent}
|
||||||
onClientSlashCommand={handleClientSlashCommand}
|
onClientSlashCommand={handleClientSlashCommand}
|
||||||
footer={composerFooter}
|
footer={composerFooter}
|
||||||
|
isCompactLayout={isCompactComposerLayout}
|
||||||
/>
|
/>
|
||||||
</ReanimatedAnimated.View>
|
</ReanimatedAnimated.View>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user