feat(obico): AI print-failure detection via self-hosted Obico ML API (#172)

Adds a Failure Detection tab under Settings that wires Bambuddy to a
  self-hosted Obico ml_api container — no cloud, no account, no WebSocket.
  While a print is running, the detection service periodically hands the
  printer's camera snapshot URL to the ML API and smooths scores over
  time (30-frame warmup + EWM, alpha=2/13, short/long rolling means) so
  one noisy frame can't trigger an action. When the smoothed score
  crosses HIGH, the configured action fires exactly once per print:
  notify, pause, or pause-and-cut-power (via linked smart plugs).

  - Backend: new obico_detection + obico_smoothing + obico_actions
    services, /obico/status and /obico/test-connection routes
    (SETTINGS_READ / SETTINGS_UPDATE), six obico_* AppSettings fields
    with validators for sensitivity/action/enabled_printers.
  - Frontend: FailureDetectionSettings component (enable, ML URL + test,
    sensitivity, action, poll interval, per-printer monitor list, live
    status + detection history), new sidebar tab with service-active
    bullet, toast on save.
  - Tests: 17 detection unit tests + 15 smoothing unit tests + 4
    frontend component tests.
  - Docs: README bullet, CHANGELOG entry, wiki page under Analytics,
    website features.html entry.
This commit is contained in:
maziggy
2026-04-13 09:54:26 +02:00
parent f1483f525d
commit eec7793955
25 changed files with 2119 additions and 448 deletions
+47
View File
@@ -925,6 +925,12 @@ export interface AppSettings {
ldap_group_mapping: string;
ldap_auto_provision: boolean;
ldap_default_group: string;
obico_enabled: boolean;
obico_ml_url: string;
obico_sensitivity: 'low' | 'medium' | 'high';
obico_action: 'notify' | 'pause' | 'pause_and_off';
obico_poll_interval: number;
obico_enabled_printers: string;
}
export type AppSettingsUpdate = Partial<AppSettings>;
@@ -1832,6 +1838,37 @@ export interface LocalBackupFile {
created_at: string;
}
export interface ObicoDetectionEvent {
printer_id: number;
task_name: string;
timestamp: string;
current_p: number;
score: number;
class: 'safe' | 'warning' | 'failure';
detections: number;
}
export interface ObicoStatus {
is_running: boolean;
last_error: string | null;
per_printer: Record<string, { class: string; frame_count: number; score: number }>;
thresholds: { low: number; high: number };
history: ObicoDetectionEvent[];
enabled: boolean;
ml_url: string;
sensitivity: 'low' | 'medium' | 'high';
action: 'notify' | 'pause' | 'pause_and_off';
poll_interval: number;
external_url_configured: boolean;
}
export interface ObicoTestConnection {
ok: boolean;
status_code: number | null;
body: string | null;
error: string | null;
}
export interface GitHubTestConnectionResponse {
success: boolean;
message: string;
@@ -4518,6 +4555,16 @@ export const api = {
deleteLocalBackup: (filename: string) =>
request<{ success: boolean; message: string }>(`/local-backup/backups/${encodeURIComponent(filename)}`, { method: 'DELETE' }),
// Obico AI failure detection
getObicoStatus: () =>
request<ObicoStatus>('/obico/status'),
testObicoConnection: (url: string) =>
request<ObicoTestConnection>('/obico/test-connection', {
method: 'POST',
body: JSON.stringify({ url }),
}),
// Local Presets (OrcaSlicer imports)
getLocalPresets: () =>
request<LocalPresetsResponse>('/local-presets/'),