Change copilot and playground to toggled views

This commit is contained in:
akhisud3195 2025-10-01 23:18:49 +04:00
parent 96fd8b10ca
commit 1b9dda4a0a
4 changed files with 71 additions and 97 deletions

View file

@ -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"

View file

@ -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"

View file

@ -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();

View file

@ -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}