VoiceAssist Docs

Knowledge Base

Uploading and indexing clinical sources

stabledocs2025-12-02devops
adminpanelspecs

Admin Panel Specifications

Overview

The VoiceAssist Admin Panel provides a centralized web interface for system configuration, monitoring, and management. Accessible at admin.asimo.io.

Technology Stack

Frontend

  • Framework: React 18+ with TypeScript
  • Build Tool: Vite
  • Styling: Tailwind CSS
  • Component Library: shadcn/ui or Tremor (for dashboards)
  • Charts: Recharts or Chart.js
  • Tables: TanStack Table (React Table v8)
  • State Management: Zustand or React Context

Backend

  • Framework: FastAPI (Python)
  • Authentication: JWT with admin role
  • Database: PostgreSQL for admin data
  • Real-time: WebSocket for live metrics

Standard API Envelope

All admin API calls return a standard envelope. See server/README.md for complete specification.

Use the same TypeScript types and fetch helper from WEB_APP_SPECS.md (can be shared package or duplicated).

Usage Example - KB Management

// admin/hooks/useKBManagement.ts import { useMutation, useQuery } from "@tanstack/react-query"; import { fetchAPI, APIError } from "@/lib/api"; import { KnowledgeDocument, IndexingJob } from "@/types"; // From DATA_MODEL.md import { toast } from "@/lib/toast"; export function useKBDocuments() { return useQuery({ queryKey: ["kb-documents"], queryFn: async () => { return fetchAPI<KnowledgeDocument[]>("/api/admin/kb/documents"); }, }); } export function useUploadDocument() { return useMutation({ mutationFn: async (file: File) => { const formData = new FormData(); formData.append("file", file); return fetchAPI<IndexingJob>("/api/admin/kb/upload", { method: "POST", body: formData, headers: { // Don't set Content-Type, let browser set it with boundary }, }); }, onSuccess: (job) => { toast.success(`Upload started: ${job.id}`); }, onError: (error: APIError) => { if (error.code === "VALIDATION_ERROR") { toast.error("Invalid file format. Only PDF and DOCX supported."); } else if (error.code === "CONFLICT") { toast.error("A document with this name already exists"); } else { toast.error(`Upload failed: ${error.message}`); } }, }); } export function useReindexDocuments() { return useMutation({ mutationFn: async (docIds: string[]) => { return fetchAPI<{ job_count: number }>("/api/admin/kb/reindex", { method: "POST", body: JSON.stringify({ document_ids: docIds }), }); }, }); }

API Integration Examples

Note: For canonical entity definitions (JSON Schema, Pydantic, TypeScript), see DATA_MODEL.md. This section provides usage examples specific to the admin panel.

Knowledge Base Management (TypeScript):

// services/api/admin.ts import axios from "axios"; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, withCredentials: true, // Send JWT cookies headers: { "Content-Type": "application/json", }, }); export interface DocumentUploadResponse { documentId: string; filename: string; status: string; message: string; } export interface DocumentListResponse { id: string; filename: string; sourceType: string; specialty: string; status: string; fileSize: number; chunkCount?: number; uploadedAt: string; indexedAt?: string; } export interface ReindexRequest { documentIds: string[]; force: boolean; } export interface ReindexResponse { jobId: string; documentCount: number; status: string; message: string; } // Upload document export async function uploadDocument( file: File, sourceType: string, specialty: string, ): Promise<DocumentUploadResponse> { const formData = new FormData(); formData.append("file", file); formData.append("sourceType", sourceType); formData.append("specialty", specialty); const response = await api.post("/api/admin/knowledge/upload", formData, { headers: { "Content-Type": "multipart/form-data", }, }); return response.data; } // List documents export async function listDocuments( skip: number = 0, limit: number = 50, status?: string, ): Promise<DocumentListResponse[]> { const response = await api.get("/api/admin/knowledge/documents", { params: { skip, limit, status }, }); return response.data; } // Trigger reindex export async function triggerReindex(documentIds: string[] = [], force: boolean = false): Promise<ReindexResponse> { const response = await api.post("/api/admin/knowledge/reindex", { documentIds, force, }); return response.data; } // Get vector DB stats export async function getVectorDBStats(): Promise<VectorDBStatsResponse> { const response = await api.get("/api/admin/knowledge/stats"); return response.data; }

React Hook for Document Management:

// hooks/useDocuments.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import * as adminApi from '@/services/api/admin'; export function useDocuments(skip = 0, limit = 50, status?: string) { return useQuery({ queryKey: ['documents', skip, limit, status], queryFn: () => adminApi.listDocuments(skip, limit, status), }); } export function useUploadDocument() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ file, sourceType, specialty, }: { file: File; sourceType: string; specialty: string; }) => adminApi.uploadDocument(file, sourceType, specialty), onSuccess: () => { // Invalidate documents list to refetch queryClient.invalidateQueries({ queryKey: ['documents'] }); }, }); } export function useReindexDocuments() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ documentIds, force, }: { documentIds: string[]; force: boolean; }) => adminApi.triggerReindex(documentIds, force), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['documents'] }); }, }); } // Usage in component function KnowledgeBaseManager() { const { data: documents, isLoading } = useDocuments(0, 50); const uploadMutation = useUploadDocument(); const reindexMutation = useReindexDocuments(); const handleUpload = (file: File) => { uploadMutation.mutate({ file, sourceType: 'textbook', specialty: 'cardiology', }); }; const handleReindexAll = () => { reindexMutation.mutate({ documentIds: [], // Empty = reindex all force: false, }); }; return ( <div> <button onClick={handleReindexAll} disabled={reindexMutation.isPending}> Reindex All Documents </button> {/* Document list and upload UI */} </div> ); }

Real-time Metrics with WebSocket:

// hooks/useRealtimeMetrics.ts import { useEffect, useState } from 'react'; export interface SystemMetrics { cpuUsage: number; memoryUsage: number; diskUsage: number; activeSessions: number; apiCallsToday: number; errorRate: number; } export function useRealtimeMetrics() { const [metrics, setMetrics] = useState<SystemMetrics | null>(null); const [isConnected, setIsConnected] = useState(false); useEffect(() => { const ws = new WebSocket(`${import.meta.env.VITE_WS_URL}/admin/metrics`); ws.onopen = () => { setIsConnected(true); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); setMetrics(data); }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; ws.onclose = () => { setIsConnected(false); }; return () => { ws.close(); }; }, []); return { metrics, isConnected }; } // Usage in dashboard component function Dashboard() { const { metrics, isConnected } = useRealtimeMetrics(); if (!isConnected) { return <div>Connecting to metrics stream...</div>; } if (!metrics) { return <div>Loading metrics...</div>; } return ( <div className="grid grid-cols-4 gap-4"> <MetricCard title="Active Sessions" value={metrics.activeSessions} /> <MetricCard title="API Calls Today" value={metrics.apiCallsToday} /> <MetricCard title="CPU Usage" value={`${metrics.cpuUsage}%`} /> <MetricCard title="Error Rate" value={`${metrics.errorRate}%`} /> </div> ); }

Authentication & Authorization

Access Control

  • Admin Role: Full access to all features
  • Viewer Role: Read-only access (future)
  • API Keys: Secure management with limited scopes

Security

  • Strong password requirements
  • Two-factor authentication (TOTP)
  • Session timeout
  • Login attempt limiting
  • Audit log of all admin actions

Interface Layout

┌─────────────────────────────────────────────────────┐
│  Header                                              │
│  [Logo] Admin Panel          Dr. Nazmy [Logout] ⚙️  │
├──────────────┬──────────────────────────────────────┤
│              │                                       │
│  Sidebar     │  Main Content Area                   │
│  Menu        │                                       │
│              │  [Dashboard/Settings/etc content]    │
│  📊 Dashboard│                                       │
│  ⚙️  System  │                                       │
│  🤖 AI Models│                                       │
│  📚 Knowledge│                                       │
│  👤 Users    │                                       │
│  📈 Analytics│                                       │
│  🔌 Integrations                                     │
│  🔒 Security │                                       │
│  📋 Logs     │                                       │
│              │                                       │
└──────────────┴──────────────────────────────────────┘

Core Pages

1. Dashboard (/dashboard)

Full Dashboard Wireframe:

┌─────────────────────────────────────────────────────────────────────────────────────┐
│ VoiceAssist Admin                   🔔 Alerts (2)     Dr. Nazmy ▼     [Logout]     │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  Dashboard                                                    Last updated: 10s ago │
│                                                                                      │
│  ┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐    │
│  │ Active Sessions  │ API Calls Today  │ Avg Response     │ Error Rate       │    │
│  │                  │                  │                  │                  │    │
│  │      3           │      1,247       │      1.8s        │      0.3%        │    │
│  │  ↑ +1 today      │  ↑ +12% vs avg  │  ↓ -0.2s         │  ↓ -0.1%         │    │
│  └──────────────────┴──────────────────┴──────────────────┴──────────────────┘    │
│                                                                                      │
│  ┌───────────────────────────────┬───────────────────────────────────────────┐    │
│  │ System Resources              │ Service Status                             │    │
│  │                               │                                            │    │
│  │  CPU Usage          68%  ████ │  ┌───────────────────────┬──────────┐    │    │
│  │  Memory Usage       45%  ███  │  │ FastAPI Backend       │  🟢 Up   │    │    │
│  │  GPU Usage (Ollama) 82%  █████│  │ PostgreSQL            │  🟢 Up   │    │    │
│  │  Disk Space         28%  ██   │  │ Redis Cache           │  🟢 Up   │    │    │
│  │  Network I/O        ↑12MB/s   │  │ Vector DB (Qdrant)    │  🟢 Up   │    │    │
│  │  WS Connections     3 active  │  │ Ollama (Local LLM)    │  🟢 Up   │    │    │
│  │                               │  │ OpenAI API            │  🟢 Up   │    │    │
│  │  [View Details]               │  │ Nextcloud             │  🟡 Slow │    │    │
│  │                               │  └───────────────────────┴──────────┘    │    │
│  │                               │                                            │    │
│  └───────────────────────────────┴───────────────────────────────────────────┘    │
│                                                                                      │
│  ┌──────────────────────────────────────────────────────────────────────────┐     │
│  │ Response Time (Last 24 Hours)                                             │     │
│  │                                                                            │     │
│  │  5s  ┤                                                                     │     │
│  │  4s  ┤                                                                     │     │
│  │  3s  ┤          ▄                                                          │     │
│  │  2s  ┤  ▄▄▄▄▄▄▄█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  Cloud API                         │     │
│  │  1s  ┤▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  Local Model                       │     │
│  │  0s  └─┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──────────                     │     │
│  │       00 02 04 06 08 10 12 14 16 18 20 22 Now                            │     │
│  └──────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
│  ┌──────────────────────────────────┬──────────────────────────────────────┐      │
│  │ Recent Activity                  │ Quick Actions                         │      │
│  │                                  │                                       │      │
│  │  [10:32] Query: HTN management   │  [⟳ Restart Services]                │      │
│  │  [10:28] Document indexed: DAPA  │  [🗑️ Clear Cache]                    │      │
│  │  [10:15] Query: Drug interaction │  [📚 Update Knowledge Base]          │      │
│  │  [09:58] Error: Nextcloud timeout│  [📋 View System Logs]               │      │
│  │  [09:45] Query: Lab interpretation│ [✓ Run Health Check]                │      │
│  │                                  │                                       │      │
│  │  [View All Activity]             │                                       │      │
│  └──────────────────────────────────┴──────────────────────────────────────┘      │
│                                                                                      │
│  ┌──────────────────────────────────────────────────────────────────────────┐     │
│  │ Alerts & Notifications                                                    │     │
│  │                                                                            │     │
│  │  ⚠️  [10:33] Nextcloud response time degraded (1.2s → 3.5s)              │     │
│  │  ℹ️  [08:00] Backup completed successfully (Database: 2.3GB)             │     │
│  │                                                                            │     │
│  │  [View All Alerts]                                                        │     │
│  └──────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

