From 34ead6019440df46f42c56b482ce6884d2bb5e98 Mon Sep 17 00:00:00 2001 From: James Brunton Date: Thu, 11 Jun 2026 17:28:05 +0100 Subject: [PATCH] Kill off agents pane now that we have FAB (#6613) # Description of Changes Kill off agents pane now that we have the FAB. Also fixes a bug with the FAB where it would sometimes fail to render the chat, and fixes a duplicated entry in the Vite config which was throwing a warning --- .../core/components/agents/AgentsPanel.tsx | 43 -- .../src/core/components/chat/ChatContext.tsx | 3 - .../components/tools/FullscreenToolPanel.tsx | 17 +- .../tools/FullscreenToolSurface.tsx | 6 - .../core/components/tools/RightSidebar.tsx | 129 +---- .../src/core/components/tools/ToolPanel.css | 36 -- .../components/agents/AgentsPanel.css | 483 ------------------ .../components/agents/AgentsPanel.tsx | 275 ---------- .../components/chat/ChatContext.tsx | 19 - .../proprietary/components/chat/ChatFAB.tsx | 17 +- .../proprietary/components/chat/ChatPanel.css | 31 +- frontend/editor/vite.config.ts | 5 - 12 files changed, 58 insertions(+), 1006 deletions(-) delete mode 100644 frontend/editor/src/core/components/agents/AgentsPanel.tsx delete mode 100644 frontend/editor/src/proprietary/components/agents/AgentsPanel.css delete mode 100644 frontend/editor/src/proprietary/components/agents/AgentsPanel.tsx diff --git a/frontend/editor/src/core/components/agents/AgentsPanel.tsx b/frontend/editor/src/core/components/agents/AgentsPanel.tsx deleted file mode 100644 index d5aeee493..000000000 --- a/frontend/editor/src/core/components/agents/AgentsPanel.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Core stubs for the right-rail Agents UI. - * - * The real implementations live in {@code proprietary/components/agents/AgentsPanel.tsx} - * and shadow these stubs via the {@code @app/*} alias cascade when the proprietary - * build is active. Core builds render nothing, so the right rail collapses to the - * tool list unchanged. - */ - -/** Whether the right rail should reserve space for agents UI. False in core. */ -export function useAgentsEnabled(): boolean { - return false; -} - -/** - * Whether the agent chat panel is currently open. Core builds have no chat, - * so this always returns false. Proprietary builds bridge to the ChatContext. - */ -export function useAgentChatOpen(): boolean { - return false; -} - -/** Inline "Agents" section rendered above the tool list in {@code ToolPicker}. */ -export function AgentsSection() { - return null; -} - -/** - * Icon-only agent button rendered in the collapsed (minimised) right rail. - * Returns null in core; proprietary renders the Stirling agent shortcut. - */ -export function AgentsCollapsedButton(_props: { onExpand: () => void }) { - return null; -} - -/** - * Agents card rendered inside the fullscreen tool picker. Matches the visual - * language of the fullscreen category cards (gradient border, title, items). - * Returns null in core; proprietary renders the Stirling agent. - */ -export function AgentsFullscreenSection() { - return null; -} diff --git a/frontend/editor/src/core/components/chat/ChatContext.tsx b/frontend/editor/src/core/components/chat/ChatContext.tsx index 621fafd6e..e944bd5c2 100644 --- a/frontend/editor/src/core/components/chat/ChatContext.tsx +++ b/frontend/editor/src/core/components/chat/ChatContext.tsx @@ -7,12 +7,9 @@ export function useChat() { return { messages: [] as never[], - isOpen: false, isLoading: false, progress: null, progressLog: [] as never[], - toggleOpen: () => {}, - setOpen: (_open: boolean) => {}, sendMessage: async (_content: string) => {}, clearChat: () => {}, }; diff --git a/frontend/editor/src/core/components/tools/FullscreenToolPanel.tsx b/frontend/editor/src/core/components/tools/FullscreenToolPanel.tsx index 68a0bb3a0..94eb5de45 100644 --- a/frontend/editor/src/core/components/tools/FullscreenToolPanel.tsx +++ b/frontend/editor/src/core/components/tools/FullscreenToolPanel.tsx @@ -3,11 +3,6 @@ import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; import { useIsMobile } from "@app/hooks/useIsMobile"; import { usePreferences } from "@app/contexts/PreferencesContext"; import { useWorkbenchBar } from "@app/contexts/WorkbenchBarContext"; -import { - AgentsFullscreenSection, - useAgentChatOpen, - useAgentsEnabled, -} from "@app/components/agents/AgentsPanel"; import FullscreenToolSurface from "@app/components/tools/FullscreenToolSurface"; import { ToolId } from "@app/types/toolId"; import type { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry"; @@ -16,13 +11,11 @@ import type { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry"; export function useIsFullscreenExpanded(): boolean { const { toolPanelMode, leftPanelView, readerMode } = useToolWorkflow(); const isMobile = useIsMobile(); - const agentChatOpen = useAgentChatOpen(); return ( toolPanelMode === "fullscreen" && leftPanelView === "toolPicker" && !isMobile && - !readerMode && - !agentChatOpen + !readerMode ); } @@ -50,8 +43,6 @@ export function FullscreenToolPanel({ geometry }: FullscreenToolPanelProps) { handleToolSelect, } = useToolWorkflow(); const isMobile = useIsMobile(); - const agentsEnabled = useAgentsEnabled(); - const agentChatOpen = useAgentChatOpen(); const { setAllButtonsDisabled } = useWorkbenchBar(); const { preferences, updatePreference } = usePreferences(); @@ -59,8 +50,7 @@ export function FullscreenToolPanel({ geometry }: FullscreenToolPanelProps) { toolPanelMode === "fullscreen" && leftPanelView === "toolPicker" && !isMobile && - !readerMode && - !agentChatOpen; + !readerMode; useEffect(() => { setAllButtonsDisabled(fullscreenExpanded); @@ -94,9 +84,6 @@ export function FullscreenToolPanel({ geometry }: FullscreenToolPanelProps) { } onExitFullscreenMode={() => setToolPanelMode("sidebar")} geometry={geometry} - agentsSlot={ - agentsEnabled && !searchQuery ? : null - } /> ); } diff --git a/frontend/editor/src/core/components/tools/FullscreenToolSurface.tsx b/frontend/editor/src/core/components/tools/FullscreenToolSurface.tsx index e1ad45d46..608878522 100644 --- a/frontend/editor/src/core/components/tools/FullscreenToolSurface.tsx +++ b/frontend/editor/src/core/components/tools/FullscreenToolSurface.tsx @@ -25,8 +25,6 @@ interface FullscreenToolSurfaceProps { onToggleDescriptions: () => void; onExitFullscreenMode: () => void; geometry: ToolPanelGeometry | null; - /** Optional agents block rendered above the tool list. */ - agentsSlot?: React.ReactNode; } const FullscreenToolSurface = ({ @@ -41,7 +39,6 @@ const FullscreenToolSurface = ({ onToggleDescriptions, onExitFullscreenMode: _onExitFullscreenMode, geometry, - agentsSlot, }: FullscreenToolSurfaceProps) => { const { t } = useTranslation(); const surfaceRef = useRef(null); @@ -92,9 +89,6 @@ const FullscreenToolSurface = ({ className="tool-panel__fullscreen-scroll" offsetScrollbars > - {agentsSlot && ( -
{agentsSlot}
- )} ( - null, - ); - - const handleChatClose = useCallback(() => { - withViewTransition(() => setChatOpen(false)); - setChatWidthPx(DEFAULT_CHAT_WIDTH_PX); - }, [setChatOpen]); - - const handleResizeChatPointerDown = useCallback( - (e: React.PointerEvent) => { - e.preventDefault(); - chatDragState.current = { startX: e.clientX, startWidth: chatWidthPx }; - setIsChatDragging(true); - document.body.style.cursor = "col-resize"; - document.body.style.userSelect = "none"; - - const onMove = (ev: PointerEvent) => { - if (!chatDragState.current) return; - const delta = chatDragState.current.startX - ev.clientX; - setChatWidthPx( - Math.max( - MIN_CHAT_WIDTH_PX, - Math.min( - MAX_CHAT_WIDTH_PX, - chatDragState.current.startWidth + delta, - ), - ), - ); - }; - - const cleanup = () => { - chatDragState.current = null; - setIsChatDragging(false); - document.body.style.removeProperty("cursor"); - document.body.style.removeProperty("user-select"); - window.removeEventListener("pointermove", onMove); - window.removeEventListener("pointerup", cleanup); - window.removeEventListener("pointercancel", cleanup); - }; - - window.addEventListener("pointermove", onMove); - window.addEventListener("pointerup", cleanup); - window.addEventListener("pointercancel", cleanup); - }, - [chatWidthPx], - ); - const handleShowAllTools = () => { withViewTransition(() => setAllToolsView(true)); }; @@ -186,12 +123,11 @@ export default function RightSidebar() { policiesEnabled && !allToolsView && leftPanelView === "toolPicker"; // When Policies are shown, the search moves OUT of the header to sit between // the Policies and Tools sections (separating them); otherwise it stays in the - // header. Show the header search when there's a close button, or when agents - // are off in the default tool-picker view (inline filtering). + // header. Show the header search when there's a close button, or in the + // default tool-picker view. const showInlineSearch = showPolicies && !showCloseButton; const showHeaderSearch = - !showInlineSearch && - (showCloseButton || (!agentsEnabled && leftPanelView === "toolPicker")); + !showInlineSearch && (showCloseButton || leftPanelView === "toolPicker"); const handleHeaderBack = () => { if (inToolView) { @@ -224,13 +160,6 @@ export default function RightSidebar() { ? (toolRegistry[selectedToolKey as ToolId] ?? null) : null; - // Agents header + section is hidden when: - // - the AI engine is off, or - // - the user is in the all-tools view, or - // - a specific tool is being rendered (leftPanelView ≠ "toolPicker"). - const showAgents = - agentsEnabled && !allToolsView && leftPanelView === "toolPicker"; - // The detail takeover replaces the tool list ONLY in the same default view — // never over an open tool or the all-tools view (which must keep priority). // A lingering selection is harmless: it stays hidden behind a tool and the @@ -243,7 +172,6 @@ export default function RightSidebar() { const computedWidth = () => { if (isMobile) return "100%"; - if (isChatOpen) return `${chatWidthPx}px`; if (!isPanelVisible) return "3.5rem"; return expandedWidth; }; @@ -279,41 +207,17 @@ export default function RightSidebar() { ref={toolPanelRef} data-sidebar="tool-panel" data-tour={fullscreenExpanded ? undefined : "tool-panel"} - className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : isChatOpen ? "" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${ + className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${ isRainbowMode ? rainbowStyles.rainbowPaper : "" } ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`} style={{ width: computedWidth(), padding: "0", - ...(isChatDragging ? { transition: "none" } : {}), }} > {/* Headless: enforces enabled policies on every uploaded file. */} {policiesEnabled && } - {!fullscreenExpanded && isChatOpen && ( -
-
- -
- )} - - {!fullscreenExpanded && !isChatOpen && !isPanelVisible && !isMobile && ( + {!fullscreenExpanded && !isPanelVisible && !isMobile && (
-
@@ -358,7 +261,7 @@ export default function RightSidebar() {
)} - {!fullscreenExpanded && !isChatOpen && isPanelVisible && ( + {!fullscreenExpanded && isPanelVisible && (
- ) : ( - showAgents && ( - - {t("agents.section_title", "Agents")} - - ) - )} + ) : null} {showCloseButton ? ( )} - {showAgents && } - {showPolicies && ( )} diff --git a/frontend/editor/src/core/components/tools/ToolPanel.css b/frontend/editor/src/core/components/tools/ToolPanel.css index 103a24ad9..9995781e3 100644 --- a/frontend/editor/src/core/components/tools/ToolPanel.css +++ b/frontend/editor/src/core/components/tools/ToolPanel.css @@ -385,15 +385,6 @@ } } -.tool-panel__section-label { - font-size: 0.7rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); - line-height: 1; -} - .tool-panel__mode-toggle { transition: transform 0.2s ease; } @@ -855,33 +846,6 @@ gap: 0.75rem; } -/* Agents card lives in its own column above the tool grid. It uses the same - .tool-panel__fullscreen-group base styles but with a colourful gradient - border so it stands out as a distinct surface (and doesn't blend into the - neighbouring category cards). */ -.tool-panel__fullscreen-agents { - padding: 1.5rem 1.75rem 0; -} - -.tool-panel__fullscreen-group--agents { - position: relative; - background: - linear-gradient(var(--fullscreen-bg-group), var(--fullscreen-bg-group)) - padding-box, - linear-gradient( - 135deg, - var(--mantine-color-blue-6) 0%, - var(--mantine-color-violet-6) 45%, - var(--mantine-color-pink-6) 100% - ) - border-box; - border: 1.5px solid transparent; -} - -.tool-panel__fullscreen-section-icon--agents { - color: var(--mantine-color-blue-6); -} - @keyframes tool-panel-fullscreen-slide-in { from { transform: translateX(6%) scaleX(0.85); diff --git a/frontend/editor/src/proprietary/components/agents/AgentsPanel.css b/frontend/editor/src/proprietary/components/agents/AgentsPanel.css deleted file mode 100644 index 864c9527d..000000000 --- a/frontend/editor/src/proprietary/components/agents/AgentsPanel.css +++ /dev/null @@ -1,483 +0,0 @@ -/* ─── Chat takeover ─────────────────────────────────────────────────────── */ - -.agents-takeover { - position: fixed; - right: 0; - top: 0; - bottom: 0; - display: flex; - flex-direction: column; - background: var(--bg-toolbar); - border-left: 1px solid var(--border-subtle); - view-transition-name: agents-rail; -} - -.agents-takeover__resize-handle { - position: absolute; - left: -3px; - top: 0; - bottom: 0; - width: 7px; - cursor: col-resize; - z-index: 10; - touch-action: none; -} - -.agents-takeover__resize-handle::after { - content: ""; - position: absolute; - left: 3px; - top: 0; - bottom: 0; - width: 1px; - background: transparent; - transition: - background 150ms ease, - width 150ms ease, - left 150ms ease; -} - -.agents-takeover__resize-handle:hover::after { - left: 2px; - width: 3px; - background: var(--mantine-color-blue-4); - opacity: 0.6; -} - -/* ─── Sidebar agents section ─────────────────────────────────────────────── */ - -.agents-section { - padding: 0.5rem 0.75rem 0.875rem; - border-bottom: 1px solid var(--border-subtle); - background: var(--bg-toolbar); - flex-shrink: 0; - view-transition-name: agents-rail; -} - -/* Hero button — real Stirling agent */ -.agent-button { - display: block; - width: 100%; - padding: 0.75rem; - border: 1px solid - color-mix(in srgb, var(--mantine-color-blue-5) 40%, var(--border-subtle)); - border-radius: 0.625rem; - background: linear-gradient( - 135deg, - color-mix( - in srgb, - var(--mantine-color-blue-6) 6%, - var(--mantine-color-body) - ) - 0%, - var(--mantine-color-body) 100% - ); - transition: - background 150ms ease-out, - border-color 150ms ease-out; -} - -.agent-button:hover { - border-color: color-mix( - in srgb, - var(--mantine-color-blue-5) 65%, - var(--border-subtle) - ); - background: linear-gradient( - 135deg, - color-mix( - in srgb, - var(--mantine-color-blue-6) 12%, - var(--mantine-color-default-hover) - ) - 0%, - var(--mantine-color-default-hover) 100% - ); -} - -.agent-button__logo { - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--mantine-color-blue-filled); - flex-shrink: 0; - position: relative; -} - -/* Coming-soon agent rows */ -.agents-sidebar-list { - display: flex; - flex-direction: column; - gap: 0.375rem; - margin-top: 0.375rem; -} - -.agent-button--coming-soon { - display: block; - width: 100%; - padding: 0.75rem; - border: 1px solid var(--border-subtle); - border-radius: 0.625rem; - background: var(--mantine-color-body); - cursor: not-allowed !important; - opacity: 0.65; - transition: - opacity 180ms ease-out, - transform 180ms ease-out, - border-color 120ms ease-out; -} - -@starting-style { - .agent-button--coming-soon { - opacity: 0; - transform: translateY(5px); - } -} - -.agent-button--coming-soon:hover { - opacity: 0.85; - border-color: color-mix( - in srgb, - var(--mantine-color-gray-4) 60%, - var(--border-subtle) - ); - background: var(--mantine-color-default-hover); -} - -.agent-button__icon-plain { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.75rem; - height: 1.75rem; - color: var(--mantine-color-dimmed); - flex-shrink: 0; -} - -.agents-view-all { - all: unset; - display: block; - width: 100%; - margin-top: 0.375rem; - padding: 0.25rem 0.625rem; - font-size: 0.75rem; - font-weight: 500; - color: var(--mantine-color-dimmed); - text-align: center; - cursor: pointer; - opacity: 0.7; - border-radius: 0.375rem; - transition: opacity 120ms ease-out; - box-sizing: border-box; -} - -.agents-view-all:hover { - opacity: 1; -} - -/* Dark mode — sidebar */ -[data-mantine-color-scheme="dark"] .agent-button--coming-soon { - background: transparent; - border-color: color-mix( - in srgb, - var(--mantine-color-dark-4) 60%, - transparent - ); -} - -[data-mantine-color-scheme="dark"] .agent-button--coming-soon:hover { - border-color: var(--mantine-color-dark-3); - background: var(--mantine-color-dark-6); -} - -[data-mantine-color-scheme="dark"] .agent-button { - border-color: color-mix( - in srgb, - var(--mantine-color-blue-7) 55%, - var(--border-subtle) - ); - background: linear-gradient( - 135deg, - color-mix(in srgb, var(--mantine-color-blue-9) 35%, transparent) 0%, - transparent 100% - ); -} - -[data-mantine-color-scheme="dark"] .agent-button:hover { - border-color: color-mix( - in srgb, - var(--mantine-color-blue-5) 60%, - var(--border-subtle) - ); - background: linear-gradient( - 135deg, - color-mix( - in srgb, - var(--mantine-color-blue-8) 45%, - rgba(255, 255, 255, 0.04) - ) - 0%, - rgba(255, 255, 255, 0.04) 100% - ); -} - -[data-mantine-color-scheme="dark"] .agent-button__logo { - color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled)); -} - -/* ─── Collapsed-rail agent button ────────────────────────────────────────── */ - -.agents-collapsed-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - padding: 0; - border: none; - border-radius: 0.5rem; - background: transparent; - color: var(--mantine-color-blue-filled); - cursor: pointer; - transition: background 120ms ease-out; - position: relative; -} - -.agents-collapsed-btn:hover { - background: var(--mantine-color-default-hover); -} - -/* ─── Running / in-progress status dot ──────────────────────────────────── */ - -.agent-status-dot { - position: absolute; - bottom: 0; - right: 0; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--mantine-color-blue-5); - border: 1.5px solid var(--mantine-color-body); - animation: agent-dot-pulse 2.4s ease-in-out infinite; - pointer-events: none; -} - -@keyframes agent-dot-pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.45; - } -} - -/* Blue outline on hero Stirling button when agent is running */ -.agent-button.agent-button--running { - border-color: color-mix( - in srgb, - var(--mantine-color-blue-5) 60%, - var(--border-subtle) - ); -} - -[data-mantine-color-scheme="dark"] .agent-button.agent-button--running { - border-color: color-mix( - in srgb, - var(--mantine-color-blue-4) 55%, - var(--border-subtle) - ); -} - -@media (prefers-reduced-motion: reduce) { - .agent-status-dot { - animation: none; - } -} - -/* ─── Fullscreen hero card ───────────────────────────────────────────────── */ - -/* - * .tool-panel__fullscreen-group--agents gives the gradient border via the - * padding-box / border-box trick. We only override the padding-box with a - * very light tint so the card doesn't read as a colourful surface. - */ -.agents-hero { - padding: 1.25rem 1.5rem 1.5rem; - border-radius: 1rem; - background: - linear-gradient( - 125deg, - color-mix(in srgb, var(--mantine-color-blue-1) 35%, white) 0%, - color-mix(in srgb, var(--mantine-color-violet-1) 45%, white) 50%, - color-mix(in srgb, var(--mantine-color-pink-0) 35%, white) 100% - ) - padding-box, - linear-gradient( - 135deg, - var(--mantine-color-blue-6) 0%, - var(--mantine-color-violet-6) 45%, - var(--mantine-color-pink-6) 100% - ) - border-box; -} - -/* Body: left CTA + right grid */ -.agents-hero__body { - display: flex; - gap: 2rem; - align-items: flex-start; -} - -/* ── Left CTA — plain content, only the button inside is interactive ── */ -.agents-hero__cta { - display: flex; - flex-direction: column; - gap: 0; - flex: 0 0 18rem; - box-sizing: border-box; - padding: 0.25rem 0; -} - -.agents-hero__cta-logo { - color: var(--mantine-color-blue-filled); - margin-bottom: 0.625rem; -} - -.agents-hero__cta-headline { - font-size: 1.375rem; -} - -.agents-hero__cta-btn { - all: unset; - margin-top: 1.25rem; - display: inline-flex; - align-items: center; - padding: 0.4375rem 1rem; - border-radius: 0.5rem; - background: var(--mantine-color-blue-6); - color: white; - font-size: 0.8125rem; - font-weight: 600; - line-height: 1; - width: fit-content; - cursor: pointer; - transition: background 0.15s ease; -} - -.agents-hero__cta-btn:hover { - background: var(--mantine-color-blue-7); -} - -.agents-hero__cta-btn:focus-visible { - outline: 2px solid var(--mantine-color-blue-5); - outline-offset: 3px; - border-radius: 0.5rem; -} - -/* ── Right 2×3 grid — sizes to content, pushed to the right ── */ -.agents-hero__grid { - flex: 0 0 auto; - margin-left: auto; - display: grid; - /* auto columns: each column = widest item in that column */ - grid-template-columns: repeat(2, auto); - gap: 0.625rem; - align-content: start; - align-self: start; -} - -/* Solid white cards — no opacity so the white is opaque */ -.agents-hero__grid-item { - all: unset; - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 0.875rem; - border: 1px solid rgba(0, 0, 0, 0.07); - border-radius: 0.75rem; - background: var(--mantine-color-body); - cursor: not-allowed; - box-sizing: border-box; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); -} - -.agents-hero__grid-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: var(--mantine-color-blue-5); -} - -.agents-hero__grid-body { - min-width: 0; - flex: 1; -} - -/* ── Dark mode — fullscreen ── */ -[data-mantine-color-scheme="dark"] .agents-hero { - background: - linear-gradient( - 125deg, - color-mix(in srgb, var(--mantine-color-blue-9) 55%, #1a1b2e) 0%, - color-mix(in srgb, var(--mantine-color-violet-9) 45%, #1a1b2e) 50%, - color-mix(in srgb, var(--mantine-color-pink-9) 40%, #1a1b2e) 100% - ) - padding-box, - linear-gradient( - 135deg, - var(--mantine-color-blue-6) 0%, - var(--mantine-color-violet-6) 45%, - var(--mantine-color-pink-6) 100% - ) - border-box; -} - -[data-mantine-color-scheme="dark"] .agents-hero__cta-logo { - color: var(--mantine-color-blue-3); -} - -[data-mantine-color-scheme="dark"] .agents-hero__grid-item { - background: var(--fullscreen-bg-item); - border-color: var(--fullscreen-border-subtle-70); - box-shadow: none; -} - -[data-mantine-color-scheme="dark"] .agents-hero__grid-icon { - color: var(--mantine-color-blue-3); -} - -/* ─── agents-rail view transition — slide up ─────────────────────────────── */ - -::view-transition-old(agents-rail) { - animation: vt-agents-out 150ms ease-out forwards; -} - -::view-transition-new(agents-rail) { - animation: vt-agents-in 260ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards; -} - -@keyframes vt-agents-out { - to { - opacity: 0; - } -} - -@keyframes vt-agents-in { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@media (prefers-reduced-motion: reduce) { - ::view-transition-old(agents-rail), - ::view-transition-new(agents-rail) { - animation-duration: 1ms !important; - } -} diff --git a/frontend/editor/src/proprietary/components/agents/AgentsPanel.tsx b/frontend/editor/src/proprietary/components/agents/AgentsPanel.tsx deleted file mode 100644 index 8799596d9..000000000 --- a/frontend/editor/src/proprietary/components/agents/AgentsPanel.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import { useState } from "react"; -import type { ElementType } from "react"; -import { Box, Group, Text, UnstyledButton } from "@mantine/core"; -import TableChartRoundedIcon from "@mui/icons-material/TableChartRounded"; -import SummarizeRoundedIcon from "@mui/icons-material/SummarizeRounded"; -import VisibilityOffRoundedIcon from "@mui/icons-material/VisibilityOffRounded"; -import GavelRoundedIcon from "@mui/icons-material/GavelRounded"; -import AssignmentRoundedIcon from "@mui/icons-material/AssignmentRounded"; -import CodeRoundedIcon from "@mui/icons-material/CodeRounded"; -import { useTranslation } from "react-i18next"; -import { useAppConfig } from "@app/contexts/AppConfigContext"; -import { useChat } from "@app/components/chat/ChatContext"; -import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip"; -import { StirlingLogoOutline } from "@app/components/agents/StirlingLogoOutline"; -import { withViewTransition } from "@app/utils/viewTransition"; -import "@app/components/agents/AgentsPanel.css"; - -interface ComingSoonAgent { - id: string; - nameKey: string; - descriptionKey: string; - Icon: ElementType; -} - -const COMING_SOON_AGENTS: ComingSoonAgent[] = [ - { - id: "data-extraction", - nameKey: "agents.data_extraction_name", - descriptionKey: "agents.data_extraction_description", - Icon: TableChartRoundedIcon, - }, - { - id: "doc-summary", - nameKey: "agents.doc_summary_name", - descriptionKey: "agents.doc_summary_description", - Icon: SummarizeRoundedIcon, - }, - { - id: "auto-redaction", - nameKey: "agents.auto_redaction_name", - descriptionKey: "agents.auto_redaction_description", - Icon: VisibilityOffRoundedIcon, - }, - { - id: "compliance", - nameKey: "agents.compliance_name", - descriptionKey: "agents.compliance_description", - Icon: GavelRoundedIcon, - }, - { - id: "form-filler", - nameKey: "agents.form_filler_name", - descriptionKey: "agents.form_filler_description", - Icon: AssignmentRoundedIcon, - }, - { - id: "pdf-to-markdown", - nameKey: "agents.pdf_to_markdown_name", - descriptionKey: "agents.pdf_to_markdown_description", - Icon: CodeRoundedIcon, - }, -]; - -export function useAgentsEnabled(): boolean { - const { config } = useAppConfig(); - return Boolean(config?.aiEngineEnabled); -} - -export function useAgentChatOpen(): boolean { - const { isOpen } = useChat(); - return isOpen; -} - -const PREVIEW_COUNT = 3; - -/** Sidebar agents section — Stirling as hero CTA, coming-soon agents below. */ -export function AgentsSection() { - const { t } = useTranslation(); - const { isOpen, setOpen, isLoading } = useChat(); - const enabled = useAgentsEnabled(); - const [showAll, setShowAll] = useState(false); - - if (!enabled || isOpen) return null; - - const comingSoonLabel = t("agents.coming_soon", "Coming soon"); - const visibleAgents = showAll - ? COMING_SOON_AGENTS - : COMING_SOON_AGENTS.slice(0, PREVIEW_COUNT); - - return ( - - {/* Main Stirling agent — real and clickable */} - withViewTransition(() => setOpen(true))} - aria-label={t("agents.stirling_name", "Stirling")} - > - - - - {isLoading && } - - - - {t("agents.stirling_name", "Stirling")} - - - {isLoading - ? t("agents.stirling_running", "Running...") - : t( - "agents.stirling_description", - "Your general-purpose PDF assistant", - )} - - - - - - {/* Coming-soon agents */} -
- {visibleAgents.map(({ id, nameKey, descriptionKey, Icon }) => ( - - - - - - - - - {t(nameKey)} - - - {t(descriptionKey)} - - - - - - ))} -
- - {!showAll ? ( - - ) : ( - - )} -
- ); -} - -/** Icon-only agent button in the collapsed (minimised) right rail. */ -export function AgentsCollapsedButton({ onExpand }: { onExpand: () => void }) { - const { t } = useTranslation(); - const { setOpen, isLoading } = useChat(); - const enabled = useAgentsEnabled(); - - if (!enabled) return null; - - const label = t("agents.stirling_tooltip", "Stirling agent"); - - return ( - - { - onExpand(); - setOpen(true); - }} - aria-label={label} - className="agents-collapsed-btn" - > - - {isLoading && } - - - ); -} - -/** - * Fullscreen hero card — Stirling CTA on the left, 2×3 coming-soon grid on - * the right. Matches the gradient border of the other fullscreen category cards. - */ -export function AgentsFullscreenSection() { - const { t } = useTranslation(); - const { isOpen, setOpen } = useChat(); - const enabled = useAgentsEnabled(); - - if (!enabled || isOpen) return null; - - const comingSoonLabel = t("agents.coming_soon", "Coming soon"); - - return ( -
-
- {/* Left: Stirling content — only the button is interactive */} -
-
- -
- - {t("agents.stirling_full_name", "Stirling General Agent")} - - - {t( - "agents.stirling_long_description", - "General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents.", - )} - - -
- - {/* Right: 2×3 grid of coming-soon agents */} -
- {COMING_SOON_AGENTS.map(({ id, nameKey, descriptionKey, Icon }) => ( - - - - ))} -
-
-
- ); -} diff --git a/frontend/editor/src/proprietary/components/chat/ChatContext.tsx b/frontend/editor/src/proprietary/components/chat/ChatContext.tsx index 0c9cb314b..c60abe513 100644 --- a/frontend/editor/src/proprietary/components/chat/ChatContext.tsx +++ b/frontend/editor/src/proprietary/components/chat/ChatContext.tsx @@ -183,7 +183,6 @@ interface AiWorkflowResponse { interface ChatState { messages: ChatMessage[]; - isOpen: boolean; isLoading: boolean; progress: AiWorkflowProgress | null; /** Ordered log of every progress event in the current request. UI shows the last N entries. */ @@ -200,8 +199,6 @@ type ChatAction = | { type: "SET_LOADING"; loading: boolean } | { type: "SET_PROGRESS"; progress: AiWorkflowProgress | null } | { type: "APPEND_PROGRESS"; progress: AiWorkflowProgress } - | { type: "TOGGLE_OPEN" } - | { type: "SET_OPEN"; open: boolean } | { type: "CLEAR" }; function chatReducer(state: ChatState, action: ChatAction): ChatState { @@ -231,10 +228,6 @@ function chatReducer(state: ChatState, action: ChatAction): ChatState { action.progress, ], }; - case "TOGGLE_OPEN": - return { ...state, isOpen: !state.isOpen }; - case "SET_OPEN": - return { ...state, isOpen: action.open }; case "CLEAR": return { ...state, @@ -363,13 +356,10 @@ async function consumeSSEStream( interface ChatContextValue { messages: ChatMessage[]; - isOpen: boolean; isLoading: boolean; progress: AiWorkflowProgress | null; /** Ordered log of every progress event for the current in-flight request. */ progressLog: AiWorkflowProgress[]; - toggleOpen: () => void; - setOpen: (open: boolean) => void; sendMessage: (content: string) => Promise; /** Abort any in-flight request and reset the chat to an empty conversation. */ clearChat: () => void; @@ -379,7 +369,6 @@ const ChatContext = createContext(null); const initialState: ChatState = { messages: [], - isOpen: false, isLoading: false, progress: null, progressLog: [], @@ -465,11 +454,6 @@ export function ChatProvider({ children }: { children: ReactNode }) { [fileActions, downloadFile], ); - const toggleOpen = useCallback(() => dispatch({ type: "TOGGLE_OPEN" }), []); - const setOpen = useCallback( - (open: boolean) => dispatch({ type: "SET_OPEN", open }), - [], - ); const clearChat = useCallback(() => { abortRef.current?.abort(); abortRef.current = null; @@ -649,12 +633,9 @@ export function ChatProvider({ children }: { children: ReactNode }) { { + // The overlay only mounts once the AI engine is enabled (config can load + // after first render), so re-measure when that flips true rather than only + // on initial mount, otherwise the default position never gets computed. + if (!enabled) return; const pos = getDefaultPos(); if (pos) setRndPos(pos); - }, []); + }, [enabled]); // Clear the reset timer on unmount to avoid state updates on dead components. // Also ensure body user-select is restored if we unmount mid-resize. @@ -140,6 +145,12 @@ export function ChatFAB() { { + // Fallback: ensure a position exists before opening, in case the + // layout effect measured before the overlay was laid out. + if (rndPos === null) { + const pos = getDefaultPos(); + if (pos) setRndPos(pos); + } setIsOpen(true); setHasUnviewedResult(false); }} diff --git a/frontend/editor/src/proprietary/components/chat/ChatPanel.css b/frontend/editor/src/proprietary/components/chat/ChatPanel.css index 62402108f..3f66123e9 100644 --- a/frontend/editor/src/proprietary/components/chat/ChatPanel.css +++ b/frontend/editor/src/proprietary/components/chat/ChatPanel.css @@ -35,7 +35,6 @@ transition: background 120ms ease-out, border-color 120ms ease-out; - /* Pair with .agent-button to morph between the card and the pill. */ view-transition-name: stirling-agent; } @@ -64,6 +63,36 @@ position: relative; } +/* Running / in-progress status dot, anchored to the agent pill icon. */ +.agent-status-dot { + position: absolute; + bottom: 0; + right: 0; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--mantine-color-blue-5); + border: 1.5px solid var(--mantine-color-body); + animation: agent-dot-pulse 2.4s ease-in-out infinite; + pointer-events: none; +} + +@keyframes agent-dot-pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.45; + } +} + +@media (prefers-reduced-motion: reduce) { + .agent-status-dot { + animation: none; + } +} + .chat-panel__agent-pill--loading { border-color: color-mix( in srgb, diff --git a/frontend/editor/vite.config.ts b/frontend/editor/vite.config.ts index bb672250d..67d796753 100644 --- a/frontend/editor/vite.config.ts +++ b/frontend/editor/vite.config.ts @@ -242,11 +242,6 @@ export default defineConfig(async ({ mode }) => { }, }, }, - resolve: { - alias: { - "@shared": path.resolve(__dirname, "../shared"), - }, - }, optimizeDeps: { exclude: ["@embedpdf/pdfium"], },