diff --git a/apps/code/src/renderer/components/MainLayout.tsx b/apps/code/src/renderer/components/MainLayout.tsx index d25f27967..1e6b76afb 100644 --- a/apps/code/src/renderer/components/MainLayout.tsx +++ b/apps/code/src/renderer/components/MainLayout.tsx @@ -25,6 +25,7 @@ import { useTasks } from "@features/tasks/hooks/useTasks"; import { TourOverlay } from "@features/tour/components/TourOverlay"; import { useTourStore } from "@features/tour/stores/tourStore"; import { createFirstTaskTour } from "@features/tour/tours/createFirstTaskTour"; +import { WorkView } from "@features/work/components/WorkView"; import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { useIntegrations } from "@hooks/useIntegrations"; import { Box, Flex } from "@radix-ui/themes"; @@ -45,6 +46,8 @@ export function MainLayout() { taskInputReportAssociation, taskInputCloudRepository, } = useNavigationStore(); + const mode = useNavigationStore((s) => s.mode); + const isCodeMode = mode === "code"; const { isOpen: commandMenuOpen, setOpen: setCommandMenuOpen, @@ -105,46 +108,55 @@ export function MainLayout() { - {view.type === "task-input" && ( - - )} + {isCodeMode ? ( + <> + {view.type === "task-input" && ( + + )} - {view.type === "task-detail" && view.data && ( - - )} + {view.type === "task-detail" && view.data && ( + + )} + + {view.type === "folder-settings" && } - {view.type === "folder-settings" && } + {view.type === "inbox" && } - {view.type === "inbox" && } + {view.type === "archived" && } - {view.type === "archived" && } + {view.type === "command-center" && } - {view.type === "command-center" && } + {view.type === "skills" && } - {view.type === "skills" && } + {view.type === "mcp-servers" && } - {view.type === "mcp-servers" && } - {view.type === "setup" && } + {view.type === "setup" && } + + ) : ( + + )} - + {isCodeMode && ( + + )} void; onAttachFiles?: (files: File[]) => void; onEmptyChange?: (isEmpty: boolean) => void; + onTextChange?: (text: string) => void; onFocus?: () => void; onBlur?: () => void; // manual submit override (for flows like new-task that submit outside the editor hook) @@ -90,6 +91,7 @@ export const PromptInput = forwardRef( onCancel, onAttachFiles, onEmptyChange, + onTextChange, onFocus, onBlur, onSubmitClick, @@ -138,6 +140,7 @@ export const PromptInput = forwardRef( onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }); diff --git a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index 9dbe099ac..895cad8ce 100644 --- a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -48,6 +48,7 @@ export interface UseTiptapEditorOptions { onBashCommand?: (command: string) => void; onBashModeChange?: (isBashMode: boolean) => void; onEmptyChange?: (isEmpty: boolean) => void; + onTextChange?: (text: string) => void; onFocus?: () => void; onBlur?: () => void; } @@ -198,6 +199,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, } = options; @@ -214,6 +216,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }); @@ -223,6 +226,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }; @@ -496,6 +500,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { }, onUpdate: ({ editor: e }) => { const text = e.getText(); + callbackRefs.current.onTextChange?.(text); const newBashMode = enableBashMode && text.trimStart().startsWith("!"); if (newBashMode !== prevBashModeRef.current) { diff --git a/apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx b/apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx new file mode 100644 index 000000000..d3de906b0 --- /dev/null +++ b/apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx @@ -0,0 +1,40 @@ +import { Box, Flex } from "@radix-ui/themes"; +import { type AppMode, useNavigationStore } from "@stores/navigationStore"; + +const MODES: { value: AppMode; label: string }[] = [ + { value: "code", label: "Code" }, + { value: "work", label: "Work" }, +]; + +export function ModeSwitcher() { + const mode = useNavigationStore((s) => s.mode); + const setMode = useNavigationStore((s) => s.setMode); + + return ( + + + {MODES.map((m) => { + const isActive = mode === m.value; + return ( + + ); + })} + + + ); +} diff --git a/apps/code/src/renderer/features/sessions/components/SessionView.tsx b/apps/code/src/renderer/features/sessions/components/SessionView.tsx index b64c73d5b..afcd50f3e 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionView.tsx @@ -42,6 +42,7 @@ import { ModelSelector } from "./ModelSelector"; import { PlanStatusBar } from "./PlanStatusBar"; import { ReasoningLevelSelector } from "./ReasoningLevelSelector"; import { RawLogsView } from "./raw-logs/RawLogsView"; +import { TryInPostHogWorkBanner } from "./TryInPostHogWorkBanner"; interface SessionViewProps { events: AcpMessage[]; @@ -250,9 +251,27 @@ export function SessionView({ ); const [isDraggingFile, setIsDraggingFile] = useState(false); + const [showPostHogWorkBanner, setShowPostHogWorkBanner] = useState(false); + const [postHogWorkBannerDismissed, setPostHogWorkBannerDismissed] = + useState(false); const editorRef = useRef(null); const dragCounterRef = useRef(0); + const handlePromptTextChange = useCallback( + (text: string) => { + if (postHogWorkBannerDismissed) return; + if (/pineapple/i.test(text)) { + setShowPostHogWorkBanner(true); + } + }, + [postHogWorkBannerDismissed], + ); + + const handleDismissPostHogWorkBanner = useCallback(() => { + setShowPostHogWorkBanner(false); + setPostHogWorkBannerDismissed(true); + }, []); + const firstPendingPermission = useMemo(() => { const entries = Array.from(pendingPermissions.entries()); if (entries.length === 0) return null; @@ -615,8 +634,14 @@ export function SessionView({ : { maxWidth: CHAT_CONTENT_MAX_WIDTH } } > + {showPostHogWorkBanner && ( + + )} void; +} + +export function TryInPostHogWorkBanner({ + onDismiss, +}: TryInPostHogWorkBannerProps) { + return ( + + + + + + + + Try this in PostHog Work + + + Looks like you're trying to generate shareholder value. Try + continuing this task in PostHog Work. + + + + + + + + + ); +} diff --git a/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx b/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx index 81dc03740..06c1ecefe 100644 --- a/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx +++ b/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx @@ -1,5 +1,6 @@ import { useArchivedTaskIds } from "@features/archive/hooks/useArchivedTaskIds"; import { SidebarUsageBar } from "@features/billing/components/SidebarUsageBar"; +import { ModeSwitcher } from "@features/mode-switcher/components/ModeSwitcher"; import { ArchiveIcon } from "@phosphor-icons/react"; import { Box, Flex } from "@radix-ui/themes"; import { useNavigationStore } from "@stores/navigationStore"; @@ -13,14 +14,18 @@ export const SidebarContent: React.FC = () => { const navigateToArchived = useNavigationStore( (state) => state.navigateToArchived, ); + const mode = useNavigationStore((state) => state.mode); + const isCodeMode = mode === "code"; + return ( + - + {isCodeMode && } - {archivedTaskIds.size > 0 && ( + {isCodeMode && archivedTaskIds.size > 0 && (