Dashboard Components Summary

System metrics defined in OBSERVABILITY.md.

System Overview Cards:

  • Active Sessions: Current WebSocket/REST sessions
  • API Calls Today: Total API requests with trend indicator
  • Avg Response Time: Mean latency with change indicator
  • Error Rate: Percentage of failed requests with trend

Real-Time Metrics:

  • CPU, Memory, GPU usage with bar indicators
  • Disk space utilization
  • Network I/O (upload/download)
  • Active WebSocket connections count

Service Status Table:

  • Health check status for each service (🟢 Up, 🟡 Degraded, 🔴 Down)
  • Last check timestamp
  • Click to view detailed metrics

Response Time Chart:

  • Line chart showing 24-hour latency trends
  • Separate lines for local model vs cloud API
  • Hoverable data points

Recent Activity Feed:

  • Last 10-20 system events
  • Query summaries (sanitized)
  • Document indexing events
  • Error notifications

Quick Actions:

  • One-click service management
  • Cache clearing
  • Knowledge base updates
  • Log viewing
  • Health check execution

2. System Settings (/system)

System settings are global - they affect all users and the entire VoiceAssist instance.

System Settings Interface

Complete TypeScript interface for system-level configuration:

// admin-panel/src/types/systemSettings.ts export interface SystemSettings { // General System Configuration general: { systemName: string; // Display name (e.g., "VoiceAssist Production") systemDescription: string; // Description for documentation timezone: string; // Default system timezone (IANA) language: "en" | "es" | "fr"; // Default interface language maintenanceMode: boolean; // Enable maintenance mode (blocks users) maintenanceMessage: string; // Message shown during maintenance }; // Data Retention & Cleanup dataRetention: { conversationLogs: number; // Days to keep conversation logs (7-365) errorLogs: number; // Days to keep error logs (30-365) accessLogs: number; // Days to keep access logs (30-730) auditLogs: number; // Days to keep audit logs (365-2555) tempFiles: number; // Days to keep temp files (1-30) autoCleanup: boolean; // Enable automatic cleanup cleanupSchedule: string; // Cron expression for cleanup }; // Backup Configuration backup: { enabled: boolean; destination: "local" | "nextcloud" | "s3" | "custom"; schedule: string; // Cron expression retention: number; // Number of backups to keep includeDatabase: boolean; includeVectorDB: boolean; includeDocuments: boolean; includeConfiguration: boolean; includeLogs: boolean; compression: "none" | "gzip" | "zstd"; encryption: boolean; }; // Model Routing & AI Configuration ai: { // Default model preferences defaultLocalModel: string; // e.g., "llama-3.1-8b" defaultCloudModel: string; // e.g., "gpt-4-turbo" defaultEmbeddingModel: string; // e.g., "text-embedding-3-large" // Routing rules routingStrategy: "auto" | "always_local" | "always_cloud"; phiDetectionEnabled: boolean; // Auto-detect PHI and route to local phiKeywords: string[]; // Keywords that trigger PHI detection // Performance limits maxConcurrentRequests: number; // Max concurrent AI requests requestTimeout: number; // Timeout in seconds retryAttempts: number; // Number of retry attempts // Rate limiting (per user) rateLimits: { queriesPerMinute: number; queriesPerHour: number; queriesPerDay: number; }; // Cost controls costLimits: { dailyLimit: number; // $ per day monthlyLimit: number; // $ per month alertThreshold: number; // % threshold for alerts (e.g., 80) }; }; // Logging & Monitoring logging: { level: "DEBUG" | "INFO" | "WARN" | "ERROR"; structuredLogging: boolean; // JSON format logToFile: boolean; logToConsole: boolean; logToSyslog: boolean; sensitiveDataRedaction: boolean; // Redact PHI/PII from logs performanceLogging: boolean; // Log slow queries performanceThreshold: number; // ms threshold for slow query logging }; // Security & Privacy security: { // Session management sessionTimeout: number; // Minutes of inactivity maxSessionDuration: number; // Max session duration in hours requireStrongPasswords: boolean; passwordMinLength: number; passwordRequireSpecialChars: boolean; passwordExpiryDays: number; // 0 = never // Two-factor authentication twoFactorRequired: boolean; twoFactorMethod: "totp" | "sms" | "email"; // API security apiRateLimiting: boolean; apiRateLimit: number; // Requests per minute corsOrigins: string[]; // Allowed CORS origins // Audit logging auditAllActions: boolean; auditLoginAttempts: boolean; auditDataAccess: boolean; auditConfigChanges: boolean; }; // Email Notifications (for alerts) email: { enabled: boolean; smtpHost: string; smtpPort: number; smtpUsername: string; smtpPassword: string; // Encrypted in storage smtpTLS: boolean; fromAddress: string; adminEmails: string[]; // Admins to notify }; // Feature Flags features: { voiceEnabled: boolean; fileUploadEnabled: boolean; knowledgeBaseEnabled: boolean; nextcloudIntegration: boolean; calendarIntegration: boolean; emailIntegration: boolean; webSearchEnabled: boolean; betaFeatures: boolean; }; // Resource Limits resources: { maxUploadSize: number; // MB maxDocuments: number; // Max documents in KB (0 = unlimited) maxVectorDBSize: number; // GB (0 = unlimited) maxConcurrentUsers: number; // (0 = unlimited) }; } // Default system settings export const DEFAULT_SYSTEM_SETTINGS: SystemSettings = { general: { systemName: "VoiceAssist", systemDescription: "Medical AI Assistant", timezone: "UTC", language: "en", maintenanceMode: false, maintenanceMessage: "System is currently under maintenance. Please check back soon.", }, dataRetention: { conversationLogs: 30, errorLogs: 90, accessLogs: 90, auditLogs: 365, tempFiles: 7, autoCleanup: true, cleanupSchedule: "0 2 * * *", // Daily at 2 AM }, backup: { enabled: true, destination: "local", schedule: "0 2 * * *", // Daily at 2 AM retention: 7, includeDatabase: true, includeVectorDB: true, includeDocuments: true, includeConfiguration: true, includeLogs: false, compression: "gzip", encryption: true, }, ai: { defaultLocalModel: "llama-3.1-8b", defaultCloudModel: "gpt-4-turbo", defaultEmbeddingModel: "text-embedding-3-large", routingStrategy: "auto", phiDetectionEnabled: true, phiKeywords: ["patient", "MRN", "DOB", "SSN", "medical record"], maxConcurrentRequests: 10, requestTimeout: 60, retryAttempts: 3, rateLimits: { queriesPerMinute: 20, queriesPerHour: 100, queriesPerDay: 1000, }, costLimits: { dailyLimit: 50, monthlyLimit: 1000, alertThreshold: 80, }, }, logging: { level: "INFO", structuredLogging: true, logToFile: true, logToConsole: true, logToSyslog: false, sensitiveDataRedaction: true, performanceLogging: true, performanceThreshold: 1000, }, security: { sessionTimeout: 30, maxSessionDuration: 8, requireStrongPasswords: true, passwordMinLength: 12, passwordRequireSpecialChars: true, passwordExpiryDays: 90, twoFactorRequired: false, twoFactorMethod: "totp", apiRateLimiting: true, apiRateLimit: 60, corsOrigins: [], auditAllActions: true, auditLoginAttempts: true, auditDataAccess: true, auditConfigChanges: true, }, email: { enabled: false, smtpHost: "", smtpPort: 587, smtpUsername: "", smtpPassword: "", smtpTLS: true, fromAddress: "", adminEmails: [], }, features: { voiceEnabled: true, fileUploadEnabled: true, knowledgeBaseEnabled: true, nextcloudIntegration: true, calendarIntegration: true, emailIntegration: false, webSearchEnabled: true, betaFeatures: false, }, resources: { maxUploadSize: 500, maxDocuments: 0, maxVectorDBSize: 0, maxConcurrentUsers: 0, }, };

Settings Comparison: User vs System

Setting CategoryUser Settings (Per-User)System Settings (Global)
Theme✅ User chooses dark/light❌ Not configurable globally
Language✅ User's preferred language✅ Default system language
Voice Input✅ User enables/disables✅ System enables feature
Citations✅ User's display preference❌ Not applicable
Logging❌ Not user-configurable✅ System log level
Backups❌ Not user-configurable✅ System backup schedule
AI Models✅ User preference (fast/quality)✅ System default models
Rate Limits❌ Applied system-wide✅ System rate limit rules
Data Retention✅ User's conversation retention✅ System-wide retention policy

System Settings Backend API

# app/api/endpoints/admin/system_settings.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.api.deps import get_db, get_admin_user from app.models.user import User from app.models.system_settings import SystemSettings as SystemSettingsModel from app.api.schemas.system_settings import SystemSettingsSchema from app.core.config import settings router = APIRouter() @router.get("/system/settings", response_model=SystemSettingsSchema) async def get_system_settings( db: Session = Depends(get_db), current_user: User = Depends(get_admin_user) ): """ Get system settings. Admin only. """ system_settings = db.query(SystemSettingsModel).first() if not system_settings: # Return defaults return SystemSettingsSchema.get_defaults() return system_settings.settings @router.patch("/system/settings", response_model=SystemSettingsSchema) async def update_system_settings( settings_update: SystemSettingsSchema, db: Session = Depends(get_db), current_user: User = Depends(get_admin_user) ): """ Update system settings. Admin only. Validates settings and logs changes to audit log. """ system_settings = db.query(SystemSettingsModel).first() if not system_settings: # Create initial settings system_settings = SystemSettingsModel(settings={}) db.add(system_settings) # Merge updates updated_settings = {**system_settings.settings, **settings_update.dict(exclude_unset=True)} # Validate critical settings if updated_settings['ai']['maxConcurrentRequests'] < 1: raise HTTPException(status_code=400, detail="maxConcurrentRequests must be >= 1") if updated_settings['dataRetention']['conversationLogs'] < 7: raise HTTPException(status_code=400, detail="Conversation log retention must be >= 7 days") # Log change to audit log from app.models.audit_log import AuditLog audit_entry = AuditLog( user_id=current_user.id, action='system_settings_update', resource='system_settings', changes=settings_update.dict(exclude_unset=True) ) db.add(audit_entry) system_settings.settings = updated_settings db.commit() db.refresh(system_settings) # Trigger settings reload in all services from app.core.events import emit_settings_change emit_settings_change() return system_settings.settings @router.post("/system/settings/validate") async def validate_system_settings( settings: SystemSettingsSchema, current_user: User = Depends(get_admin_user) ): """ Validate system settings without saving. Returns validation errors if any. """ errors = [] # Validate email settings if enabled if settings.email.enabled: if not settings.email.smtpHost: errors.append("SMTP host is required when email is enabled") if not settings.email.fromAddress: errors.append("From address is required when email is enabled") # Validate backup settings if settings.backup.enabled: if settings.backup.retention < 1: errors.append("Backup retention must be at least 1") # Validate AI settings if settings.ai.rateLimits.queriesPerMinute < 1: errors.append("Rate limit must be at least 1 query per minute") if errors: return {"valid": False, "errors": errors} return {"valid": True, "errors": []}

