"use client";
import type { McpAiCopy } from "./copy";
import { type GuidedNav, type GuidedStep } from "@12-apps/onboarding";
import { providerForHostId, type AiHostGuide } from "../guide";
import { activeAgo, resolveHost, type AiConnection } from "./ai-connection-utils";
import {
ConfigureStageStep,
ConfigureStep,
ConfirmStep,
ConnectStep,
CopyUrlStep,
InstallStep,
} from "./ai-flow-steps";
import { AiStatusBoard, type DisconnectHandler, type HostStatus } from "./ai-status-board";
import { HostSelectStep } from "./host-select-step";
export type { AiConnection } from "./ai-connection-utils";
export { connectedSummary, connectedTitle } from "./ai-connection-utils";
/** The completed status board: green for each connected host, red for the rest. */
export function StatusBoard({
nav,
connections,
hosts,
onDisconnect,
copy,
}: {
nav: GuidedNav;
connections: readonly AiConnection[];
hosts: readonly AiHostGuide[];
onDisconnect?: DisconnectHandler;
copy: McpAiCopy;
}): React.JSX.Element {
const legacyHostId = (nav.data.connectedHost ?? nav.data.selectedHost) as string | undefined;
const statuses: HostStatus[] = hosts.map((host) => {
const provider = providerForHostId(host.id);
// Prefer a provider-attributed connection; fall back to a legacy null-host
// connection only for the host the owner completed the flow with.
const connection =
(provider ? connections.find((c) => c.host === provider) : undefined) ??
(host.id === legacyHostId ? connections.find((c) => c.host === null) : undefined) ??
null;
return {
host,
connected: connection !== null,
detail: connection ? activeAgo(connection.lastActiveAt, copy.summary) : undefined,
};
});
/** Re-enter this host's setup — the manual copy path, or its one-click install. */
const goToConnect = (hostId: string): void => {
nav.goTo(hosts.find((h) => h.id === hostId)?.pluginUrl ? "install" : "copy", {
selectedHost: hostId,
});
};
/**
* Disconnecting the LAST assistant leaves the section configured with nothing
* configured: a collapsed "IA conectada" summary hiding an all-red board, with
* the setup instructions two clicks away. So when the revoke empties the
* board, walk straight back into that host's instructions — the owner who just
* cut an assistant off is the likeliest person to reconnect one, and the steps
* are the only thing left worth showing.
*
* Cutting one of SEVERAL leaves the board meaningful, so it stays put.
* `statuses` is the pre-revoke picture, which is what makes "was that the last
* one?" answerable here without waiting for the app to re-read.
*/
const disconnect: DisconnectHandler | undefined = onDisconnect
? async (hostId) => {
await onDisconnect(hostId);
const revoked = providerForHostId(hostId);
const othersLive = statuses.some(
(status) => status.connected && providerForHostId(status.host.id) !== revoked,
);
if (!othersLive) goToConnect(hostId);
}
: undefined;
return (
);
}
/** The first step (host picker) — picking a card advances to the right path. */
function selectStep(hosts: readonly AiHostGuide[], copy: McpAiCopy): GuidedStep {
return {
id: "select",
label: copy.flow.steps.select,
render: (nav: GuidedNav) => (
nav.goTo(hosts.find((h) => h.id === hostId)?.pluginUrl ? "install" : "copy", {
selectedHost: hostId,
})
}
/>
),
};
}
/** The Copiar-URL step, shared by every manual flow. */
function copyStep(endpointUrl: string, copy: McpAiCopy): GuidedStep {
return {
id: "copy",
label: copy.flow.steps.copyUrl,
render: (nav: GuidedNav) => ,
};
}
/**
* The middle steps for the full manual flow. A host with `configureStages`
* (e.g. ChatGPT) gets one step per stage — each with its own deep link — after
* Copiar URL; otherwise the default copy → configure → connect split.
*/
function manualMiddle(
host: AiHostGuide,
hosts: readonly AiHostGuide[],
endpointUrl: string,
connectPrompt: string,
copy: McpAiCopy,
): GuidedStep[] {
if (host.configureStages && host.configureStages.length > 0) {
return [
copyStep(endpointUrl, copy),
...host.configureStages.map((stage) => ({
id: stage.id,
label: stage.label,
render: (nav: GuidedNav) => (
),
})),
];
}
return [
copyStep(endpointUrl, copy),
{
id: "configure",
label: copy.flow.steps.configure,
render: (nav: GuidedNav) => (
),
},
{
id: "connect",
label: copy.flow.steps.connect,
render: (nav: GuidedNav) => (
),
},
];
}
/** The single middle step for the simplified plugin flow: install. */
function simpleMiddle(
hosts: readonly AiHostGuide[],
connectPrompt: string,
copy: McpAiCopy,
): GuidedStep[] {
return [
{
id: "install",
label: copy.flow.steps.install,
render: (nav: GuidedNav) => (
),
},
];
}
/**
* Build the wizard's steps for the selected host's path: the simplified
* `install` flow when the host has a published `pluginUrl`, the per-stage flow
* when it declares `configureStages`, otherwise the full copy → configure →
* connect flow. `select` and `confirm` bookend all of them.
*/
export function buildFlowSteps(opts: {
host: AiHostGuide;
hosts: readonly AiHostGuide[];
endpointUrl: string;
connectPrompt: string;
connections: readonly AiConnection[];
onRetest: () => void;
copy: McpAiCopy;
}): GuidedStep[] {
const { host, hosts, endpointUrl, connectPrompt, connections, onRetest, copy } = opts;
const middle = host.pluginUrl
? simpleMiddle(hosts, connectPrompt, copy)
: manualMiddle(host, hosts, endpointUrl, connectPrompt, copy);
const confirm: GuidedStep = {
id: "confirm",
label: copy.flow.steps.confirm,
render: (nav: GuidedNav) => (
),
};
return [selectStep(hosts, copy), ...middle, confirm];
}