import React from "react"; import { Button } from "@vertesia/ui/core"; import { useUITranslation } from "@vertesia/ui/i18n"; import { MarkdownRenderer } from "@vertesia/ui/widgets"; import { MessageSquare, CheckCircle, XCircle, AlertCircle, HelpCircle, Send } from "lucide-react"; /** Option for user to select */ export interface AskUserOption { id: string; label: string; description?: string; icon?: React.ReactNode; } /** Props for the AskUserWidget component */ export interface AskUserWidgetProps { /** The question or prompt to display */ question: string; /** Optional description or additional context */ description?: string; /** Options for the user to choose from */ options?: AskUserOption[]; /** Called when user selects an option (single select mode) */ onSelect?: (optionId: string) => void; /** Called when user submits selected options (multi select mode) */ onMultiSelect?: (optionIds: string[]) => void; /** Called when user submits a free-form response */ onSubmit?: (response: string) => void; /** Whether to show a text input for free-form response */ allowFreeResponse?: boolean; /** Whether to allow multiple selections with checkboxes */ multiSelect?: boolean; /** Placeholder for free-form input */ placeholder?: string; /** Whether the widget is in a loading/processing state */ isLoading?: boolean; /** Custom icon to display */ icon?: React.ReactNode; /** Variant for styling */ variant?: "default" | "warning" | "info" | "success"; /** Hide the default icon */ hideIcon?: boolean; /** Hide the border */ hideBorder?: boolean; // Styling props for full customization /** Additional className for the outer container */ className?: string; /** Additional className for the card wrapper */ cardClassName?: string; /** Additional className for the header section */ headerClassName?: string; /** Additional className for the icon wrapper */ iconClassName?: string; /** Additional className for the question text */ questionClassName?: string; /** Additional className for the description text */ descriptionClassName?: string; /** Additional className for the options container */ optionsClassName?: string; /** Additional className for option buttons */ buttonClassName?: string; /** Additional className for the input container */ inputContainerClassName?: string; /** Additional className for the text input */ inputClassName?: string; /** Additional className for the submit button */ submitButtonClassName?: string; } const VARIANT_STYLES = { default: { border: "border-l-attention", bg: "bg-amber-50 dark:bg-amber-900/20", icon: "text-attention", }, warning: { border: "border-l-destructive", bg: "bg-red-50 dark:bg-red-900/20", icon: "text-destructive", }, info: { border: "border-l-info", bg: "bg-blue-50 dark:bg-blue-900/20", icon: "text-info", }, success: { border: "border-l-success", bg: "bg-green-50 dark:bg-green-900/20", icon: "text-success", }, }; const VARIANT_ICONS = { default: HelpCircle, warning: AlertCircle, info: MessageSquare, success: CheckCircle, }; /** * AskUserWidget - A styled component for displaying agent prompts/questions to users * * Use this when the agent needs user input, confirmation, or selection from options. * Supports both option selection and free-form text input. */ export function AskUserWidget({ question, description, options, onSelect, onMultiSelect, onSubmit, allowFreeResponse = false, multiSelect = false, placeholder, isLoading = false, icon, variant = "default", hideIcon = false, hideBorder = false, // Styling props className, cardClassName, headerClassName, iconClassName, questionClassName, descriptionClassName, optionsClassName, buttonClassName, inputContainerClassName, inputClassName, submitButtonClassName, }: AskUserWidgetProps) { const { t } = useUITranslation(); const resolvedPlaceholder = placeholder ?? t('agent.typeYourResponse'); const [inputValue, setInputValue] = React.useState(""); const [selectedOptions, setSelectedOptions] = React.useState>(new Set()); const inputRef = React.useRef(null); const styles = VARIANT_STYLES[variant]; const DefaultIcon = VARIANT_ICONS[variant]; const toggleOption = (optionId: string) => { setSelectedOptions((prev) => { const next = new Set(prev); if (next.has(optionId)) { next.delete(optionId); } else { next.add(optionId); } return next; }); }; const handleMultiSubmit = () => { if (selectedOptions.size > 0 && onMultiSelect) { onMultiSelect(Array.from(selectedOptions)); setSelectedOptions(new Set()); } }; const handleSubmit = () => { if (inputValue.trim() && onSubmit) { onSubmit(inputValue.trim()); setInputValue(""); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; const borderClass = hideBorder ? "" : `border-l-4 ${styles.border}`; return (
{/* Header with icon and question */}
{!hideIcon && (
{icon || }
)}
{question}
{description && (

{description}

)}
{/* Options */} {options && options.length > 0 && (
{multiSelect ? ( /* Multi-select mode with checkboxes */
{options.map((option) => ( ))}
) : ( /* Single-select mode - always use full-width card layout for clarity */
{options.map((option) => ( ))}
)}
)} {/* Free-form input */} {allowFreeResponse && (
setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder={resolvedPlaceholder} disabled={isLoading} className={`flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent ${inputClassName || ""}`} />
)}
); } /** * Simple confirmation widget - Yes/No options */ export interface ConfirmationWidgetProps { question: string; description?: string; onConfirm: () => void; onCancel: () => void; confirmLabel?: string; cancelLabel?: string; isLoading?: boolean; variant?: "default" | "warning"; className?: string; } export function ConfirmationWidget({ question, description, onConfirm, onCancel, confirmLabel, cancelLabel, isLoading = false, variant = "default", className, }: ConfirmationWidgetProps) { const { t } = useUITranslation(); const resolvedConfirmLabel = confirmLabel ?? t('agent.yes'); const resolvedCancelLabel = cancelLabel ?? t('agent.no'); return ( , }, { id: "cancel", label: resolvedCancelLabel, icon: , }, ]} onSelect={(id) => { if (id === "confirm") onConfirm(); else onCancel(); }} /> ); }