jim800121chen 5c1c37d151 feat(frontend): classification 結果呈現與推論期控制項
detection 畫框、classification 改在影像右上角疊標籤(不畫框),
並在推論面板提供即時切換解析方式與上傳 label 檔。

- 新增 InferenceOverlay 依 taskType 分派;ClassificationOverlay 用
  DOM 而非 canvas,讓 CJK 排版與 aria-live 交給瀏覽器處理
- 標籤防閃爍:挑戰者需連續 3 幀居冠才切換,或信心度領先 15% 直接切;
  低於門檻立即清空(顯示過期標籤比空白更糟)
- 身分比對優先用 classIndex,避免換 label 檔時被誤判為換類別
- 推論設定卡片:即時切 detection/classification、上傳 .txt label、
  清除 label,皆不需重燒
- classification-result 原本無條件渲染,導致 detection 模式下側欄
  永遠顯示「分類結果」與空圖表,改為依 taskType 切換
- 清掉 camera-overlay 每幀執行的 debug console.log
- 補 ResizeObserver stub,原本任何渲染 InferencePanel 的測試都會
  在 jsdom 直接拋錯

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:27:28 +08:00

54 lines
1.5 KiB
TypeScript

'use client';
import type { InferenceResult } from '@/types/inference';
import { isClassificationTask } from '@/lib/classification';
import { CameraOverlay } from './camera-overlay';
import { ClassificationOverlay } from './classification-overlay';
interface InferenceOverlayProps {
result: InferenceResult | null | undefined;
width: number;
height: number;
confidenceThreshold: number;
}
/**
* Dispatches the frame overlay based on the task type reported by the
* inference result itself.
*
* `result.taskType` is preferred over the model metadata because it reflects
* what the Python post-processor actually did — metadata can disagree with the
* executed code path.
*
* Note the branch is written as "is this classification?" rather than "is this
* detection?" on purpose: the detection task type string has been inconsistent
* across the stack (`detection` vs `object_detection`, plan R-4), so detection
* is the fall-through default and stays correct under either spelling.
*/
export function InferenceOverlay({
result,
width,
height,
confidenceThreshold,
}: InferenceOverlayProps) {
if (isClassificationTask(result?.taskType)) {
return (
<ClassificationOverlay
classifications={result?.classifications || []}
width={width}
height={height}
confidenceThreshold={confidenceThreshold}
/>
);
}
return (
<CameraOverlay
detections={result?.detections || []}
width={width}
height={height}
confidenceThreshold={confidenceThreshold}
/>
);
}