import type { MachineView, Session, SessionMachineAccess } from "@opengeni/sdk";
import { useEffect, useId, useRef, useState, type ReactNode } from "react";
import { useSessionStart, type UseSessionStartOptions } from "../hooks/use-session-start";
import { useOpenGeniText } from "../messages/runtime";
import { machineDisplayName } from "../sandbox-label-model";
import {
personalMachineCopy,
sessionAdmissionCopy,
sessionAdmissionMachineChangedCopy,
sessionAdmissionNeedsDifferentMachine,
sessionAdmissionReason,
} from "../session-admission-model";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
/**
* Why a chat that runs on a personal machine can't take somebody's message,
* with the ways forward. `queued` explains a saved message the runtime
* refused; otherwise it warns about a draft before it is sent.
*/
export function PersonalMachineExplanation({
access,
queued = false,
action,
}: {
access: SessionMachineAccess;
queued?: boolean | undefined;
/** A machine picker or other way forward, shown before the copy-request button. */
action?: ReactNode | undefined;
}) {
const t = useOpenGeniText();
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">("idle");
const copy = personalMachineCopy(t, access, { queued });
const request = copy.ownerRequest;
return (
<>
) : null}
);
}
export type SessionMachineChoicesProps = {
/** Machines the saved message can move to; see `savedMessageMachineChoices`. */
machines: readonly MachineView[];
/** Move the chat; resolve to an error to show, or null once it moved. */
onChoose: (machine: MachineView) => Promise;
disabled?: boolean | undefined;
/** A more specific reason for the last failed move (for example, the machine went offline). */
failureDetail?: string | null | undefined;
};
/**
* A compact "Choose machine" disclosure listing the machines the person may
* move the chat to. Moving never resends anything: the saved message runs only
* after an explicit recheck.
*/
export function SessionMachineChoices({
machines,
onChoose,
disabled,
failureDetail,
}: SessionMachineChoicesProps) {
const t = useOpenGeniText();
const listId = useId();
const [open, setOpen] = useState(false);
const [moving, setMoving] = useState(null);
const [error, setError] = useState(null);
return (
// `contents`: the toggle sits in its parent's action row (beside "Copy
// request"); the open list wraps onto its own full-width line below it.
{open ? (
{machines.length === 0 ? (
{t("sessionAdmission.noMachineChoices")}
) : (
{machines.map((machine) => (
))}
)}
{t("sessionAdmission.savedMessageMachineNote")}
{error ? (
{failureDetail || error}
) : null}
) : null}
);
}
export type SessionStartNoticeProps = UseSessionStartOptions & {
/** The person may control this session. The server enforces it either way. */
canControl?: boolean | undefined;
/** Another control action (send, pause, resume) is in flight. */
busy?: boolean | undefined;
/** Warn before sending when the chat runs on somebody else's personal machine. */
showPreSendWarning?: boolean | undefined;
};
/**
* Everything a chat embedding needs to explain why a message can't start:
* the admission block for a saved message the runtime refused and, before
* sending, the personal-machine warning. Offers the machines the person may
* use, moves the chat, and rechecks with one fenced Resume, so the saved
* message is kept and never sent twice. Access control is the server's; this
* only explains it and offers the allowed ways forward. Every word comes from
* the message catalog (`sessionAdmission.*`).
*/
export function SessionStartNotice({
canControl = true,
busy = false,
showPreSendWarning = true,
...options
}: SessionStartNoticeProps) {
const start = useSessionStart(options);
const { session } = options;
const picker = start.canChooseMachine ? (
) : undefined;
if (start.reason) {
return (
);
}
if (!showPreSendWarning || !start.warnBeforeSending) return null;
return ;
}