import type { ReactNode } from "react";
import { CircleAlert, Loader2, RotateCcw, X } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { isNativeRuntime } from "@/lib/runtime";
import type { NanobotFeatureInfo } from "@/lib/types";
import { cn } from "@/lib/utils";
export const SETTINGS_SEARCH_INPUT_CLASS = cn(
"border-border/45 bg-settings-surface transition-colors hover:border-border/70",
"focus-visible:border-border/70 focus-visible:bg-background",
);
export function CapabilityInstallNotice({
title,
description,
installing = false,
}: {
title: string;
description: string;
installing?: boolean;
}) {
return (
);
}
export function NanobotFeatureInstallDialog({
feature,
installing,
onOpenChange,
onConfirm,
}: {
feature: NanobotFeatureInfo | null;
installing: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (feature: NanobotFeatureInfo) => void | Promise;
}) {
const { t } = useTranslation();
const tx = (key: string, fallback: string, values?: Record) =>
t(key, { defaultValue: fallback, ...(values ?? {}) });
const name = feature?.display_name || feature?.name || "";
return (
);
}
export function DismissibleStatusMessage({
message,
isError,
onDismiss,
}: {
message: string;
isError: boolean;
onDismiss: () => void;
}) {
const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
return (
{message}
);
}
export function RestartRequiredNotice({
message,
onRestart,
isRestarting,
}: {
message: string;
onRestart?: () => void;
isRestarting?: boolean;
}) {
const { t } = useTranslation();
return (
{message}
{onRestart ? (
) : null}
);
}
export function SettingsSectionTitle({ children }: { children: ReactNode }) {
return (
{children}
);
}
export function SettingsGroup({ children }: { children: ReactNode }) {
return (
);
}
export function SettingsRow({
title,
description,
children,
}: {
title: string;
description?: string;
children?: ReactNode;
}) {
return (
{title}
{description ? (
{description}
) : null}
{children ?
{children}
: null}
);
}
export function ReadOnlyRow({
title,
value,
description,
}: {
title: string;
value: string;
description?: string;
}) {
return (
{value}
);
}
export function RestartSettingsFooter({
dirty,
saving,
pendingRestart,
disabled = false,
message,
dirtyMessage,
pendingMessage,
onSave,
onRestart,
onReset,
isRestarting,
}: {
dirty: boolean;
saving: boolean;
pendingRestart: boolean;
disabled?: boolean;
message?: string;
dirtyMessage?: string;
pendingMessage?: string;
onSave: () => void;
onRestart?: () => void;
onReset?: () => void;
isRestarting?: boolean;
}) {
const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
const isNativeHost = isNativeRuntime();
const restartLabel = isNativeHost
? tx("app.system.restartEngine", "Restart engine")
: t("app.system.restart");
const restartingLabel = isNativeHost
? tx("app.system.restartingEngine", "Restarting engine...")
: t("app.system.restarting");
const statusMessage =
message ??
(pendingRestart && !dirty
? pendingMessage ?? tx("settings.status.savedRestartApply", "Saved. Restart when ready.")
: dirty
? dirtyMessage ?? t("settings.status.unsaved")
: undefined);
const statusTone = disabled ? "danger" : dirty || pendingRestart ? "accent" : undefined;
return (
{statusMessage}
{pendingRestart && !dirty && onRestart ? (
) : null}
{onReset ? (
) : null}
);
}
export function SettingsStatusMessage({
children,
tone,
}: {
children?: ReactNode;
tone?: "accent" | "danger";
}) {
if (!children) return null;
return (
{tone ? (
) : null}
{children}
);
}
export function StatusPill({
children,
tone = "neutral",
}: {
children: ReactNode;
tone?: "neutral" | "success" | "warning";
}) {
return (
{children}
);
}
export function NumberInput({
value,
min,
max,
onChange,
suffix,
}: {
value: number;
min: number;
max: number;
onChange: (value: number) => void;
suffix?: string;
}) {
return (
{
const parsed = Number(event.target.value);
if (Number.isFinite(parsed)) onChange(parsed);
}}
className="h-8 w-24 max-w-full rounded-full text-[13px]"
/>
{suffix ? {suffix} : null}
);
}