import React, { useRef, useState } from 'react'; import { ActivityIndicator, KeyboardAvoidingView, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native'; import { ArrowUp, X } from 'lucide-react-native'; import { SvgXml } from 'react-native-svg'; import { ConnectorBrandIcon, hasConnectorBrandIcon } from '../connectors/connectorBrandIcons'; import { connectorLabel } from '../connectors/connectorLabel'; import { SUPERAGENT_IDEA_CATEGORIES } from './agentIdeas.generated'; import { useAgentBi } from '../../analytics/mixpanelContext'; import { SUPERAGENT_MARK_SVG } from '../branding/superagentMark'; import { editorShellStyles } from '../editor/editorShellStyles'; import { styles } from '../../styles'; import { themedColor } from '../../theme'; import type { SuperagentCreateAgentInput } from '../../types'; /** * No-agents home, mirroring the DS web superagent home (DsAgentHomePage / * DsAgentHero): the warm "dark-stone" surface, a two-line hero with the * Superagent flower mark + accent word, a create-a-task composer, and the * category-tabbed idea cards. Tapping an idea fills the composer (prompt text + * connector chips) like the web — it does NOT create an agent; only Send does, * passing the typed prompt + selected connectors through `onCreateAgent`. */ // How much of the viewport the ideas section occupies below the hero. Generous // so the label + tabs + the first couple of suggestions are clearly visible // above the host's floating Apps/Superagents tab bar (which overlays the bottom // of the scroll area) — the hero still gets the larger, upper share. const IDEAS_PEEK = 360; export function NoAgentsHero({ isCreating, onCreateAgent, }: { isCreating: boolean; // Creates the agent from the composer: `initialMessage` is the typed/idea prompt, // `connectorIds` the connectors surfaced by a tapped idea card. onCreateAgent: (input?: SuperagentCreateAgentInput) => void; }) { const bi = useAgentBi(); const [prompt, setPrompt] = useState(''); const [connectorIds, setConnectorIds] = useState([]); // Owns the hero's vertical scroll: measure the viewport (so the hero fills it and // the ideas only peek at the bottom, web-style) and scroll the composer back into // view when a tapped idea fills it. const heroScrollRef = useRef(null); const [heroViewportH, setHeroViewportH] = useState(0); // The prompt text last seeded by a tapped idea; lets us drop the idea's // connector chips once the user edits the prompt into a different task. const [ideaPrompt, setIdeaPrompt] = useState(''); // The tapped idea's identity, carried onto the create for BI; cleared on divergence. const [activeIdea, setActiveIdea] = useState<{ key: string; category: string } | null>(null); const [categoryKey, setCategoryKey] = useState(SUPERAGENT_IDEA_CATEGORIES[0].key); const category = SUPERAGENT_IDEA_CATEGORIES.find((c) => c.key === categoryKey) ?? SUPERAGENT_IDEA_CATEGORIES[0]; const canSend = prompt.trim().length > 0 && !isCreating; // Tapping an idea fills the composer (text + connectors) instead of creating — // the user reviews/edits and then hits Send. const fillFromIdea = (cardKey: string, cardPrompt: string, cardIcons: string[]) => { void bi.trackHome('Idea Card Click', { idea_key: cardKey, idea_category: categoryKey }); setPrompt(cardPrompt); setIdeaPrompt(cardPrompt); setActiveIdea({ key: cardKey, category: categoryKey }); setConnectorIds(cardIcons.filter(hasConnectorBrandIcon)); // Bring the composer back into view so the user sees the prompt it just filled. // Defer past the state-driven reflow (connector chips grow the content) so // scrollTo lands on the final offset instead of a stale one. requestAnimationFrame(() => { heroScrollRef.current?.scrollTo({ y: 0, animated: true }); }); }; // Editing the prompt away from the tapped idea clears its suggested connectors, // so a replaced task doesn't submit stale connector chips. Appending to or // trimming the idea text keeps them — only a real divergence clears (web DS // parity, see useDsAgentCreate.handlePromptChange). const onPromptChange = (text: string) => { setPrompt(text); if (!ideaPrompt) return; const typed = text.trim().toLowerCase(); const idea = ideaPrompt.trim().toLowerCase(); const stillSameIdea = idea.length > 0 && (typed.startsWith(idea) || idea.startsWith(typed)); if (!stillSameIdea) { setConnectorIds([]); setIdeaPrompt(''); setActiveIdea(null); } }; const submit = () => { if (!canSend) return; onCreateAgent({ initialMessage: prompt.trim() || undefined, connectorIds: connectorIds.length > 0 ? connectorIds : undefined, ideaKey: activeIdea?.key, ideaCategory: activeIdea?.category, }); }; return ( setHeroViewportH((prev) => Math.max(prev, e.nativeEvent?.layout.height))} > {/* Hero block fills the measured viewport minus a peek of the ideas section below, centering the title + composer like the web hero. */} Give your Superagent its first task. {connectorIds.length > 0 ? ( {connectorIds.map((id) => ( {connectorLabel(id)} setConnectorIds((current) => current.filter((c) => c !== id))} > ))} ) : null} [ styles.heroSendButton, !canSend && styles.heroSendButtonDisabled, pressed && styles.pressed, ]} > {isCreating ? ( ) : ( )} Or start from one of these ideas: {/* Ideas section — lighter DS "standard" surface band, set off from the dark-stone hero canvas above (web DS parity). */} {/* Category tabs — same nav-tab treatment as the settings modal / editor drawer */} {SUPERAGENT_IDEA_CATEGORIES.map((cat) => { const selected = cat.key === categoryKey; return ( setCategoryKey(cat.key)} style={({ pressed }) => [ editorShellStyles.segmentButton, selected && editorShellStyles.segmentButtonActive, pressed && styles.pressed, ]} > {cat.label} ); })} {/* Idea suggestions — list of rows divided by separators (title + description) */} {category.cards.map((card) => ( fillFromIdea(card.key, card.prompt || card.title, card.icons)} style={({ pressed }) => [styles.ideaRow, pressed && styles.cardPressed]} > {card.title} {card.description ? {card.description} : null} {card.icons.length > 0 ? ( {card.icons.filter(hasConnectorBrandIcon).map((id) => ( ))} ) : null} ))} ); }