import { useMemo, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { ArrowUpRight } from "lucide-react"; import LessonPlanIcon from "#/icons/lesson-plan.svg?react"; import { Typography } from "#/ui/typography"; import { I18nKey } from "#/i18n/declaration"; import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer"; import { useHandleBuildPlanClick } from "#/hooks/use-handle-build-plan-click"; import { cn } from "#/utils/utils"; import { useSelectConversationTab } from "#/hooks/use-select-conversation-tab"; import { planComponents, createPlanComponents, } from "#/components/features/markdown/plan-components"; import { useScrollContext } from "#/context/scroll-context"; const MAX_CONTENT_LENGTH = 300; // Shine effect class for streaming text const SHINE_TEXT_CLASS = "shine-text"; // Plan components with shine effect applied for streaming state const shineComponents = createPlanComponents(SHINE_TEXT_CLASS); interface PlanPreviewProps { /** Raw plan content from PLAN.md file */ planContent?: string | null; /** Whether the plan content is actively being streamed */ isStreaming?: boolean; /** Whether the Build button should be disabled (e.g., while streaming) */ isBuildDisabled?: boolean; } /* eslint-disable i18next/no-literal-string */ export function PlanPreview({ planContent, isStreaming, isBuildDisabled, }: PlanPreviewProps) { const { t } = useTranslation("openhands"); const { navigateToTab } = useSelectConversationTab(); const { handleBuildPlanClick } = useHandleBuildPlanClick(); const { scrollDomToBottom } = useScrollContext(); const handleViewClick = () => { navigateToTab("planner"); }; // Handle Build action with scroll to bottom const handleBuildClick = useCallback( (event?: React.MouseEvent) => { handleBuildPlanClick(event); scrollDomToBottom(); }, [handleBuildPlanClick, scrollDomToBottom], ); // Truncate plan content for preview const truncatedContent = useMemo(() => { if (!planContent) return ""; if (planContent.length <= MAX_CONTENT_LENGTH) return planContent; return `${planContent.slice(0, MAX_CONTENT_LENGTH)}...`; }, [planContent]); if (!planContent) { return null; } return (
{/* Header */}
{t(I18nKey.COMMON$PLAN_MD)}
{/* Content */}
{truncatedContent && ( <> {truncatedContent} {planContent && planContent.length > MAX_CONTENT_LENGTH && ( )} )}
{/* Footer */}
); }