diff --git a/packages/app/src/panels/draft-panel.tsx b/packages/app/src/panels/draft-panel.tsx index be9623c60..5db7b5372 100644 --- a/packages/app/src/panels/draft-panel.tsx +++ b/packages/app/src/panels/draft-panel.tsx @@ -18,8 +18,15 @@ function useDraftPanelDescriptor() { } function DraftPanel() { - const { serverId, workspaceId, tabId, target, openFileInWorkspace, retargetCurrentTab } = - usePaneContext(); + const { + serverId, + workspaceId, + tabId, + target, + openFileInWorkspace, + openImportSheet, + retargetCurrentTab, + } = usePaneContext(); const { isInteractive } = usePaneFocus(); invariant(target.kind === "draft", "DraftPanel requires draft target"); @@ -52,6 +59,7 @@ function DraftPanel() { isPaneFocused={isInteractive} onOpenWorkspaceFile={handleOpenWorkspaceFile} onCreated={handleCreated} + onOpenImportSheet={openImportSheet} /> ); } diff --git a/packages/app/src/panels/pane-context.tsx b/packages/app/src/panels/pane-context.tsx index 4ee0c6c0a..34c5869f5 100644 --- a/packages/app/src/panels/pane-context.tsx +++ b/packages/app/src/panels/pane-context.tsx @@ -11,6 +11,7 @@ export interface PaneContextValue { closeCurrentTab: () => void; retargetCurrentTab: (target: WorkspaceTabTarget) => void; openFileInWorkspace: (filePath: string) => void; + openImportSheet: () => void; } export interface PaneFocusContextValue { diff --git a/packages/app/src/screens/workspace/composer-import-pill.tsx b/packages/app/src/screens/workspace/composer-import-pill.tsx new file mode 100644 index 000000000..25267a834 --- /dev/null +++ b/packages/app/src/screens/workspace/composer-import-pill.tsx @@ -0,0 +1,63 @@ +import { useCallback, useMemo, useState } from "react"; +import { Pressable, Text, View } from "react-native"; +import { StyleSheet, withUnistyles } from "react-native-unistyles"; +import { Import as ImportIcon } from "lucide-react-native"; +import type { Theme } from "@/styles/theme"; + +const ThemedImportIcon = withUnistyles(ImportIcon); +const iconColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted }); + +interface ComposerImportPillProps { + onPress: () => void; + disabled?: boolean; +} + +export function ComposerImportPill({ onPress, disabled = false }: ComposerImportPillProps) { + const [isHovered, setIsHovered] = useState(false); + const handleHoverIn = useCallback(() => setIsHovered(true), []); + const handleHoverOut = useCallback(() => setIsHovered(false), []); + const bodyStyle = useMemo(() => [styles.body, isHovered && styles.bodyHovered], [isHovered]); + return ( + + + + + Import agent + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + row: { + flexDirection: "row", + }, + body: { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + paddingHorizontal: theme.spacing[3], + paddingVertical: theme.spacing[2], + borderRadius: theme.borderRadius.md, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.borderAccent, + backgroundColor: theme.colors.surface1, + }, + bodyHovered: { + backgroundColor: theme.colors.surface2, + }, + label: { + color: theme.colors.foreground, + fontSize: theme.fontSize.sm, + }, +})); diff --git a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx index c8aca2261..6c2837052 100644 --- a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx +++ b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx @@ -4,6 +4,7 @@ import { StyleSheet } from "react-native-unistyles"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import invariant from "tiny-invariant"; import { Composer } from "@/components/composer"; +import { ComposerImportPill } from "@/screens/workspace/composer-import-pill"; import { FileDropZone } from "@/components/file-drop-zone"; import { AgentStreamView } from "@/components/agent-stream-view"; import { composerWorkspaceAttachment } from "@/attachments/composer-workspace-attachments"; @@ -28,7 +29,7 @@ import { useWorkspaceAttachmentScopeKey, } from "@/attachments/workspace-attachments-store"; import type { UserMessageImageAttachment } from "@/types/stream"; -import { useIsCompactFormFactor } from "@/constants/layout"; +import { MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout"; import { isWeb } from "@/constants/platform"; const EMPTY_PENDING_PERMISSIONS = new Map(); @@ -278,6 +279,7 @@ interface WorkspaceDraftAgentTabProps { isPaneFocused: boolean; onCreated: (snapshot: AgentSnapshotPayload) => void; onOpenWorkspaceFile: (input: { filePath: string }) => void; + onOpenImportSheet?: () => void; } export function WorkspaceDraftAgentTab({ @@ -288,6 +290,7 @@ export function WorkspaceDraftAgentTab({ isPaneFocused, onCreated, onOpenWorkspaceFile, + onOpenImportSheet, }: WorkspaceDraftAgentTabProps) { const insets = useSafeAreaInsets(); const client = useHostRuntimeClient(serverId); @@ -590,6 +593,13 @@ export function WorkspaceDraftAgentTab({ + {onOpenImportSheet ? ( + + + + + + ) : null} ({ width: "100%", backgroundColor: theme.colors.surface0, }, + importPillRow: { + width: "100%", + paddingHorizontal: theme.spacing[4], + paddingTop: theme.spacing[3], + paddingBottom: theme.spacing[3], + alignItems: "center", + }, + importPillContent: { + width: "100%", + maxWidth: MAX_CONTENT_WIDTH, + flexDirection: "row", + }, errorContainer: { marginTop: theme.spacing[2], paddingHorizontal: theme.spacing[3], diff --git a/packages/app/src/screens/workspace/workspace-pane-content.test.tsx b/packages/app/src/screens/workspace/workspace-pane-content.test.tsx index c4c422809..d700da58c 100644 --- a/packages/app/src/screens/workspace/workspace-pane-content.test.tsx +++ b/packages/app/src/screens/workspace/workspace-pane-content.test.tsx @@ -64,6 +64,7 @@ function buildContent() { onCloseCurrentTab: vi.fn(), onRetargetCurrentTab: vi.fn(), onOpenWorkspaceFile: vi.fn(), + onOpenImportSheet: vi.fn(), }); } diff --git a/packages/app/src/screens/workspace/workspace-pane-content.tsx b/packages/app/src/screens/workspace/workspace-pane-content.tsx index f0d9b3ab8..cef49ad59 100644 --- a/packages/app/src/screens/workspace/workspace-pane-content.tsx +++ b/packages/app/src/screens/workspace/workspace-pane-content.tsx @@ -24,6 +24,7 @@ export interface BuildWorkspacePaneContentModelInput { onCloseCurrentTab: () => void; onRetargetCurrentTab: (target: WorkspaceTabDescriptor["target"]) => void; onOpenWorkspaceFile: (filePath: string) => void; + onOpenImportSheet: () => void; } export function buildWorkspacePaneContentModel({ @@ -34,6 +35,7 @@ export function buildWorkspacePaneContentModel({ onCloseCurrentTab, onRetargetCurrentTab, onOpenWorkspaceFile, + onOpenImportSheet, }: BuildWorkspacePaneContentModelInput): WorkspacePaneContentModel { ensurePanelsRegistered(); const registration = getPanelRegistration(tab.kind); @@ -50,6 +52,7 @@ export function buildWorkspacePaneContentModel({ closeCurrentTab: onCloseCurrentTab, retargetCurrentTab: onRetargetCurrentTab, openFileInWorkspace: onOpenWorkspaceFile, + openImportSheet: onOpenImportSheet, }, }; } diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 5bb8cf134..2725c430c 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -2833,6 +2833,7 @@ function WorkspaceScreenContent({ } handleOpenFileFromChat({ filePath }); }, + onOpenImportSheet: openImportSheet, }), [ handleCloseTabById, @@ -2841,6 +2842,7 @@ function WorkspaceScreenContent({ navigateToTabId, normalizedServerId, normalizedWorkspaceId, + openImportSheet, openWorkspaceTabFocused, persistenceKey, convertWorkspaceDraftToAgent,