2:I[7012,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],"MarkdownRenderer"] 4:I[9856,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],""] 5:I[4126,[],""] 7:I[9630,[],""] 8:I[4278,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"HeadingProvider"] 9:I[1476,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Header"] a:I[3167,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Sidebar"] b:I[7409,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"PageFrame"] 3:T3397, # Conversation Branching and Keyboard Shortcuts Implementation Plan **Date:** 2025-11-23 **Status:** In Progress **Epic:** Advanced Chat Features --- ## 🎯 Overview This document tracks the implementation of two major features: 1. **Conversation Branching** - Allow users to fork conversations at any message 2. **Enhanced Keyboard Shortcuts** - Global shortcuts with help dialog --- ## ✅ Completed Work ### Backend (Database & Models) - [x] **Message Model Updates** (`services/api-gateway/app/models/message.py`) - Added `parent_message_id` column (UUID, nullable, indexed) - Added `branch_id` column (String(100), nullable, indexed) - Added self-referential foreign key for parent_message_id - [x] **Alembic Migration** (`services/api-gateway/alembic/versions/006_add_branching_support.py`) - Migration ID: 006 - Revises: 005 - Adds columns to messages table with proper constraints and indexes - Includes upgrade() and downgrade() methods --- ## 🚧 Remaining Backend Work ### 1. API Endpoints #### Update Existing Endpoints **File:** `services/api-gateway/app/api/realtime.py` (or similar) Update message response schemas to include: ```python { "id": "uuid", "session_id": "uuid", "content": "...", "role": "user|assistant|system", "parent_message_id": "uuid|null", # NEW "branch_id": "string|null", # NEW "created_at": "datetime", ... } ``` #### New Branch Endpoint **Endpoint:** `POST /api/conversations/{conversation_id}/branches` **Request Body:** ```json { "parent_message_id": "uuid", "initial_message": "string" (optional) } ``` **Response:** ```json { "branch_id": "string", "conversation_id": "uuid", "parent_message_id": "uuid", "created_at": "datetime" } ``` **Logic:** 1. Validate parent message exists and belongs to conversation 2. Generate unique branch_id (e.g., `branch-{timestamp}-{short-uuid}`) 3. If initial_message provided, create first message in branch 4. Return branch details ### 2. Message Creation Update Update message creation endpoint to accept optional `branch_id`: **File:** `services/api-gateway/app/api/realtime.py` ```python async def create_message( session_id: UUID, content: str, role: str, branch_id: Optional[str] = None, # NEW parent_message_id: Optional[UUID] = None, # NEW ... ): message = Message( session_id=session_id, content=content, role=role, branch_id=branch_id or "main", # Default to "main" branch parent_message_id=parent_message_id, ... ) ... ``` ### 3. Branch Queries Add helper methods for branch management: ```python def get_branch_messages(session_id: UUID, branch_id: str) -> List[Message]: """Get all messages in a specific branch, maintaining conversation order""" # Start from first message, follow parent_message_id chain ... def get_branch_tree(session_id: UUID) -> Dict: """Get complete branch structure for visualization""" ... def get_available_branches(session_id: UUID) -> List[Dict]: """Get list of all branches with metadata""" ... ``` ### 4. Tests **File:** `services/api-gateway/tests/test_branches.py` (NEW) ```python def test_create_branch(): # Test creating a new branch from a message ... def test_get_branch_messages(): # Test retrieving messages in correct order ... def test_branch_isolation(): # Ensure branches don't interfere with each other ... def test_invalid_parent_message(): # Test error handling for invalid parent ... ``` --- ## 🚧 Frontend Work ### 1. Type Updates **File:** `packages/types/src/index.ts` ```typescript export interface Message { id: string; conversationId?: string; role: "user" | "assistant" | "system"; content: string; // NEW: Branching support parentId?: string; branchId?: string; delta?: string; citations?: Citation[]; attachments?: string[]; timestamp: number; metadata?: MessageMetadata; } // NEW: Branch interface export interface Branch { id: string; conversationId: string; parentMessageId: string; createdAt: string; messageCount: number; } ``` ### 2. API Client Updates **File:** `packages/api-client/src/index.ts` ```typescript // Add to VoiceAssistApiClient class async createBranch( conversationId: string, parentMessageId: string, initialMessage?: string ): Promise { const response = await this.client.post>( `/conversations/${conversationId}/branches`, { parent_message_id: parentMessageId, initial_message: initialMessage } ); return response.data.data!; } async getBranchMessages( conversationId: string, branchId: string ): Promise { const response = await this.client.get>( `/conversations/${conversationId}/branches/${branchId}/messages` ); return response.data.data!; } async listBranches(conversationId: string): Promise { const response = await this.client.get>( `/conversations/${conversationId}/branches` ); return response.data.data!; } ``` ### 3. Branch Management Hook **File:** `apps/web-app/src/hooks/useBranching.ts` (NEW) ```typescript /** * useBranching Hook * Manages conversation branching state and operations */ import { useState, useCallback } from "react"; import { useAuth } from "./useAuth"; import type { Branch } from "@voiceassist/types"; export function useBranching(conversationId: string) { const { apiClient } = useAuth(); const [currentBranchId, setCurrentBranchId] = useState("main"); const [branches, setBranches] = useState([]); const [isLoading, setIsLoading] = useState(false); const createBranch = useCallback( async (parentMessageId: string) => { setIsLoading(true); try { const branch = await apiClient.createBranch(conversationId, parentMessageId); setBranches((prev) => [...prev, branch]); setCurrentBranchId(branch.id); return branch; } catch (error) { console.error("Failed to create branch:", error); throw error; } finally { setIsLoading(false); } }, [conversationId, apiClient], ); const switchBranch = useCallback((branchId: string) => { setCurrentBranchId(branchId); }, []); const loadBranches = useCallback(async () => { setIsLoading(true); try { const branchList = await apiClient.listBranches(conversationId); setBranches(branchList); } catch (error) { console.error("Failed to load branches:", error); } finally { setIsLoading(false); } }, [conversationId, apiClient]); return { currentBranchId, branches, isLoading, createBranch, switchBranch, loadBranches, }; } ``` ### 4. UI Components #### Branch Button (MessageBubble) **File:** `apps/web-app/src/components/chat/MessageBubble.tsx` Add to MessageActionMenu: ```typescript {!isSystem && ( )} ``` #### Branch Sidebar **File:** `apps/web-app/src/components/chat/BranchSidebar.tsx` (NEW) ```typescript import { useBranching } from '@/hooks/useBranching'; export function BranchSidebar({ conversationId }: { conversationId: string }) { const { branches, currentBranchId, switchBranch, isLoading } = useBranching(conversationId); return (

