import * as React from "react";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import { Bot, ChevronLeft, ChevronRight, SquarePen, X } from "lucide-react";
import { cn } from "@/lib/utils";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { ChatInputArea } from "@/components/ui/chat-input-area";
import { Spinner } from "@/components/ui/spinner";
import {
SupportContextChip,
SupportSuggestedQuestion,
SupportStepGuideCard,
SupportArticleCard,
} from "./support-agent-primitives";
import type {
SupportAgentContext,
SupportAgentRichContent,
} from "./support-agent-primitives";
/**
* SupportAgentPanel — WealthX DS (Organism)
*
* Right-side slide-over panel providing system guidance to backoffice users.
* Answers questions about how to use WealthX, optionally grounded in the
* current page and entity context (Rovo pattern).
*
* Follows Jira Rovo's sidebar chat pattern exactly:
* — Non-blocking: no overlay, main UI remains interactive behind the panel
* — Two header modes: home (bot icon) vs chat (back arrow + conversation title)
* — Context chip: optional page+entity label below header (Rovo: context awareness)
* — Home state: New Chat CTA button + Recents list + Suggested questions
* — Chat state: message thread with optional rich content cards
* — Rich messages: AI responses can embed StepGuideCard or ArticleCard
*
* Pure display component — all state and API calls are managed by the consumer.
* Only local state: textarea value (input) and textarea height (auto-resize).
*
* Layout — Home state:
* Header — [Bot icon] Support Assistant [✕]
* Content — [✏ New chat]
* RECENTS
* > Previous conversation title
* View all →
* SUGGESTED
* > Suggested question card
* Footer — textarea + send
*
* Layout — Chat state:
* Header — [←] Conversation title [✕]
* Content — chat thread (bubbles + optional rich cards)
* Footer — textarea + send
*/
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface SupportAgentMessage {
id: string;
role: "user" | "assistant";
/** Plain text content always shown. */
content: string;
/**
* Optional rich content rendered below the text bubble.
* Supported: step-guide | article
*/
richContent?: SupportAgentRichContent;
/** True while the assistant response is streaming in. */
isStreaming?: boolean;
/**
* Status text shown beside the animated dots while the response streams
* (e.g. "Checking the help center"). Rendered only while the bubble is empty
* and streaming. The trailing "…" animates on its own — do not include dots here.
*/
streamingLabel?: string;
/** True if the message errored. */
isErrored?: boolean;
}
/** A recent conversation entry shown in the home-state Recents list. */
export interface SupportAgentRecentConversation {
id: string;
/** Short title (first user message or AI-generated summary). */
title: string;
}
export interface SupportAgentPanelProps {
open: boolean;
onClose: () => void;
/** Chat message history. Empty array = show home state. */
messages?: SupportAgentMessage[];
/**
* Recent conversations shown in the home state Recents list (Rovo pattern).
* Clicking an entry fires onOpenConversation(id).
*/
recentConversations?: SupportAgentRecentConversation[];
/**
* Pre-set help questions shown in the home state (Rovo: conversation starters).
* Shown below Recents when provided.
*/
suggestedQuestions?: string[];
/**
* Title shown in the chat-mode header (replaces bot icon row).
* Typically the first user message or an AI-generated session title.
* Required for the back-arrow chat header to appear.
*/
conversationTitle?: string;
/**
* Current backoffice page context — page name and optional entity being viewed.
* When provided, renders a SupportContextChip below the panel header so the
* AI can ground responses to the user's current location (Rovo pattern).
*
* Example: { pageLabel: "Loan Applications", entityLabel: "John Smith — #4521" }
*/
context?: SupportAgentContext;
/** True while the assistant is generating a response. */
isStreaming?: boolean;
/** True while initial data is loading — shows full-panel spinner. */
isLoading?: boolean;
/** Called when the user submits a message. Input is cleared after firing. */
onSendMessage?: (text: string) => void;
/** Called when the user selects files via the attachment button. */
onAttachFile?: (files: FileList) => void;
/** Called when the user selects images via the image upload button. */
onAttachImage?: (files: FileList) => void;
/** Called when the user clicks "New chat" — resets to home state. */
onNewChat?: () => void;
/**
* Called when the user clicks ← in the chat header to return to the home state.
* If not provided, no back button is shown.
*/
onBack?: () => void;
/** Called when the user clicks a recent conversation entry. */
onOpenConversation?: (id: string) => void;
/** Called when the user clicks "View all conversations". */
onViewAllConversations?: () => void;
/**
* Pagination for the all-conversations list. When true, a "Load more" control is
* shown at the bottom of the all-conversations view; clicking it fires
* {@link onLoadMoreConversations}. The consumer appends the next page to
* `recentConversations`.
*/
hasMoreConversations?: boolean;
/** Called when the user requests the next page in the all-conversations view. */
onLoadMoreConversations?: () => void;
/** True while the next page is loading — shows a spinner on the "Load more" control. */
isLoadingMoreConversations?: boolean;
/** True when older messages can be loaded for the active conversation. */
hasMoreMessages?: boolean;
/** True while an `onLoadMoreMessages` request is in flight. */
isLoadingMoreMessages?: boolean;
/** Fired when the user scrolls near the top of the chat — fetch older messages. */
onLoadMoreMessages?: () => void;
className?: string;
}
// ---------------------------------------------------------------------------
// TypingIndicator — three bouncing dots (same pattern as AiAssistantDrawer)
// ---------------------------------------------------------------------------
function SupportTypingIndicator() {
return (
{[0, 150, 300].map((delay) => (
))}
);
}
// ---------------------------------------------------------------------------
// StreamingStatus — optional status label with an animated "…" ellipsis
// (e.g. "Checking the help center" + animated dots), shown while streaming.
// ---------------------------------------------------------------------------
function StreamingStatus({ label }: { label?: string }) {
// No label → the standard DS typing indicator (3 bouncing dots).
if (!label) return
Failed to send. Please try again.
)}Loading…
No conversations yet.
)}Recents
Suggested
How can I help you today?
)}