From 68c7973281eb8ccc5b2ae115002a04514f19a65c Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Fri, 20 Feb 2026 22:55:00 +0000 Subject: [PATCH] fix: Pi harness - dynamic models, correct RPC protocol, proper event handling Backend: - /api/pi/models now calls 'pi --list-models' with stored API keys - pi-bridge.ts: callback-based event handling (matches pi-monorepo) - pi-bridge.ts: correct RPC format (type: 'prompt' not jsonrpc) - pi-bridge.ts: pass API keys to Pi process env - websocket.ts: event handler runs in background, no blocking - rest.ts: fix user home path (getHomeDir instead of hardcoded) Frontend: - Fix /api/ double prefix in useChatSessions, useChatGroups, useModels - Add PROVIDER_DISPLAY mapping in SystemSettings.tsx - Provider tabs show friendly names (e.g., 'OpenCode Zen') UI (from previous session): - Grouped session list with collapsible folders - CreateGroupDialog, GroupContextMenu, SessionContextMenu components --- .../ChatHistory/CreateGroupDialog.tsx | 138 ++++++ .../ChatHistory/GroupContextMenu.tsx | 122 ++++++ .../Screens/Dashboard/ChatHistory/Screen.tsx | 179 +++++--- .../ChatHistory/SessionContextMenu.tsx | 141 +++++++ .../Dashboard/Settings/SystemSettings.tsx | 23 +- src/apps/officer-web/state/useChatGroups.ts | 10 +- src/apps/officer-web/state/useChatSessions.ts | 12 +- src/apps/officer-web/state/useModels.ts | 2 +- src/servers/api/pi/pi-bridge.ts | 308 ++++++++------ src/servers/api/pi/rest.ts | 106 +++-- src/servers/api/pi/websocket.ts | 394 ++++++++---------- 11 files changed, 978 insertions(+), 457 deletions(-) create mode 100644 src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx create mode 100644 src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx create mode 100644 src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx new file mode 100644 index 00000000..5ae11530 --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx @@ -0,0 +1,138 @@ +import { useState } from 'react'; +import { X } from 'lucide-react'; +import { useChatGroups } from '@/state/useChatGroups'; + +type CreateGroupDialogProps = { + onClose: () => void; +}; + +function toSlug(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, ''); +} + +export function CreateGroupDialog({ onClose }: CreateGroupDialogProps) { + const { createGroup } = useChatGroups(); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + + if (!name.trim()) { + setError('Name is required'); + return; + } + + const slug = toSlug(name); + if (!slug) { + setError('Name must contain at least one alphanumeric character'); + return; + } + + setIsSubmitting(true); + setError(null); + + try { + await createGroup(name.trim(), slug, description.trim() || undefined); + onClose(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to create group'); + setIsSubmitting(false); + } + } + + function handleBackdropClick(e: React.MouseEvent) { + if (e.target === e.currentTarget) { + onClose(); + } + } + + return ( +
+
+ {/* Header */} +
+

Create Group

+ +
+ + {/* Form */} +
+
+ + setName(e.target.value)} + placeholder="e.g., Work Projects" + className="w-full px-3 py-2 rounded-md border border-duck-dark/20 dark:border-foreground/20 bg-background text-duck-dark dark:text-foreground placeholder:text-duck-dark/40 dark:placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-duck-teal/50" + autoFocus + disabled={isSubmitting} + /> + {name && ( +

+ Slug: {toSlug(name) || '(invalid)'} +

+ )} +
+ +
+ +