📚 Docs / Chatbot UI Design Recommendation for Lawyer Assistant

Chatbot UI Design Recommendation for Lawyer Assistant

This document presents a comprehensive design recommendation for building a modern, professional Electron desktop application for the Legal Document Retrieval System. The interface will provide an intuitive way for users to interact with the RAG (Retrieval-Augmented Generation) pipeline, ask legal questions, and receive answers with source citations.

Overview

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Electron App                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Main Process (Node.js)                  │   │
│  │  • App lifecycle management                          │   │
│  │  • Window management                                 │   │
│  │  • Python backend spawning                           │   │
│  │  • IPC communication                                 │   │
│  │  • File system access                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                   │
│                    IPC Bridge                                │
│                          │                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │         Renderer Process (React + TypeScript)        │   │
│  │  • Chat UI                                           │   │
│  │  • Source viewer                                     │   │
│  │  • Settings                                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                   │
│                    HTTP/WebSocket                            │
│                          │                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │         Python Backend (FastAPI/Flask)               │   │
│  │  • ChatPipeline                                      │   │
│  │  • RAG processing                                    │   │
│  │  • Document management                               │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Key Design Principles

  1. Professional & Trustworthy - Legal domain requires a clean, authoritative design
  2. Clean & Modern - Contemporary design with clear visual hierarchy
  3. Information-Rich - Display sources, citations, and document references prominently
  4. Responsive - Works seamlessly on desktop, tablet, and mobile
  5. Accessible - WCAG 2.1 compliance for broad usability
  6. Offline-Capable - Core functionality works without internet

Technology Stack

Frontend (Renderer Process)

Electron Main Process

Backend (Python)

Project Structure

lawyer-assistant/
├── frontend/electron/           # Electron main process
│   ├── src/
│   │   ├── main/
│   │   │   ├── index.ts        # Main entry point
│   │   │   ├── window.ts       # Window management
│   │   │   ├── menu.ts         # Application menu
│   │   │   ├── tray.ts         # System tray
│   │   │   └── python-bridge.ts # Python process management
│   │   ├── preload/
│   │   │   └── index.ts        # Context bridge
│   │   └── utils/
│   │       └── paths.ts        # File paths utility
│   ├── electron-builder.yml
│   └── package.json
├── frontend/                    # React UI (Renderer)
│   ├── src/
│   │   ├── components/
│   │   │   ├── Chat/
│   │   │   │   ├── ChatArea.tsx
│   │   │   │   ├── Message.tsx
│   │   │   │   ├── UserMessage.tsx
│   │   │   │   ├── AssistantMessage.tsx
│   │   │   │   ├── TypingIndicator.tsx
│   │   │   │   └── InputBar.tsx
│   │   │   ├── Sources/
│   │   │   │   ├── SourcesPanel.tsx
│   │   │   │   ├── SourceItem.tsx
│   │   │   │   └── SourceViewerModal.tsx
│   │   │   ├── Sidebar/
│   │   │   │   ├── Sidebar.tsx
│   │   │   │   └── ConversationList.tsx
│   │   │   ├── Layout/
│   │   │   │   ├── Header.tsx
│   │   │   │   └── TitleBar.tsx
│   │   │   └── UI/
│   │   │       └── ...
│   │   ├── hooks/
│   │   ├── services/
│   │   │   └── api.ts
│   │   ├── store/
│   │   ├── types/
│   │   └── App.tsx
│   ├── index.html
│   ├── package.json
│   ├── vite.config.ts
│   └── tsconfig.json
├── backend/                      # Python backend
│   ├── main.py                   # FastAPI server entry
│   ├── api/
│   │   ├── chat.py               # Chat endpoints
│   │   └── documents.py          # Document endpoints
│   ├── backend/legal_retrieval/  # Existing RAG code
│   └── requirements.txt
├── package.json                  # Root package.json
└── README.md

Layout Design

Desktop Layout

┌─────────────────────────────────────────────────────────────────┐
│ ─ Title Bar (Custom) ────────────────────────────────────────── │
│ [Logo] Lawyer Assistant             [─] [□] [×]                │
├──────────────┬──────────────────────────────┬───────────────────┤
│              │                              │                   │
│   Sidebar    │       Chat Area              │   Sources Panel   │
│   (280px)    │                              │   (320px)         │
│              │  ┌─────────────────────┐    │                   │
│  [New Chat]  │  │ User: What is...    │    │  Sources [3]      │
│              │  └─────────────────────┘    │                   │
│  History     │  ┌─────────────────────┐    │  ┌─────────────┐ │
│  ──────────  │  │ Assistant: Based    │    │  │ [1] Contract │ │
│  • Chat 1    │  │ on the document     │    │  │ Page 5      │ │
│  • Chat 2    │  │ [1] [2] [3]         │◄───│  │ [View Full] │ │
│  • Chat 3    │  └─────────────────────┘    │  └─────────────┘ │
│              │                              │                   │
│  Documents   │  ┌─────────────────────┐    │  ┌─────────────┐ │
│  ──────────  │  │ User: Can you...    │    │  │ [2] Clause   │ │
│  📄 doc1.pdf │  └─────────────────────┘    │  │ Page 12      │ │
│  📄 doc2.pdf │                              │  │ [View Full] │ │
│              │  ┌─────────────────────┐    │  └─────────────┘ │
│              │  │ [Thinking...]       │    │                   │
│              │  └─────────────────────┘    │                   │
│              │                              │                   │
├──────────────┴──────────────────────────────┴───────────────────┤
│ ─ Input Bar ────────────────────────────────────────────────── │
│ [Mode: RAG ▾] │ Type your question...              │ [Send]   │
└─────────────────────────────────────────────────────────────────┘

