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)}
))}
);
}
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) => (
-
{index + 1}
{step}
))}
{tryIt ? (
{tx("settings.channels.tryIt", "Try it")}
{tryIt}
) : null}
);
}