| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- const API_BASE = '/api/v1';
- async function request<T>(
- endpoint: string,
- options: RequestInit = {}
- ): Promise<T> {
- const response = await fetch(`${API_BASE}${endpoint}`, {
- ...options,
- headers: {
- 'Content-Type': 'application/json',
- ...options.headers,
- },
- });
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
- return response.json();
- }
- // Printer types
- export interface Printer {
- id: number;
- name: string;
- serial_number: string;
- ip_address: string;
- access_code: string;
- model: string | null;
- is_active: boolean;
- auto_archive: boolean;
- created_at: string;
- updated_at: string;
- }
- export interface HMSError {
- code: string;
- module: number;
- severity: number; // 1=fatal, 2=serious, 3=common, 4=info
- }
- export interface PrinterStatus {
- id: number;
- name: string;
- connected: boolean;
- state: string | null;
- current_print: string | null;
- subtask_name: string | null;
- gcode_file: string | null;
- progress: number | null;
- remaining_time: number | null;
- layer_num: number | null;
- total_layers: number | null;
- temperatures: {
- bed?: number;
- bed_target?: number;
- nozzle?: number;
- nozzle_target?: number;
- chamber?: number;
- } | null;
- cover_url: string | null;
- hms_errors: HMSError[];
- }
- export interface PrinterCreate {
- name: string;
- serial_number: string;
- ip_address: string;
- access_code: string;
- model?: string;
- auto_archive?: boolean;
- }
- // Archive types
- export interface ArchiveDuplicate {
- id: number;
- print_name: string | null;
- created_at: string;
- match_type: 'exact' | 'similar'; // 'exact' = hash match, 'similar' = name match
- }
- export interface Archive {
- id: number;
- printer_id: number | null;
- filename: string;
- file_path: string;
- file_size: number;
- content_hash: string | null;
- thumbnail_path: string | null;
- timelapse_path: string | null;
- duplicates: ArchiveDuplicate[] | null;
- duplicate_count: number;
- print_name: string | null;
- print_time_seconds: number | null;
- actual_time_seconds: number | null; // Computed from started_at/completed_at
- time_accuracy: number | null; // Percentage: 100 = perfect, >100 = faster than estimated
- filament_used_grams: number | null;
- filament_type: string | null;
- filament_color: string | null;
- layer_height: number | null;
- total_layers: number | null;
- nozzle_diameter: number | null;
- bed_temperature: number | null;
- nozzle_temperature: number | null;
- status: string;
- started_at: string | null;
- completed_at: string | null;
- extra_data: Record<string, unknown> | null;
- makerworld_url: string | null;
- designer: string | null;
- is_favorite: boolean;
- tags: string | null;
- notes: string | null;
- cost: number | null;
- photos: string[] | null;
- failure_reason: string | null;
- energy_kwh: number | null;
- energy_cost: number | null;
- created_at: string;
- }
- export interface ArchiveStats {
- total_prints: number;
- successful_prints: number;
- failed_prints: number;
- total_print_time_hours: number;
- total_filament_grams: number;
- total_cost: number;
- prints_by_filament_type: Record<string, number>;
- prints_by_printer: Record<string, number>;
- average_time_accuracy: number | null;
- time_accuracy_by_printer: Record<string, number> | null;
- total_energy_kwh: number;
- total_energy_cost: number;
- }
- export interface BulkUploadResult {
- uploaded: number;
- failed: number;
- results: Array<{ filename: string; id: number; status: string }>;
- errors: Array<{ filename: string; error: string }>;
- }
- // Settings types
- export interface AppSettings {
- auto_archive: boolean;
- save_thumbnails: boolean;
- capture_finish_photo: boolean;
- default_filament_cost: number;
- currency: string;
- energy_cost_per_kwh: number;
- }
- export type AppSettingsUpdate = Partial<AppSettings>;
- // Cloud types
- export interface CloudAuthStatus {
- is_authenticated: boolean;
- email: string | null;
- }
- export interface CloudLoginResponse {
- success: boolean;
- needs_verification: boolean;
- message: string;
- }
- export interface SlicerSetting {
- setting_id: string;
- name: string;
- type: string;
- version: string | null;
- user_id: string | null;
- updated_time: string | null;
- }
- export interface SlicerSettingsResponse {
- filament: SlicerSetting[];
- printer: SlicerSetting[];
- process: SlicerSetting[];
- }
- export interface CloudDevice {
- dev_id: string;
- name: string;
- dev_model_name: string | null;
- dev_product_name: string | null;
- online: boolean;
- }
- // Smart Plug types
- export interface SmartPlug {
- id: number;
- name: string;
- ip_address: string;
- printer_id: number | null;
- enabled: boolean;
- auto_on: boolean;
- auto_off: boolean;
- off_delay_mode: 'time' | 'temperature';
- off_delay_minutes: number;
- off_temp_threshold: number;
- username: string | null;
- password: string | null;
- last_state: string | null;
- last_checked: string | null;
- created_at: string;
- updated_at: string;
- }
- export interface SmartPlugCreate {
- name: string;
- ip_address: string;
- printer_id?: number | null;
- enabled?: boolean;
- auto_on?: boolean;
- auto_off?: boolean;
- off_delay_mode?: 'time' | 'temperature';
- off_delay_minutes?: number;
- off_temp_threshold?: number;
- username?: string | null;
- password?: string | null;
- }
- export interface SmartPlugUpdate {
- name?: string;
- ip_address?: string;
- printer_id?: number | null;
- enabled?: boolean;
- auto_on?: boolean;
- auto_off?: boolean;
- off_delay_mode?: 'time' | 'temperature';
- off_delay_minutes?: number;
- off_temp_threshold?: number;
- username?: string | null;
- password?: string | null;
- }
- export interface SmartPlugEnergy {
- power: number | null; // Current watts
- voltage: number | null; // Volts
- current: number | null; // Amps
- today: number | null; // kWh used today
- yesterday: number | null; // kWh used yesterday
- total: number | null; // Total kWh
- factor: number | null; // Power factor (0-1)
- apparent_power: number | null; // VA
- reactive_power: number | null; // VAr
- }
- export interface SmartPlugStatus {
- state: string | null;
- reachable: boolean;
- device_name: string | null;
- energy: SmartPlugEnergy | null;
- }
- export interface SmartPlugTestResult {
- success: boolean;
- state: string | null;
- device_name: string | null;
- }
- // Print Queue types
- export interface PrintQueueItem {
- id: number;
- printer_id: number;
- archive_id: number;
- position: number;
- scheduled_time: string | null;
- require_previous_success: boolean;
- auto_off_after: boolean;
- status: 'pending' | 'printing' | 'completed' | 'failed' | 'skipped' | 'cancelled';
- started_at: string | null;
- completed_at: string | null;
- error_message: string | null;
- created_at: string;
- archive_name?: string | null;
- archive_thumbnail?: string | null;
- printer_name?: string | null;
- }
- export interface PrintQueueItemCreate {
- printer_id: number;
- archive_id: number;
- scheduled_time?: string | null;
- require_previous_success?: boolean;
- auto_off_after?: boolean;
- }
- export interface PrintQueueItemUpdate {
- printer_id?: number;
- position?: number;
- scheduled_time?: string | null;
- require_previous_success?: boolean;
- auto_off_after?: boolean;
- }
- // MQTT Logging types
- export interface MQTTLogEntry {
- timestamp: string;
- topic: string;
- direction: 'in' | 'out';
- payload: Record<string, unknown>;
- }
- export interface MQTTLogsResponse {
- logging_enabled: boolean;
- logs: MQTTLogEntry[];
- }
- // API functions
- export const api = {
- // Printers
- getPrinters: () => request<Printer[]>('/printers/'),
- getPrinter: (id: number) => request<Printer>(`/printers/${id}`),
- createPrinter: (data: PrinterCreate) =>
- request<Printer>('/printers/', {
- method: 'POST',
- body: JSON.stringify(data),
- }),
- updatePrinter: (id: number, data: Partial<PrinterCreate>) =>
- request<Printer>(`/printers/${id}`, {
- method: 'PATCH',
- body: JSON.stringify(data),
- }),
- deletePrinter: (id: number) =>
- request<void>(`/printers/${id}`, { method: 'DELETE' }),
- getPrinterStatus: (id: number) =>
- request<PrinterStatus>(`/printers/${id}/status`),
- connectPrinter: (id: number) =>
- request<{ connected: boolean }>(`/printers/${id}/connect`, {
- method: 'POST',
- }),
- disconnectPrinter: (id: number) =>
- request<{ connected: boolean }>(`/printers/${id}/disconnect`, {
- method: 'POST',
- }),
- // MQTT Debug Logging
- enableMQTTLogging: (printerId: number) =>
- request<{ logging_enabled: boolean }>(`/printers/${printerId}/logging/enable`, {
- method: 'POST',
- }),
- disableMQTTLogging: (printerId: number) =>
- request<{ logging_enabled: boolean }>(`/printers/${printerId}/logging/disable`, {
- method: 'POST',
- }),
- getMQTTLogs: (printerId: number) =>
- request<MQTTLogsResponse>(`/printers/${printerId}/logging`),
- clearMQTTLogs: (printerId: number) =>
- request<{ status: string }>(`/printers/${printerId}/logging`, {
- method: 'DELETE',
- }),
- // Printer File Manager
- getPrinterFiles: (printerId: number, path = '/') =>
- request<{
- path: string;
- files: Array<{
- name: string;
- is_directory: boolean;
- size: number;
- path: string;
- }>;
- }>(`/printers/${printerId}/files?path=${encodeURIComponent(path)}`),
- getPrinterFileDownloadUrl: (printerId: number, path: string) =>
- `${API_BASE}/printers/${printerId}/files/download?path=${encodeURIComponent(path)}`,
- deletePrinterFile: (printerId: number, path: string) =>
- request<{ status: string; path: string }>(`/printers/${printerId}/files?path=${encodeURIComponent(path)}`, {
- method: 'DELETE',
- }),
- getPrinterStorage: (printerId: number) =>
- request<{ used_bytes: number | null; free_bytes: number | null }>(`/printers/${printerId}/storage`),
- // Archives
- getArchives: (printerId?: number, limit = 50, offset = 0) => {
- const params = new URLSearchParams();
- if (printerId) params.set('printer_id', String(printerId));
- params.set('limit', String(limit));
- params.set('offset', String(offset));
- return request<Archive[]>(`/archives/?${params}`);
- },
- getArchive: (id: number) => request<Archive>(`/archives/${id}`),
- updateArchive: (id: number, data: {
- printer_id?: number | null;
- print_name?: string;
- is_favorite?: boolean;
- tags?: string;
- notes?: string;
- cost?: number;
- failure_reason?: string | null;
- }) =>
- request<Archive>(`/archives/${id}`, {
- method: 'PATCH',
- body: JSON.stringify(data),
- }),
- toggleFavorite: (id: number) =>
- request<Archive>(`/archives/${id}/favorite`, { method: 'POST' }),
- deleteArchive: (id: number) =>
- request<void>(`/archives/${id}`, { method: 'DELETE' }),
- getArchiveStats: () => request<ArchiveStats>('/archives/stats'),
- getArchiveDuplicates: (id: number) =>
- request<{ duplicates: ArchiveDuplicate[]; count: number }>(`/archives/${id}/duplicates`),
- backfillContentHashes: () =>
- request<{ updated: number; errors: Array<{ id: number; error: string }> }>('/archives/backfill-hashes', {
- method: 'POST',
- }),
- getArchiveThumbnail: (id: number) => `${API_BASE}/archives/${id}/thumbnail`,
- getArchiveDownload: (id: number) => `${API_BASE}/archives/${id}/download`,
- getArchiveGcode: (id: number) => `${API_BASE}/archives/${id}/gcode`,
- getArchiveTimelapse: (id: number) => `${API_BASE}/archives/${id}/timelapse`,
- scanArchiveTimelapse: (id: number) =>
- request<{ status: string; message: string; filename?: string }>(`/archives/${id}/timelapse/scan`, {
- method: 'POST',
- }),
- uploadArchiveTimelapse: async (archiveId: number, file: File): Promise<{ status: string; filename: string }> => {
- const formData = new FormData();
- formData.append('file', file);
- const response = await fetch(`${API_BASE}/archives/${archiveId}/timelapse/upload`, {
- method: 'POST',
- body: formData,
- });
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
- return response.json();
- },
- // Photos
- getArchivePhotoUrl: (archiveId: number, filename: string) =>
- `${API_BASE}/archives/${archiveId}/photos/${encodeURIComponent(filename)}`,
- uploadArchivePhoto: async (archiveId: number, file: File): Promise<{ status: string; filename: string; photos: string[] }> => {
- const formData = new FormData();
- formData.append('file', file);
- const response = await fetch(`${API_BASE}/archives/${archiveId}/photos`, {
- method: 'POST',
- body: formData,
- });
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
- return response.json();
- },
- deleteArchivePhoto: (archiveId: number, filename: string) =>
- request<{ status: string; photos: string[] | null }>(`/archives/${archiveId}/photos/${encodeURIComponent(filename)}`, {
- method: 'DELETE',
- }),
- // QR Code
- getArchiveQRCodeUrl: (archiveId: number, size = 200) =>
- `${API_BASE}/archives/${archiveId}/qrcode?size=${size}`,
- getArchiveCapabilities: (id: number) =>
- request<{
- has_model: boolean;
- has_gcode: boolean;
- build_volume: { x: number; y: number; z: number };
- }>(`/archives/${id}/capabilities`),
- // Project Page
- getArchiveProjectPage: (id: number) =>
- request<{
- title: string | null;
- description: string | null;
- designer: string | null;
- designer_user_id: string | null;
- license: string | null;
- copyright: string | null;
- creation_date: string | null;
- modification_date: string | null;
- origin: string | null;
- profile_title: string | null;
- profile_description: string | null;
- profile_cover: string | null;
- profile_user_id: string | null;
- profile_user_name: string | null;
- design_model_id: string | null;
- design_profile_id: string | null;
- design_region: string | null;
- model_pictures: Array<{ name: string; path: string; url: string }>;
- profile_pictures: Array<{ name: string; path: string; url: string }>;
- thumbnails: Array<{ name: string; path: string; url: string }>;
- }>(`/archives/${id}/project-page`),
- updateArchiveProjectPage: (id: number, data: {
- title?: string;
- description?: string;
- designer?: string;
- license?: string;
- copyright?: string;
- profile_title?: string;
- profile_description?: string;
- }) =>
- request(`/archives/${id}/project-page`, {
- method: 'PATCH',
- body: JSON.stringify(data),
- }),
- getArchiveProjectImageUrl: (archiveId: number, imagePath: string) =>
- `${API_BASE}/archives/${archiveId}/project-image/${encodeURIComponent(imagePath)}`,
- getArchiveForSlicer: (id: number, filename: string) =>
- `${API_BASE}/archives/${id}/file/${encodeURIComponent(filename.endsWith('.3mf') ? filename : filename + '.3mf')}`,
- reprintArchive: (archiveId: number, printerId: number) =>
- request<{ status: string; printer_id: number; archive_id: number; filename: string }>(
- `/archives/${archiveId}/reprint?printer_id=${printerId}`,
- { method: 'POST' }
- ),
- uploadArchive: async (file: File, printerId?: number): Promise<Archive> => {
- const formData = new FormData();
- formData.append('file', file);
- const url = printerId
- ? `${API_BASE}/archives/upload?printer_id=${printerId}`
- : `${API_BASE}/archives/upload`;
- const response = await fetch(url, {
- method: 'POST',
- body: formData,
- });
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
- return response.json();
- },
- uploadArchivesBulk: async (files: File[], printerId?: number): Promise<BulkUploadResult> => {
- const formData = new FormData();
- files.forEach((file) => formData.append('files', file));
- const url = printerId
- ? `${API_BASE}/archives/upload-bulk?printer_id=${printerId}`
- : `${API_BASE}/archives/upload-bulk`;
- const response = await fetch(url, {
- method: 'POST',
- body: formData,
- });
- if (!response.ok) {
- const error = await response.json().catch(() => ({}));
- throw new Error(error.detail || `HTTP ${response.status}`);
- }
- return response.json();
- },
- // Settings
- getSettings: () => request<AppSettings>('/settings/'),
- updateSettings: (data: AppSettingsUpdate) =>
- request<AppSettings>('/settings/', {
- method: 'PUT',
- body: JSON.stringify(data),
- }),
- resetSettings: () =>
- request<AppSettings>('/settings/reset', { method: 'POST' }),
- checkFfmpeg: () =>
- request<{ installed: boolean; path: string | null }>('/settings/check-ffmpeg'),
- // Cloud
- getCloudStatus: () => request<CloudAuthStatus>('/cloud/status'),
- cloudLogin: (email: string, password: string, region = 'global') =>
- request<CloudLoginResponse>('/cloud/login', {
- method: 'POST',
- body: JSON.stringify({ email, password, region }),
- }),
- cloudVerify: (email: string, code: string) =>
- request<CloudLoginResponse>('/cloud/verify', {
- method: 'POST',
- body: JSON.stringify({ email, code }),
- }),
- cloudSetToken: (access_token: string) =>
- request<CloudAuthStatus>('/cloud/token', {
- method: 'POST',
- body: JSON.stringify({ access_token }),
- }),
- cloudLogout: () =>
- request<{ success: boolean }>('/cloud/logout', { method: 'POST' }),
- getCloudSettings: (version = '01.09.00.00') =>
- request<SlicerSettingsResponse>(`/cloud/settings?version=${version}`),
- getCloudSettingDetail: (settingId: string) =>
- request<Record<string, unknown>>(`/cloud/settings/${settingId}`),
- getCloudDevices: () => request<CloudDevice[]>('/cloud/devices'),
- // Smart Plugs
- getSmartPlugs: () => request<SmartPlug[]>('/smart-plugs/'),
- getSmartPlug: (id: number) => request<SmartPlug>(`/smart-plugs/${id}`),
- getSmartPlugByPrinter: (printerId: number) => request<SmartPlug | null>(`/smart-plugs/by-printer/${printerId}`),
- createSmartPlug: (data: SmartPlugCreate) =>
- request<SmartPlug>('/smart-plugs/', {
- method: 'POST',
- body: JSON.stringify(data),
- }),
- updateSmartPlug: (id: number, data: SmartPlugUpdate) =>
- request<SmartPlug>(`/smart-plugs/${id}`, {
- method: 'PATCH',
- body: JSON.stringify(data),
- }),
- deleteSmartPlug: (id: number) =>
- request<void>(`/smart-plugs/${id}`, { method: 'DELETE' }),
- controlSmartPlug: (id: number, action: 'on' | 'off' | 'toggle') =>
- request<{ success: boolean; action: string }>(`/smart-plugs/${id}/control`, {
- method: 'POST',
- body: JSON.stringify({ action }),
- }),
- getSmartPlugStatus: (id: number) =>
- request<SmartPlugStatus>(`/smart-plugs/${id}/status`),
- testSmartPlugConnection: (ip_address: string, username?: string | null, password?: string | null) =>
- request<SmartPlugTestResult>('/smart-plugs/test-connection', {
- method: 'POST',
- body: JSON.stringify({ ip_address, username, password }),
- }),
- // Print Queue
- getQueue: (printerId?: number, status?: string) => {
- const params = new URLSearchParams();
- if (printerId) params.set('printer_id', String(printerId));
- if (status) params.set('status', status);
- return request<PrintQueueItem[]>(`/queue/?${params}`);
- },
- getQueueItem: (id: number) => request<PrintQueueItem>(`/queue/${id}`),
- addToQueue: (data: PrintQueueItemCreate) =>
- request<PrintQueueItem>('/queue/', {
- method: 'POST',
- body: JSON.stringify(data),
- }),
- updateQueueItem: (id: number, data: PrintQueueItemUpdate) =>
- request<PrintQueueItem>(`/queue/${id}`, {
- method: 'PATCH',
- body: JSON.stringify(data),
- }),
- removeFromQueue: (id: number) =>
- request<{ message: string }>(`/queue/${id}`, { method: 'DELETE' }),
- reorderQueue: (items: { id: number; position: number }[]) =>
- request<{ message: string }>('/queue/reorder', {
- method: 'POST',
- body: JSON.stringify({ items }),
- }),
- cancelQueueItem: (id: number) =>
- request<{ message: string }>(`/queue/${id}/cancel`, { method: 'POST' }),
- stopQueueItem: (id: number) =>
- request<{ message: string }>(`/queue/${id}/stop`, { method: 'POST' }),
- };
|