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 Category | User 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:
- PostgreSQL
system_settingstable (single row) - Redis cache for fast access (with 5-minute TTL)
- File backup in
/etc/voiceassist/system.json(read on startup)
When settings change:
- Database is updated
- Redis cache is invalidated
- WebSocket broadcast to all services
- 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.
| Endpoint | Method | Purpose | Request | Response |
|---|---|---|---|---|
/api/admin/kb/documents | GET | List documents | Query params (filters) | APIEnvelope<KnowledgeDocument[]> |
/api/admin/kb/documents/{id} | GET | Get document details | - | APIEnvelope<KnowledgeDocument> |
/api/admin/kb/documents | POST | Upload document | FormData (file) | APIEnvelope<IndexingJob> |
/api/admin/kb/documents/{id} | DELETE | Delete document | - | APIEnvelope<{success: true}> |
/api/admin/kb/jobs | GET | List indexing jobs | Query params (state filter) | APIEnvelope<IndexingJob[]> |
/api/admin/kb/jobs/{id} | GET | Get job details | - | APIEnvelope<IndexingJob> |
/api/admin/kb/jobs/{id}/retry | POST | Retry failed job | - | APIEnvelope<IndexingJob> |
/api/admin/kb/reindex | POST | Bulk reindex | {document_ids: string[]} | APIEnvelope<{job_count: number}> |
/api/admin/kb/search | POST | Test 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 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
┌──────────────────────────────────────────┐
│ 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:
| Integration | Category | API Key Required | PHI Safe | Purpose |
|---|---|---|---|---|
| OpenEvidence | Medical Search | Yes | Yes (external) | Evidence-based medicine search |
| PubMed (NCBI) | Medical Search | No | Yes (external) | Biomedical literature search |
| Nextcloud | File Storage | No (internal) | No (local PHI) | Document storage and retrieval |
| CalDAV Server | Calendar | No (internal) | No (local PHI) | Calendar events (via Nextcloud) |
| Google Custom Search | Web Search | Yes | Yes (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:
- TOOLS_AND_INTEGRATIONS.md - Complete tools layer specification
- ORCHESTRATION_DESIGN.md - Backend tool execution flow
- DATA_MODEL.md - ToolCall and ToolResult entities
- OBSERVABILITY.md - Tool metrics and monitoring
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_URLenvironment 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:
- User clicks "Ask AI" floating action button
- Dialog opens with text input field
- Page context is pre-filled (user can modify)
- Submit sends request to
/api/ai/docs/ask - AI response includes relevant doc citations
- 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_toolfor semantic search across documentation - Leverages Qdrant
platform_docscollection (1536-dim embeddings) - Returns top-k relevant passages with citations
- Confidence score indicates answer quality
See Also:
- DOCUMENTATION_SITE_SPECS.md - Full docs site specifications
- Agent API Reference - AI agent endpoints
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
-
Configure OIDC providers (API Gateway):
- Set
NEXTCLOUD_URL,NEXTCLOUD_OAUTH_CLIENT_ID,NEXTCLOUD_OAUTH_CLIENT_SECRET, andNEXTCLOUD_OAUTH_REDIRECT_URIin.envfor Nextcloud SSO. - Optionally set
GOOGLE_OAUTH_CLIENT_ID/SECRETorMICROSOFT_CLIENT_ID/SECRETwith their redirect URIs if federating logins. - Restart the API Gateway;
configure_oidc_from_settings()registers providers and caches JWKS for validation.
- Set
-
Enable CardDAV + contacts:
- Provide
NEXTCLOUD_WEBDAV_URL,NEXTCLOUD_CALDAV_USERNAME, andNEXTCLOUD_CALDAV_PASSWORDfor CardDAV access. - The
CardDAVServicenow supports sync tokens; call/api/integrations/contactsendpoints to keep address books in sync.
- Provide
-
Finalize IMAP/SMTP email:
- Supply
EMAIL_IMAP_HOST,EMAIL_IMAP_PORT,EMAIL_SMTP_HOST,EMAIL_SMTP_PORT,EMAIL_USERNAME, andEMAIL_PASSWORDin.env. - The email service will reconnect automatically on IMAP failures and respects TLS/STARTTLS based on configuration.
- Supply
-
Package and install Nextcloud apps:
- Run
bash nextcloud-apps/package.shto createbuild/*.tar.gzarchives forvoiceassist-client,voiceassist-admin, andvoiceassist-docs. - Upload/enable the apps in Nextcloud; routes under
/apps/<app>/api/*mirror API Gateway endpoints for calendar, files, contacts, and email.
- Run
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
-
Access Nextcloud Admin
- Navigate to http://localhost:8080
- Login as admin
- Go to Apps (top right) → Search
-
Install OIDC App
Search: "OpenID Connect" Install: "OpenID Connect user backend" -
Install External Storage (if not installed)
Search: "External storage support" Enable if not already enabled
Configure OIDC Provider
-
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
-
Save Credentials
# Example values (yours will be different): Client ID: s8dh2k3j4h5k6j7h8k9j0 Client Secret: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Create Test User
-
Users → Add User
- Username:
testdoc - Display name:
Test Doctor - Email:
testdoc@example.com - Password:
testdoc123 - Groups:
users(create if needed)
- Username:
-
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
-
OIDC Tokens
- Use short-lived access tokens (15 minutes)
- Implement refresh tokens
- Store tokens securely (encrypted in Redis)
- Validate tokens on every request
-
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
-
PHI Considerations
- Medical notes in Nextcloud must be encrypted
- Use Nextcloud's encryption module
- Never log file contents
- Implement access controls in Nextcloud
-
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