|
|
@@ -81,6 +81,7 @@ import {
|
|
|
MoreHorizontal,
|
|
|
SlidersHorizontal,
|
|
|
Stethoscope,
|
|
|
+ ScanEye,
|
|
|
LineChart as LineChartIcon,
|
|
|
LayoutGrid,
|
|
|
MonitorPlay,
|
|
|
@@ -101,6 +102,7 @@ import { EmbeddedCameraViewer } from '../components/EmbeddedCameraViewer';
|
|
|
import { CameraWall } from '../components/CameraWall';
|
|
|
import { MQTTDebugModal } from '../components/MQTTDebugModal';
|
|
|
import { HMSErrorModal, filterKnownHMSErrors } from '../components/HMSErrorModal';
|
|
|
+import { AiDetectionModal } from '../components/AiDetectionModal';
|
|
|
import { PrinterQueueWidget } from '../components/PrinterQueueWidget';
|
|
|
import { AMSHistoryModal } from '../components/AMSHistoryModal';
|
|
|
import { AmsBackupModal } from '../components/AmsBackupModal';
|
|
|
@@ -1772,6 +1774,9 @@ function PrinterCard({
|
|
|
bedTempPresets = BED_TEMP_DEFAULTS,
|
|
|
chamberTempPresets = CHAMBER_TEMP_DEFAULTS,
|
|
|
fanSpeedPresets = FAN_SPEED_DEFAULTS,
|
|
|
+ aiDetectionEnabled = false,
|
|
|
+ aiDetection,
|
|
|
+ aiLastError = null,
|
|
|
}: {
|
|
|
printer: Printer;
|
|
|
hideIfDisconnected?: boolean;
|
|
|
@@ -1810,6 +1815,9 @@ function PrinterCard({
|
|
|
bedTempPresets?: readonly [number, number, number];
|
|
|
chamberTempPresets?: readonly [number, number, number];
|
|
|
fanSpeedPresets?: readonly [number, number, number];
|
|
|
+ aiDetectionEnabled?: boolean;
|
|
|
+ aiDetection?: { class: string; frame_count: number; score: number };
|
|
|
+ aiLastError?: string | null;
|
|
|
}) {
|
|
|
const { t } = useTranslation();
|
|
|
const queryClient = useQueryClient();
|
|
|
@@ -1826,6 +1834,8 @@ function PrinterCard({
|
|
|
const [showPowerOffConfirm, setShowPowerOffConfirm] = useState(false);
|
|
|
const [haToggleConfirm, setHaToggleConfirm] = useState<SmartPlug | null>(null);
|
|
|
const [showHMSModal, setShowHMSModal] = useState(false);
|
|
|
+ // #1546: AI failure detection modal — opens from the AI badge.
|
|
|
+ const [showAiModal, setShowAiModal] = useState(false);
|
|
|
// #1762: AMS Filament Backup status / control modal — opens from the badge.
|
|
|
const [amsBackupModalOpen, setAmsBackupModalOpen] = useState(false);
|
|
|
const [showStopConfirm, setShowStopConfirm] = useState(false);
|
|
|
@@ -3429,6 +3439,39 @@ function PrinterCard({
|
|
|
</button>
|
|
|
);
|
|
|
})()}
|
|
|
+ {/* AI failure detection badge (#1546) — always shown while detection
|
|
|
+ is enabled for this printer, like the other health badges. Gray
|
|
|
+ "Idle" outside a monitored print, class-colored during one. */}
|
|
|
+ {aiDetectionEnabled && (() => {
|
|
|
+ const cls = aiDetection
|
|
|
+ ? (aiDetection.class === 'failure' || aiDetection.class === 'warning' ? aiDetection.class : 'safe')
|
|
|
+ : 'idle';
|
|
|
+ const colorClass =
|
|
|
+ cls === 'failure'
|
|
|
+ ? 'bg-status-error/20 text-status-error'
|
|
|
+ : cls === 'warning'
|
|
|
+ ? 'bg-status-warning/20 text-status-warning'
|
|
|
+ : cls === 'safe'
|
|
|
+ ? 'bg-status-ok/20 text-status-ok'
|
|
|
+ : 'bg-bambu-dark-tertiary text-bambu-gray';
|
|
|
+ return (
|
|
|
+ <button
|
|
|
+ onClick={() => setShowAiModal(true)}
|
|
|
+ className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs cursor-pointer hover:opacity-80 transition-opacity ${colorClass}`}
|
|
|
+ title={
|
|
|
+ aiDetection
|
|
|
+ ? t('printers.aiDetection.tooltip', {
|
|
|
+ status: t(`printers.aiDetection.${cls}`),
|
|
|
+ score: aiDetection.score.toFixed(3),
|
|
|
+ })
|
|
|
+ : t('printers.aiDetection.tooltipIdle')
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <ScanEye className="w-3 h-3" />
|
|
|
+ {t(`printers.aiDetection.${cls}`)}
|
|
|
+ </button>
|
|
|
+ );
|
|
|
+ })()}
|
|
|
{/* Maintenance Status Indicator */}
|
|
|
{maintenanceInfo && (
|
|
|
<button
|
|
|
@@ -6286,6 +6329,16 @@ function PrinterCard({
|
|
|
/>
|
|
|
)}
|
|
|
|
|
|
+ {/* AI failure detection modal (#1546) */}
|
|
|
+ {showAiModal && (
|
|
|
+ <AiDetectionModal
|
|
|
+ printerName={printer.name}
|
|
|
+ detection={aiDetection}
|
|
|
+ lastError={aiLastError}
|
|
|
+ onClose={() => setShowAiModal(false)}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
{/* AMS Filament Backup status / control modal (#1762) */}
|
|
|
{amsBackupModalOpen && status && (
|
|
|
<AmsBackupModal
|
|
|
@@ -7882,6 +7935,22 @@ export function PrintersPage() {
|
|
|
staleTime: 60 * 1000, // 1 minute
|
|
|
});
|
|
|
|
|
|
+ // Live AI failure detection state for the card badges (#1546). Matches the
|
|
|
+ // 10s refetch of the Failure Detection settings panel.
|
|
|
+ const { data: obicoPrinterStatus } = useQuery({
|
|
|
+ queryKey: ['obico-printer-status'],
|
|
|
+ queryFn: api.getObicoPrinterStatus,
|
|
|
+ refetchInterval: 10000,
|
|
|
+ });
|
|
|
+ // Badge visibility: detection enabled AND this printer in the monitored set
|
|
|
+ // (monitored_printers null = all). Live per-print state is passed separately.
|
|
|
+ const isAiMonitored = useCallback(
|
|
|
+ (printerId: number) =>
|
|
|
+ obicoPrinterStatus?.enabled === true &&
|
|
|
+ (obicoPrinterStatus.monitored_printers === null || obicoPrinterStatus.monitored_printers.includes(printerId)),
|
|
|
+ [obicoPrinterStatus],
|
|
|
+ );
|
|
|
+
|
|
|
// Fetch Spoolman status to enable link spool feature
|
|
|
const { data: spoolmanStatus } = useQuery({
|
|
|
queryKey: ['spoolman-status'],
|
|
|
@@ -8854,6 +8923,9 @@ export function PrintersPage() {
|
|
|
isSelected={selectedPrinterIds.has(printer.id)}
|
|
|
onToggleSelect={toggleSelect}
|
|
|
onOpenCompactCard={openCompactCard}
|
|
|
+ aiDetectionEnabled={isAiMonitored(printer.id)}
|
|
|
+ aiDetection={obicoPrinterStatus?.enabled ? obicoPrinterStatus.per_printer[String(printer.id)] : undefined}
|
|
|
+ aiLastError={obicoPrinterStatus?.last_error ?? null}
|
|
|
/>
|
|
|
))}
|
|
|
</div>
|
|
|
@@ -8903,6 +8975,9 @@ export function PrintersPage() {
|
|
|
isSelected={selectedPrinterIds.has(printer.id)}
|
|
|
onToggleSelect={toggleSelect}
|
|
|
onOpenCompactCard={openCompactCard}
|
|
|
+ aiDetectionEnabled={isAiMonitored(printer.id)}
|
|
|
+ aiDetection={obicoPrinterStatus?.enabled ? obicoPrinterStatus.per_printer[String(printer.id)] : undefined}
|
|
|
+ aiLastError={obicoPrinterStatus?.last_error ?? null}
|
|
|
/>
|
|
|
))}
|
|
|
</div>
|