feat: enable right click on AI feature

This commit is contained in:
Tautvydas Derzinskas committed 2026-10-14 16:11:11 +02:00
1 parent 0b89ad962f
commit e07eb41168
4 files changed
+78 -6

No files matched your search

+3
View File
@@ -657,6 +657,9 @@
"subtitle": "What Thingport uses the models for.",
"hint": "Click a feature for its settings.",
"categorization": "AI Categorization",
"categorizationDescription": "Sorts models into your categories with AI, on import or in a run: it suggests one for you to review, or applies it.",
"turnOn": "Turn on",
"turnOff": "Turn off",
"openSettings": "Settings",
"settingsAria": "{{feature}} settings",
"toggleAria": "Turn {{feature}} on or off"
+3
View File
@@ -661,6 +661,9 @@
"subtitle": "Kam Thingport naudoja modelius.",
"hint": "Spustelėkite funkciją, kad pamatytumėte jos nustatymus.",
"categorization": "DI kategorizavimas",
"categorizationDescription": "Naudodamas DI surūšiuoja modelius į jūsų kategorijas importuojant arba paleidus: pasiūlo kategoriją peržiūrai arba ją pritaiko.",
"turnOn": "Įjungti",
"turnOff": "Išjungti",
"openSettings": "Nustatymai",
"settingsAria": "{{feature}} nustatymai",
"toggleAria": "Įjungti arba išjungti: {{feature}}"
@@ -20,7 +20,7 @@ import SectionedDialog from "../../components/SectionedDialog";
import AiCategorizationPanel from "../ModelsPage/AiCategorizationPanel";
import { AiLogo, hostOf } from "./aiLogos";
type Section = "settings" | "manage";
export type AiCategorizationSection = "settings" | "manage";
/** An option in the "Use AI" list: the model's logo and what to call it. */
function AiOption({ connection, label }: { connection: AiConnection | undefined; label: string }) {
@@ -40,6 +40,8 @@ type Props = {
settings: AiCategorizationSettings;
/** Switched on with a model to use; Manage can only be opened then, as nothing can run otherwise. */
active: boolean;
/** Where it opens, e.g. from the tile's menu. */
initialSection?: AiCategorizationSection;
connections: AiConnection[];
onClose: () => void;
onSaved: (settings: AiCategorizationSettings) => void;
@@ -53,13 +55,14 @@ type Draft = Pick<AiCategorizationSettings, "mode" | "threshold" | "on_import" |
export default function AiCategorizationDialog({
settings,
active,
initialSection = "settings",
connections,
onClose,
onSaved,
onUnauthorized,
}: Props) {
const { t } = useTranslation("app");
const [section, setSection] = useState<Section>("settings");
const [section, setSection] = useState<AiCategorizationSection>(initialSection);
const [saved, setSaved] = useState(false);
const [draft, setDraft] = useState<Draft>({
mode: settings.mode,
@@ -1,9 +1,16 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import AutoAwesomeOutlinedIcon from "@mui/icons-material/AutoAwesomeOutlined";
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
import TuneIcon from "@mui/icons-material/Tune";
import Alert from "@mui/material/Alert";
import Box from "@mui/material/Box";
import ButtonBase from "@mui/material/ButtonBase";
import CircularProgress from "@mui/material/CircularProgress";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Stack from "@mui/material/Stack";
import Switch from "@mui/material/Switch";
import Tooltip from "@mui/material/Tooltip";
@@ -12,7 +19,8 @@ import { UnauthorizedError } from "../../api/client";
import { settingsApi, type AiCategorizationSettings } from "../../api/settings";
import type { AiConnection } from "../../api/aiConnections";
import SectionHeader from "../../components/SectionHeader";
import AiCategorizationDialog from "./AiCategorizationDialog";
import { menuPosition, TileMenuHeader } from "../../components/Layout/ConfigurationDialog/AppTiles";
import AiCategorizationDialog, { type AiCategorizationSection } from "./AiCategorizationDialog";
import CategorizationAnimation from "./CategorizationAnimation";
import { AiLogo } from "./aiLogos";
@@ -26,7 +34,9 @@ const tileShadow = "inset 0 0 0 1px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0,
export default function AiFeaturesSection({ connections, onUnauthorized }: Props) {
const { t } = useTranslation("app");
const [settings, setSettings] = useState<AiCategorizationSettings | null>(null);
const [open, setOpen] = useState(false);
// The section the dialog is open on, or null when closed.
const [open, setOpen] = useState<AiCategorizationSection | null>(null);
const [menu, setMenu] = useState<{ top: number; left: number } | null>(null);
const [switching, setSwitching] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -82,7 +92,8 @@ export default function AiFeaturesSection({ connections, onUnauthorized }: Props
<Stack alignItems="center" spacing={0.75}>
<Tooltip title={t("adminSettings.aiFeatures.openSettings")} placement="top">
<ButtonBase
onClick={() => setOpen(true)}
onClick={() => setOpen("settings")}
onContextMenu={(event) => setMenu(menuPosition(event))}
disabled={!settings}
aria-label={t("adminSettings.aiFeatures.settingsAria", { feature: label })}
sx={{
@@ -152,12 +163,64 @@ export default function AiFeaturesSection({ connections, onUnauthorized }: Props
{error}
</Alert>
)}
<Menu
open={Boolean(menu)}
onClose={() => setMenu(null)}
anchorReference="anchorPosition"
anchorPosition={menu ?? undefined}
slotProps={{ paper: { sx: { minWidth: 240, maxWidth: 300 } } }}
>
<TileMenuHeader
logo={<AutoAwesomeOutlinedIcon fontSize="small" sx={{ color: "text.secondary" }} />}
name={label}
status={t("adminSettings.aiFeatures.categorizationDescription")}
/>
<MenuItem
disabled={!hasModel || !settings || switching}
onClick={() => {
setMenu(null);
void toggle(!enabled);
}}
>
<ListItemIcon>
<PowerSettingsNewIcon fontSize="small" />
</ListItemIcon>
<ListItemText>
{enabled ? t("adminSettings.aiFeatures.turnOff") : t("adminSettings.aiFeatures.turnOn")}
</ListItemText>
</MenuItem>
<MenuItem
disabled={!settings}
onClick={() => {
setMenu(null);
setOpen("settings");
}}
>
<ListItemIcon>
<TuneIcon fontSize="small" />
</ListItemIcon>
<ListItemText>{t("adminSettings.aiCategorization.settings")}</ListItemText>
</MenuItem>
<MenuItem
disabled={!settings || !enabled}
onClick={() => {
setMenu(null);
setOpen("manage");
}}
>
<ListItemIcon>
<AutoAwesomeOutlinedIcon fontSize="small" />
</ListItemIcon>
<ListItemText>{t("adminSettings.aiCategorization.manage")}</ListItemText>
</MenuItem>
</Menu>
{open && settings && (
<AiCategorizationDialog
settings={settings}
active={enabled}
connections={connections ?? []}
onClose={() => setOpen(false)}
initialSection={open}
onClose={() => setOpen(null)}
onSaved={setSettings}
onUnauthorized={onUnauthorized}
/>