jim800121chen 744283bd28 feat(build): 安裝包只打包指定模型並改用中文名稱
安裝包從 8 個 .nef 縮減為 2 個,models.json 保留完整定義以便日後
加回。

- Makefile 加 BUNDLED_NEFS 白名單,三平台共用 copy_bundled_data
  helper;用 POSIX find/cp 而非 rsync(Windows CI 的 Git Bash 沒有
  rsync)
- 白名單檔案不存在時 build 直接失敗,並在複製後驗證 models.json
  存在且 .nef 數量相符 —— 避免產出「安裝後 0 個模型」卻回報成功
- FCOS Detection (KL520) 改名為「物件辨識」
- Tiny YOLOv3 (KL520) 改名為「人型監測」
  (只改 name/description,id 不動以免影響既有設定與紀錄)
- Repository 啟動時過濾 .nef 不存在的模型,否則使用者會看到未打包
  的模型並在選取後拿到莫名錯誤

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

156 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type Repository struct {
models []Model
mu sync.RWMutex
}
// NewRepository 載入 models.json 的內建模型目錄。
//
// models.json 會列出所有「產品支援」的模型定義,但安裝包不一定會帶上每個
// 對應的 .nef見 Makefile 的 BUNDLED_NEFS 白名單)。因此載入後會過濾掉
// .nef 檔案實際不存在的 model —— 否則使用者會在 UI 看到選不了的模型,選下去
// 才在 flash 階段拿到 "model file not found" 這種沒頭沒尾的錯誤。
//
// 過濾只作用在 models.json 的內建模型。使用者上傳的自訂模型走 Add()
// 路徑是絕對路徑且必定存在,不受影響。
func NewRepository(dataPath string) *Repository {
r := &Repository{}
data, err := os.ReadFile(dataPath)
if err != nil {
fmt.Printf("Warning: could not load models from %s: %v\n", dataPath, err)
return r
}
var declared []Model
if err := json.Unmarshal(data, &declared); err != nil {
fmt.Printf("Warning: could not parse models JSON: %v\n", err)
return r
}
r.models = filterAvailableModels(declared, filepath.Dir(dataPath))
return r
}
// filterAvailableModels 只保留 .nef 檔案實際存在的 model。
//
// dataDir 是 models.json 所在的目錄(即 bundle 內的 data/models.json 的
// filePath 以它為基準解析。
func filterAvailableModels(models []Model, dataDir string) []Model {
available := make([]Model, 0, len(models))
for _, m := range models {
path := resolveBuiltInModelPath(m.FilePath, dataDir)
// 沒宣告 filePath 的 model 不做檔案檢查(沒有東西可以檢查),保留原行為。
if path == "" {
available = append(available, m)
continue
}
if info, err := os.Stat(path); err != nil || info.IsDir() {
fmt.Printf("[INFO] Skipping model %q (%s): .nef not bundled at %s\n", m.ID, m.Name, path)
continue
}
available = append(available, m)
}
return available
}
// resolveBuiltInModelPath 把 models.json 的 filePath 解析成實際的檔案路徑。
//
// 規則與 flash.Service.StartFlash 一致:
// - 絕對路徑 → 原樣使用
// - "data/nef/..." → 去掉 "data/" 前綴後接在 dataDir 之下
// (因為 dataDir 本身就是那個 data/ 目錄,不去掉會變成 data/data/nef/...
// - 其他相對路徑 → 直接接在 dataDir 之下
func resolveBuiltInModelPath(filePath, dataDir string) string {
if filePath == "" {
return ""
}
if filepath.IsAbs(filePath) {
return filePath
}
if strings.HasPrefix(filePath, "data/") || strings.HasPrefix(filePath, "data\\") {
return filepath.Join(dataDir, filePath[len("data/"):])
}
return filepath.Join(dataDir, filePath)
}
func (r *Repository) List(filter ModelFilter) ([]ModelSummary, int) {
r.mu.RLock()
defer r.mu.RUnlock()
var results []ModelSummary
for _, m := range r.models {
if filter.TaskType != "" && m.TaskType != filter.TaskType {
continue
}
if filter.Hardware != "" {
found := false
for _, hw := range m.SupportedHardware {
if hw == filter.Hardware {
found = true
break
}
}
if !found {
continue
}
}
if filter.Query != "" {
q := strings.ToLower(filter.Query)
if !strings.Contains(strings.ToLower(m.Name), q) &&
!strings.Contains(strings.ToLower(m.Description), q) {
continue
}
}
results = append(results, m.ToSummary())
}
return results, len(results)
}
func (r *Repository) GetByID(id string) (*Model, error) {
r.mu.RLock()
defer r.mu.RUnlock()
for i := range r.models {
if r.models[i].ID == id {
return &r.models[i], nil
}
}
return nil, fmt.Errorf("model not found: %s", id)
}
func (r *Repository) Count() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.models)
}
func (r *Repository) Add(m Model) {
r.mu.Lock()
defer r.mu.Unlock()
r.models = append(r.models, m)
}
func (r *Repository) Remove(id string) error {
r.mu.Lock()
defer r.mu.Unlock()
for i := range r.models {
if r.models[i].ID == id {
if !r.models[i].IsCustom {
return fmt.Errorf("cannot delete built-in model: %s", id)
}
r.models = append(r.models[:i], r.models[i+1:]...)
return nil
}
}
return fmt.Errorf("model not found: %s", id)
}