import React, { useMemo, useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { BookOpen, Brain, ChevronRight, Database, FileText, ListChecks, MessageSquare, Plug, ShieldCheck, Sparkles, Wrench, Zap, type LucideIcon, } from 'lucide-react-native'; import { themedColor } from '../../theme'; import type { SuperagentToolRendererProps } from '../../types'; import { buildBlueprintBlocks, hasBlueprintContent, parseBlueprint, splitHeadline, type BlueprintBlock, } from '../agentBlueprintUtils'; import { ExpandableSection } from '../primitives/ExpandableSection'; import { ToolWidgetRow } from '../primitives/ToolWidgetRow'; import { toolWidgetStyles } from '../primitives/toolWidgetStyles'; import { WidgetCard } from '../primitives/WidgetCard'; import { getSettledWidgetStatus } from '../toolWidgetUtils'; const BLOCK_ICONS: Record = { persona: Sparkles, operating_rules: ShieldCheck, data_model: Database, capabilities: Wrench, connectors: Plug, skills: BookOpen, automations: Zap, channels: MessageSquare, knowledge: FileText, memory: Brain, }; /** * build_mode — native port of the web AgentBlueprint card: hero (name + * mission), capability pills that expand into a detail panel, and collapsed * build-sequence / coverage-notes sections. The web's "Build agent" / "Refine" * footer drives the chat composer through host-only APIs (chat-send bridge + * editor store) that aren't plumbed into native renderers, so the native card * is display-only — the user replies in chat to build or refine. */ export function AgentBlueprintWidget({ isLastAssistantMessage, toolCall }: SuperagentToolRendererProps) { const status = getSettledWidgetStatus(toolCall, isLastAssistantMessage); const args = useMemo(() => parseBlueprint(toolCall), [toolCall]); const blocks = useMemo(() => buildBlueprintBlocks(args), [args]); const [activeKey, setActiveKey] = useState(null); if (status === 'running') { return ; } if (status === 'error' || status === 'stopped') { return ; } if (!hasBlueprintContent(args, blocks)) { return ( ); } const activeBlock = blocks.find((block) => block.key === activeKey) ?? null; const buildSequence = Array.isArray(args.build_sequence) ? args.build_sequence : []; return ( Agent Blueprint {args.agent_name || 'agent'} {args.mission ? ( {args.mission} ) : null} {blocks.length > 0 ? ( {blocks.map((block) => ( setActiveKey((current) => (current === block.key ? null : block.key))} /> ))} ) : null} {activeBlock ? ( {activeBlock.prose ? ( {activeBlock.prose} ) : ( activeBlock.items?.map((item, index) => ) )} ) : null} {buildSequence.length > 0 || args.coverage_notes ? ( ) : null} {buildSequence.length > 0 ? ( {buildSequence.map((step, index) => ( {`${index + 1}. ${step}`} ))} ) : null} {args.coverage_notes ? ( {args.coverage_notes} ) : null} ); } function BlockPill({ block, isActive, onPress, }: { block: BlueprintBlock; isActive: boolean; onPress: () => void; }) { const Icon = BLOCK_ICONS[block.key] ?? Sparkles; return ( {block.label} {block.items ? {block.items.length} : null} ); } /** * One list item inside an expanded block: headline (title) always shown; the * long description collapses behind a chevron. Items without a "Title — desc" * split render as plain text. */ function BlueprintItem({ text }: { text: string }) { const [open, setOpen] = useState(false); const { title, description } = splitHeadline(text); if (!description) { return {title}; } return ( setOpen((current) => !current)} style={toolWidgetStyles.panelItemHeader}> {title} {open ? {description} : null} ); }