From af7b44f60d42ccd1c0f28acf9b286e6e5c10a5fb Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 27 Jun 2025 13:24:40 -0700 Subject: [PATCH] feat: add sample prompt buttons to chat interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add quick action buttons above chat input for common prompts - Support "Get Organizations" and "React SDK Usage" sample queries - Add onSendPrompt callback to programmatically send messages - Update chat UI to show buttons when onSendPrompt handler is provided 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/client/components/chat/auth-form.tsx | 2 +- .../src/client/components/chat/chat-input.tsx | 22 ++++++- .../client/components/chat/chat-messages.tsx | 4 +- .../src/client/components/chat/chat-ui.tsx | 61 +++++++++++++------ .../src/client/components/chat/chat.tsx | 29 +++++---- .../src/client/components/chat/types.ts | 2 + .../src/client/components/ui/accordion.tsx | 2 +- .../src/client/components/ui/button.tsx | 9 +-- .../src/client/components/ui/header.tsx | 4 +- packages/mcp-test-client/package.json | 1 - 10 files changed, 91 insertions(+), 45 deletions(-) diff --git a/packages/mcp-cloudflare/src/client/components/chat/auth-form.tsx b/packages/mcp-cloudflare/src/client/components/chat/auth-form.tsx index d5effc0ef..9085ad909 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/auth-form.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/auth-form.tsx @@ -36,7 +36,7 @@ export function AuthForm({
{authError && ( -
+
{authError}
diff --git a/packages/mcp-cloudflare/src/client/components/chat/chat-input.tsx b/packages/mcp-cloudflare/src/client/components/chat/chat-input.tsx index f626f0a40..7d7574fad 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/chat-input.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/chat-input.tsx @@ -1,4 +1,6 @@ import { useEffect, useRef } from "react"; +import { Send, CircleStop } from "lucide-react"; +import { Button } from "../ui/button"; interface ChatInputProps { input: string; @@ -6,6 +8,7 @@ interface ChatInputProps { isOpen: boolean; onInputChange: (e: React.ChangeEvent) => void; onSubmit: (e: React.FormEvent) => void; + onStop: () => void; onSlashCommand?: (command: string) => void; } @@ -15,6 +18,7 @@ export function ChatInput({ isOpen, onInputChange, onSubmit, + onStop, onSlashCommand, }: ChatInputProps) { const inputRef = useRef(null); @@ -59,15 +63,29 @@ export function ChatInput({ return (
-
+
+
); diff --git a/packages/mcp-cloudflare/src/client/components/chat/chat-messages.tsx b/packages/mcp-cloudflare/src/client/components/chat/chat-messages.tsx index 67ab39d2b..5f7144597 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/chat-messages.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/chat-messages.tsx @@ -74,7 +74,7 @@ export const ChatMessages = forwardRef( const errorIsAuth = error ? isAuthError(error) : false; const errorMessage = error ? getErrorMessage(error) : null; return ( -
+
{/* Empty State when no messages */} {messages.length === 0 && (
@@ -118,7 +118,7 @@ export const ChatMessages = forwardRef( {/* Show error or loading state */} {error && errorMessage ? ( -
+
diff --git a/packages/mcp-cloudflare/src/client/components/chat/chat-ui.tsx b/packages/mcp-cloudflare/src/client/components/chat/chat-ui.tsx index d007224f2..60869c567 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/chat-ui.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/chat-ui.tsx @@ -26,6 +26,7 @@ interface ChatUIProps { onClose?: () => void; onLogout?: () => void; onSlashCommand?: (command: string) => void; + onSendPrompt?: (prompt: string) => void; } export const ChatUI = forwardRef( @@ -44,6 +45,7 @@ export const ChatUI = forwardRef( onClose, onLogout, onSlashCommand, + onSendPrompt, }, ref, ) => { @@ -53,23 +55,14 @@ export const ChatUI = forwardRef(
{showControls && ( <> -
-
+
{/* Chat Messages */} ( /> {/* Chat Input - Always pinned at bottom */} -
+
+ {/* Sample Prompt Buttons - Always visible above input */} + {onSendPrompt && ( +
+ + + +
+ )} +
diff --git a/packages/mcp-cloudflare/src/client/components/chat/chat.tsx b/packages/mcp-cloudflare/src/client/components/chat/chat.tsx index 38409a02e..064e7214a 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/chat.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/chat.tsx @@ -40,6 +40,7 @@ export function Chat({ isOpen, onClose, onLogout }: ChatProps) { reload, setMessages, setInput, + append, } = useChat({ api: "/api/chat", headers: authToken ? { Authorization: `Bearer ${authToken}` } : undefined, @@ -54,7 +55,7 @@ export function Chat({ isOpen, onClose, onLogout }: ChatProps) { enabled: true, smooth: true, dependencies: [messages, status], - delay: status === "streaming" ? 100 : 0, // More frequent updates during streaming + delay: status === "streaming" || status === "submitted" ? 100 : 0, // More frequent updates during loading }); // Clear messages function - used locally for /clear command and logout @@ -123,6 +124,15 @@ export function Chat({ isOpen, onClose, onLogout }: ChatProps) { } }, [authToken, error, reload]); + // Handle sending a prompt programmatically + const handleSendPrompt = useCallback( + (prompt: string) => { + // Clear the input and directly send the message using append + append({ role: "user", content: prompt }); + }, + [append], + ); + // Handle slash commands const handleSlashCommand = (command: string) => { // Always clear the input first for all commands @@ -171,18 +181,6 @@ export function Chat({ isOpen, onClose, onLogout }: ChatProps) { // Use a single SlidingPanel and transition between auth and chat states return ( - {/* Mobile close button - always visible */} - - {/* Auth form with fade transition */}
diff --git a/packages/mcp-cloudflare/src/client/components/chat/types.ts b/packages/mcp-cloudflare/src/client/components/chat/types.ts index a0e16deaa..7542f7610 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/types.ts +++ b/packages/mcp-cloudflare/src/client/components/chat/types.ts @@ -103,6 +103,8 @@ export interface ChatUIProps { onRetry?: () => void; onClose?: () => void; onLogout?: () => void; + onSlashCommand?: (command: string) => void; + onSendPrompt?: (prompt: string) => void; } export interface ChatMessagesProps { diff --git a/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx b/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx index 1a371aae2..deee98c8b 100644 --- a/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx +++ b/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx @@ -40,7 +40,7 @@ function AccordionTrigger({ svg]:rotate-180 text-lg text-white hover:text-violet-300 cursor-pointer ", + "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 py-4 text-left font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180 text-lg text-white hover:text-violet-300 cursor-pointer ", className, )} {...props} diff --git a/packages/mcp-cloudflare/src/client/components/ui/button.tsx b/packages/mcp-cloudflare/src/client/components/ui/button.tsx index 03b6c2013..73e98117a 100644 --- a/packages/mcp-cloudflare/src/client/components/ui/button.tsx +++ b/packages/mcp-cloudflare/src/client/components/ui/button.tsx @@ -4,17 +4,18 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../../lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer", { variants: { variant: { default: "bg-violet-300 text-black shadow-xs hover:bg-violet-300/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20", - outline: "bg-background shadow-xs hover:bg-violet-300 hover:text-black", + outline: + "bg-slate-800/50 border border-slate-600/50 shadow-xs hover:bg-slate-700/50 hover:text-white ", secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: "hover:underline hover:text-violet-300", + "bg-background shadow-xs hover:bg-violet-300 hover:text-black", + ghost: "hover:text-white hover:bg-slate-700/50 ", link: "text-primary hover:underline hover:text-violet-300 cursor-pointer", }, size: { diff --git a/packages/mcp-cloudflare/src/client/components/ui/header.tsx b/packages/mcp-cloudflare/src/client/components/ui/header.tsx index 694d8ed7c..d69f91ace 100644 --- a/packages/mcp-cloudflare/src/client/components/ui/header.tsx +++ b/packages/mcp-cloudflare/src/client/components/ui/header.tsx @@ -24,7 +24,7 @@ export const Header: React.FC = ({