## 後端(Phase 1) 新增 flash 模組(從 edge-ai-platform 搬入): - server/internal/flash/service.go:StartFlash + 模型相容性檢查 + 晶片 NEF 解析 - server/internal/flash/progress.go:Flash 進度追蹤器 - server/internal/api/ws/flash_ws.go:WebSocket 推送 flash 進度 - device_handler.go:新增 FlashDevice method + flashSvc 欄位 - router.go:新增 POST /api/devices/:id/flash + WS /ws/devices/:id/flash-progress - main.go:初始化 flash.NewService 並傳入 router 推論/攝影機/MJPEG/inference WebSocket 之前 M1 已搬好,不需改動。 Python bridge (kneron_bridge.py) 與 edge-ai-platform 完全相同,不需改動。 ## 前端 store + hooks(Phase 2) - stores/flash-store.ts(新):Zustand store — startFlash / updateProgress / retryFlash / reset - hooks/use-flash-progress.ts(新):WebSocket hook 接收 flash 進度 inference-store / camera-store / inference types / use-inference-stream / use-websocket 之前 M1 已搬好,不需改動。 ## 前端 UI 元件(Phase 3) - components/devices/flash-dialog.tsx(新):模型載入對話框 + 硬體相容性檢查 - components/devices/flash-progress.tsx(新):Flash 進度條 + 錯誤重試 camera-inference-view / camera-feed / camera-overlay / source-selector / inference-panel / performance-metrics / classification-result / confidence-slider / video-progress / batch-image-thumbnails 之前 M1 已搬好。 ## 前端頁面整合(Phase 4) - workspace/page.tsx:繁中硬編碼、顯示已載入模型名稱 - workspace/[deviceId]/workspace-client.tsx:加入 FlashDialog 按鈕 + 繁中硬編碼 - devices/[id]/device-detail-client.tsx:加入 FlashDialog + 「進入工作區」按鈕(模型已載入才顯示) - device-card.tsx:已連線 + 模型已載入時顯示「工作區」快捷按鈕 ## 使用者操作流程 裝置列表 → 連線 → 管理 → 載入模型 → 進入工作區 → 選攝影機/圖片/影片 → 開始推論 → 看 bounding box / FPS / latency 或:裝置列表 → 工作區(已有模型)→ 直接推論 ## 不搬的東西 - cluster/* 全部不搬(已砍 cluster 功能) - relay / tunnel 相關不搬 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useCallback } from 'react';
|
|
import { createWebSocket } from '@/lib/ws';
|
|
import { useFlashStore } from '@/stores/flash-store';
|
|
import type { FlashProgress } from '@/types/device';
|
|
|
|
/**
|
|
* Manages flash progress WebSocket.
|
|
* Returns a `connectAndWait` callback that creates the WebSocket and
|
|
* returns a promise that resolves once the WS is open.
|
|
*/
|
|
export function useFlashProgress(deviceId: string) {
|
|
const updateProgress = useFlashStore((s) => s.updateProgress);
|
|
const wsRef = useRef<ReturnType<typeof createWebSocket> | null>(null);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
wsRef.current?.close();
|
|
wsRef.current = null;
|
|
};
|
|
}, [deviceId]);
|
|
|
|
/**
|
|
* Creates the WebSocket connection and returns a promise that resolves
|
|
* once the connection is open. This is called imperatively (not via
|
|
* useEffect) to avoid React render-cycle timing issues.
|
|
*/
|
|
const connectAndWait = useCallback(
|
|
() =>
|
|
new Promise<void>((resolve) => {
|
|
// Close any existing connection
|
|
wsRef.current?.close();
|
|
|
|
let resolved = false;
|
|
const doResolve = () => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve();
|
|
}
|
|
};
|
|
|
|
const ws = createWebSocket(
|
|
`/ws/devices/${deviceId}/flash-progress`,
|
|
(data) => {
|
|
updateProgress(data as FlashProgress);
|
|
},
|
|
() => {
|
|
doResolve();
|
|
},
|
|
);
|
|
wsRef.current = ws;
|
|
|
|
// Safety timeout — don't block forever
|
|
setTimeout(doResolve, 3000);
|
|
}),
|
|
[deviceId, updateProgress],
|
|
);
|
|
|
|
/** Close the WebSocket connection. */
|
|
const disconnect = useCallback(() => {
|
|
wsRef.current?.close();
|
|
wsRef.current = null;
|
|
}, []);
|
|
|
|
return { connectAndWait, disconnect };
|
|
}
|