| 1 | import { |
| 2 | formatProviderSubagentStatus, |
| 3 | type ProviderSubagentStatus, |
| 4 | } from "@t3tools/client-runtime/state/thread-execution"; |
| 5 | import { isOrchestrationV2WorkActive } from "@t3tools/contracts"; |
| 6 | import { View } from "react-native"; |
| 7 | |
| 8 | import { AppText as Text } from "../../components/AppText"; |
| 9 | import { ProviderIcon } from "../../components/ProviderIcon"; |
| 10 | import { RequestActionButton } from "./RequestActionButton"; |
| 11 | import { useVisibleSecondClock } from "./use-visible-second-clock"; |
| 12 | |
| 13 | /** |
| 14 | * Replaces the composer on a provider-native subagent thread. The provider |
| 15 | * runs that conversation, so there is nothing to send; the bar says which |
| 16 | * model is working, for how long, and leads back to the parent. |
| 17 | */ |
| 18 | export function ProviderSubagentBar(props: { |
| 19 | /** Driver and catalog icon of the provider running the subagent. */ |
| 20 | readonly provider: { readonly driver: string; readonly iconUrl?: string | undefined } | null; |
| 21 | readonly modelLabel: string; |
| 22 | /** Reasoning effort as the composer names it, when the subagent has one. */ |
| 23 | readonly effortLabel: string | null; |
| 24 | /** Null until the subagent's root turn arrives. */ |
| 25 | readonly status: ProviderSubagentStatus | null; |
| 26 | readonly onOpenParent: (() => void) | null; |
| 27 | }) { |
| 28 | const live = props.status !== null && isOrchestrationV2WorkActive(props.status.status); |
| 29 | const nowMs = useVisibleSecondClock(live); |
| 30 | const statusLabel = formatProviderSubagentStatus(props.status, nowMs); |
| 31 | const modelDescription = |
| 32 | props.effortLabel === null ? props.modelLabel : `${props.modelLabel}, ${props.effortLabel}`; |
| 33 | |
| 34 | return ( |
| 35 | <View className="flex-row items-center gap-3 rounded-[20px] border border-border-subtle bg-card-alt py-2 pe-2 ps-4"> |
| 36 | {/* Only the text is one element, so "Open parent" stays reachable. */} |
| 37 | <View |
| 38 | accessible |
| 39 | accessibilityLabel={`${modelDescription} subagent, ${statusLabel}. It runs on its own and cannot take messages.`} |
| 40 | className="min-w-0 flex-1 gap-0.5" |
| 41 | > |
| 42 | <View className="min-w-0 flex-row items-center gap-1.5"> |
| 43 | {props.provider ? ( |
| 44 | <ProviderIcon |
| 45 | iconUrl={props.provider.iconUrl} |
| 46 | provider={props.provider.driver} |
| 47 | size={16} |
| 48 | /> |
| 49 | ) : null} |
| 50 | <Text numberOfLines={1} className="min-w-0 shrink font-t3-bold text-sm text-foreground"> |
| 51 | {props.modelLabel} |
| 52 | </Text> |
| 53 | {props.effortLabel === null ? null : ( |
| 54 | <Text |
| 55 | numberOfLines={1} |
| 56 | className="shrink-0 font-sans text-sm text-foreground-secondary" |
| 57 | > |
| 58 | {props.effortLabel} |
| 59 | </Text> |
| 60 | )} |
| 61 | </View> |
| 62 | <Text |
| 63 | numberOfLines={1} |
| 64 | className="font-sans text-xs text-foreground-secondary" |
| 65 | style={{ fontVariant: ["tabular-nums"] }} |
| 66 | > |
| 67 | {statusLabel} · Runs on its own |
| 68 | </Text> |
| 69 | </View> |
| 70 | {props.onOpenParent ? ( |
| 71 | <RequestActionButton label="Open parent" tone="secondary" onPress={props.onOpenParent} /> |
| 72 | ) : null} |
| 73 | </View> |
| 74 | ); |
| 75 | } |