Source Viewer Modal (Floating)

┌────────────────────────────────────────────────┐
│ 📄 Source [1] - Contract Agreement    [─][□][×] │
├────────────────────────────────────────────────┤
│ Document: contract_2024.pdf                    │
│ Page: 5 | Section: 3.2 | Relevance: 94%       │
├────────────────────────────────────────────────┤
│                                                │
│ "The parties agree that any modification to    │
│ this agreement must be made in writing and     │
│ signed by both parties. Verbal agreements      │
│ shall not be considered binding under this     │
│ contract..."                                   │
│                                                │
│ [Highlighted matching terms shown in bold]     │
│                                                │
├────────────────────────────────────────────────┤
│ [📋 Copy] [📌 Pin] [Open Document]    [Close]  │
└────────────────────────────────────────────────┘

Component Breakdown

1. Custom Title Bar

typescriptinterface TitleBarProps {
  appName: string;
  onMinimize: () => void;
  onMaximize: () => void;
  onClose: () => void;
  isMaximized: boolean;
}

2. Chat Area Component

typescriptinterface ChatAreaProps {
  messages: Message[];
  isTyping: boolean;
  onRetry: (messageId: string) => void;
  onCopy: (messageId: string) => void;
  onCitationClick: (sourceId: string) => void;
}

Message Components:

3. Source Viewer Modal

typescriptinterface SourceViewerModalProps {
  source: Source | null;
  isOpen: boolean;
  onClose: () => void;
  citationNumber: number;
}

4. Sources Panel

typescriptinterface SourcesPanelProps {
  sources: Source[];
  selectedSourceId: string | null;
  onSelectSource: (id: string) => void;
  onOpenViewer: (sourceId: string) => void;
  isOpen: boolean;
  onToggle: () => void;
}

Electron Main Process

Window Management

typescript// frontend/electron/src/main/window.ts
import { BrowserWindow } from 'electron';

export function createMainWindow(): BrowserWindow {
  const mainWindow = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 1000,
    minHeight: 600,
    frame: false, // Custom title bar
    titleBarStyle: 'hidden',
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, '../preload/index.js'),
    },
  });
  
  return mainWindow;
}

Python Backend Bridge

typescript// frontend/electron/src/main/python-bridge.ts
import { spawn, ChildProcess } from 'child_process';
import { app } from 'electron';

export class PythonBackend {
  private process: ChildProcess | null = null;
  private port: number = 8765;
  
  async start(): Promise<void> {
    const pythonPath = this.getPythonPath();
    const scriptPath = this.getBackendScript();
    
    this.process = spawn(pythonPath, [scriptPath, '--port', String(this.port)], {
      stdio: 'pipe',
    });
    
    // Wait for server to be ready
    await this.waitForReady();
  }
  
  private getPythonPath(): string {
    if (app.isPackaged) {
      // Packaged: use bundled Python
      return path.join(process.resourcesPath, 'python', 'python.exe');
    }
    // Development: use system Python
    return 'python';
  }
  
  private getBackendScript(): string {
    if (app.isPackaged) {
      return path.join(process.resourcesPath, 'python', 'main.py');
    }
    return path.join(__dirname, '../../backend/main.py');
  }
  
  async stop(): Promise<void> {
    if (this.process) {
      this.process.kill();
      this.process = null;
    }
  }
}

Preload Script (Security Bridge)

typescript// frontend/electron/src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('electronAPI', {
  // Window controls
  minimize: () => ipcRenderer.invoke('window:minimize'),
  maximize: () => ipcRenderer.invoke('window:maximize'),
  close: () => ipcRenderer.invoke('window:close'),
  
  // Python backend
  getBackendPort: () => ipcRenderer.invoke('python:port'),
  
  // File system
  openFile: () => ipcRenderer.invoke('dialog:openFile'),
  readFile: (path: string) => ipcRenderer.invoke('fs:readFile', path),
  
  // App info
  getVersion: () => ipcRenderer.invoke('app:version'),
  getPlatform: () => ipcRenderer.invoke('app:platform'),
});

IPC Communication

Main Process Handlers

typescript// frontend/electron/src/main/index.ts
import { ipcMain } from 'electron';

