import { useMemo, useState, type ReactNode } from "react"; import { Clipboard, ExternalLink, Loader2 } from "lucide-react"; import { useTranslation } from "react-i18next"; import { channelUiPresentation } from "@/channel-plugins/registry"; import { Button } from "@/components/ui/button"; import { docsUrlWithBase, type ChannelProviderPreset, type ChannelSetupPresentation, } from "@/components/settings/channels/catalog"; import { channelValidationCheckIcon, channelValidationCheckIconClass, channelValidationStatusClass, channelValidationStatusIcon, channelValidationStatusLabel, } from "@/components/settings/channels/CredentialForm"; import { useLogoFallback } from "@/hooks/useLogoFallback"; import { copyTextToClipboard } from "@/lib/clipboard"; import { logoFallbackUrls } from "@/lib/provider-brand"; import type { ChannelValidationPayload, NanobotFeatureInfo, } from "@/lib/types"; import { cn } from "@/lib/utils"; export function ChannelGuideLink({ feature, setup, chatAppsDocsUrl, compact = false, }: { feature: NanobotFeatureInfo; setup: ChannelSetupPresentation; chatAppsDocsUrl?: string; compact?: boolean; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const presentation = channelUiPresentation(feature.name, feature.webui); const logoUrls = useMemo( () => logoFallbackUrls(setup.docsLogoUrl ?? presentation?.logoUrl), [presentation?.logoUrl, setup.docsLogoUrl], ); const { logoUrl, onLogoError, onLogoLoad } = useLogoFallback(logoUrls); const Icon = presentation?.icon; const initials = presentation?.initials ?? feature.display_name.slice(0, 2).toUpperCase(); const color = presentation?.color ?? "#6B7280"; const docsUrl = docsUrlWithBase(setup.docsUrl, chatAppsDocsUrl); if (!docsUrl) return null; return ( {logoUrl ? ( ) : Icon ? ( ) : ( initials )} {setup.docsLabel ?? tx("settings.channels.officialGuide", "Official guide")} ); } export function ChannelSetupLinks({ feature, setup, chatAppsDocsUrl, }: { feature: NanobotFeatureInfo; setup: ChannelSetupPresentation; chatAppsDocsUrl?: string; }) { return (
); } export function ChannelOfficialLink({ feature, setup, }: { feature: NanobotFeatureInfo; setup: ChannelSetupPresentation; }) { const presentation = channelUiPresentation(feature.name, feature.webui); const logoUrls = useMemo( () => logoFallbackUrls(setup.docsLogoUrl ?? presentation?.logoUrl), [presentation?.logoUrl, setup.docsLogoUrl], ); const { logoUrl, onLogoError, onLogoLoad } = useLogoFallback(logoUrls); const Icon = presentation?.icon; const initials = presentation?.initials ?? feature.display_name.slice(0, 2).toUpperCase(); const color = presentation?.color ?? "#6B7280"; const label = setup.officialLabel; if (!setup.officialUrl || !label) return null; return ( {logoUrl ? ( ) : Icon ? ( ) : ( {initials} )} {label} ); } export function ChannelSetupActions({ feature, setup, onNotice, }: { feature: NanobotFeatureInfo; setup: ChannelSetupPresentation; onNotice: (message: string | null) => void; }) { const { t } = useTranslation(); if (!setup.actions?.length) return null; return (
{setup.actions.map((action) => ( ))} {channelUiPresentation(feature.name, feature.webui)?.displayName ?? feature.display_name}
); } export function ChannelProviderPresets({ presets, onApply, }: { presets: ChannelProviderPreset[]; onApply: (preset: ChannelProviderPreset) => void; }) { const { t } = useTranslation(); const [selected, setSelected] = useState(""); if (!presets.length) return null; return (
{t("settings.channels.providerPreset", { defaultValue: "Provider" })}
{presets.map((preset) => ( ))}
); } export function ChannelValidationBadge({ validation, validating, feature, }: { validation: ChannelValidationPayload | null; validating: boolean; feature: NanobotFeatureInfo; }) { const { t } = useTranslation(); const status = validation?.status ?? (feature.configured ? "configured" : "needs_setup"); const label = validating ? t("settings.channels.checking", { defaultValue: "Checking..." }) : channelValidationStatusLabel(status, t); return ( {validating ? ( ) : ( channelValidationStatusIcon(status) )} {label} ); } export function ChannelValidationDetails({ validation }: { validation: ChannelValidationPayload | null }) { const message = validation?.message; if (!validation?.identity?.name && !message) return null; return (
{validation?.identity?.name ? validation.identity.workspace ? `${validation.identity.name} ยท ${validation.identity.workspace}` : validation.identity.name : message}
); } export function ChannelValidationChecks({ validation }: { validation: ChannelValidationPayload }) { const { t } = useTranslation(); if (!validation.checks.length) return null; return (
{t("settings.channels.connectionChecks")}
{validation.checks.slice(0, 6).map((check) => (
{channelValidationCheckIcon(check.status)}
{check.label}
{check.message ? (
{check.message}
) : null} {check.action_url ? ( {t("settings.channels.open")} ) : null}
))}
); } export function ChannelSetupSteps({ steps, action, tryIt, }: { steps: string[]; action?: ReactNode; tryIt?: string; }) { const { t } = useTranslation(); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); return (
{tx("settings.channels.setupSteps", "Next steps")}
{action}
    {steps.map((step, index) => (
  1. {index + 1} {step}
  2. ))}
{tryIt ? (
{tx("settings.channels.tryIt", "Try it")} {tryIt}
) : null}
); }