Files
microdao-daarion/docs/SWAPPER-CABINET-INTEGRATION.md
2026-02-16 06:22:45 -08:00

13 KiB

Swapper Service - Cabinet Integration Guide

Version: 1.0.0
Last Updated: 2025-11-22
Status: Ready for Integration


Overview

This document describes how to integrate Swapper Service status and metrics into Node #1 and Node #2 admin consoles (cabinet interfaces).


API Endpoints for Cabinets

1. GET /api/cabinet/swapper/status

Get complete Swapper Service status for cabinet display.

Response:

{
  "service": "swapper-service",
  "status": "healthy",
  "mode": "single-active",
  "active_model": {
    "name": "deepseek-r1-70b",
    "uptime_hours": 1.5,
    "request_count": 42,
    "loaded_at": "2025-11-22T10:30:00"
  },
  "total_models": 8,
  "available_models": ["deepseek-r1-70b", "qwen2.5-coder-32b", ...],
  "loaded_models": ["deepseek-r1-70b"],
  "models": [
    {
      "name": "deepseek-r1-70b",
      "ollama_name": "deepseek-r1:70b",
      "type": "llm",
      "size_gb": 42,
      "priority": "high",
      "status": "loaded",
      "is_active": true,
      "uptime_hours": 1.5,
      "request_count": 42,
      "total_uptime_seconds": 5400.0
    }
  ],
  "timestamp": "2025-11-22T12:00:00"
}

2. GET /api/cabinet/swapper/models

Get detailed information about all models.

Response:

{
  "models": [
    {
      "name": "deepseek-r1-70b",
      "ollama_name": "deepseek-r1:70b",
      "type": "llm",
      "size_gb": 42,
      "priority": "high",
      "status": "loaded",
      "is_active": true,
      "can_load": false,
      "can_unload": true,
      "uptime_hours": 1.5,
      "request_count": 42,
      "total_uptime_seconds": 5400.0,
      "loaded_at": "2025-11-22T10:30:00"
    }
  ],
  "total": 8,
  "active_count": 1,
  "timestamp": "2025-11-22T12:00:00"
}

3. GET /api/cabinet/swapper/metrics/summary

Get summary metrics for dashboard.

Response:

{
  "summary": {
    "total_models": 8,
    "active_models": 1,
    "available_models": 8,
    "total_uptime_hours": 24.5,
    "total_requests": 150
  },
  "most_used_model": {
    "name": "deepseek-r1-70b",
    "uptime_hours": 12.3,
    "request_count": 85
  },
  "active_model": {
    "name": "deepseek-r1-70b",
    "uptime_hours": 1.5
  },
  "timestamp": "2025-11-22T12:00:00"
}

Frontend Integration Examples

React Component Example

import React, { useEffect, useState } from 'react';

interface SwapperStatus {
  service: string;
  status: string;
  mode: string;
  active_model: {
    name: string;
    uptime_hours: number;
    request_count: number;
    loaded_at: string;
  } | null;
  total_models: number;
  models: Array<{
    name: string;
    type: string;
    size_gb: number;
    status: string;
    is_active: boolean;
    uptime_hours: number;
  }>;
}

