| .. | .. |
|---|
| 14 | 14 | WsOutgoing, |
|---|
| 15 | 15 | } from "../types"; |
|---|
| 16 | 16 | import { wsClient } from "../services/websocket"; |
|---|
| 17 | +import { sendWol, isValidMac } from "../services/wol"; |
|---|
| 17 | 18 | |
|---|
| 18 | 19 | const SECURE_STORE_KEY = "pailot_server_config"; |
|---|
| 19 | 20 | |
|---|
| .. | .. |
|---|
| 24 | 25 | disconnect: () => void; |
|---|
| 25 | 26 | sendTextMessage: (text: string) => boolean; |
|---|
| 26 | 27 | sendVoiceMessage: (audioBase64: string, transcript?: string) => boolean; |
|---|
| 28 | + sendImageMessage: (imageBase64: string, caption: string, mimeType: string) => boolean; |
|---|
| 27 | 29 | sendCommand: (command: string, args?: Record<string, unknown>) => boolean; |
|---|
| 28 | 30 | saveServerConfig: (config: ServerConfig) => Promise<void>; |
|---|
| 29 | 31 | onMessageReceived: React.MutableRefObject< |
|---|
| .. | .. |
|---|
| 52 | 54 | onClose: () => setStatus("disconnected"), |
|---|
| 53 | 55 | onError: () => setStatus("disconnected"), |
|---|
| 54 | 56 | onMessage: (data) => { |
|---|
| 55 | | - onMessageReceived.current?.(data as WsIncoming); |
|---|
| 57 | + const msg = data as unknown as WsIncoming; |
|---|
| 58 | + // Handle server-side status changes (compaction indicator) |
|---|
| 59 | + if (msg.type === "status") { |
|---|
| 60 | + if (msg.status === "compacting") setStatus("compacting"); |
|---|
| 61 | + else if (msg.status === "online") setStatus("connected"); |
|---|
| 62 | + return; |
|---|
| 63 | + } |
|---|
| 64 | + onMessageReceived.current?.(msg); |
|---|
| 56 | 65 | }, |
|---|
| 57 | 66 | }); |
|---|
| 58 | 67 | }, []); |
|---|
| .. | .. |
|---|
| 70 | 79 | } |
|---|
| 71 | 80 | } |
|---|
| 72 | 81 | |
|---|
| 73 | | - function connectToServer(config: ServerConfig) { |
|---|
| 82 | + async function connectToServer(config: ServerConfig) { |
|---|
| 74 | 83 | setStatus("connecting"); |
|---|
| 75 | | - const url = `ws://${config.host}:${config.port}`; |
|---|
| 76 | | - wsClient.connect(url); |
|---|
| 84 | + |
|---|
| 85 | + // Fire-and-forget WoL — never block the WebSocket connection |
|---|
| 86 | + if (config.macAddress && isValidMac(config.macAddress)) { |
|---|
| 87 | + sendWol(config.macAddress, config.host).catch(() => {}); |
|---|
| 88 | + } |
|---|
| 89 | + |
|---|
| 90 | + // Build URL list: local first (preferred), then remote |
|---|
| 91 | + const urls: string[] = []; |
|---|
| 92 | + if (config.localHost) { |
|---|
| 93 | + urls.push(`ws://${config.localHost}:${config.port}`); |
|---|
| 94 | + } |
|---|
| 95 | + urls.push(`ws://${config.host}:${config.port}`); |
|---|
| 96 | + wsClient.connect(urls); |
|---|
| 77 | 97 | } |
|---|
| 78 | 98 | |
|---|
| 79 | 99 | const connect = useCallback( |
|---|
| .. | .. |
|---|
| 110 | 130 | [] |
|---|
| 111 | 131 | ); |
|---|
| 112 | 132 | |
|---|
| 133 | + const sendImageMessage = useCallback( |
|---|
| 134 | + (imageBase64: string, caption: string = "", mimeType: string = "image/jpeg"): boolean => { |
|---|
| 135 | + return wsClient.send({ type: "image", imageBase64, caption, mimeType }); |
|---|
| 136 | + }, |
|---|
| 137 | + [] |
|---|
| 138 | + ); |
|---|
| 139 | + |
|---|
| 113 | 140 | const sendCommand = useCallback( |
|---|
| 114 | 141 | (command: string, args?: Record<string, unknown>): boolean => { |
|---|
| 115 | 142 | const msg: WsOutgoing = { type: "command", command, args }; |
|---|
| .. | .. |
|---|
| 127 | 154 | disconnect, |
|---|
| 128 | 155 | sendTextMessage, |
|---|
| 129 | 156 | sendVoiceMessage, |
|---|
| 157 | + sendImageMessage, |
|---|
| 130 | 158 | sendCommand, |
|---|
| 131 | 159 | saveServerConfig, |
|---|
| 132 | 160 | onMessageReceived, |
|---|