System Settings Storage

System settings are stored in:

  1. PostgreSQL system_settings table (single row)
  2. Redis cache for fast access (with 5-minute TTL)
  3. File backup in /etc/voiceassist/system.json (read on startup)

When settings change:

  1. Database is updated
  2. Redis cache is invalidated
  3. WebSocket broadcast to all services
  4. Services reload configuration

3. AI Models Configuration (/models)

Model Selection

Local Models (Ollama)

┌──────────────────────────────────────────┐
│ Local Model: [Dropdown]                  │
│   ● Llama 3.1 8B                         │
│   ● Llama 3.1 70B                        │
│   ● Mistral 7B                           │
│   ● Mixtral 8x7B                         │
│                                           │
│ [Download New Model]                     │
└──────────────────────────────────────────┘

Cloud API Configuration

┌──────────────────────────────────────────┐
│ OpenAI                                    │
│   API Key: [••••••••••••key] [Test]      │
│   Model: gpt-4-turbo                     │
│   Max Tokens: 4096                       │
│   Temperature: 0.7                       │
│                                           │
│ Anthropic Claude (Optional)              │
│   API Key: [Not configured] [Add]        │
└──────────────────────────────────────────┘

Routing Logic

┌──────────────────────────────────────────┐
│ Request Routing                          │
│                                           │
│ ☑️ Auto-route based on privacy          │
│ ☐ Always use local model                │
│ ☐ Always use cloud API                  │
│                                           │
│ Classification Rules:                    │
│   • File access → Local                  │
│   • PHI detected → Local                 │
│   • Medical literature → Cloud           │
│   • Complex reasoning → Cloud            │
│                                           │
│ [Edit Rules]                             │
└──────────────────────────────────────────┘

Embedding Models

  • OpenAI text-embedding-3-large (cloud)
  • Local embedding model (if installed)
  • Benchmark performance

Model Performance

  • Average response time per model
  • Token usage and costs
  • Success/failure rates
  • Switch recommendations based on usage

4. Knowledge Base Management (/knowledge)

Full Knowledge Base Page Wireframe:

┌──────────────────────────────────────────────────────────────────────────────────────┐
│ Knowledge Base Management                                         [Upload Document] │
├──────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                       │
│  ┌────────────────────────────────────────────────────────────────────────────┐     │
│  │ 📊 Statistics                                                               │     │
│  │                                                                              │     │
│  │  ┌──────────────┬──────────────┬──────────────┬──────────────┐            │     │
│  │  │ Documents    │ Total Chunks │ Vector Size  │ Avg Query    │            │     │
│  │  │              │              │              │ Latency      │            │     │
│  │  │    247       │    128,452   │    8.4 GB    │    0.12s     │            │     │
│  │  └──────────────┴──────────────┴──────────────┴──────────────┘            │     │
│  │                                                                              │     │
│  │  Most Queried Topics: Cardiology (487) · Diabetes (312) · ID (234)         │     │
│  └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                       │
│  ┌────────────────────────────────────────────────────────────────────────────┐     │
│  │ 🔧 Indexing Jobs                                   [View All Jobs]          │     │
│  │                                                                              │     │
│  │  ⏳ In Progress: "SPRINT Trial" (PDF, 45 pages) - 67% complete             │     │
│  │  ✅ Completed: "Harrison's Ch. 234-240" - 3 minutes ago                    │     │
│  │  ❌ Failed: "corrupted_file.pdf" - Invalid format [Retry]                  │     │
│  └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                       │
│  ┌────────────────────────────────────────────────────────────────────────────┐     │
│  │ 📚 Document Library                                                         │     │
│  │                                                                              │     │
│  │  [🔍 Search documents...]                                                   │     │
│  │                                                                              │     │
│  │  Filters:  Type: [All ▼]  Specialty: [All ▼]  Status: [Indexed ▼]        │     │
│  │  Sort by: [Last Modified ▼]                              Showing 1-10 of 247│     │
│  │                                                                              │     │
│  │  ┌───┬─────────────────────┬───────────┬────────┬──────────┬────────────┐ │     │
│  │  │ ☑ │ Name                │ Type      │ Pages  │ Chunks   │ Actions    │ │     │
│  │  ├───┼─────────────────────┼───────────┼────────┼──────────┼────────────┤ │     │
│  │  │ ☐ │ Harrison's Princ... │ Textbook  │ 3,402  │  18,234  │ 👁️ ⚙️ ⬇️ 🗑️│ │     │
│  │  │   │ Internal Medicine   │           │        │          │            │ │     │
│  │  │   │ 21st Edition        │           │        │          │            │ │     │
│  │  │   │ 🏷️ Cardiology, GI   │ ✅ Indexed│        │ 12 days  │            │ │     │
│  │  ├───┼─────────────────────┼───────────┼────────┼──────────┼────────────┤ │     │
│  │  │ ☐ │ DAPA-HF Trial       │ Journal   │   15   │    145   │ 👁️ ⚙️ ⬇️ 🗑️│ │     │
│  │  │   │ NEJM 2019           │           │        │          │            │ │     │
│  │  │   │ 🏷️ Cardiology       │ ✅ Indexed│        │ 3 days   │            │ │     │
│  │  ├───┼─────────────────────┼───────────┼────────┼──────────┼────────────┤ │     │
│  │  │ ☐ │ 2023 AHA/ACC HTN    │ Guideline │   86   │    892   │ 👁️ ⚙️ ⬇️ 🗑️│ │     │
│  │  │   │ Guidelines          │           │        │          │            │ │     │
│  │  │   │ 🏷️ Cardiology, HTN  │ ✅ Indexed│        │ 1 week   │            │ │     │
│  │  ├───┼─────────────────────┼───────────┼────────┼──────────┼────────────┤ │     │
│  │  │ ☐ │ UpToDate: DKA Mgmt  │ Reference │    8   │     67   │ 👁️ ⚙️ ⬇️ 🗑️│ │     │
│  │  │   │ Updated Oct 2024    │           │        │          │            │ │     │
│  │  │   │ 🏷️ Endocrinology    │ ✅ Indexed│        │ 2 days   │            │ │     │
│  │  ├───┼─────────────────────┼───────────┼────────┼──────────┼────────────┤ │     │
│  │  │ ☐ │ IDSA Sepsis Guide   │ Guideline │   42   │    438   │ 👁️ ⚙️ ⬇️ 🗑️│ │     │
│  │  │   │ 2024 Edition        │           │        │          │            │ │     │
│  │  │   │ 🏷️ ID, ICU          │ ⏳ Indexing│       │ Now      │            │ │     │
│  │  └───┴─────────────────────┴───────────┴────────┴──────────┴────────────┘ │     │
│  │                                                                              │     │
│  │  [◀ Previous]  [1] [2] [3] ... [25]  [Next ▶]                              │     │
│  │                                                                              │     │
│  │  Bulk Actions: [☑ Select All]  [⚙️ Re-index Selected]  [🗑️ Delete Selected] │     │
│  └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                       │
└──────────────────────────────────────────────────────────────────────────────────────┘

Upload Document Modal:

┌─────────────────────────────────────────────────────────────┐
│ Upload Medical Documents                            [✕ Close]│
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                       │   │
│  │        📄 Drag & Drop Files Here                     │   │
│  │                  or                                   │   │
│  │             [Browse Files]                            │   │
│  │                                                       │   │
│  │  Supported: PDF, DOCX (max 500 MB)                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                               │
│  Selected Files:                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • harrison_chapter_45.pdf (12.4 MB)           [✕]   │   │
│  │ • ACC_HF_Guidelines_2024.pdf (5.8 MB)         [✕]   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                               │
│  Document Type: [Dropdown ▼]                                 │
│    ● Medical Textbook                                         │
│    ○ Journal Article                                          │
│    ○ Clinical Guideline                                       │
│    ○ Reference Material (UpToDate, etc.)                     │
│    ○ Trial/Study                                              │
│                                                               │
│  Specialty Tags: [Multi-select]                              │
│    [Cardiology ✕] [Endocrinology ✕] [+ Add Tag]             │
│                                                               │
│  Metadata (Optional):                                         │
│    Title: _______________________________________________     │
│    Author(s): ___________________________________________     │
│    Publication Year: [2024 ▼]                                │
│    Edition/Version: _____________________________________     │
│    DOI/PMID: ____________________________________________     │
│                                                               │
│  Indexing Options:                                            │
│    ☑ Start indexing immediately                              │
│    ☑ Extract metadata automatically                          │
│    ☑ Detect specialty from content                           │
│    ☐ High priority (index first)                            │
│                                                               │
│  [Cancel]                              [Upload & Index] ✓   │
│                                                               │
└─────────────────────────────────────────────────────────────┘

Document Detail View:

┌─────────────────────────────────────────────────────────────┐
│ Document Details                                   [✕ Close] │
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  📖 Harrison's Principles of Internal Medicine - Chapter 45  │
│     Atrial Fibrillation and Flutter                          │
│                                                               │
│  ┌──────────────────┬────────────────────────────────────┐  │
│  │ Status           │ ✅ Indexed                         │  │
│  │ Document Type    │ Medical Textbook                   │  │
│  │ Specialty        │ Cardiology, Arrhythmia             │  │
│  │ Pages            │ 18                                  │  │
│  │ Chunks Generated │ 124                                 │  │
│  │ File Size        │ 2.4 MB                              │  │
│  │ Uploaded         │ Nov 10, 2024 10:34 AM              │  │
│  │ Last Indexed     │ Nov 10, 2024 10:38 AM (4m 23s)    │  │
│  │ Uploaded By      │ Dr. Nazmy                          │  │
│  └──────────────────┴────────────────────────────────────┘  │
│                                                               │
│  Metadata:                                                    │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ Authors:  Dennis Kasper, Stephen Hauser, et al.       │  │
│  │ Edition:  21st Edition                                 │  │
│  │ Year:     2022                                         │  │
│  │ Publisher: McGraw-Hill                                 │  │
│  │ ISBN:     978-1264268504                               │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                               │
│  Usage Statistics:                                            │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ Times Cited:      487                                  │  │
│  │ Last Accessed:    2 hours ago                          │  │
│  │ Avg Relevance:    0.82 (High)                          │  │
│  │ Most Cited In:    AF management, stroke prevention    │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                               │
│  Indexing Details:                                            │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ Chunking Strategy: Semantic (500 token windows)       │  │
│  │ Embedding Model:   text-embedding-3-large             │  │
│  │ Vector Dimensions: 3072                                │  │
│  │ Processing Time:   4m 23s                              │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                               │
│  Sample Chunks (first 3 of 124):                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ 1. "Atrial fibrillation (AF) is the most common      │  │
│  │     sustained cardiac arrhythmia, affecting 1-2% of  │  │
│  │     the general population..."                        │  │
│  │                                                        │  │
│  │ 2. "Risk stratification for stroke prevention uses   │  │
│  │     the CHA2DS2-VASc score. Patients with scores     │  │
│  │     ≥2 should receive anticoagulation..."            │  │
│  │                                                        │  │
│  │ 3. "Rate control strategies include beta-blockers,   │  │
│  │     calcium channel blockers, and digoxin..."         │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                               │
│  [⬇️ Download PDF]  [⚙️ Re-index]  [🗑️ Delete Document]     │
│                                                               │
└─────────────────────────────────────────────────────────────┘

Knowledge Base Management Endpoints

