fix(pi): add snap node compatibility diagnostics and documentation
- Added detailed error logging to detect snap node compatibility issues - When Pi process exits with code 1, log helpful diagnostic info including node path - Add hint to check for snap node and reinstall via apt/nvm - Create SNAP_NODE_COMPATIBILITY.md with full troubleshooting guide - Document root cause: snap node has file descriptor incompatibility with Bun.spawn stdin pipes - Provide clear installation instructions for NodeSource and nvm alternatives
This commit is contained in:
@@ -30,6 +30,7 @@ const PROVIDER_DISPLAY: Record<string, string> = {
|
||||
anthropic: 'Anthropic',
|
||||
openai: 'OpenAI',
|
||||
opencode: 'OpenCode Zen',
|
||||
zai: 'ZAI',
|
||||
google: 'Google',
|
||||
groq: 'Groq',
|
||||
mistral: 'Mistral',
|
||||
@@ -52,12 +53,42 @@ const groups: SettingsSectionGroup[] = [
|
||||
label: 'AI',
|
||||
icon: Bot,
|
||||
sections: [
|
||||
{ key: 'ai-harnesses', icon: Terminal, title: 'Providers', description: 'Remote and local AI providers', content: <AIHarnessesSection /> },
|
||||
{ key: 'model-visibility', icon: Eye, title: 'Models', description: 'Enable or disable models', content: <ModelVisibilitySection /> },
|
||||
{ key: 'chat-defaults', icon: Terminal, title: 'Chat Defaults', description: 'Model, prompt, and temperature', content: <ChatDefaultsSection /> },
|
||||
{ key: 'tts', icon: Volume2, title: 'Text to Speech', description: 'TTS provider and voice', content: <TTSSection /> },
|
||||
{
|
||||
key: 'ai-harnesses',
|
||||
icon: Terminal,
|
||||
title: 'Providers',
|
||||
description: 'Remote and local AI providers',
|
||||
content: <AIHarnessesSection />,
|
||||
},
|
||||
{
|
||||
key: 'model-visibility',
|
||||
icon: Eye,
|
||||
title: 'Models',
|
||||
description: 'Enable or disable models',
|
||||
content: <ModelVisibilitySection />,
|
||||
},
|
||||
{
|
||||
key: 'chat-defaults',
|
||||
icon: Terminal,
|
||||
title: 'Chat Defaults',
|
||||
description: 'Model, prompt, and temperature',
|
||||
content: <ChatDefaultsSection />,
|
||||
},
|
||||
{
|
||||
key: 'tts',
|
||||
icon: Volume2,
|
||||
title: 'Text to Speech',
|
||||
description: 'TTS provider and voice',
|
||||
content: <TTSSection />,
|
||||
},
|
||||
{ key: 'stt', icon: Mic, title: 'Speech to Text', description: 'Whisper server URL', content: <STTSection /> },
|
||||
{ key: 'ocr', icon: ScanText, title: 'OCR', description: 'Vision model for text extraction', content: <OCRSection /> },
|
||||
{
|
||||
key: 'ocr',
|
||||
icon: ScanText,
|
||||
title: 'OCR',
|
||||
description: 'Vision model for text extraction',
|
||||
content: <OCRSection />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -142,7 +173,10 @@ const SystemTerminalPanel = () => {
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-duck-dark/50 dark:text-foreground/50 flex-1">Run Command</span>
|
||||
<button onClick={close} className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors">
|
||||
<button
|
||||
onClick={close}
|
||||
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
|
||||
>
|
||||
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -325,7 +359,9 @@ const DropZone = ({ label, children, onDrop }: DropZoneProps) => {
|
||||
|
||||
return (
|
||||
<div className="grid gap-1.5">
|
||||
<span className="text-xs font-medium text-duck-dark/50 dark:text-foreground/50 uppercase tracking-wide">{label}</span>
|
||||
<span className="text-xs font-medium text-duck-dark/50 dark:text-foreground/50 uppercase tracking-wide">
|
||||
{label}
|
||||
</span>
|
||||
<div
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
@@ -376,7 +412,7 @@ function ModelVisibilitySection() {
|
||||
const providers = useMemo(() => providerGroups.map((g) => g.provider), [providerGroups]);
|
||||
|
||||
// Auto-select first provider if none selected or stale
|
||||
const selected = providers.includes(activeProvider) ? activeProvider : providers[0] ?? '';
|
||||
const selected = providers.includes(activeProvider) ? activeProvider : (providers[0] ?? '');
|
||||
|
||||
const currentGroup = providerGroups.find((g) => g.provider === selected);
|
||||
|
||||
@@ -430,7 +466,11 @@ function ModelVisibilitySection() {
|
||||
}, []);
|
||||
|
||||
if (providerGroups.length === 0) {
|
||||
return <p className="text-sm text-duck-dark/40 dark:text-foreground/40">No models available. Configure API keys in AI Settings.</p>;
|
||||
return (
|
||||
<p className="text-sm text-duck-dark/40 dark:text-foreground/40">
|
||||
No models available. Configure API keys in AI Settings.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -463,17 +503,35 @@ function ModelVisibilitySection() {
|
||||
|
||||
{/* Enabled section */}
|
||||
<DropZone label="Enabled" onDrop={enableModel}>
|
||||
{enabled.length === 0 && <span className="text-xs text-duck-dark/30 dark:text-foreground/30 py-1">Drag models here to enable</span>}
|
||||
{enabled.length === 0 && (
|
||||
<span className="text-xs text-duck-dark/30 dark:text-foreground/30 py-1">Drag models here to enable</span>
|
||||
)}
|
||||
{enabled.map((m) => (
|
||||
<ModelPill key={m.key} label={m.name} modelKey={m.key} enabled onAction={disableModel} onDragStart={onDragStart} />
|
||||
<ModelPill
|
||||
key={m.key}
|
||||
label={m.name}
|
||||
modelKey={m.key}
|
||||
enabled
|
||||
onAction={disableModel}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
))}
|
||||
</DropZone>
|
||||
|
||||
{/* Disabled section */}
|
||||
<DropZone label="Disabled" onDrop={disableModel}>
|
||||
{disabled.length === 0 && <span className="text-xs text-duck-dark/30 dark:text-foreground/30 py-1">All models enabled</span>}
|
||||
{disabled.length === 0 && (
|
||||
<span className="text-xs text-duck-dark/30 dark:text-foreground/30 py-1">All models enabled</span>
|
||||
)}
|
||||
{disabled.map((m) => (
|
||||
<ModelPill key={m.key} label={m.name} modelKey={m.key} enabled={false} onAction={enableModel} onDragStart={onDragStart} />
|
||||
<ModelPill
|
||||
key={m.key}
|
||||
label={m.name}
|
||||
modelKey={m.key}
|
||||
enabled={false}
|
||||
onAction={enableModel}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
))}
|
||||
</DropZone>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user