Conversation Branches

{isLoading ? ( ) : (
    {branches.map(branch => (
  • ))}
)}
); } ``` ### 5. Keyboard Shortcuts Enhancement **File:** `apps/web-app/src/hooks/useKeyboardShortcuts.ts` Already exists - enhance with: - Cmd/Ctrl + K → Open conversation search - Cmd/Ctrl + B → Toggle branch sidebar - Cmd/Ctrl + Shift + B → Create branch at current message #### Keyboard Shortcuts Dialog **File:** `apps/web-app/src/components/KeyboardShortcutsDialog.tsx` (NEW) ```typescript import { KEYBOARD_SHORTCUTS } from '@/hooks/useKeyboardShortcuts'; export function KeyboardShortcutsDialog({ isOpen, onClose }: Props) { return ( Keyboard Shortcuts
{KEYBOARD_SHORTCUTS.map(shortcut => (
{shortcut.description} {shortcut.metaKey ? '⌘' : shortcut.ctrlKey ? 'Ctrl' : ''} {shortcut.key}
))}
); } ``` --- ## 📋 Testing Checklist ### Backend Tests - [ ] Create branch endpoint returns valid branch_id - [ ] Messages created in branch have correct branch_id - [ ] Querying branch messages returns only that branch's messages - [ ] Parent message validation works correctly - [ ] Migration applies and rolls back cleanly ### Frontend Tests - [ ] Branch button appears on messages - [ ] Creating branch updates UI state - [ ] Switching branches loads correct messages - [ ] Keyboard shortcuts trigger expected actions - [ ] Shortcuts dialog displays all shortcuts - [ ] Branch sidebar shows all branches - [ ] Branch persistence across page refresh --- ## 🚀 Deployment Steps 1. **Apply Migration:** ```bash cd services/api-gateway source venv/bin/activate alembic upgrade head ``` 2. **Verify Migration:** ```bash psql $DATABASE_URL -c "\d messages" # Should show parent_message_id and branch_id columns ``` 3. **Run Backend Tests:** ```bash make test ``` 4. **Build Frontend:** ```bash pnpm build ``` 5. **Run Frontend Tests:** ```bash pnpm test ``` 6. **Deploy to Production:** - Backend: Docker compose up with new image - Frontend: Deploy build artifacts - Run smoke tests --- ## 📝 Notes & Considerations ### Branch ID Format - Use format: `branch-{timestamp}-{shortUUID}` - Main conversation uses branch_id="main" - Ensures uniqueness and sortability ### Performance - Index on `branch_id` for fast filtering - Index on `parent_message_id` for tree traversal - Consider caching branch structure for large conversations ### UI/UX - Visual indicator showing current branch - Breadcrumb showing branch lineage - Confirmation before switching branches with unsaved changes - Color-code different branches for easy identification ### Future Enhancements - Branch merging - Branch naming/descriptions - Branch comparison view - Export branch to new conversation - Branch permissions/sharing --- ## 🐛 Known Issues 1. **Migration requires manual application** - Alembic autogenerate needs database connection 2. **WebSocket timing in tests** - Known issue documented in KNOWN_ISSUES.md 3. **ESM import issues with react-syntax-highlighter** - Affects 5 test suites --- **Status:** Phase 3 Complete - Frontend UI Components and Keyboard Shortcuts Ready **Completed in Phase 2:** - ✅ Backend API endpoints (`/api/conversations/{id}/branches`) - ✅ Pydantic schemas and response models - ✅ Router registered in main.py - ✅ Frontend Message type extended with `parentId` and `branchId` - ✅ Frontend Branch type and CreateBranchRequest added - ✅ API client methods: `createBranch()`, `listBranches()`, `getBranchMessages()` **Completed in Phase 3:** - ✅ `useBranching` hook for branch state management (`apps/web-app/src/hooks/useBranching.ts`) - ✅ Branch button added to MessageActionMenu (`apps/web-app/src/components/chat/MessageActionMenu.tsx`) - ✅ Branch button wired up in MessageBubble (`apps/web-app/src/components/chat/MessageBubble.tsx`) - ✅ BranchSidebar component created (`apps/web-app/src/components/chat/BranchSidebar.tsx`) - ✅ Keyboard shortcuts hook (`apps/web-app/src/hooks/useKeyboardShortcuts.ts`) - Cmd/Ctrl + B: Toggle branch sidebar - Cmd/Ctrl + Shift + B: Create branch from current message - Cmd/Ctrl + Enter: Send message - Escape: Cancel editing **Next Steps:** 1. Integrate components into main chat page 2. Add tests for branching features 3. Apply database migration (`alembic upgrade head`) 4. End-to-end testing of branching workflow 6:["slug","archive/BRANCHING_AND_SHORTCUTS_IMPLEMENTATION","c"] 0:["X7oMT3VrOffzp0qvbeOas",[[["",{"children":["docs",{"children":[["slug","archive/BRANCHING_AND_SHORTCUTS_IMPLEMENTATION","c"],{"children":["__PAGE__?{\"slug\":[\"archive\",\"BRANCHING_AND_SHORTCUTS_IMPLEMENTATION\"]}",{}]}]}]},"$undefined","$undefined",true],["",{"children":["docs",{"children":[["slug","archive/BRANCHING_AND_SHORTCUTS_IMPLEMENTATION","c"],{"children":["__PAGE__",{},[["$L1",["$","div",null,{"children":[["$","div",null,{"className":"mb-6 flex items-center justify-between gap-4","children":[["$","div",null,{"children":[["$","p",null,{"className":"text-sm text-gray-500 dark:text-gray-400","children":"Docs / Raw"}],["$","h1",null,{"className":"text-3xl font-bold text-gray-900 dark:text-white","children":"Branching And Shortcuts Implementation"}],["$","p",null,{"className":"text-sm text-gray-600 dark:text-gray-400","children":["Sourced from"," ",["$","code",null,{"className":"font-mono text-xs","children":["docs/","archive/BRANCHING_AND_SHORTCUTS_IMPLEMENTATION.md"]}]]}]]}],["$","a",null,{"href":"https://github.com/mohammednazmy/VoiceAssist/edit/main/docs/archive/BRANCHING_AND_SHORTCUTS_IMPLEMENTATION.md","target":"_blank","rel":"noreferrer","className":"inline-flex items-center gap-2 rounded-md border border-gray-200 dark:border-gray-700 px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:border-primary-500 dark:hover:border-primary-400 hover:text-primary-700 dark:hover:text-primary-300","children":"Edit on GitHub"}]]}],["$","div",null,{"className":"rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6","children":["$","$L2",null,{"content":"$3"}]}],["$","div",null,{"className":"mt-6 flex flex-wrap gap-2 text-sm","children":[["$","$L4",null,{"href":"/reference/all-docs","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"← All documentation"}],["$","$L4",null,{"href":"/","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"Home"}]]}]]}],null],null],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children","$6","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7f586cdbbaa33ff7.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"__className_f367f3 h-full bg-white dark:bg-gray-900","children":[["$","a",null,{"href":"#main-content","className":"skip-to-content","children":"Skip to main content"}],["$","$L8",null,{"children":[["$","$L9",null,{}],["$","$La",null,{}],["$","main",null,{"id":"main-content","className":"lg:pl-64","role":"main","aria-label":"Documentation content","children":["$","$Lb",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]]}]]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Branching And Shortcuts Implementation | Docs | VoiceAssist Docs"}],["$","meta","3",{"name":"description","content":"**Date:** 2025-11-23"}],["$","meta","4",{"name":"keywords","content":"VoiceAssist,documentation,medical AI,voice assistant,healthcare,HIPAA,API"}],["$","meta","5",{"name":"robots","content":"index, follow"}],["$","meta","6",{"name":"googlebot","content":"index, follow"}],["$","link","7",{"rel":"canonical","href":"https://assistdocs.asimo.io"}],["$","meta","8",{"property":"og:title","content":"VoiceAssist Documentation"}],["$","meta","9",{"property":"og:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","10",{"property":"og:url","content":"https://assistdocs.asimo.io"}],["$","meta","11",{"property":"og:site_name","content":"VoiceAssist Docs"}],["$","meta","12",{"property":"og:type","content":"website"}],["$","meta","13",{"name":"twitter:card","content":"summary"}],["$","meta","14",{"name":"twitter:title","content":"VoiceAssist Documentation"}],["$","meta","15",{"name":"twitter:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","16",{"name":"next-size-adjust"}]] 1:null