All KB management endpoints use the standard API envelope pattern. See server/README.md for complete specification.

EndpointMethodPurposeRequestResponse
/api/admin/kb/documentsGETList documentsQuery params (filters)APIEnvelope<KnowledgeDocument[]>
/api/admin/kb/documents/{id}GETGet document details-APIEnvelope<KnowledgeDocument>
/api/admin/kb/documentsPOSTUpload documentFormData (file)APIEnvelope<IndexingJob>
/api/admin/kb/documents/{id}DELETEDelete document-APIEnvelope<{success: true}>
/api/admin/kb/jobsGETList indexing jobsQuery params (state filter)APIEnvelope<IndexingJob[]>
/api/admin/kb/jobs/{id}GETGet job details-APIEnvelope<IndexingJob>
/api/admin/kb/jobs/{id}/retryPOSTRetry failed job-APIEnvelope<IndexingJob>
/api/admin/kb/reindexPOSTBulk reindex{document_ids: string[]}APIEnvelope<{job_count: number}>
/api/admin/kb/searchPOSTTest search{query, filters}APIEnvelope<SearchResult[]>

All endpoints return standard APIEnvelope as defined in server/README.md. All entity types reference DATA_MODEL.md.


Indexing Job UI Flow

Complete flow from document upload to indexed status:

┌────────────────────────────────────────────────────────────────┐
│                   Document Upload & Indexing Flow               │
└────────────────────────────────────────────────────────────────┘

User selects file
    ↓
[1. Client validation]
    └─→ Check file type (PDF, DOCX)
    └─→ Check file size (< 50MB)
    ↓
[2. Upload: POST /api/admin/kb/documents]
    └─→ FormData with file
    └─→ Returns: APIEnvelope<IndexingJob>
    ↓
[3. Job created: state = "pending"]
    └─→ Show in "Indexing Jobs" list
    └─→ Display: "Queued"
    ↓
[4. Backend worker picks up job]
    └─→ State transition: pending → running
    ↓
[5. Job running: state = "running"]
    └─→ UI polls: GET /api/admin/kb/jobs/{id}
    └─→ Display progress: "Processing... {processed_chunks}/{total_chunks}"
    └─→ Show spinner
    ↓
[6a. Success: state = "completed"]
    └─→ Display: "✓ Indexed successfully"
    └─→ Show document in KB list
    └─→ Stop polling

[6b. Failure: state = "failed"]
    └─→ Display: "✗ Failed: {error_message}"
    └─→ Show "Retry" button
    └─→ Stop polling

[7. User clicks "Retry" (if failed)]
    └─→ POST /api/admin/kb/jobs/{id}/retry
    └─→ Job state: failed → pending
    └─→ Resume polling

Polling Strategy:
- Interval: 2 seconds while job is "running"
- Exponential backoff if server errors
- Stop when state is "completed" or "failed"
- Timeout after 5 minutes (show error)

Alternative: WebSocket Updates
- Connect to /ws/admin/jobs
- Receive real-time state updates
- No polling needed

Indexing Jobs Hook Example

Complete React hook for managing indexing jobs:

// admin/hooks/useIndexingJobs.ts import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { fetchAPI } from "@/lib/api"; import { IndexingJob } from "@/types"; // From DATA_MODEL.md interface UseIndexingJobsOptions { stateFilter?: "pending" | "running" | "completed" | "failed"; pollInterval?: number; // Milliseconds (default: 2000) } export function useIndexingJobs(options: UseIndexingJobsOptions = {}) { const { stateFilter, pollInterval = 2000 } = options; const queryClient = useQueryClient(); // Fetch jobs list const jobsQuery = useQuery({ queryKey: ["indexing-jobs", stateFilter], queryFn: async () => { const params = new URLSearchParams(); if (stateFilter) params.append("state", stateFilter); return fetchAPI<IndexingJob[]>(`/api/admin/kb/jobs?${params.toString()}`); }, refetchInterval: (data) => { // Poll if any jobs are running const hasRunningJobs = data?.some((job) => job.state === "running"); return hasRunningJobs ? pollInterval : false; }, }); // Retry failed job const retryJob = useMutation({ mutationFn: async (jobId: string) => { return fetchAPI<IndexingJob>(`/api/admin/kb/jobs/${jobId}/retry`, { method: "POST", }); }, onSuccess: () => { // Refetch jobs list queryClient.invalidateQueries({ queryKey: ["indexing-jobs"] }); toast.success("Job retry initiated"); }, onError: (error: APIError) => { if (error.code === "VALIDATION_ERROR") { toast.error("Cannot retry: Max retries exceeded"); } else { toast.error(`Retry failed: ${error.message}`); } }, }); // Bulk reindex const reindexDocuments = useMutation({ mutationFn: async (documentIds: string[]) => { return fetchAPI<{ job_count: number }>("/api/admin/kb/reindex", { method: "POST", body: JSON.stringify({ document_ids: documentIds }), }); }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["indexing-jobs"] }); toast.success(`${data.job_count} indexing jobs created`); }, }); return { jobs: jobsQuery.data || [], isLoading: jobsQuery.isLoading, isError: jobsQuery.isError, error: jobsQuery.error, retryJob: retryJob.mutate, isRetrying: retryJob.isPending, reindexDocuments: reindexDocuments.mutate, isReindexing: reindexDocuments.isPending, }; } // Fetch single job with detailed progress export function useIndexingJob(jobId: string) { return useQuery({ queryKey: ["indexing-job", jobId], queryFn: async () => { return fetchAPI<IndexingJob>(`/api/admin/kb/jobs/${jobId}`); }, refetchInterval: (data) => { // Poll while job is running return data?.state === "running" ? 2000 : false; }, enabled: !!jobId, }); }

Usage in Component:

// admin/components/IndexingJobsList.tsx export function IndexingJobsList() { const { jobs, isLoading, retryJob } = useIndexingJobs({ stateFilter: undefined, // Show all jobs }); if (isLoading) return <Spinner />; return ( <div className="jobs-list"> <h2>Indexing Jobs</h2> <table> <thead> <tr> <th>Document</th> <th>State</th> <th>Progress</th> <th>Started</th> <th>Actions</th> </tr> </thead> <tbody> {jobs.map(job => ( <tr key={job.id}> <td>{job.doc_key}</td> <td> <JobStateBadge state={job.state} /> </td> <td> {job.state === 'running' && ( <ProgressBar value={job.processed_chunks} max={job.total_chunks || 100} /> )} {job.processed_chunks}/{job.total_chunks || '?'} </td> <td>{formatDate(job.started_at)}</td> <td> {job.state === 'failed' && ( <button onClick={() => retryJob(job.id)}> Retry </button> )} </td> </tr> ))} </tbody> </table> </div> ); }

Document Library Features

Table View:

  • Checkbox for bulk operations
  • Sortable columns (name, type, pages, indexed date)
  • Expandable rows showing tags and metadata
  • Status indicators (✅ Indexed, ⏳ Indexing, ❌ Failed, 📝 Pending)
  • Action buttons (👁️ View, ⚙️ Re-index, ⬇️ Download, 🗑️ Delete)

Filters & Search:

  • Full-text search across titles, authors, content
  • Filter by document type (textbook, journal, guideline, etc.)
  • Filter by specialty (multi-select)
  • Filter by status (indexed, pending, failed)
  • Filter by date range
  • Sort by: name, date added, date modified, pages, usage count

Document Actions:

  • 👁️ View details & metadata with usage stats
  • ⚙️ Re-index (with progress tracking)
  • ⬇️ Download original PDF
  • 🗑️ Delete (with confirmation)
  • 📊 View citation analytics

Bulk Operations

Available Actions:

  • Select all / Select filtered
  • Batch re-indexing with priority queue
  • Batch delete with confirmation
  • Export metadata to CSV/JSON
  • Bulk tag editing
  • Backup selected documents

Indexing Status Panel

Real-time Progress:

  • Current job name and status
  • Progress bar with percentage
  • Estimated time remaining
  • Chunks processed / total
  • Error count

Queue Management:

  • List of pending indexing jobs
  • Priority queue reordering
  • Pause/resume indexing
  • Cancel job option
  • Retry failed jobs

Vector Database Statistics

Metrics Display:

  • Total documents indexed
  • Total text chunks generated
  • Total vector embeddings
  • Database disk size
  • Index memory usage
  • Average query latency
  • Query throughput (queries/sec)
  • Cache hit rate

Top Queried Topics:

  • Bar chart of most-accessed specialties
  • Most cited documents
  • Trending topics this week
  • Underutilized documents

5. User Management (/users)

Note: For single-user initially, this section is minimal. Expandable for multi-user future.

User Profile

  • Name and email
  • Profile picture
  • Medical specialty
  • Preferences

Session Management

  • Active sessions
  • Device/browser info
  • Last activity
  • Revoke session

API Keys (Personal)

  • Generate API keys for programmatic access
  • Scoped permissions
  • Usage tracking
  • Revoke keys

6. Analytics & Usage (/analytics)

Query Analytics

Overview Metrics

  • Total queries (by period)
  • Unique topics
  • Most common medical specialties
  • Peak usage times

Query Type Distribution (Pie Chart)

  • Medical literature: 40%
  • Textbook queries: 25%
  • System commands: 20%
  • Web search: 15%

Popular Topics (Bar Chart)

  • Cardiology: 120 queries
  • Diabetes: 95 queries
  • Infectious Disease: 78 queries
  • ...

Response Time Trends (Line Chart)

  • Average response time over 30 days
  • Separate lines for local vs cloud

Cost Analysis

API Usage

Month: November 2024

OpenAI API:
  - Total tokens: 2,450,000
  - Estimated cost: $45.00
  - Breakdown:
    • GPT-4: $38.00 (1.8M tokens)
    • Embeddings: $5.00 (650k tokens)
    • Realtime API: $2.00 (4.5 hours)

Other APIs:
  - OpenEvidence: $10.00 (subscription)

Total: $55.00

Cost Trends (Line Chart)

  • Daily/monthly spend over time
  • Forecast next month

Knowledge Base Analytics

  • Most accessed documents
  • Citation frequency
  • Specialties most queried
  • Recent additions performance

User Activity

  • Sessions per day
  • Average session duration
  • Conversation length (messages)
  • Voice vs text usage ratio

Export Reports

  • Generate PDF reports
  • CSV data export
  • Custom date ranges

7. Integrations (/integrations)

Nextcloud

┌──────────────────────────────────────────┐
│ Nextcloud Integration                    │
│                                           │
│ Status: 🟢 Connected                     │
│ URL: https://asimo.io/nextcloud          │
│ Username: mohammednazmy                  │
│                                           │
│ ☑️ Auto-index medical documents          │
│ ☑️ Backup conversations                  │
│ ☐ Sync bookmarks                         │
│                                           │
│ Last Sync: 2 minutes ago                 │
│                                           │
│ [Test Connection] [Disconnect]           │
└──────────────────────────────────────────┘

Calendar (macOS/Google)

┌──────────────────────────────────────────┐
│ Calendar Integration                     │
│                                           │
│ Status: 🟢 Enabled                       │
│ Source: macOS Calendar (AppleScript)     │
│                                           │
│ ☑️ Read events                           │
│ ☑️ Create events                         │
│ ☑️ Update events                         │
│ ☐ Delete events (disabled for safety)   │
│                                           │
│ [Test] [Configure]                       │
└──────────────────────────────────────────┘

Email