export const SwapperStatusCard: React.FC = () => {
  const [status, setStatus] = useState<SwapperStatus | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchStatus = async () => {
      try {
        const response = await fetch('http://localhost:8890/api/cabinet/swapper/status');
        const data = await response.json();
        setStatus(data);
      } catch (error) {
        console.error('Error fetching Swapper status:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchStatus();
    const interval = setInterval(fetchStatus, 30000); // Update every 30 seconds

    return () => clearInterval(interval);
  }, []);

  if (loading) return <div>Loading...</div>;
  if (!status) return <div>Error loading status</div>;

  return (
    <div className="swapper-status-card">
      <h3>Swapper Service</h3>
      <div className="status-info">
        <p>Status: <span className={status.status}>{status.status}</span></p>
        <p>Mode: {status.mode}</p>
        <p>Total Models: {status.total_models}</p>
      </div>
      
      {status.active_model && (
        <div className="active-model">
          <h4>Active Model</h4>
          <p>Name: {status.active_model.name}</p>
          <p>Uptime: {status.active_model.uptime_hours.toFixed(2)} hours</p>
          <p>Requests: {status.active_model.request_count}</p>
        </div>
      )}

      <div className="models-list">
        <h4>Available Models</h4>
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Type</th>
              <th>Size (GB)</th>
              <th>Status</th>
              <th>Uptime (hours)</th>
            </tr>
          </thead>
          <tbody>
            {status.models.map((model) => (
              <tr key={model.name} className={model.is_active ? 'active' : ''}>
                <td>{model.name}</td>
                <td>{model.type}</td>
                <td>{model.size_gb}</td>
                <td>{model.status}</td>
                <td>{model.uptime_hours.toFixed(2)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
};

Vue Component Example

<template>
  <div class="swapper-status-card">
    <h3>Swapper Service</h3>
    
    <div class="status-info">
      <p>Status: <span :class="status.status">{{ status.status }}</span></p>
      <p>Mode: {{ status.mode }}</p>
      <p>Total Models: {{ status.total_models }}</p>
    </div>
    
    <div v-if="status.active_model" class="active-model">
      <h4>Active Model</h4>
      <p>Name: {{ status.active_model.name }}</p>
      <p>Uptime: {{ status.active_model.uptime_hours.toFixed(2) }} hours</p>
      <p>Requests: {{ status.active_model.request_count }}</p>
    </div>
    
    <div class="models-list">
      <h4>Available Models</h4>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Type</th>
            <th>Size (GB)</th>
            <th>Status</th>
            <th>Uptime (hours)</th>
          </tr>
        </thead>
        <tbody>
          <tr 
            v-for="model in status.models" 
            :key="model.name"
            :class="{ active: model.is_active }"
          >
            <td>{{ model.name }}</td>
            <td>{{ model.type }}</td>
            <td>{{ model.size_gb }}</td>
            <td>{{ model.status }}</td>
            <td>{{ model.uptime_hours.toFixed(2) }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

interface SwapperStatus {
  service: string;
  status: string;
  mode: string;
  active_model: {
    name: string;
    uptime_hours: number;
    request_count: number;
  } | null;
  total_models: number;
  models: Array<{
    name: string;
    type: string;
    size_gb: number;
    status: string;
    is_active: boolean;
    uptime_hours: number;
  }>;
}

const status = ref<SwapperStatus | null>(null);
const loading = ref(true);

const fetchStatus = async () => {
  try {
    const response = await fetch('http://localhost:8890/api/cabinet/swapper/status');
    const data = await response.json();
    status.value = data;
  } catch (error) {
    console.error('Error fetching Swapper status:', error);
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  fetchStatus();
  const interval = setInterval(fetchStatus, 30000);
  
  onUnmounted(() => clearInterval(interval));
});
</script>

Dashboard Widgets

Widget 1: Active Model Display

<Widget title="Active Model">
  {activeModel ? (
    <div>
      <h4>{activeModel.name}</h4>
      <p>Uptime: {activeModel.uptime_hours.toFixed(2)} hours</p>
      <p>Requests: {activeModel.request_count}</p>
      <p>Loaded: {new Date(activeModel.loaded_at).toLocaleString()}</p>
    </div>
  ) : (
    <p>No model loaded</p>
  )}
</Widget>

Widget 2: Model List with Actions

<Widget title="Models">
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Status</th>
        <th>Uptime</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
      {models.map(model => (
        <tr key={model.name}>
          <td>{model.name}</td>
          <td>
            <Badge status={model.status}>
              {model.is_active ? 'Active' : model.status}
            </Badge>
          </td>
          <td>{model.uptime_hours.toFixed(2)}h</td>
          <td>
            {model.can_load && (
              <Button onClick={() => loadModel(model.name)}>
                Load
              </Button>
            )}
            {model.can_unload && (
              <Button onClick={() => unloadModel(model.name)}>
                Unload
              </Button>
            )}
          </td>
        </tr>
      ))}
    </tbody>
  </table>
</Widget>

Widget 3: Metrics Summary

<Widget title="Metrics Summary">
  <div className="metrics-grid">
    <MetricCard 
      label="Total Models" 
      value={summary.total_models} 
    />
    <MetricCard 
      label="Active Models" 
      value={summary.active_models} 
    />
    <MetricCard 
      label="Total Uptime" 
      value={`${summary.total_uptime_hours.toFixed(2)}h`} 
    />
    <MetricCard 
      label="Total Requests" 
      value={summary.total_requests} 
    />
  </div>
  
  {mostUsedModel && (
    <div className="most-used">
      <h4>Most Used Model</h4>
      <p>{mostUsedModel.name}</p>
      <p>{mostUsedModel.uptime_hours.toFixed(2)} hours</p>
    </div>
  )}
</Widget>

Integration Steps

Step 1: Add API Client

Create a service to fetch Swapper data:

// services/swapperService.ts
export const swapperService = {
  async getStatus() {
    const response = await fetch('http://localhost:8890/api/cabinet/swapper/status');
    return response.json();
  },
  
  async getModels() {
    const response = await fetch('http://localhost:8890/api/cabinet/swapper/models');
    return response.json();
  },
  
  async getMetricsSummary() {
    const response = await fetch('http://localhost:8890/api/cabinet/swapper/metrics/summary');
    return response.json();
  },
  
  async loadModel(modelName: string) {
    const response = await fetch(
      `http://localhost:8890/models/${modelName}/load`,
      { method: 'POST' }
    );
    return response.json();
  },
  
  async unloadModel(modelName: string) {
    const response = await fetch(
      `http://localhost:8890/models/${modelName}/unload`,
      { method: 'POST' }
    );
    return response.json();
  }
};

Step 2: Add to Admin Console

Add Swapper section to admin console sidebar:

// Admin Console Sidebar
const menuItems = [
  { id: 'overview', label: 'Overview', icon: 'dashboard' },
  { id: 'members', label: 'Members & Roles', icon: 'users' },
  { id: 'agents', label: 'Agents', icon: 'robot' },
  { id: 'swapper', label: 'Swapper Service', icon: 'swap' }, // Add this
  { id: 'settings', label: 'Settings', icon: 'settings' },
];

Step 3: Create Swapper Page

// pages/SwapperPage.tsx
export const SwapperPage: React.FC = () => {
  return (
    <div className="swapper-page">
      <PageHeader title="Swapper Service" />
      
      <div className="swapper-grid">
        <SwapperStatusCard />
        <SwapperMetricsSummary />
        <SwapperModelsList />
      </div>
    </div>
  );
};

Node-Specific Configuration

Node #1 (Production Server)

const SWAPPER_URL = 'http://swapper-service:8890'; // Internal Docker network

Node #2 (MacBook Development)

const SWAPPER_URL = 'http://localhost:8890'; // Local development

Error Handling

try {
  const status = await swapperService.getStatus();
  // Handle success
} catch (error) {
  if (error.status === 503) {
    // Service unavailable
    showError('Swapper Service is not available');
  } else if (error.status === 404) {
    // Model not found
    showError('Model not found');
  } else {
    // Generic error
    showError('Error loading Swapper status');
  }
}

Real-time Updates

Use polling or WebSocket for real-time updates:

// Polling example
useEffect(() => {
  const interval = setInterval(async () => {
    const status = await swapperService.getStatus();
    setStatus(status);
  }, 30000); // Update every 30 seconds
  
  return () => clearInterval(interval);
}, []);

Styling Recommendations

.swapper-status-card {
  background: white;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.active-model {
  background: #e8f5e9;
  padding: 15px;
  border-radius: 4px;
  margin: 15px 0;
}

.models-list table {
  width: 100%;
  border-collapse: collapse;
}

.models-list tr.active {
  background: #fff3e0;
}

.status-badge {
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.status-badge.loaded {
  background: #4caf50;
  color: white;
}

.status-badge.unloaded {
  background: #9e9e9e;
  color: white;
}

Testing

// Test Swapper API integration
describe('Swapper Service Integration', () => {
  it('should fetch status', async () => {
    const status = await swapperService.getStatus();
    expect(status).toHaveProperty('service', 'swapper-service');
    expect(status).toHaveProperty('status', 'healthy');
  });
  
  it('should load model', async () => {
    const result = await swapperService.loadModel('deepseek-r1-70b');
    expect(result.status).toBe('success');
  });
});

Last Updated: 2025-11-22
Maintained by: Ivan Tytar & DAARION Team
Status: Ready for Integration