fix(vision): handle unsupported platforms gracefully for training

Add platform detection before attempting TensorFlow installation.
TensorFlow doesn't have wheels for ARM Linux (like Synology NAS),
so the hardware detection now returns a clear "Platform Not Supported"
message instead of failing during pip install.

Changes:
- Add isPlatformSupported() check in config.ts
- Check platform before attempting venv setup in hardware/route.ts
- Check platform before starting training in train/route.ts
- Show user-friendly message in HardwareCard for unsupported platforms
- Add data/vision-training/collected/ to .gitignore

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2026-01-06 09:53:52 -06:00
parent e1e0e6cc0c
commit 2eee17e159
5 changed files with 105 additions and 22 deletions

1
apps/web/.gitignore vendored
View File

@@ -63,4 +63,5 @@ data/uploads/
# ML training data
training-data/
data/vision-training/collected/
scripts/train-column-classifier/.venv/

View File

@@ -15,6 +15,45 @@ const execAsync = promisify(exec)
const cwd = process.cwd()
/**
* Check if the current platform supports TensorFlow training.
* TensorFlow doesn't have wheels for all platforms (e.g., ARM-based NAS devices).
*/
export function isPlatformSupported(): { supported: boolean; reason?: string } {
const platform = process.platform
const arch = process.arch
// TensorFlow supports:
// - macOS on x86_64 and arm64 (Apple Silicon with tensorflow-macos)
// - Linux on x86_64 (and some arm64 builds)
// - Windows on x86_64
if (platform === 'darwin') {
// macOS - both Intel and Apple Silicon are supported
return { supported: true }
}
if (platform === 'linux') {
if (arch === 'x64') {
return { supported: true }
}
// ARM Linux (like Synology NAS) typically doesn't have TensorFlow wheels
return {
supported: false,
reason: `TensorFlow is not available for Linux ${arch}. Training should be done on a machine with x86_64 or Apple Silicon.`,
}
}
if (platform === 'win32' && arch === 'x64') {
return { supported: true }
}
return {
supported: false,
reason: `TensorFlow is not available for ${platform} ${arch}. Training should be done on macOS, Linux x86_64, or Windows x86_64.`,
}
}
/**
* Path to the training scripts directory
*/

View File

@@ -1,6 +1,6 @@
import { spawn } from 'child_process'
import path from 'path'
import { ensureVenvReady, PYTHON_ENV, TRAINING_PYTHON, TRAINING_SCRIPTS_DIR } from '../config'
import { ensureVenvReady, isPlatformSupported, PYTHON_ENV, TRAINING_PYTHON, TRAINING_SCRIPTS_DIR } from '../config'
/**
* Hardware detection result from Python/TensorFlow
@@ -28,6 +28,26 @@ const CACHE_TTL_MS = 60 * 60 * 1000 // 1 hour
* Runs a Python script that queries TensorFlow for available devices.
*/
export async function GET(): Promise<Response> {
// Check platform support first - don't even try to set up venv on unsupported platforms
const platformCheck = isPlatformSupported()
if (!platformCheck.supported) {
const unsupportedResult: HardwareInfo = {
available: false,
device: 'unsupported',
deviceName: 'Platform Not Supported',
deviceType: 'unsupported',
details: {
platform: process.platform,
arch: process.arch,
},
error: platformCheck.reason || 'This platform does not support TensorFlow training',
}
return new Response(JSON.stringify(unsupportedResult), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
}
// Return cached result if valid
const now = Date.now()
if (cachedHardwareInfo && now - cacheTimestamp < CACHE_TTL_MS) {

View File

@@ -1,6 +1,6 @@
import { spawn, type ChildProcess } from 'child_process'
import path from 'path'
import { ensureVenvReady, PYTHON_ENV, TRAINING_PYTHON } from '../config'
import { ensureVenvReady, isPlatformSupported, PYTHON_ENV, TRAINING_PYTHON } from '../config'
/**
* Training configuration options
@@ -25,6 +25,19 @@ let activeAbortController: AbortController | null = null
* Only one training session can run at a time.
*/
export async function POST(request: Request): Promise<Response> {
// Check platform support first
const platformCheck = isPlatformSupported()
if (!platformCheck.supported) {
return new Response(
JSON.stringify({
error: 'Platform not supported',
details: platformCheck.reason,
hint: 'Training should be done on macOS, Linux x86_64, or Windows x86_64',
}),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
)
}
// Check if training is already running
if (activeProcess && !activeProcess.killed) {
return new Response(

View File

@@ -70,29 +70,39 @@ export function HardwareCard({
}
if (hardwareInfo?.error) {
const isUnsupportedPlatform = hardwareInfo.device === 'unsupported'
return (
<div className={css({ textAlign: 'center', py: 4 })}>
<div className={css({ fontSize: '2xl', mb: 2 })}></div>
<div className={css({ color: 'red.400', mb: 2 })}>Hardware setup failed</div>
<div className={css({ fontSize: 'sm', color: 'gray.500', mb: 4 })}>
{hardwareInfo.error}
<div className={css({ fontSize: '2xl', mb: 2 })}>
{isUnsupportedPlatform ? '🚫' : '⚠️'}
</div>
<button
type="button"
onClick={fetchHardware}
className={css({
px: 4,
py: 2,
bg: 'blue.600',
color: 'white',
borderRadius: 'lg',
border: 'none',
cursor: 'pointer',
_hover: { bg: 'blue.500' },
})}
>
Retry Detection
</button>
<div className={css({ color: isUnsupportedPlatform ? 'yellow.400' : 'red.400', mb: 2 })}>
{isUnsupportedPlatform ? 'Platform Not Supported' : 'Hardware setup failed'}
</div>
<div className={css({ fontSize: 'sm', color: 'gray.400', mb: 4, px: 2 })}>
{isUnsupportedPlatform
? 'Training requires macOS, Linux x86_64, or Windows. Run training on your local development machine instead.'
: hardwareInfo.error}
</div>
{!isUnsupportedPlatform && (
<button
type="button"
onClick={fetchHardware}
className={css({
px: 4,
py: 2,
bg: 'blue.600',
color: 'white',
borderRadius: 'lg',
border: 'none',
cursor: 'pointer',
_hover: { bg: 'blue.500' },
})}
>
Retry Detection
</button>
)}
</div>
)
}