┌──────────────────────────────────────────┐
│ Email Integration                        │
│                                           │
│ Status: 🟡 Configured but disabled       │
│                                           │
│ IMAP Server: imap.gmail.com              │
│ SMTP Server: smtp.gmail.com              │
│ Email: doctor@example.com                │
│                                           │
│ ☐ Enable email access                   │
│                                           │
│ [Configure] [Test]                       │
└──────────────────────────────────────────┘

PubMed

┌──────────────────────────────────────────┐
│ PubMed / NCBI                            │
│                                           │
│ Status: 🟢 Active (Public API)           │
│                                           │
│ API Key (optional): [Not set]            │
│   (Increases rate limit if provided)     │
│                                           │
│ Queries today: 87 / 10,000 limit         │
│                                           │
│ [Test API]                               │
└──────────────────────────────────────────┘

OpenEvidence

┌──────────────────────────────────────────┐
│ OpenEvidence                             │
│                                           │
│ Status: 🔴 Not configured                │
│                                           │
│ API Key: [_______________] [Save]        │
│                                           │
│ [Sign up] [Test]                         │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ Web Search                               │
│                                           │
│ Provider: [Brave Search API ▼]          │
│ API Key: [••••••••••••key]              │
│                                           │
│ ☑️ Enable web search                     │
│                                           │
│ [Test] [Configure]                       │
└──────────────────────────────────────────┘

8. Security & Privacy (/security)

Privacy Settings

┌──────────────────────────────────────────┐
│ Data Retention                           │
│                                           │
│ Conversation logs: [30 days ▼]          │
│ Error logs: [90 days ▼]                 │
│ Access logs: [1 year ▼]                 │
│                                           │
│ ☑️ Log all queries (for debugging)       │
│ ☐ Include PHI in logs (DANGEROUS)       │
└──────────────────────────────────────────┘

PHI Detection Rules

  • Keyword list (MRN, patient name patterns, etc.)
  • Regex patterns
  • Directory blacklist (never send to cloud)
  • Test PHI detector with examples

Data Handling Policy

  • Document current privacy approach
  • HIPAA compliance checklist
  • Link to full privacy policy

Encryption

  • Encryption at rest: Status
  • Encryption in transit: Status
  • Key management

Access Logs

  • Admin login history
  • Failed login attempts
  • API access patterns
  • Suspicious activity alerts

9. Logs & Monitoring (/logs)

Log Viewer

Real-Time Logs

┌──────────────────────────────────────────────────┐
│ [■ Pause] [⟳ Auto-refresh] [⬇️ Download]        │
├──────────────────────────────────────────────────┤
│ 2024-11-19 14:32:15 [INFO] New query received   │
│ 2024-11-19 14:32:16 [INFO] Routed to cloud      │
│ 2024-11-19 14:32:18 [INFO] Response generated   │
│ 2024-11-19 14:32:18 [DEBUG] Citations: 2        │
│ 2024-11-19 14:33:01 [ERROR] Nextcloud timeout   │
│ 2024-11-19 14:33:02 [WARN] Retrying connection  │
└──────────────────────────────────────────────────┘

Filters

  • Log level (DEBUG, INFO, WARN, ERROR)
  • Service (backend, vector-db, ollama, etc.)
  • Time range
  • Search text

Error Tracking

  • Error summary dashboard
  • Error rate over time
  • Most common errors
  • Stack traces
  • Error resolution status

Performance Logs

  • Slow query log (> 5s)
  • API latency tracking
  • Database query performance

10. Backups & Maintenance (/maintenance)

Backup Configuration

┌──────────────────────────────────────────┐
│ Automated Backups                        │
│                                           │
│ Destination: Nextcloud                   │
│ Schedule: Daily at 2:00 AM               │
│                                           │
│ Backup includes:                         │
│ ☑️ PostgreSQL database                   │
│ ☑️ Vector database                       │
│ ☑️ Uploaded documents                    │
│ ☑️ Configuration files                   │
│ ☐ Conversation logs                      │
│                                           │
│ Retention: [7 days ▼]                    │
│                                           │
│ Last backup: 8 hours ago                 │
│ Status: Success                          │
│                                           │
│ [Run Backup Now] [Restore]               │
└──────────────────────────────────────────┘

System Maintenance

  • Database vacuum and optimization
  • Clear old logs
  • Rebuild vector index
  • Clear Redis cache
  • Prune old Docker images

Health Checks

  • Run comprehensive system test
  • Check all integrations
  • Verify model availability
  • Test API endpoints
  • Database connectivity

Update Management

  • Check for updates
  • View changelog
  • Schedule update
  • Rollback to previous version

Mobile Responsiveness

  • Responsive layout for tablet access
  • Essential metrics on mobile
  • Touch-friendly controls
  • Simplified navigation

Notifications & Alerts

Alert Types

  • Critical: Service down
  • Warning: High error rate, disk space low
  • Info: Update available, backup completed

Notification Channels

  • In-app notifications
  • Email alerts
  • Webhook (Slack, Discord, etc.)

Alert Configuration

  • Set thresholds (CPU > 90%, error rate > 5%, etc.)
  • Enable/disable specific alerts
  • Quiet hours

Tools & External Integrations

The admin panel provides comprehensive management of the tools layer and external service integrations. See TOOLS_AND_INTEGRATIONS.md for complete tool specifications.

Tools Overview Dashboard

// Page: /admin/tools interface ToolStatus { tool_name: string; enabled: boolean; category: 'calendar' | 'file' | 'medical' | 'calculation' | 'search'; total_calls_24h: number; success_rate: number; avg_duration_ms: number; last_error?: string; last_error_at?: string; phi_enabled: boolean; requires_confirmation: boolean; } // Display: Grid of tool cards {tools.map(tool => ( <ToolCard> <Header> <Icon /> {tool.tool_name} <StatusBadge success_rate={tool.success_rate} /> </Header> <Metrics> <Stat label="Calls (24h)" value={tool.total_calls_24h} /> <Stat label="Success Rate" value={`${tool.success_rate}%`} /> <Stat label="Avg Duration" value={`${tool.avg_duration_ms}ms`} /> </Metrics> <Footer> <Toggle checked={tool.enabled} onChange={toggleTool} /> <Button onClick={viewDetails}>Details</Button> </Footer> </ToolCard> ))}

Key Metrics:

  • Total tool calls per tool (24h, 7d, 30d)
  • Success rate percentage
  • Average execution duration
  • Error rate and last error
  • PHI detection rate

Tool Configuration

// Page: /admin/tools/:tool_name interface ToolConfiguration { tool_name: string; enabled: boolean; timeout_seconds: number; rate_limit_per_user: number; rate_limit_window_seconds: number; requires_confirmation: boolean; phi_enabled: boolean; custom_settings?: Record<string, any>; } // Example: Calendar Tool Configuration <Form> <Toggle label="Enable Tool" checked={config.enabled} /> <NumberInput label="Timeout (seconds)" value={config.timeout_seconds} min={5} max={300} /> <NumberInput label="Rate Limit (calls per user)" value={config.rate_limit_per_user} min={1} max={1000} /> <NumberInput label="Rate Limit Window (seconds)" value={config.rate_limit_window_seconds} options={[60, 300, 3600]} /> <Toggle label="Require User Confirmation" checked={config.requires_confirmation} description="High-risk actions require explicit user approval" /> <Toggle label="Allow PHI" checked={config.phi_enabled} description="Enable this tool for queries containing PHI" /> <Button type="submit">Save Configuration</Button> </Form>

External API Integrations

// Page: /admin/integrations interface ExternalIntegration { name: string; category: 'medical_search' | 'calendar' | 'file_storage' | 'guidelines'; status: 'connected' | 'disconnected' | 'error'; api_key_configured: boolean; last_tested_at?: string; last_test_status?: 'success' | 'failure'; requests_24h: number; error_rate: number; } // List of integrations: const integrations: ExternalIntegration[] = [ { name: 'OpenEvidence', category: 'medical_search', status: 'connected', api_key_configured: true, last_tested_at: '2025-11-20T10:30:00Z', last_test_status: 'success', requests_24h: 342, error_rate: 0.02, }, { name: 'PubMed (NCBI E-utilities)', category: 'medical_search', status: 'connected', api_key_configured: false, // Public API last_tested_at: '2025-11-20T09:15:00Z', last_test_status: 'success', requests_24h: 156, error_rate: 0.01, }, // ... more integrations ]; <IntegrationsList> {integrations.map(integration => ( <IntegrationCard> <Header> <StatusIndicator status={integration.status} /> <h3>{integration.name}</h3> <CategoryBadge category={integration.category} /> </Header> <Metrics> <Stat label="Requests (24h)" value={integration.requests_24h} /> <Stat label="Error Rate" value={`${(integration.error_rate * 100).toFixed(1)}%`} /> <Stat label="Last Tested" value={formatRelativeTime(integration.last_tested_at)} /> </Metrics> <Actions> <Button onClick={() => configureIntegration(integration.name)}> Configure </Button> <Button onClick={() => testConnection(integration.name)}> Test Connection </Button> </Actions> </IntegrationCard> ))} </IntegrationsList>

Supported Integrations:

IntegrationCategoryAPI Key RequiredPHI SafePurpose
OpenEvidenceMedical SearchYesYes (external)Evidence-based medicine search
PubMed (NCBI)Medical SearchNoYes (external)Biomedical literature search
NextcloudFile StorageNo (internal)No (local PHI)Document storage and retrieval
CalDAV ServerCalendarNo (internal)No (local PHI)Calendar events (via Nextcloud)
Google Custom SearchWeb SearchYesYes (external)General medical web search

Integration Configuration UI

// Page: /admin/integrations/:integration_name interface IntegrationConfig { name: string; enabled: boolean; api_key?: string; api_url?: string; timeout_seconds: number; retry_attempts: number; rate_limit?: number; custom_headers?: Record<string, string>; } // Example: OpenEvidence Configuration <Form> <TextInput label="API Key" type="password" value={config.api_key} placeholder="sk_test_..." helpText="Get your API key from openevidence.com/api-keys" /> <TextInput label="API Base URL" value={config.api_url} placeholder="https://api.openevidence.com/v1" /> <NumberInput label="Timeout (seconds)" value={config.timeout_seconds} min={10} max={60} /> <NumberInput label="Retry Attempts" value={config.retry_attempts} min={0} max={5} /> <NumberInput label="Rate Limit (requests/minute)" value={config.rate_limit} helpText="Leave empty for no limit" /> <Button type="button" onClick={testConnection}> Test Connection </Button> <Button type="submit"> Save Configuration </Button> </Form>

Tool Invocation Logs

// Page: /admin/tools/logs interface ToolInvocationLog { id: string; tool_name: string; user_email: string; session_id: string; call_id: string; arguments: Record<string, any>; status: 'completed' | 'failed' | 'timeout' | 'cancelled'; duration_ms: number; phi_detected: boolean; confirmation_required: boolean; user_confirmed?: boolean; error_code?: string; error_message?: string; created_at: string; } // Display: Searchable table with filters <ToolLogsTable> <Filters> <Select label="Tool" options={allTools} value={filter.tool_name} /> <Select label="Status" options={['all', 'completed', 'failed', 'timeout']} value={filter.status} /> <Toggle label="PHI Only" checked={filter.phi_only} /> <DateRangePicker label="Date Range" value={filter.date_range} /> </Filters> <Table> <thead> <tr> <th>Timestamp</th> <th>Tool</th> <th>User</th> <th>Status</th> <th>Duration</th> <th>PHI</th> <th>Actions</th> </tr> </thead> <tbody> {logs.map(log => ( <tr> <td>{formatDateTime(log.created_at)}</td> <td><code>{log.tool_name}</code></td> <td>{log.user_email}</td> <td><StatusBadge status={log.status} /></td> <td>{log.duration_ms}ms</td> <td>{log.phi_detected && <PHIBadge />}</td> <td> <Button onClick={() => viewDetails(log)}>View</Button> </td> </tr> ))} </tbody> </Table> </ToolLogsTable>

