mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-12 19:55:19 +02:00
Change copilot and playground to toggled views
This commit is contained in:
parent
96fd8b10ca
commit
1b9dda4a0a
4 changed files with 71 additions and 97 deletions
|
|
@ -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={
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Draft-only: switch to Playground */}
|
||||
{onTogglePanel && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onTogglePanel}
|
||||
className="bg-blue-50 text-blue-700 hover:bg-blue-100"
|
||||
showHoverContent={true}
|
||||
hoverContent="Chat with assistant"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Chat } from "./components/chat";
|
|||
import { Panel } from "@/components/common/panel-common";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip } from "@heroui/react";
|
||||
import { CheckIcon, CopyIcon, PlusIcon, InfoIcon, BugIcon, BugOffIcon, MessageCircle } from "lucide-react";
|
||||
import { CheckIcon, CopyIcon, PlusIcon, InfoIcon, BugIcon, BugOffIcon, MessageCircle, Sparkles } from "lucide-react";
|
||||
|
||||
export function App({
|
||||
hidden = false,
|
||||
|
|
@ -73,6 +73,17 @@ export function App({
|
|||
subtitle={hasAgents ? "Chat with your assistant" : "Create an agent to start chatting"}
|
||||
rightActions={hasAgents ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Draft-only: switch to Copilot */}
|
||||
{onTogglePanel && !isLiveWorkflow && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="bg-blue-50 text-blue-700 hover:bg-blue-100"
|
||||
onClick={onTogglePanel}
|
||||
>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -280,80 +280,7 @@ export function TopBar({
|
|||
</CustomButton>
|
||||
</div>}
|
||||
|
||||
{/* View controls (hidden in live mode) */}
|
||||
{!isLive && (<div className="flex items-center gap-2 mr-2">
|
||||
{(() => {
|
||||
// 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 (
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button variant="light" size="sm" aria-label="Layout options" className="h-8 min-w-0 bg-transparent text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100/60 dark:hover:bg-zinc-800/50 border border-transparent gap-1 px-2">
|
||||
{/* 3-pane layout icon */}
|
||||
<svg width="26" height="18" viewBox="0 0 18 12" aria-hidden="true">
|
||||
<rect x="0.5" y="0.5" width="17" height="11" rx="1" fill="none" stroke="currentColor" strokeWidth="1" opacity="0.6" />
|
||||
<rect x="2" y="2" width="4" height="8" rx="0.5" fill="currentColor" opacity="0.8" />
|
||||
<rect x="7" y="2" width="4" height="8" rx="0.5" fill="currentColor" opacity="0.6" />
|
||||
<rect x="12" y="2" width="4" height="8" rx="0.5" fill="currentColor" opacity="0.4" />
|
||||
</svg>
|
||||
<ChevronDownIcon size={14} />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Choose layout" selectionMode="single" selectedKeys={[selectedKey]} closeOnSelect={true} onSelectionChange={(keys) => {
|
||||
const key = Array.from(keys as Set<string>)[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);
|
||||
}}>
|
||||
<DropdownItem key="show-all" isDisabled={disableShowAll} className={selectedKey==='show-all' ? 'bg-zinc-100 dark:bg-zinc-800' : ''} startContent={<input type="radio" readOnly checked={selectedKey==='show-all'} className="accent-zinc-600 dark:accent-zinc-300" />}>Show All</DropdownItem>
|
||||
<DropdownItem key="hide-agents" isDisabled={disableHideAgents} className={selectedKey==='hide-agents' ? 'bg-zinc-100 dark:bg-zinc-800' : ''} startContent={<input type="radio" readOnly checked={selectedKey==='hide-agents'} className="accent-zinc-600 dark:accent-zinc-300" />}>Hide Agents</DropdownItem>
|
||||
<DropdownItem key="hide-chat" isDisabled={disableHideChat} className={selectedKey==='hide-chat' ? 'bg-zinc-100 dark:bg-zinc-800' : ''} startContent={<input type="radio" readOnly checked={selectedKey==='hide-chat'} className="accent-zinc-600 dark:accent-zinc-300" />}>Hide Chat</DropdownItem>
|
||||
<DropdownItem key="hide-skipper" isDisabled={disableHideSkipper} className={selectedKey==='hide-skipper' ? 'bg-zinc-100 dark:bg-zinc-800' : ''} startContent={<input type="radio" readOnly checked={selectedKey==='hide-skipper'} className="accent-zinc-600 dark:accent-zinc-300" />}>Hide Skipper</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
);
|
||||
})()}
|
||||
</div>)}
|
||||
{/* Layout dropdown removed per design */}
|
||||
|
||||
{/* Deploy CTA - conditional based on auto-publish mode */}
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
@ -376,6 +303,7 @@ export function TopBar({
|
|||
<DropdownMenu aria-label="Assistant access options">
|
||||
<DropdownItem
|
||||
key="chat"
|
||||
className={!isLive ? 'hidden' : ''}
|
||||
startContent={<MessageCircleIcon size={16} />}
|
||||
onPress={() => {
|
||||
onUseAssistantClick();
|
||||
|
|
|
|||
|
|
@ -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<ViewMode>(() => {
|
||||
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({
|
|||
<ResizablePanel
|
||||
key={`entity-list-hydration`}
|
||||
minSize={10}
|
||||
defaultSize={getPanelRatios(viewMode).entityList}
|
||||
defaultSize={preservedSizes.entities || getPanelRatios(viewMode).entityList}
|
||||
id="entities"
|
||||
data-panel-id="entities"
|
||||
order={1}
|
||||
className={`${isLeftPanelCollapsed ? 'hidden' : ''}`}
|
||||
>
|
||||
|
|
@ -2089,22 +2110,22 @@ export function WorkflowEditor({
|
|||
<ResizableHandle withHandle className={`w-[3px] bg-transparent ${(isLeftPanelCollapsed) ? 'hidden' : ''}`} />
|
||||
)}
|
||||
{(viewMode === 'two_agents_chat' || viewMode === 'three_all') && (
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).chatApp} id="chat" order={2} className="overflow-hidden">
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.chat || getPanelRatios(viewMode).chatApp} id="chat" data-panel-id="chat" order={2} className="overflow-hidden">
|
||||
{/* Minimal mount of Chat during SSR hydration */}
|
||||
<div className="h-full" />
|
||||
</ResizablePanel>
|
||||
)}
|
||||
{(viewMode === 'three_all') && (<ResizableHandle withHandle className="w-[3px] bg-transparent" />)}
|
||||
{(viewMode === 'two_agents_skipper' || viewMode === 'three_all') && (
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).copilot} id="copilot" order={3} className="overflow-hidden">
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.copilot || getPanelRatios(viewMode).copilot} id="copilot" data-panel-id="copilot" order={3} className="overflow-hidden">
|
||||
<div className="h-full" />
|
||||
</ResizablePanel>
|
||||
)}
|
||||
{(viewMode === 'two_chat_skipper') && (
|
||||
<>
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).chatApp} id="chat" order={1} className="overflow-hidden"><div className="h-full" /></ResizablePanel>
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.chat || getPanelRatios(viewMode).chatApp} id="chat" data-panel-id="chat" order={1} className="overflow-hidden"><div className="h-full" /></ResizablePanel>
|
||||
<ResizableHandle withHandle className="w-[3px] bg-transparent" />
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).copilot} id="copilot" order={2} className="overflow-hidden"><div className="h-full" /></ResizablePanel>
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.copilot || getPanelRatios(viewMode).copilot} id="copilot" data-panel-id="copilot" order={2} className="overflow-hidden"><div className="h-full" /></ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
</ResizablePanelGroup>
|
||||
|
|
@ -2115,8 +2136,9 @@ export function WorkflowEditor({
|
|||
<ResizablePanel
|
||||
key={`entity-list-main`}
|
||||
minSize={10}
|
||||
defaultSize={getPanelRatios(viewMode).entityList}
|
||||
defaultSize={preservedSizes.entities || getPanelRatios(viewMode).entityList}
|
||||
id="entities"
|
||||
data-panel-id="entities"
|
||||
order={1}
|
||||
className={`${isLeftPanelCollapsed ? 'hidden' : ''}`}
|
||||
>
|
||||
|
|
@ -2183,7 +2205,7 @@ export function WorkflowEditor({
|
|||
)}
|
||||
|
||||
{/* Playground column - always mounted; hide via viewMode */}
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).chatApp} id="chat" order={2} className={`overflow-hidden relative ${viewMode === 'two_agents_skipper' ? 'hidden' : ''}`}>
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.chat || getPanelRatios(viewMode).chatApp} id="chat" data-panel-id="chat" order={2} className={`overflow-hidden relative ${viewMode === 'two_agents_skipper' ? 'hidden' : ''}`}>
|
||||
<ChatApp
|
||||
key={'' + state.present.chatKey}
|
||||
projectId={projectId}
|
||||
|
|
@ -2292,7 +2314,7 @@ export function WorkflowEditor({
|
|||
)}
|
||||
|
||||
{/* Copilot column - always mounted; hide via viewMode */}
|
||||
<ResizablePanel minSize={20} defaultSize={getPanelRatios(viewMode).copilot} id="copilot" order={viewMode === 'three_all' ? 3 : 2} className={`overflow-hidden relative ${viewMode === 'two_agents_chat' ? 'hidden' : ''}`}>
|
||||
<ResizablePanel minSize={20} defaultSize={preservedSizes.copilot || getPanelRatios(viewMode).copilot} id="copilot" data-panel-id="copilot" order={viewMode === 'three_all' ? 3 : 2} className={`overflow-hidden relative ${viewMode === 'two_agents_chat' ? 'hidden' : ''}`}>
|
||||
<Copilot
|
||||
ref={copilotRef}
|
||||
projectId={projectId}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue