From 7cfde7826c799ba034916409fb5f98468df02f37 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Fri, 27 Feb 2026 04:12:57 -0300 Subject: [PATCH] feat(chat): redesign chat input bottom bar and improve animations Reorganize bottom bar layout: agent selector, file upload, and mode selector grouped on the left; model name and send button on the right. - Model selector shows text name only (no logo), strips provider prefix - Agent button uses animated SVG gradient dashed border (chart-1 to chart-4) - File upload and mode selector use consistent bordered icon buttons - Agent badge header animates in/out with grid-rows collapse + ease-out - File node chip uses design system tokens instead of hardcoded colors - Simplify home greeting to "What's on your mind, {name}?" Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/mesh/src/web/components/chat/input.tsx | 179 +++++++++++++----- .../src/web/components/chat/select-mode.tsx | 78 ++------ .../src/web/components/chat/select-model.tsx | 26 ++- .../components/chat/select-virtual-mcp.tsx | 45 ++++- .../web/components/chat/tiptap/file/node.tsx | 4 +- .../components/chat/tiptap/file/uploader.tsx | 15 +- apps/mesh/src/web/routes/orgs/home/page.tsx | 24 +-- 7 files changed, 215 insertions(+), 156 deletions(-) diff --git a/apps/mesh/src/web/components/chat/input.tsx b/apps/mesh/src/web/components/chat/input.tsx index fb371a0b8b..c4cff24c2b 100644 --- a/apps/mesh/src/web/components/chat/input.tsx +++ b/apps/mesh/src/web/components/chat/input.tsx @@ -1,4 +1,3 @@ -import { IntegrationIcon } from "@/web/components/integration-icon.tsx"; import { calculateUsageStats } from "@/web/lib/usage-utils.ts"; import { getAgentColor } from "@/web/utils/agent-color"; import { Button } from "@deco/ui/components/button.tsx"; @@ -95,15 +94,51 @@ function DecopilotIconButton({ - + {!open && Decopilot} @@ -198,17 +233,10 @@ function VirtualMCPBadge({ type="button" disabled={disabled} className={cn( - "flex items-center gap-1.5 hover:opacity-80 transition-opacity", + "flex items-center gap-1.5 px-2 py-1 rounded-md hover:opacity-80 transition-opacity", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", )} > - } - /> {virtualMcp.title} @@ -335,35 +363,90 @@ export function ChatInput() { } }; + // Track whether a non-Decopilot agent is active + const hasAgentBadge = + !!selectedVirtualMcp?.id && !isDecopilot(selectedVirtualMcp.id); + + // Track if wrapper visuals should still show (stays true during exit animation) + const [showWrapper, setShowWrapper] = useState(false); + if (hasAgentBadge && !showWrapper) { + setShowWrapper(true); + } + + const handleGridTransitionEnd = () => { + if (!hasAgentBadge) { + setShowWrapper(false); + lastAgentRef.current = null; + } + }; + + // Keep last active agent + color for exit animation + const lastAgentRef = useRef<{ + id: string; + virtualMcps: VirtualMCPInfo[]; + color: ReturnType; + } | null>(null); + const color = selectedVirtualMcp ? getAgentColor(selectedVirtualMcp.id) : null; + if (hasAgentBadge && selectedVirtualMcp?.id) { + lastAgentRef.current = { id: selectedVirtualMcp.id, virtualMcps, color }; + } + + const badgeAgent = hasAgentBadge ? selectedVirtualMcp : null; + const badgeAgentId = badgeAgent?.id ?? lastAgentRef.current?.id; + const badgeVirtualMcps = badgeAgent + ? virtualMcps + : (lastAgentRef.current?.virtualMcps ?? []); + // Use current color when active, last color during exit animation + const wrapperBg = color?.bg ?? lastAgentRef.current?.color?.bg; + return ( -
+
{/* Virtual MCP wrapper with badge */} -
+ {/* Colored background overlay - stays during exit animation */} + {showWrapper && ( +
)} - > + {/* Highlight floats above the form area */} - {/* Virtual MCP Badge Header */} - {selectedVirtualMcp?.id && !isDecopilot(selectedVirtualMcp.id) && ( - - )} + {/* Virtual MCP Badge Header - animated expand/collapse */} +
+
+ {badgeAgentId && ( + + )} +
+
{/* Inner container with the input */} -
+
@@ -391,14 +473,13 @@ export function ChatInput() { {/* Bottom Actions Row */}
- {/* Left Actions (agent selector and usage stats) */} -
+ {/* Left Actions (agent, file upload, mode) */} +
{isRunInProgress && ( Run in progress )} - {/* Always show selector button - DecopilotIconButton for Decopilot, VirtualMCPSelector for others */} {selectedVirtualMcp && isDecopilot(selectedVirtualMcp.id) ? ( )} + + {contextWindow && lastTotalTokens > 0 && ( - {/* Right Actions (model, mode, file upload, send button) */} -
+ {/* Right Actions (model, send) */} +
- - + {(() => { + const Icon = MODE_CONFIGS[selectedMode]?.icon ?? ArrowsRight; + return ; + })()} + - {MODE_CONFIGS[selectedMode]?.description ?? "Choose agent mode"} + {MODE_CONFIGS[selectedMode]?.label ?? "Choose agent mode"} diff --git a/apps/mesh/src/web/components/chat/select-model.tsx b/apps/mesh/src/web/components/chat/select-model.tsx index 561c1f53d0..1bb2c30af6 100644 --- a/apps/mesh/src/web/components/chat/select-model.tsx +++ b/apps/mesh/src/web/components/chat/select-model.tsx @@ -518,21 +518,19 @@ function SelectedModelDisplay({ ); } + // Strip provider prefix (e.g. "Anthropic: Claude Sonnet 4.5" → "Claude Sonnet 4.5") + const displayName = model.title.includes(": ") + ? model.title.split(": ").slice(1).join(": ") + : model.title; + return ( -
- {model.logo && ( - {model.title} - )} - - {model.title} +
+ + {displayName}
); @@ -837,13 +835,13 @@ function FallbackModelDisplay({ const id = selectedModel.thinking.id; const shortName = id.split("/").pop() ?? id; return ( -
- +
+ {shortName}
); diff --git a/apps/mesh/src/web/components/chat/select-virtual-mcp.tsx b/apps/mesh/src/web/components/chat/select-virtual-mcp.tsx index ba38d66764..db2792cacf 100644 --- a/apps/mesh/src/web/components/chat/select-virtual-mcp.tsx +++ b/apps/mesh/src/web/components/chat/select-virtual-mcp.tsx @@ -271,10 +271,10 @@ export function VirtualMCPSelector({ type="button" disabled={disabled} className={cn( - "flex items-center justify-center size-8 rounded-full transition-colors shrink-0", + "relative flex items-center justify-center size-8 rounded-md text-muted-foreground/75 transition-colors shrink-0", disabled ? "cursor-not-allowed opacity-50" - : "cursor-pointer hover:bg-accent", + : "cursor-pointer hover:text-muted-foreground", className, )} aria-label={placeholder} @@ -283,12 +283,47 @@ export function VirtualMCPSelector({ } - className="rounded-md shrink-0 aspect-square" + className="rounded-md shrink-0 absolute inset-0 size-full" /> ) : ( - + <> + + + + + + + + + + + + )} diff --git a/apps/mesh/src/web/components/chat/tiptap/file/node.tsx b/apps/mesh/src/web/components/chat/tiptap/file/node.tsx index 4956903f7e..ea0490344e 100644 --- a/apps/mesh/src/web/components/chat/tiptap/file/node.tsx +++ b/apps/mesh/src/web/components/chat/tiptap/file/node.tsx @@ -77,7 +77,7 @@ function FileNodeView(props: NodeViewProps) { "inline-flex items-center gap-1", "cursor-default select-none", "text-xs font-light", - "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", + "bg-muted text-muted-foreground", isSelected && "outline-2 outline-blue-300 outline-offset-0", )} > @@ -85,7 +85,7 @@ function FileNodeView(props: NodeViewProps) { {name} - + {isImage ? ( - + {!modelSupportsFilesValue diff --git a/apps/mesh/src/web/routes/orgs/home/page.tsx b/apps/mesh/src/web/routes/orgs/home/page.tsx index 8dec5b4fd4..e03be5d1af 100644 --- a/apps/mesh/src/web/routes/orgs/home/page.tsx +++ b/apps/mesh/src/web/routes/orgs/home/page.tsx @@ -37,14 +37,6 @@ import { toast } from "sonner"; /** * Get time-based greeting */ -function getTimeBasedGreeting(): string { - const hour = new Date().getHours(); - if (hour >= 5 && hour < 12) return "Morning"; - if (hour >= 12 && hour < 17) return "Afternoon"; - if (hour >= 17 && hour < 22) return "Evening"; - return "Night"; -} - // ---------- Main Content ---------- function HomeContent() { @@ -63,7 +55,6 @@ function HomeContent() { const [isThreadsSidebarOpen, setIsThreadsSidebarOpen] = useState(false); const userName = session?.user?.name?.split(" ")[0] || "there"; - const greeting = getTimeBasedGreeting(); // Use Decopilot as default agent const defaultAgent = getWellKnownDecopilotVirtualMCP(org.id); @@ -173,9 +164,9 @@ function HomeContent() { ) : (
-
+
{/* Agent Image */} -
+
{/* Greeting */} -
-

- {greeting} {userName}, -

-

- What are we building today? +

+

+ What's on your mind, {userName}?

{/* Chat Input */} -
+