Tool Usage Analytics

// Page: /admin/analytics/tools interface ToolAnalytics { tool_name: string; period: '24h' | '7d' | '30d'; total_calls: number; unique_users: number; success_count: number; failure_count: number; timeout_count: number; avg_duration_ms: number; p95_duration_ms: number; p99_duration_ms: number; phi_detection_rate: number; calls_per_day: Array<{ date: string; count: number }>; } // Visualizations: <Analytics> <MetricsGrid> <MetricCard title="Total Tool Calls" value={analytics.total_calls} trend={+12.5} period="vs last 7d" /> <MetricCard title="Success Rate" value={`${(analytics.success_count / analytics.total_calls * 100).toFixed(1)}%`} trend={+2.3} /> <MetricCard title="Avg Duration" value={`${analytics.avg_duration_ms}ms`} trend={-45} trendLabel="faster" /> <MetricCard title="Unique Users" value={analytics.unique_users} trend={+8} /> </MetricsGrid> <TimeSeriesChart title="Tool Calls Over Time" data={analytics.calls_per_day} xKey="date" yKey="count" /> <BarChart title="Tool Usage by Tool" data={toolsUsage} xKey="tool_name" yKey="total_calls" /> <PieChart title="Tool Status Distribution" data={[ { label: 'Success', value: analytics.success_count }, { label: 'Failed', value: analytics.failure_count }, { label: 'Timeout', value: analytics.timeout_count }, ]} /> </Analytics>

Tool Health Monitoring

// Component: ToolHealthMonitor interface ToolHealth { tool_name: string; status: 'healthy' | 'degraded' | 'down'; last_successful_call?: string; consecutive_failures: number; health_check_at: string; } <HealthMonitor> {toolsHealth.map(health => ( <HealthCard status={health.status}> <h4>{health.tool_name}</h4> {health.status === 'down' && ( <Alert severity="error"> Tool is down. {health.consecutive_failures} consecutive failures. Last success: {formatRelativeTime(health.last_successful_call)} </Alert> )} {health.status === 'degraded' && ( <Alert severity="warning"> Tool performance degraded. Error rate above threshold. </Alert> )} {health.status === 'healthy' && ( <Alert severity="success"> Tool operating normally. </Alert> )} </HealthCard> ))} </HealthMonitor>

Related Documentation:

API for Admin Panel

Endpoints

GET    /api/admin/dashboard          - Dashboard metrics
GET    /api/admin/services/status    - Service health
POST   /api/admin/services/restart   - Restart service
GET    /api/admin/models             - List models
PATCH  /api/admin/models/config      - Update model config
GET    /api/admin/knowledge          - List documents
POST   /api/admin/knowledge/upload   - Upload document
DELETE /api/admin/knowledge/:id      - Delete document
POST   /api/admin/knowledge/reindex  - Trigger reindex
GET    /api/admin/analytics/queries  - Query analytics
GET    /api/admin/analytics/costs    - Cost data
GET    /api/admin/integrations       - Integration status
PATCH  /api/admin/integrations/:name - Update integration
GET    /api/admin/logs               - Fetch logs
GET    /api/admin/users              - List users
POST   /api/admin/backup             - Trigger backup
GET    /api/admin/health             - System health check

Contextual Help & AI Assistant

The admin panel includes integrated help features that connect to the documentation site and provide AI-powered assistance.

HelpButton Component

Available from packages/ui, links to relevant documentation from any admin page:

// Usage in admin pages import { HelpButton } from "@voiceassist/ui"; <HelpButton docPath="admin/security" // Path to docs page section="permissions" // Optional section anchor variant="icon" | "text" | "both" // Display mode size="sm" | "md" | "lg" // Button size /> // Opens: https://assistdocs.asimo.io/admin/security#permissions

Implementation:

  • Location: packages/ui/src/components/HelpButton.tsx
  • Uses NEXT_PUBLIC_DOCS_URL environment variable
  • Opens docs in new tab with appropriate section hash
  • Keyboard accessible (Tab + Enter)

AskAIButton Component

Embedded in admin panel pages to provide contextual AI assistance:

// Location: apps/admin-panel/src/components/shared/AskAIButton.tsx <AskAIButton context={{ page: "security", // Current page context feature: "audit-logs", // Specific feature userId: currentUser.id, // Optional user context }} position="bottom-right" // Fab position />

User Flow:

  1. User clicks "Ask AI" floating action button
  2. Dialog opens with text input field
  3. Page context is pre-filled (user can modify)
  4. Submit sends request to /api/ai/docs/ask
  5. AI response includes relevant doc citations
  6. User can click citations to open full documentation

API Endpoint:

// POST /api/ai/docs/ask interface DocsAskRequest { question: string; context?: { page?: string; feature?: string; userId?: string; }; } interface DocsAskResponse { answer: string; citations: Array<{ doc_path: string; title: string; section?: string; relevance: number; }>; confidence: number; }

Backend Integration:

  • Uses docs_search_tool for semantic search across documentation
  • Leverages Qdrant platform_docs collection (1536-dim embeddings)
  • Returns top-k relevant passages with citations
  • Confidence score indicates answer quality

See Also:


Security Considerations

  • Admin panel on separate subdomain
  • Strong authentication required
  • Rate limiting on all endpoints
  • Audit log all admin actions
  • No sensitive data in client-side code
  • HTTPS only
  • CSRF protection

Future Enhancements

Advanced Features

  • Multi-user management with roles
  • Team collaboration features
  • Custom dashboard widgets
  • Scheduled reports via email
  • Mobile admin app
  • A/B testing for model performance
  • Cost optimization recommendations
  • Automated scaling suggestions

AI-Powered Admin

  • Anomaly detection in metrics
  • Predictive maintenance alerts
  • Intelligent log analysis
  • Automatic optimization suggestions

Nextcloud Integration Guide

Overview

VoiceAssist integrates with Nextcloud for identity management, file storage, calendar, and email functionality.

Current Status (Phase 6): VoiceAssist now has working CalDAV calendar integration, WebDAV file auto-indexing, and email service skeleton.

Implementation Notes:

  • Phase 2: Nextcloud added to docker-compose.yml, OCS API integration created
  • Phase 6: CalDAV calendar operations, WebDAV file auto-indexer, email service skeleton
  • Phase 7+: Full OIDC authentication, complete email integration, CardDAV contacts

For development, Phase 2+ includes Nextcloud directly in the VoiceAssist docker-compose.yml stack. For production, you may choose to:

  • Continue using the integrated Nextcloud (simpler deployment)
  • Use a separate Nextcloud installation (more flexible, as described in the "Separate Stack" section below)

Phase 2: Integrated Nextcloud Setup

What Was Implemented

Phase 2 added Nextcloud directly to the VoiceAssist docker-compose.yml stack for simplified local development:

Docker Services Added:

  • nextcloud: Nextcloud 29 (Apache), accessible at http://localhost:8080
  • nextcloud-db: PostgreSQL 16 database for Nextcloud

Integration Service Created:

  • NextcloudService (services/api-gateway/app/services/nextcloud.py): OCS API client for user provisioning and management

Environment Variables:

NEXTCLOUD_URL=http://nextcloud:80 # Internal Docker network URL NEXTCLOUD_ADMIN_USER=admin # Nextcloud admin username NEXTCLOUD_ADMIN_PASSWORD=<from .env> # Nextcloud admin password NEXTCLOUD_DB_PASSWORD=<from .env> # Nextcloud database password

OCS API Integration:

  • User creation via OCS API (/ocs/v1.php/cloud/users)
  • User existence check
  • Health check for Nextcloud connectivity
  • Authentication with admin credentials

Phase 6 Enhancements Implemented:

  • ✅ CalDAV calendar integration (full CRUD operations)
  • ✅ WebDAV file auto-indexing into knowledge base
  • ✅ Email service skeleton (IMAP/SMTP basics)

Future Enhancements (Phase 7+):

  • OIDC authentication integration
  • Full email integration with message parsing
  • CardDAV contacts integration
  • Full user provisioning workflow

Deployment Steps for OIDC, Contacts, and Email

  1. Configure OIDC providers (API Gateway):

    • Set NEXTCLOUD_URL, NEXTCLOUD_OAUTH_CLIENT_ID, NEXTCLOUD_OAUTH_CLIENT_SECRET, and NEXTCLOUD_OAUTH_REDIRECT_URI in .env for Nextcloud SSO.
    • Optionally set GOOGLE_OAUTH_CLIENT_ID/SECRET or MICROSOFT_CLIENT_ID/SECRET with their redirect URIs if federating logins.
    • Restart the API Gateway; configure_oidc_from_settings() registers providers and caches JWKS for validation.
  2. Enable CardDAV + contacts:

    • Provide NEXTCLOUD_WEBDAV_URL, NEXTCLOUD_CALDAV_USERNAME, and NEXTCLOUD_CALDAV_PASSWORD for CardDAV access.
    • The CardDAVService now supports sync tokens; call /api/integrations/contacts endpoints to keep address books in sync.
  3. Finalize IMAP/SMTP email:

    • Supply EMAIL_IMAP_HOST, EMAIL_IMAP_PORT, EMAIL_SMTP_HOST, EMAIL_SMTP_PORT, EMAIL_USERNAME, and EMAIL_PASSWORD in .env.
    • The email service will reconnect automatically on IMAP failures and respects TLS/STARTTLS based on configuration.
  4. Package and install Nextcloud apps:

    • Run bash nextcloud-apps/package.sh to create build/*.tar.gz archives for voiceassist-client, voiceassist-admin, and voiceassist-docs.
    • Upload/enable the apps in Nextcloud; routes under /apps/<app>/api/* mirror API Gateway endpoints for calendar, files, contacts, and email.

Quick Start (Phase 2)

If you have Phase 2 installed, Nextcloud is already running:

# Access Nextcloud open http://localhost:8080 # Default credentials (first-time setup) Username: admin Password: (value from NEXTCLOUD_ADMIN_PASSWORD in .env) # Check Nextcloud health from API Gateway docker exec voiceassist-server python -c " from app.services.nextcloud import NextcloudService import asyncio svc = NextcloudService() result = asyncio.run(svc.health_check()) print(f'Nextcloud healthy: {result}') "

Phase 2 Limitations:

  • OIDC integration not yet implemented (JWT tokens used for auth instead)
  • WebDAV/CalDAV integration not yet implemented
  • User provisioning is manual via Nextcloud UI

Phase 6: Calendar & File Integration

What Was Implemented

Phase 6 adds real integration with Nextcloud Calendar and Files, plus email service foundation:

Services Created:

  • CalDAVService (services/api-gateway/app/services/caldav_service.py): Full CalDAV protocol support for calendar operations
  • NextcloudFileIndexer (services/api-gateway/app/services/nextcloud_file_indexer.py): Automatic medical document discovery and indexing
  • EmailService (services/api-gateway/app/services/email_service.py): IMAP/SMTP skeleton for future email integration
  • Integration API (services/api-gateway/app/api/integrations.py): Unified REST API for all integrations

Calendar Features (CalDAV):

  • List all calendars for authenticated user
  • Get events within date range with filtering
  • Create new calendar events with full metadata
  • Update existing events (summary, time, location, description)
  • Delete calendar events
  • Timezone-aware event handling
  • Recurring event support
  • Error handling for connection and parsing failures

File Auto-Indexing (WebDAV):

  • Discover medical documents in configurable Nextcloud directories
  • Automatic indexing into Phase 5 knowledge base
  • Supported formats: PDF, TXT, MD
  • Duplicate detection (prevents re-indexing)
  • Metadata tracking (file path, size, modification time)
  • Integration with Phase 5 KBIndexer for embedding generation
  • Batch scanning with progress reporting

API Endpoints Added:

Calendar Operations:
  GET    /api/integrations/calendar/calendars
  GET    /api/integrations/calendar/events
  POST   /api/integrations/calendar/events
  PUT    /api/integrations/calendar/events/{uid}
  DELETE /api/integrations/calendar/events/{uid}

File Indexing:
  POST   /api/integrations/files/scan-and-index
  POST   /api/integrations/files/index

Email (Skeleton):
  GET    /api/integrations/email/folders
  GET    /api/integrations/email/messages
  POST   /api/integrations/email/send

Configuration Required:

# Add to ~/VoiceAssist/.env: # CalDAV Configuration NEXTCLOUD_CALDAV_URL=http://nextcloud:80/remote.php/dav NEXTCLOUD_CALDAV_USERNAME=admin NEXTCLOUD_CALDAV_PASSWORD=<from NEXTCLOUD_ADMIN_PASSWORD> # WebDAV Configuration NEXTCLOUD_WEBDAV_URL=http://nextcloud:80/remote.php/dav/files/admin/ NEXTCLOUD_WEBDAV_USERNAME=admin NEXTCLOUD_WEBDAV_PASSWORD=<from NEXTCLOUD_ADMIN_PASSWORD> # Watch Directories for Auto-Indexing NEXTCLOUD_WATCH_DIRECTORIES=/Documents,/Medical_Guidelines

Testing Phase 6 Integrations

Test Calendar Operations:

# List calendars curl -X GET http://localhost:8000/api/integrations/calendar/calendars \ -H "Authorization: Bearer $ACCESS_TOKEN" # Create event curl -X POST http://localhost:8000/api/integrations/calendar/events \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "summary": "Patient Consultation", "start": "2025-01-25T14:00:00Z", "end": "2025-01-25T15:00:00Z", "description": "Follow-up appointment", "location": "Clinic Room 3" }' # Get events in date range curl -X GET "http://localhost:8000/api/integrations/calendar/events?start_date=2025-01-20&end_date=2025-01-31" \ -H "Authorization: Bearer $ACCESS_TOKEN"

Test File Auto-Indexing:

# First, add some medical documents to Nextcloud # Via Nextcloud web UI: Upload PDFs to /Documents folder # Scan and index all files curl -X POST "http://localhost:8000/api/integrations/files/scan-and-index?source_type=guideline" \ -H "Authorization: Bearer $ACCESS_TOKEN" # Response includes: # { # "files_discovered": 10, # "files_indexed": 8, # "files_failed": 0, # "files_skipped": 2 (already indexed) # } # Index specific file curl -X POST http://localhost:8000/api/integrations/files/index \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "file_path": "/Documents/hypertension_guideline.pdf", "source_type": "guideline", "title": "2024 Hypertension Management Guidelines" }'

Verify Integration:

# Files should now be searchable via Phase 5 RAG curl -X POST http://localhost:8000/api/realtime/query \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query": "What are first-line treatments for hypertension?"}' # Response should include citations from indexed guideline

Phase 6 Limitations

Not Yet Implemented:

  • OIDC authentication (still using JWT tokens from Phase 2)
  • Per-user credentials (currently using admin credentials for all operations)
  • CardDAV contacts integration
  • Complete email integration (skeleton only)
  • Calendar event notifications/reminders
  • Conflict resolution for calendar syncing
  • Incremental file indexing (currently full scans)

Security Note: Current implementation uses admin credentials for all Nextcloud operations. Production deployments should implement per-user credential management with secure storage (encrypted in database or secrets manager).


Architecture Decision (for Separate Stack Deployment)

Key Principle: Nextcloud and VoiceAssist are independent deployments that communicate via standard APIs.

Separate Stacks:
├── Nextcloud Stack (~/Nextcloud-Dev/)
│   ├── Identity & SSO (OIDC)
│   ├── File Storage (WebDAV)
│   ├── Calendar (CalDAV)
│   ├── Email (IMAP/SMTP)
│   └── User Directory
│
└── VoiceAssist Stack (~/VoiceAssist/)
    ├── Microservices
    ├── Databases
    ├── Observability
    └── Integration with Nextcloud via APIs

Why Separate?

Benefits:Independence - Update either system without affecting the other ✅ Flexibility - Use existing Nextcloud installation in production ✅ Clarity - Clear separation of concerns ✅ Scalability - Scale each system independently ✅ Maintainability - Easier to troubleshoot and maintain ✅ Reusability - Nextcloud can serve multiple applications

Integration Method:

  • HTTP/HTTPS APIs (OIDC, WebDAV, CalDAV, CardDAV)
  • Environment variables for configuration
  • No shared Docker networks or volumes
  • No shared databases

Local Development Setup

Directory Structure

~/Nextcloud-Dev/                    # Nextcloud development stack
├── docker-compose.yml              # Nextcloud + Database
├── .env                            # Nextcloud environment
├── data/                           # Nextcloud files
│   ├── data/                       # User files
│   ├── config/                     # Nextcloud config
│   └── apps/                       # Nextcloud apps
└── db/                             # Nextcloud database

~/VoiceAssist/                      # VoiceAssist stack (this repo)
├── docker-compose.yml              # VoiceAssist services
├── .env                            # Includes NEXTCLOUD_* variables
├── services/                       # Microservices
└── data/                           # VoiceAssist data

Step 1: Set Up Nextcloud Dev Stack

Create ~/Nextcloud-Dev directory

mkdir -p ~/Nextcloud-Dev cd ~/Nextcloud-Dev

Create docker-compose.yml

version: "3.8" services: nextcloud-db: image: postgres:15-alpine environment: POSTGRES_DB: nextcloud POSTGRES_USER: nextcloud POSTGRES_PASSWORD: nextcloud_dev_password volumes: - nextcloud-db:/var/lib/postgresql/data networks: - nextcloud-network healthcheck: test: ["CMD-SHELL", "pg_isready -U nextcloud"] interval: 10s timeout: 5s retries: 5 nextcloud: image: nextcloud:latest ports: - "8080:80" environment: - POSTGRES_HOST=nextcloud-db - POSTGRES_DB=nextcloud - POSTGRES_USER=nextcloud - POSTGRES_PASSWORD=nextcloud_dev_password - NEXTCLOUD_ADMIN_USER=admin - NEXTCLOUD_ADMIN_PASSWORD=admin_dev_password - NEXTCLOUD_TRUSTED_DOMAINS=localhost nextcloud.local - OVERWRITEPROTOCOL=http - OVERWRITEHOST=localhost:8080 volumes: - nextcloud-data:/var/www/html depends_on: nextcloud-db: condition: service_healthy networks: - nextcloud-network healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:80/status.php || exit 1"] interval: 30s timeout: 10s retries: 3 networks: nextcloud-network: driver: bridge volumes: nextcloud-db: driver: local nextcloud-data: driver: local

Create .env file

cat > .env <<'EOF' # Nextcloud Dev Environment # Database POSTGRES_DB=nextcloud POSTGRES_USER=nextcloud POSTGRES_PASSWORD=nextcloud_dev_password # Nextcloud Admin NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=admin_dev_password # Nextcloud Config NEXTCLOUD_TRUSTED_DOMAINS=localhost nextcloud.local EOF

Start Nextcloud Dev Stack

cd ~/Nextcloud-Dev # Start Nextcloud docker compose up -d # Wait for Nextcloud to initialize (first start takes 2-3 minutes) docker compose logs -f nextcloud # Check when you see: "Initializing finished"

Access Nextcloud

URL: http://localhost:8080
Username: admin
Password: admin_dev_password

Step 2: Configure Nextcloud for VoiceAssist

Install Required Apps

  1. Access Nextcloud Admin

  2. Install OIDC App

    Search: "OpenID Connect"
    Install: "OpenID Connect user backend"
    
  3. Install External Storage (if not installed)

    Search: "External storage support"
    Enable if not already enabled
    

Configure OIDC Provider

  1. Settings → Administration → Security → OAuth 2.0

    • Click "Add client"
    • Name: VoiceAssist
    • Redirection URI: http://localhost:8000/auth/callback
    • Type: Confidential
    • Click "Add"
    • Copy the Client ID and Client Secret - you'll need these
  2. Save Credentials

    # Example values (yours will be different): Client ID: s8dh2k3j4h5k6j7h8k9j0 Client Secret: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Create Test User

  1. Users → Add User

    • Username: testdoc
    • Display name: Test Doctor
    • Email: testdoc@example.com
    • Password: testdoc123
    • Groups: users (create if needed)
  2. Verify User Can Login

    • Logout as admin
    • Login as testdoc
    • Verify access works

Step 3: Configure VoiceAssist to Connect to Nextcloud

Update ~/VoiceAssist/.env

Add these variables to your VoiceAssist .env file:

#============================================== # Nextcloud Integration (Separate Stack) #============================================== # Base URL of Nextcloud instance NEXTCLOUD_BASE_URL=http://localhost:8080 # OIDC Configuration NEXTCLOUD_OIDC_ISSUER=http://localhost:8080 NEXTCLOUD_CLIENT_ID=s8dh2k3j4h5k6j7h8k9j0 # From Nextcloud OAuth config NEXTCLOUD_CLIENT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 # From Nextcloud NEXTCLOUD_REDIRECT_URI=http://localhost:8000/auth/callback # WebDAV (for file access) NEXTCLOUD_WEBDAV_URL=http://localhost:8080/remote.php/dav NEXTCLOUD_WEBDAV_USERNAME=admin NEXTCLOUD_WEBDAV_PASSWORD=admin_dev_password # CalDAV (for calendar integration) NEXTCLOUD_CALDAV_URL=http://localhost:8080/remote.php/dav/calendars NEXTCLOUD_CALDAV_USERNAME=admin NEXTCLOUD_CALDAV_PASSWORD=admin_dev_password # CardDAV (for contacts) NEXTCLOUD_CARDDAV_URL=http://localhost:8080/remote.php/dav/addressbooks # Admin credentials (for service account operations) NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=admin_dev_password

Step 4: Test Integration

Once VoiceAssist services are running (Phase 2+), test the integration:

# Test 1: Check Nextcloud is reachable curl http://localhost:8080/status.php # Should return JSON with Nextcloud status # Test 2: Test OIDC discovery curl http://localhost:8080/.well-known/openid-configuration # Should return OIDC configuration # Test 3: Test WebDAV access curl -u admin:admin_dev_password \ http://localhost:8080/remote.php/dav # Should return WebDAV capabilities # Test 4: From VoiceAssist auth service # (This will be implemented in Phase 2) docker exec voiceassist-auth-service python -c " from app.integrations.nextcloud import NextcloudClient client = NextcloudClient() print(client.test_connection()) " # Should print: Connection successful

Production Setup

Assumptions

In production, you likely have:

  • Existing Nextcloud installation (e.g., https://cloud.asimo.io)
  • OR need to deploy Nextcloud separately on Ubuntu server
  • SSL certificates already configured
  • MFA enabled for all users
  • Regular backups in place

Integration Steps

1. Identify Nextcloud Instance

# If you have existing Nextcloud: NEXTCLOUD_BASE_URL=https://cloud.asimo.io # If deploying fresh Nextcloud on Ubuntu: # Follow Nextcloud installation guide first # https://docs.nextcloud.com/server/latest/admin_manual/installation/

2. Configure OIDC in Production Nextcloud

Same steps as local dev, but with production URLs:

Name: VoiceAssist Production
Redirection URI: https://voiceassist.asimo.io/auth/callback
Type: Confidential

3. Update Production VoiceAssist Environment

# In Ubuntu server: ~/VoiceAssist/.env NEXTCLOUD_BASE_URL=https://cloud.asimo.io NEXTCLOUD_OIDC_ISSUER=https://cloud.asimo.io NEXTCLOUD_CLIENT_ID=<production_client_id> NEXTCLOUD_CLIENT_SECRET=<production_client_secret> NEXTCLOUD_REDIRECT_URI=https://voiceassist.asimo.io/auth/callback # Use service account credentials NEXTCLOUD_ADMIN_USER=voiceassist_service NEXTCLOUD_ADMIN_PASSWORD=<secure_password>

4. Create Service Account in Nextcloud

Best Practice: Don't use admin account for API access

1. Create user: voiceassist_service
2. Add to group: voiceassist_services
3. Grant necessary permissions:
   - WebDAV access
   - CalDAV access
   - User provisioning (if needed)
4. Generate app password for this account

5. Test Production Integration

# SSH to Ubuntu server ssh user@asimo.io # Test Nextcloud connectivity curl https://cloud.asimo.io/status.php # Test from VoiceAssist docker exec voiceassist-auth-service \ python /app/scripts/test_nextcloud.py

Integration Features

1. Authentication (OIDC)

VoiceAssist Auth Service implements OAuth 2.0 / OIDC client:

# services/auth-service/app/integrations/nextcloud_oidc.py from authlib.integrations.starlette_client import OAuth oauth = OAuth() oauth.register( name='nextcloud', client_id=settings.NEXTCLOUD_CLIENT_ID, client_secret=settings.NEXTCLOUD_CLIENT_SECRET, server_metadata_url=f'{settings.NEXTCLOUD_OIDC_ISSUER}/.well-known/openid-configuration', client_kwargs={'scope': 'openid profile email'} ) @app.get('/auth/login') async def login(request: Request): redirect_uri = settings.NEXTCLOUD_REDIRECT_URI return await oauth.nextcloud.authorize_redirect(request, redirect_uri) @app.get('/auth/callback') async def auth_callback(request: Request): token = await oauth.nextcloud.authorize_access_token(request) user_info = token.get('userinfo') # Create/update user in VoiceAssist database # Generate VoiceAssist JWT token # Return to client

2. File Storage (WebDAV)

File Indexer Service accesses Nextcloud files:

# services/file-indexer/app/integrations/nextcloud_webdav.py from webdav3.client import Client class NextcloudFileClient: def __init__(self): self.client = Client({ 'webdav_hostname': settings.NEXTCLOUD_WEBDAV_URL, 'webdav_login': settings.NEXTCLOUD_WEBDAV_USERNAME, 'webdav_password': settings.NEXTCLOUD_WEBDAV_PASSWORD }) def list_files(self, path='/'): return self.client.list(path) def download_file(self, remote_path, local_path): self.client.download_sync( remote_path=remote_path, local_path=local_path ) def upload_file(self, local_path, remote_path): self.client.upload_sync( remote_path=remote_path, local_path=local_path )

3. Calendar (CalDAV)

Calendar Service integrates with Nextcloud calendars:

# services/calendar-email/app/integrations/nextcloud_caldav.py import caldav from datetime import datetime class NextcloudCalendarClient: def __init__(self): self.client = caldav.DAVClient( url=settings.NEXTCLOUD_CALDAV_URL, username=settings.NEXTCLOUD_CALDAV_USERNAME, password=settings.NEXTCLOUD_CALDAV_PASSWORD ) self.principal = self.client.principal() def get_calendars(self): return self.principal.calendars() def create_event(self, calendar_name, title, start, end, description=''): calendar = self.get_calendar_by_name(calendar_name) event = calendar.save_event( dtstart=start, dtend=end, summary=title, description=description ) return event

4. Email (IMAP/SMTP or Nextcloud Mail API)

Email Service can use:

  • Option A: IMAP/SMTP directly
  • Option B: Nextcloud Mail API (if available)
# services/calendar-email/app/integrations/nextcloud_email.py import imaplib import smtplib from email.mime.text import MIMEText class NextcloudEmailClient: def __init__(self): # IMAP for reading self.imap = imaplib.IMAP4_SSL('cloud.asimo.io', 993) self.imap.login( settings.NEXTCLOUD_EMAIL_USERNAME, settings.NEXTCLOUD_EMAIL_PASSWORD ) # SMTP for sending self.smtp = smtplib.SMTP_SSL('cloud.asimo.io', 465) self.smtp.login( settings.NEXTCLOUD_EMAIL_USERNAME, settings.NEXTCLOUD_EMAIL_PASSWORD ) def fetch_recent_emails(self, mailbox='INBOX', count=10): self.imap.select(mailbox) _, messages = self.imap.search(None, 'ALL') # Fetch and parse emails return emails def send_email(self, to, subject, body): msg = MIMEText(body) msg['Subject'] = subject msg['From'] = settings.NEXTCLOUD_EMAIL_USERNAME msg['To'] = to self.smtp.send_message(msg)

Security Considerations

Authentication Security

  1. OIDC Tokens

    • Use short-lived access tokens (15 minutes)
    • Implement refresh tokens
    • Store tokens securely (encrypted in Redis)
    • Validate tokens on every request
  2. API Credentials

    • Use app passwords instead of user passwords
    • Rotate credentials regularly
    • Store in environment variables, not code
    • Use separate service account for API access

Network Security

Local Development:

  • HTTP is acceptable for localhost
  • Consider self-signed certs for HTTPS practice

Production:

  • ALWAYS use HTTPS for Nextcloud communication
  • Validate SSL certificates
  • Use certificate pinning for critical operations
  • Implement request signing for sensitive operations

Data Privacy

  1. PHI Considerations

    • Medical notes in Nextcloud must be encrypted
    • Use Nextcloud's encryption module
    • Never log file contents
    • Implement access controls in Nextcloud
  2. Audit Logging

    • Log all Nextcloud API access
    • Track file downloads/uploads
    • Monitor authentication attempts
    • Alert on suspicious activity

Troubleshooting

Nextcloud Connection Issues

# Test 1: Can VoiceAssist reach Nextcloud? docker exec voiceassist-auth-service \ curl http://localhost:8080/status.php # Test 2: Check OIDC configuration curl http://localhost:8080/.well-known/openid-configuration # Test 3: Verify OAuth client exists # Login to Nextcloud → Settings → Administration → Security → OAuth 2.0 # Should see VoiceAssist client # Test 4: Check redirect URI matches # In Nextcloud OAuth config: http://localhost:8000/auth/callback # In VoiceAssist .env: NEXTCLOUD_REDIRECT_URI=http://localhost:8000/auth/callback

Authentication Failures

Problem: "Invalid redirect URI"

Solution: Ensure NEXTCLOUD_REDIRECT_URI in .env matches exactly
what's configured in Nextcloud OAuth client

Problem: "Client authentication failed"

Solution: Verify CLIENT_ID and CLIENT_SECRET are correct
Check for typos, extra spaces

Problem: "Token validation failed"

Solution: Ensure NEXTCLOUD_OIDC_ISSUER is correct
Check that Nextcloud OIDC app is enabled

WebDAV Issues

Problem: "Unauthorized" when accessing files

Solution: Verify NEXTCLOUD_WEBDAV_USERNAME and PASSWORD
Check that user has permission to access files

Problem: "Method not allowed"

Solution: Ensure URL is correct: /remote.php/dav
Not /webdav or /dav

CalDAV Issues

Problem: "Calendar not found"

Solution: Verify calendar exists for the user
Check CALDAV_URL includes /calendars/username/calendar-name

Maintenance

Updating Nextcloud Dev Stack

cd ~/Nextcloud-Dev # Pull latest image docker compose pull # Restart with new image docker compose down docker compose up -d # Check logs docker compose logs -f nextcloud

Backing Up Nextcloud Dev Data

# Backup volumes docker run --rm \ -v nextcloud-dev_nextcloud-data:/data \ -v ~/Nextcloud-Dev/backups:/backup \ ubuntu tar czf /backup/nextcloud-data-$(date +%Y%m%d).tar.gz /data docker run --rm \ -v nextcloud-dev_nextcloud-db:/data \ -v ~/Nextcloud-Dev/backups:/backup \ ubuntu tar czf /backup/nextcloud-db-$(date +%Y%m%d).tar.gz /data

Cleaning Up

cd ~/Nextcloud-Dev # Stop and remove containers docker compose down # Remove volumes (WARNING: deletes all data) docker compose down -v # Start fresh docker compose up -d

Summary

Local Development Checklist

  • Created ~/Nextcloud-Dev directory
  • Created docker-compose.yml for Nextcloud
  • Started Nextcloud stack
  • Accessed http://localhost:8080
  • Logged in as admin
  • Installed OIDC app
  • Created OAuth client for VoiceAssist
  • Copied CLIENT_ID and CLIENT_SECRET
  • Updated ~/VoiceAssist/.env with Nextcloud variables
  • Created test user in Nextcloud
  • Verified Nextcloud connectivity from VoiceAssist

Production Deployment Checklist

  • Identified/deployed production Nextcloud instance
  • Configured OIDC with production URLs
  • Created service account for VoiceAssist
  • Generated app password
  • Updated VoiceAssist production .env
  • Tested connectivity from VoiceAssist
  • Verified HTTPS is enforced
  • Enabled MFA for all users
  • Configured backup strategy
  • Set up monitoring

Integration Status

Track which integrations are implemented:

  • Phase 0: Documentation complete
  • Phase 1: N/A (databases only)
  • Phase 2: Nextcloud Docker services added, OCS API integration service created, basic user provisioning API (OIDC deferred)
  • Phase 3: N/A (internal services)
  • Phase 4: N/A (voice pipeline)
  • Phase 5: N/A (medical AI)
  • Phase 6: CalDAV calendar operations (CRUD), WebDAV file auto-indexing, Email service skeleton
  • Phase 7: Full OIDC authentication, CardDAV contacts, Complete email integration
  • Phase 8: N/A (observability)
  • Phase 9: N/A (IaC)
  • Phase 10: Load test Nextcloud integration

Phase 6 Deliverables (Completed):

  • ✅ CalDAV Service with full event CRUD operations
  • ✅ Nextcloud File Indexer for automatic KB population
  • ✅ Email Service skeleton (IMAP/SMTP basics)
  • ✅ Integration API endpoints (/api/integrations/*)
  • ✅ Comprehensive integration tests with mocks
  • ✅ Documentation updates

Deferred to Phase 7+:

  • ⏳ OIDC authentication flow
  • ⏳ Per-user credential management
  • ⏳ CardDAV contacts integration
  • ⏳ Full email integration (parsing, threading, search)
  • ⏳ Calendar notifications and reminders
  • ⏳ Incremental file indexing with change detection

References