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:
@@ -85,12 +85,7 @@ function SessionChat({ sessionId, model }: SessionChatProps) {
|
||||
navigate('/chat', { replace: true });
|
||||
}}
|
||||
/>
|
||||
<EmbeddableChat
|
||||
chat={chat}
|
||||
sessionId={sessionId}
|
||||
initialModel={model ?? undefined}
|
||||
className="flex-1 min-h-0"
|
||||
/>
|
||||
<EmbeddableChat chat={chat} sessionId={sessionId} initialModel={model ?? undefined} className="flex-1 min-h-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -132,13 +127,13 @@ function NewChat({ allowHostMode }: { allowHostMode?: boolean }) {
|
||||
const location = useLocation();
|
||||
const locationState = location.state as ChatLocationState;
|
||||
const { user } = useAuth();
|
||||
const showToggle = allowHostMode && user?.role === 'Super Admin';
|
||||
const [cwdMode, setCwdMode] = useState<'user' | 'host'>('user');
|
||||
const isSuperAdmin = user?.role === 'Super Admin';
|
||||
|
||||
const chat = usePiChat(undefined, locationState?.model);
|
||||
|
||||
const sandboxed = showToggle ? cwdMode === 'user' : true;
|
||||
const cwd = !sandboxed ? { path: getHostHome() } : locationState?.cwd;
|
||||
// Super Admin always operates as host — no toggle needed
|
||||
const sandboxed = !isSuperAdmin;
|
||||
const cwd = isSuperAdmin ? { path: getHostHome() } : locationState?.cwd;
|
||||
|
||||
const initialMessage = locationState?.initialMessage
|
||||
? {
|
||||
@@ -157,7 +152,6 @@ function NewChat({ allowHostMode }: { allowHostMode?: boolean }) {
|
||||
isGenerating={chat.isGenerating}
|
||||
onDelete={undefined}
|
||||
/>
|
||||
{!chat.hasStarted && showToggle && <CwdToggle cwdMode={cwdMode} onChange={setCwdMode} />}
|
||||
<EmbeddableChat
|
||||
chat={chat}
|
||||
sessionId={undefined}
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles } from 'lucide-react';
|
||||
import { useWorkspace } from '../../components/Workspace';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useTerminalMode } from './useTerminalMode';
|
||||
|
||||
export const TerminalHeader = ({ panelId }: { panelId: string }) => {
|
||||
export const TerminalHeader = () => {
|
||||
const { cwd } = useWorkspace();
|
||||
const { user } = useAuth();
|
||||
const { mode, toggle } = useTerminalMode(panelId);
|
||||
const isHost = mode === 'host';
|
||||
const scoped = cwd !== '~';
|
||||
const Icon = isHost && !scoped ? Monitor : TerminalSquare;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon className="h-3.5 w-3.5 shrink-0" />
|
||||
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="text-xs font-medium shrink-0">Terminal</span>
|
||||
{user?.role === 'Super Admin' && !scoped && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-white/10 hover:bg-white/20 transition-colors cursor-pointer shrink-0"
|
||||
>
|
||||
{isHost ? 'Host' : 'Home'}
|
||||
</button>
|
||||
)}
|
||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,16 @@ type UsePiChatOptions = {
|
||||
};
|
||||
|
||||
export function usePiChat(initialSessionId?: string, initialModel?: string | null, options?: UsePiChatOptions) {
|
||||
const { replaceUrl = true, storage, resourceChatDir, taskInfo, projectScoped, context, contextId, onTurnComplete } = options ?? {};
|
||||
const {
|
||||
replaceUrl = true,
|
||||
storage,
|
||||
resourceChatDir,
|
||||
taskInfo,
|
||||
projectScoped,
|
||||
context,
|
||||
contextId,
|
||||
onTurnComplete,
|
||||
} = options ?? {};
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [streamingText, setStreamingText] = useState('');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
@@ -77,9 +86,10 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
|
||||
|
||||
function commitStreaming() {
|
||||
if (!streamingRef.current) return;
|
||||
setMessages((prev) => [...prev, { role: 'assistant', id: crypto.randomUUID(), text: streamingRef.current }]);
|
||||
const text = streamingRef.current;
|
||||
streamingRef.current = '';
|
||||
setStreamingText('');
|
||||
setMessages((prev) => [...prev, { role: 'assistant', id: crypto.randomUUID(), text }]);
|
||||
}
|
||||
|
||||
function handleMessage(data: unknown) {
|
||||
@@ -286,7 +296,10 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
|
||||
setHasStarted(true);
|
||||
}
|
||||
|
||||
setMessages((prev) => [...prev, { role: 'user', text: displayText ?? text, ...(images?.length ? { images } : {}) }]);
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ role: 'user', text: displayText ?? text, ...(images?.length ? { images } : {}) },
|
||||
]);
|
||||
setIsGenerating(true);
|
||||
streamingRef.current = '';
|
||||
setStreamingText('');
|
||||
|
||||
@@ -30,7 +30,11 @@ export function usePiModels() {
|
||||
queryKey: ['PI_MODELS'],
|
||||
enabled: isAuthenticated,
|
||||
queryFn: async () => {
|
||||
const data = await client.get<{ models: ModelOption[]; providerNames?: Record<string, string>; hostHome?: string }>('/pi/models');
|
||||
const data = await client.get<{
|
||||
models: ModelOption[];
|
||||
providerNames?: Record<string, string>;
|
||||
hostHome?: string;
|
||||
}>('/pi/models');
|
||||
|
||||
if (data.providerNames) {
|
||||
globalProviderNames = data.providerNames;
|
||||
@@ -48,13 +52,14 @@ export function usePiModels() {
|
||||
return models;
|
||||
}
|
||||
|
||||
/** Filter models by system-wide access policy. New providers pass through. */
|
||||
/** Filter models by system-wide access policy. Super Admin sees all. New providers pass through. */
|
||||
export function useVisiblePiModels() {
|
||||
const models = usePiModels();
|
||||
const { user } = useAuth();
|
||||
const { policy } = useAccessPolicy();
|
||||
const allowed = policy.allowedModels;
|
||||
|
||||
if (allowed.length === 0) return models;
|
||||
if (user?.role === 'Super Admin' || allowed.length === 0) return models;
|
||||
|
||||
const allowedSet = new Set(allowed);
|
||||
const allowedProviderSet = new Set(allowed.map((key) => key.split(':')[0]));
|
||||
|
||||
Reference in New Issue
Block a user