ipcMain.handle('window:minimize', () => mainWindow.minimize());
ipcMain.handle('window:maximize', () => {
  if (mainWindow.isMaximized()) {
    mainWindow.unmaximize();
  } else {
    mainWindow.maximize();
  }
});
ipcMain.handle('window:close', () => mainWindow.close());

ipcMain.handle('python:port', () => pythonBackend.getPort());

Frontend API Service

typescript// frontend/src/services/api.ts
class ApiService {
  private baseUrl: string;
  
  constructor() {
    // In development, use localhost
    // In production, get port from Electron
    this.baseUrl = 'http://localhost:8765';
  }
  
  async sendMessage(request: ChatRequest): Promise<ChatResponse> {
    const response = await fetch(`${this.baseUrl}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request),
    });
    return response.json();
  }
  
  async getDocuments(): Promise<Document[]> {
    const response = await fetch(`${this.baseUrl}/api/documents`);
    return response.json();
  }
}

Python Backend API

python# backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

app = FastAPI()

# Allow Electron renderer
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # Vite dev server
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.post("/api/chat")
async def chat(request: ChatRequest):
    # Use existing ChatPipeline
    response = pipeline.chat(
        query=request.query,
        mode=request.mode,
        top_k=request.top_k,
    )
    return response.to_dict()

@app.get("/api/documents")
async def get_documents():
    # Return list of ingested documents
    pass

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8765)

Packaging & Distribution

electron-builder.yml

yamlappId: com.lawyerassistant.app
productName: Lawyer Assistant
directories:
  output: dist
files:
  - frontend/dist/**/*
  - frontend/electron/dist/**/*
extraResources:
  - from: backend/
    to: python/
    filter:
      - "**/*.py"
      - "requirements.txt"
  - from: python-env/
    to: python/env/
win:
  target: nsis
  icon: assets/icon.ico
mac:
  target: dmg
  icon: assets/icon.icns
linux:
  target: AppImage
  icon: assets/icon.png

Build Scripts

json{
  "scripts": {
    "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\" \"npm run dev:electron\"",
    "dev:backend": "cd backend && python main.py",
    "dev:frontend": "cd frontend && npm run dev",
    "dev:electron": "cd electron && npm run dev",
    "build": "npm run build:frontend && npm run build:electron",
    "build:frontend": "cd frontend && npm run build",
    "build:electron": "cd electron && npm run build",
    "package": "npm run build && cd electron && npm run package",
    "package:win": "npm run build && cd electron && npm run package:win",
    "package:mac": "npm run build && cd electron && npm run package:mac",
    "package:linux": "npm run build && cd electron && npm run package:linux"
  }
}

Styling & Theming

Color Palette (Light Mode)

css:root {
  --primary: #2563eb;
  --primary-hover: #1d4ed8;
  --background: #ffffff;
  --surface: #f8fafc;
  --text-primary: #0f172a;
  --text-secondary: #64748b;
  --border: #e2e8f0;
  --citation: #8b5cf6;
}

Color Palette (Dark Mode)

css[data-theme="dark"] {
  --background: #0f172a;
  --surface: #1e293b;
  --text-primary: #f8fafc;
  --text-secondary: #94a3b8;
  --border: #334155;
}

Typography

cssfont-family: 'Inter', 'Segoe UI', system-ui, sans-serif;

Interactive Features

1. Citation Interactions

2. Source Viewer Modal

3. Keyboard Shortcuts

Security Considerations

  1. Context Isolation - Enabled by default
  2. Node Integration - Disabled in renderer
  3. Preload Script - Secure bridge only
  4. CSP Headers - Strict content security policy
  5. Input Sanitization - All IPC data validated
  6. Local Backend - No external network access required

Implementation Phases

Phase 1: Project Setup

Phase 2: Core Chat

Phase 3: Sources & Citations

Phase 4: History & Settings

Phase 5: Polish & Packaging

Estimated Timeline

Dependencies

Frontend

json{
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "zustand": "^4.4.0",
  "react-markdown": "^9.0.0",
  "lucide-react": "^0.294.0"
}

Electron

json{
  "electron": "^28.0.0",
  "electron-builder": "^24.0.0",
  "electron-vite": "^2.0.0"
}

Backend

fastapi>=0.104.0
uvicorn>=0.24.0

This design document provides a comprehensive roadmap for building a professional, secure, and feature-rich Electron desktop application for the Lawyer Assistant system.

Questions, answered

Short, self-contained answers about this guide.

How are citations shown in the chat UI?

Inline citation chips render inside the answer and link to the exact source passage; opening one focuses the source viewer. The design doc covers the chip states and interaction details.

What is the selection chip?

When you right-click a passage and choose 'Ask assistant about selection', the raw selection appears as a chip above the query so you (and the pipeline) know exactly what text is being asked about.

How does streaming feel?

Tokens render live with reasoning streamed into a separate thinking panel, keeping the answer readable while the model deliberates. The doc details the event-to-component mapping.