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:
1
apps/web/.gitignore
vendored
1
apps/web/.gitignore
vendored
@@ -63,4 +63,5 @@ data/uploads/
|
||||
|
||||
# ML training data
|
||||
training-data/
|
||||
data/vision-training/collected/
|
||||
scripts/train-column-classifier/.venv/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user