fix(mission-control): dedupe whisper binaries and clean WSL package metadata
Prevent duplicate React keys by de-duplicating whisper binary names and improve WSL package reporting so system metadata shows stable, user-friendly versions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a7790b7115
commit
e5acd1dc26
@ -2,12 +2,36 @@ import { NextResponse } from 'next/server';
|
||||
import { exec, execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { readFileSync } from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const IS_MAC = process.platform === 'darwin';
|
||||
const IS_WSL =
|
||||
process.platform === 'linux' &&
|
||||
(() => {
|
||||
try {
|
||||
return readFileSync('/proc/version', 'utf-8').toLowerCase().includes('microsoft');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
async function readWindowsCommand(command: string): Promise<string | null> {
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
'powershell.exe',
|
||||
['-NoProfile', '-Command', command],
|
||||
{ timeout: 4000 }
|
||||
);
|
||||
const value = stdout.replace(/\r/g, '').trim();
|
||||
return value || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache slow commands with TTL
|
||||
let staticCache: {
|
||||
@ -29,6 +53,12 @@ async function getChipInfo(): Promise<string> {
|
||||
);
|
||||
return stdout.trim();
|
||||
}
|
||||
if (IS_WSL) {
|
||||
const winCpu = await readWindowsCommand(
|
||||
'(Get-CimInstance Win32_Processor | Select-Object -First 1 -ExpandProperty Name)'
|
||||
);
|
||||
if (winCpu) return winCpu;
|
||||
}
|
||||
// Linux / WSL2
|
||||
const { stdout } = await execAsync(
|
||||
"lscpu 2>/dev/null | grep 'Model name' | sed 's/.*:\\s*//' || cat /proc/cpuinfo | grep 'model name' | head -1 | sed 's/.*: //'"
|
||||
@ -110,17 +140,60 @@ async function getBrewPackages(): Promise<Array<{ name: string; version: string
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Linux / WSL2 — check via version commands (ffmpeg uses -version, others use --version)
|
||||
// Linux / WSL2 — report installed tools in a stable way (avoid noisy shell errors).
|
||||
for (const pkg of targets) {
|
||||
const bin = pkg === 'whisper-cpp' ? 'whisper-cli' : pkg;
|
||||
const flag = bin === 'ffmpeg' ? '-version' : '--version';
|
||||
try {
|
||||
const { stdout } = await execAsync(`${bin} ${flag} 2>&1 | head -1`, { timeout: 3000 });
|
||||
if (stdout.trim()) {
|
||||
results.push({ name: pkg, version: stdout.trim() });
|
||||
if (pkg === 'ffmpeg') {
|
||||
const { stdout } = await execAsync('ffmpeg -version 2>/dev/null | head -1', {
|
||||
timeout: 3000,
|
||||
});
|
||||
if (stdout.trim()) {
|
||||
results.push({ name: pkg, version: stdout.trim() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pkg === 'whisper-cpp') {
|
||||
const { stdout } = await execAsync('command -v whisper-cli 2>/dev/null', { timeout: 2000 });
|
||||
const path = stdout.trim();
|
||||
if (path) {
|
||||
results.push({ name: pkg, version: `installed (${path})` });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ollama on Linux/WSL may be a Windows-hosted daemon; prefer API version if reachable.
|
||||
if (pkg === 'ollama') {
|
||||
const ollamaUrl = process.env.OLLAMA_URL || process.env.OLLAMA_HOST || 'http://localhost:11434';
|
||||
const normalized = ollamaUrl.startsWith('http') ? ollamaUrl : `http://${ollamaUrl}`;
|
||||
try {
|
||||
const res = await fetch(`${normalized.replace(/\/+$/, '')}/api/version`, {
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(2500),
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as { version?: string };
|
||||
if (data.version) {
|
||||
results.push({ name: pkg, version: data.version });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fall through to local binary check
|
||||
}
|
||||
|
||||
const { stdout } = await execAsync('command -v ollama 2>/dev/null', { timeout: 2000 });
|
||||
const path = stdout.trim();
|
||||
if (path) {
|
||||
const { stdout: ver } = await execAsync('ollama --version 2>/dev/null | head -1', {
|
||||
timeout: 2000,
|
||||
});
|
||||
const v = ver.trim();
|
||||
results.push({ name: pkg, version: v || `installed (${path})` });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// not installed
|
||||
// skip unavailable tool
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,6 +277,17 @@ async function getAccurateMemory(): Promise<{
|
||||
const ratio = appMemory / total;
|
||||
const pressure = ratio > 0.85 ? 'critical' : ratio > 0.7 ? 'warning' : 'normal';
|
||||
|
||||
if (IS_WSL) {
|
||||
const winTotalRaw = await readWindowsCommand(
|
||||
'[string](Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory'
|
||||
);
|
||||
const winTotal = winTotalRaw ? Number.parseInt(winTotalRaw.replace(/\D/g, ''), 10) : 0;
|
||||
if (Number.isFinite(winTotal) && winTotal > total) {
|
||||
// Keep WSL usage metrics, but report host RAM capacity for Mission Control display.
|
||||
return { total: winTotal, appMemory, cached, free, pressure };
|
||||
}
|
||||
}
|
||||
|
||||
return { total, appMemory, cached, free, pressure };
|
||||
} catch {
|
||||
// fall through to generic fallback
|
||||
@ -240,7 +324,7 @@ export async function GET() {
|
||||
ollamaDiskUsage: ollamaDisk,
|
||||
cpuCores: cpuCount,
|
||||
uptime,
|
||||
platform: `${os.type()} ${os.release()}`,
|
||||
platform: IS_WSL ? 'Windows (via WSL2)' : `${os.type()} ${os.release()}`,
|
||||
arch: os.arch(),
|
||||
nodeVersion: process.version,
|
||||
brewPackages: staticInfo.brewPackages,
|
||||
|
||||
@ -13,21 +13,23 @@ async function getWhisperBinaries(): Promise<string[]> {
|
||||
try {
|
||||
if (IS_MAC) {
|
||||
const { stdout } = await execAsync('ls /opt/homebrew/bin/whisper-* 2>/dev/null');
|
||||
return stdout
|
||||
const bins = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map(p => p.split('/').pop() || p);
|
||||
return Array.from(new Set(bins));
|
||||
}
|
||||
// Linux / WSL2 — check common locations
|
||||
const { stdout } = await execAsync(
|
||||
'ls /usr/local/bin/whisper-* /usr/bin/whisper-* 2>/dev/null || which whisper-cli 2>/dev/null'
|
||||
);
|
||||
return stdout
|
||||
const bins = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map(p => p.split('/').pop() || p);
|
||||
return Array.from(new Set(bins));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
@ -70,8 +72,9 @@ async function getWhisperModels(): Promise<{
|
||||
|
||||
async function getWhisperVersion(): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await execAsync('whisper-cli --version 2>&1 || echo "unknown"');
|
||||
return stdout.trim();
|
||||
const { stdout } = await execAsync('whisper-cli -h 2>&1 | head -1');
|
||||
const line = stdout.trim();
|
||||
return line || 'installed';
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user