diff --git a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx index ca765952..f690bbf4 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx @@ -11,7 +11,7 @@ import { Action as WorkflowDispatch } from "@/app/projects/[projectId]/workflow/ import { Panel } from "@/components/common/panel-common"; import { ComposeBoxCopilot } from "@/components/common/compose-box-copilot"; import { Messages } from "./components/messages"; -import { CopyIcon, CheckIcon, PlusIcon, XIcon, InfoIcon, Sparkles } from "lucide-react"; +import { CopyIcon, CheckIcon, PlusIcon, XIcon, InfoIcon, Sparkles, MessageCircle } from "lucide-react"; import { useCopilot } from "./use-copilot"; import { BillingUpgradeModal } from "@/components/common/billing-upgrade-modal"; import { SHOW_COPILOT_MARQUEE } from "@/app/lib/feature_flags"; @@ -376,6 +376,19 @@ export const Copilot = forwardRef<{ handleUserMessage: (message: string) => void subtitle="Build your assistant" rightActions={
+ {/* Draft-only: switch to Playground */} + {onTogglePanel && ( + + )} + )}
} - {/* View controls (hidden in live mode) */} - {!isLive && (
- {(() => { - // Current visibility booleans - const showAgents = viewMode !== "two_chat_skipper"; - const showChat = viewMode !== "two_agents_skipper"; - const showSkipper = viewMode !== "two_agents_chat"; - - // Determine selected radio option - type RadioKey = 'show-all' | 'hide-agents' | 'hide-chat' | 'hide-skipper'; - let selectedKey: RadioKey = 'show-all'; - if (!(showAgents && showChat && showSkipper)) { - if (!showAgents) selectedKey = 'hide-agents'; - else if (!showChat) selectedKey = 'hide-chat'; - else if (!showSkipper) selectedKey = 'hide-skipper'; - } - - // Map radio selection to viewMode - const setByKey = (key: RadioKey) => { - switch (key) { - case 'show-all': - onSetViewMode('three_all'); - break; - case 'hide-agents': - onSetViewMode('two_chat_skipper'); - break; - case 'hide-chat': - onSetViewMode('two_agents_skipper'); - break; - case 'hide-skipper': - onSetViewMode('two_agents_chat'); - break; - } - }; - - // Disable rules - // When there are zero agents, allow only Show All and Hide Chat - const zeroAgents = !hasAgents; - const disableShowAll = false; // always allow switching to 3-pane view - const disableHideAgents = zeroAgents; // cannot hide agents if none exist - const disableHideChat = false; // allow hide chat even with zero agents (default) - const disableHideSkipper = zeroAgents; // keep skipper visible when no agents - - return ( - - - - - { - const key = Array.from(keys as Set)[0] as RadioKey; - const zeroAgents = !hasAgents; - // Allow only permitted options when zero agents - if (zeroAgents && key !== 'show-all' && key !== 'hide-chat') return; - if (key === 'hide-chat' && disableHideChat) return; - setByKey(key); - }}> - }>Show All - }>Hide Agents - }>Hide Chat - }>Hide Skipper - - - ); - })()} -
)} + {/* Layout dropdown removed per design */} {/* Deploy CTA - conditional based on auto-publish mode */}
@@ -376,6 +303,7 @@ export function TopBar({ } onPress={() => { onUseAssistantClick(); diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx index 476d8366..629db9be 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx @@ -1023,24 +1023,17 @@ export function WorkflowEditor({ // View mode state controls top-level layout visibility (not unmounting panes) type ViewMode = "two_agents_chat" | "two_agents_skipper" | "two_chat_skipper" | "three_all"; const [viewMode, setViewMode] = useState(() => { - if (typeof window === 'undefined') return "three_all"; + if (typeof window === 'undefined') return isLive ? "three_all" : "two_agents_skipper"; const fromUrl = new URLSearchParams(window.location.search).get('view'); const valid: ViewMode[] = ["two_agents_chat", "two_agents_skipper", "two_chat_skipper", "three_all"]; if (fromUrl && (valid as string[]).includes(fromUrl)) { localStorage.setItem('workflow_view_mode', fromUrl); return fromUrl as ViewMode; } - const storedViewMode = localStorage.getItem('workflow_view_mode') as ViewMode; - const hasAgents = workflow.agents.length > 0; - - // If workflow has agents and stored view mode is "Hide chat" (two_agents_skipper), - // override to show all panels by default - if (hasAgents && storedViewMode === 'two_agents_skipper') { - return "three_all"; - } - - return storedViewMode || "three_all"; + if (storedViewMode) return storedViewMode; + // Default: in draft show Agents + Copilot (hide chat). In live keep prior default. + return isLive ? "three_all" : "two_agents_skipper"; }); const updateViewMode = useCallback((mode: ViewMode) => { @@ -1053,6 +1046,12 @@ export function WorkflowEditor({ dispatch({ type: "unselect_agent" }); } + // Clear preserved sizes when switching to a different view mode + // This ensures fresh ratios are used unless we're toggling between two_agents_chat and two_agents_skipper + if (mode !== 'two_agents_chat' && mode !== 'two_agents_skipper') { + setPreservedSizes({}); + } + if (typeof window !== 'undefined') { localStorage.setItem('workflow_view_mode', mode); const url = new URL(window.location.href); @@ -1088,6 +1087,7 @@ export function WorkflowEditor({ const saving = useRef(false); const [showCopySuccess, setShowCopySuccess] = useState(false); const [activePanel, setActivePanel] = useState<'playground' | 'copilot'>('copilot'); + const [preservedSizes, setPreservedSizes] = useState<{[key: string]: number}>({}); const [isInitialState, setIsInitialState] = useState(true); const [showBuildModeBanner, setShowBuildModeBanner] = useState(false); const [isLeftPanelCollapsed, setIsLeftPanelCollapsed] = useState(false); @@ -1850,12 +1850,32 @@ export function WorkflowEditor({ setActivePanel(activePanel === 'playground' ? 'copilot' : 'playground'); return; } - if (viewMode === 'two_agents_chat') updateViewMode('two_agents_skipper'); - else if (viewMode === 'two_agents_skipper') updateViewMode('two_agents_chat'); + if (viewMode === 'two_agents_chat') { + const agentsPct = getAgentsPanelPercent(); + // Maintain ratio: agents vs chat -> agents vs skipper + setPreservedSizes({ entities: agentsPct, copilot: Math.max(0, 100 - agentsPct) }); + updateViewMode('two_agents_skipper'); + } else if (viewMode === 'two_agents_skipper') { + const agentsPct = getAgentsPanelPercent(); + // Maintain ratio: agents vs skipper -> agents vs chat + setPreservedSizes({ entities: agentsPct, chat: Math.max(0, 100 - agentsPct) }); + updateViewMode('two_agents_chat'); + } else if (viewMode === 'two_chat_skipper') updateViewMode('two_chat_skipper'); } } + // Helper: percentage width of Agents panel within the visible panel group + function getAgentsPanelPercent() { + if (typeof window === 'undefined') return getPanelRatios(viewMode).entityList; + const entitiesPanel = document.querySelector('[data-panel-id="entities"]'); + if (!entitiesPanel || !entitiesPanel.parentElement) return getPanelRatios(viewMode).entityList; + const rect = (entitiesPanel as HTMLElement).getBoundingClientRect(); + const parentRect = (entitiesPanel.parentElement as HTMLElement).getBoundingClientRect(); + if (!parentRect.width) return getPanelRatios(viewMode).entityList; + return (rect.width / parentRect.width) * 100; + } + function handleToggleLeftPanel() { setIsLeftPanelCollapsed(!isLeftPanelCollapsed); } @@ -2031,8 +2051,9 @@ export function WorkflowEditor({ @@ -2089,22 +2110,22 @@ export function WorkflowEditor({ )} {(viewMode === 'two_agents_chat' || viewMode === 'three_all') && ( - + {/* Minimal mount of Chat during SSR hydration */}
)} {(viewMode === 'three_all') && ()} {(viewMode === 'two_agents_skipper' || viewMode === 'three_all') && ( - +
)} {(viewMode === 'two_chat_skipper') && ( <> -
+
-
+
)} @@ -2115,8 +2136,9 @@ export function WorkflowEditor({ @@ -2183,7 +2205,7 @@ export function WorkflowEditor({ )} {/* Playground column - always mounted; hide via viewMode */} - + +