fix(local-tool): sidebar icon + Wails Windows icon.ico + 掃裝置路徑修復
sidebar E icon:
- frontend/src/components/layout/sidebar.tsx 左上角的 "E" 方塊換成 <img> 指向
/visiona-logo.png,從 frontend/public/visiona-logo.png serve
Wails 桌面/工作列 icon:
- 把 branding/icon.ico 複製到 visiona-local/build/windows/icon.ico
- Wails v2 Windows build 偵測到這個檔案就會直接用它當 exe embedded icon,
不再從 appicon.png 自動 resize(解析度更好)
掃裝置根因修復:
1. server main.go:新增 resolveBridgeScript() 智慧找 kneron_bridge.py
- 優先 <exe>/scripts/kneron_bridge.py
- fallback <exe>/../scripts/... 對應 Windows installer 的 {app}\bin\ + {app}\scripts\ 佈局
- fallback <exe>/../Resources/scripts/... 對應 macOS app bundle
2. server kneron/detector.go:ResolvePython 重寫
- 最高優先:VISIONA_PYTHON 環境變數(由 Wails 殼層注入)
- 加入 visiona-local 新路徑:%APPDATA%\visiona-local\runtime\venv、
~/Library/Application Support/visiona-local/runtime/venv、
~/.local/share/visiona-local/runtime/venv
- 保留 edge-ai-platform 舊路徑作為 legacy fallback
3. visiona-local/app.go:spawn server 時 export VISIONA_PYTHON=<pyBin>
讓 detector 直接用 Wails 殼層已經 resolve 好的 python interpreter,
不再自己獨立去找造成不一致。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
50a3f73acd
commit
f8533a6b04
BIN
local-tool/frontend/public/visiona-logo.png
Normal file
BIN
local-tool/frontend/public/visiona-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@ -21,9 +21,12 @@ export function Sidebar() {
|
|||||||
<aside className="flex h-full w-60 flex-col border-r bg-card">
|
<aside className="flex h-full w-60 flex-col border-r bg-card">
|
||||||
<div className="flex h-14 items-center border-b px-4">
|
<div className="flex h-14 items-center border-b px-4">
|
||||||
<Link href="/" className="flex items-center gap-2 font-semibold">
|
<Link href="/" className="flex items-center gap-2 font-semibold">
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold">
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
E
|
<img
|
||||||
</div>
|
src="/visiona-logo.png"
|
||||||
|
alt="visionA Local"
|
||||||
|
className="h-8 w-8 rounded-lg"
|
||||||
|
/>
|
||||||
<span className="text-lg">{t('nav.appName')}</span>
|
<span className="text-lg">{t('nav.appName')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,12 +13,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ResolvePython finds the best Python interpreter for the given script path.
|
// ResolvePython finds the best Python interpreter for the given script path.
|
||||||
// Search order: script-local venv → parent venv → platform config dir venv → system python3/python.
|
//
|
||||||
|
// Search order:
|
||||||
|
// 1. VISIONA_PYTHON env var (highest priority — set by Wails shell when it
|
||||||
|
// has already provisioned a bundled venv)
|
||||||
|
// 2. Script-local venv / parent venv
|
||||||
|
// 3. %APPDATA%\visiona-local\runtime\venv (Windows installer seeded venv)
|
||||||
|
// 4. ~/.local/share/visiona-local/runtime/venv (Linux)
|
||||||
|
// 5. ~/Library/Application Support/visiona-local/runtime/venv (macOS)
|
||||||
|
// 6. Legacy: ~/.edge-ai-platform/venv / %LOCALAPPDATA%\EdgeAIPlatform\venv
|
||||||
|
// 7. System python3 / python
|
||||||
func ResolvePython(scriptPath string) string {
|
func ResolvePython(scriptPath string) string {
|
||||||
|
// 1. Environment variable override (set by Wails shell)
|
||||||
|
if p := os.Getenv("VISIONA_PYTHON"); p != "" {
|
||||||
|
if _, err := os.Stat(p); err == nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scriptDir := filepath.Dir(scriptPath)
|
scriptDir := filepath.Dir(scriptPath)
|
||||||
parentDir := filepath.Dir(scriptDir)
|
parentDir := filepath.Dir(scriptDir)
|
||||||
|
|
||||||
// Build candidate list with both Unix and Windows venv layouts
|
// 2. Script-local / parent venv
|
||||||
var candidates []string
|
var candidates []string
|
||||||
for _, base := range []string{scriptDir, parentDir} {
|
for _, base := range []string{scriptDir, parentDir} {
|
||||||
candidates = append(candidates,
|
candidates = append(candidates,
|
||||||
@ -27,14 +43,22 @@ func ResolvePython(scriptPath string) string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3-5. Platform-specific data dir (visiona-local)
|
||||||
if home, err := os.UserHomeDir(); err == nil {
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
candidates = append(candidates,
|
candidates = append(candidates,
|
||||||
|
// Windows: %APPDATA%\visiona-local\runtime\venv
|
||||||
|
filepath.Join(os.Getenv("APPDATA"), "visiona-local", "runtime", "venv", "Scripts", "python.exe"),
|
||||||
|
// macOS
|
||||||
|
filepath.Join(home, "Library", "Application Support", "visiona-local", "runtime", "venv", "bin", "python3"),
|
||||||
|
// Linux
|
||||||
|
filepath.Join(home, ".local", "share", "visiona-local", "runtime", "venv", "bin", "python3"),
|
||||||
|
// 6. Legacy edge-ai-platform paths (backwards compat for upgrades)
|
||||||
filepath.Join(home, ".edge-ai-platform", "venv", "bin", "python3"),
|
filepath.Join(home, ".edge-ai-platform", "venv", "bin", "python3"),
|
||||||
filepath.Join(home, ".edge-ai-platform", "venv", "Scripts", "python.exe"),
|
filepath.Join(home, ".edge-ai-platform", "venv", "Scripts", "python.exe"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Windows, also check %LOCALAPPDATA%\EdgeAIPlatform\venv
|
// 6b. Legacy %LOCALAPPDATA%\EdgeAIPlatform\venv
|
||||||
if appData := os.Getenv("LOCALAPPDATA"); appData != "" {
|
if appData := os.Getenv("LOCALAPPDATA"); appData != "" {
|
||||||
candidates = append(candidates,
|
candidates = append(candidates,
|
||||||
filepath.Join(appData, "EdgeAIPlatform", "venv", "Scripts", "python.exe"),
|
filepath.Join(appData, "EdgeAIPlatform", "venv", "Scripts", "python.exe"),
|
||||||
@ -42,12 +66,15 @@ func ResolvePython(scriptPath string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range candidates {
|
for _, p := range candidates {
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, err := os.Stat(p); err == nil {
|
if _, err := os.Stat(p); err == nil {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to system python
|
// 7. Fallback to system python
|
||||||
for _, name := range []string{"python3", "python"} {
|
for _, name := range []string{"python3", "python"} {
|
||||||
if p, err := exec.LookPath(name); err == nil {
|
if p, err := exec.LookPath(name); err == nil {
|
||||||
return p
|
return p
|
||||||
|
|||||||
@ -49,6 +49,33 @@ func baseDir(devMode bool) string {
|
|||||||
return filepath.Dir(exe)
|
return filepath.Dir(exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveBridgeScript finds kneron_bridge.py across different packaging layouts.
|
||||||
|
//
|
||||||
|
// Possible locations (tried in order):
|
||||||
|
// 1. <base>/scripts/kneron_bridge.py — dev mode or flat layout
|
||||||
|
// 2. <base>/../scripts/kneron_bridge.py — Windows/Linux installer: binary in {app}/bin, scripts in {app}/scripts
|
||||||
|
// 3. <base>/../Resources/scripts/kneron_bridge.py — macOS app bundle: binary in Contents/MacOS, scripts in Contents/Resources
|
||||||
|
// 4. ./scripts/kneron_bridge.py — cwd fallback
|
||||||
|
func resolveBridgeScript(base string) string {
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Join(base, "scripts", "kneron_bridge.py"),
|
||||||
|
filepath.Join(base, "..", "scripts", "kneron_bridge.py"),
|
||||||
|
filepath.Join(base, "..", "Resources", "scripts", "kneron_bridge.py"),
|
||||||
|
filepath.Join(".", "scripts", "kneron_bridge.py"),
|
||||||
|
}
|
||||||
|
for _, c := range candidates {
|
||||||
|
abs, err := filepath.Abs(c)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info, err := os.Stat(abs); err == nil && !info.IsDir() {
|
||||||
|
return abs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Nothing found — return the default so downstream logs a clear error
|
||||||
|
return filepath.Join(base, "scripts", "kneron_bridge.py")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
@ -101,7 +128,9 @@ func main() {
|
|||||||
|
|
||||||
// Initialize device manager
|
// Initialize device manager
|
||||||
registry := device.NewRegistry()
|
registry := device.NewRegistry()
|
||||||
deviceMgr := device.NewManager(registry, cfg.MockMode, cfg.MockDeviceCount, filepath.Join(base, "scripts", "kneron_bridge.py"))
|
bridgeScript := resolveBridgeScript(base)
|
||||||
|
logger.Info("Kneron bridge script: %s", bridgeScript)
|
||||||
|
deviceMgr := device.NewManager(registry, cfg.MockMode, cfg.MockDeviceCount, bridgeScript)
|
||||||
deviceMgr.SetLogBroadcaster(logBroadcaster)
|
deviceMgr.SetLogBroadcaster(logBroadcaster)
|
||||||
deviceMgr.Start()
|
deviceMgr.Start()
|
||||||
|
|
||||||
|
|||||||
@ -377,6 +377,12 @@ func (a *App) startServer() error {
|
|||||||
env = append(env, "VISIONA_BUNDLE_BIN_DIR="+binDir)
|
env = append(env, "VISIONA_BUNDLE_BIN_DIR="+binDir)
|
||||||
fmt.Fprintln(os.Stderr, "[visiona-local] bundle bin dir:", binDir)
|
fmt.Fprintln(os.Stderr, "[visiona-local] bundle bin dir:", binDir)
|
||||||
}
|
}
|
||||||
|
// 注入 python interpreter 路徑給 server(kneron detector 會讀 VISIONA_PYTHON)
|
||||||
|
// 避免 detector 自己去 resolve 又走不同的路徑邏輯造成不一致。
|
||||||
|
if !a.mockMode && pyBin != "" {
|
||||||
|
env = append(env, "VISIONA_PYTHON="+pyBin)
|
||||||
|
fmt.Fprintln(os.Stderr, "[visiona-local] python interpreter:", pyBin)
|
||||||
|
}
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
if stdoutLog != nil {
|
if stdoutLog != nil {
|
||||||
cmd.Stdout = stdoutLog
|
cmd.Stdout = stdoutLog
|
||||||
|
|||||||
BIN
local-tool/visiona-local/build/windows/icon.ico
Normal file
BIN
local-tool/visiona-local/build/windows/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Loading…
x
Reference in New Issue
Block a user