Files
microdao-daarion/SWAPPER-INTEGRATION-GUIDE.md
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

11 KiB
Raw Permalink Blame History

Swapper Service - Інтеграція в кабінети Node #1 та Node #2

Дата: 2025-11-22
Статус: Готово до інтеграції


📋 Крок 1: Запуск Swapper Service

Node #2 (MacBook - Development)

Варіант A: Docker (якщо Docker запущений)

cd /Users/apple/github-projects/microdao-daarion

# Запустити Swapper Service
docker-compose up -d swapper-service

# Перевірити статус
docker-compose ps swapper-service

# Перевірити логи
docker-compose logs -f swapper-service

# Перевірити health
curl http://localhost:8890/health

Варіант B: Локально (без Docker)

cd /Users/apple/github-projects/microdao-daarion/services/swapper-service

# Запустити скрипт (створює venv та встановлює залежності)
./start.sh

Варіант C: Вручну

cd /Users/apple/github-projects/microdao-daarion/services/swapper-service

# Створити virtual environment
python3 -m venv venv
source venv/bin/activate

# Встановити залежності
pip install -r requirements.txt

# Налаштувати змінні оточення
export OLLAMA_BASE_URL=http://localhost:11434
export SWAPPER_CONFIG_PATH=./config/swapper_config.yaml
export SWAPPER_MODE=single-active

# Запустити
python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8890

Node #1 (Production Server)

# SSH до сервера
ssh root@144.76.224.179

# Перейти в директорію проекту
cd /opt/microdao-daarion

# Оновити код
git pull origin main

# Запустити Swapper Service
docker-compose up -d swapper-service

# Перевірити статус
docker-compose ps swapper-service
curl http://localhost:8890/health

📋 Крок 2: Перевірка роботи

Тест 1: Health Check

curl http://localhost:8890/health

Очікуваний результат:

{
  "status": "healthy",
  "service": "swapper-service",
  "active_model": null,
  "mode": "single-active"
}

Тест 2: Status для кабінету

curl http://localhost:8890/api/cabinet/swapper/status | python3 -m json.tool

Очікуваний результат:

{
  "service": "swapper-service",
  "status": "healthy",
  "mode": "single-active",
  "active_model": null,
  "total_models": 8,
  "available_models": [
    "deepseek-r1-70b",
    "qwen2.5-coder-32b",
    "gemma2-27b",
    ...
  ],
  "loaded_models": [],
  "models": [...]
}

Тест 3: Метрики

curl http://localhost:8890/api/cabinet/swapper/metrics/summary | python3 -m json.tool

📋 Крок 3: Інтеграція в кабінети

3.1 Додати Swapper секцію в Sidebar

Файл: src/components/AdminConsole/Sidebar.tsx (або аналогічний)

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' }, // ← Додати це
  { id: 'settings', label: 'Settings', icon: 'settings' },
];

3.2 Створити Swapper сторінку

Файл: src/pages/Admin/SwapperPage.tsx

import React from 'react';
import { SwapperPage } from '@/services/swapper-service/cabinet-integration';
import '@/services/swapper-service/cabinet-integration.css';

export default function SwapperAdminPage() {
  return <SwapperPage />;
}

3.3 Додати маршрут

Файл: src/routes/admin.tsx (або аналогічний)

import SwapperAdminPage from '@/pages/Admin/SwapperPage';

const adminRoutes = [
  { path: '/admin/overview', component: OverviewPage },
  { path: '/admin/members', component: MembersPage },
  { path: '/admin/agents', component: AgentsPage },
  { path: '/admin/swapper', component: SwapperAdminPage }, // ← Додати це
  { path: '/admin/settings', component: SettingsPage },
];

3.4 Налаштувати API URL

Файл: .env.local (або .env)

# Node #2 (MacBook - Development)
NEXT_PUBLIC_SWAPPER_URL=http://localhost:8890

# Node #1 (Production Server)
NEXT_PUBLIC_SWAPPER_URL=http://swapper-service:8890
# або через Nginx proxy:
NEXT_PUBLIC_SWAPPER_URL=https://gateway.daarion.city/api/swapper

3.5 Копіювати компоненти

# Копіювати компоненти в проект
cp services/swapper-service/cabinet-integration.tsx src/components/Swapper/
cp services/swapper-service/cabinet-integration.css src/styles/swapper.css

📋 Крок 4: Альтернативна інтеграція (якщо немає React)

4.1 Vanilla JavaScript

Файл: public/swapper-dashboard.html

<!DOCTYPE html>
<html>
<head>
  <title>Swapper Service Dashboard</title>
  <link rel="stylesheet" href="/styles/swapper.css">
</head>
<body>
  <div id="swapper-app"></div>
  <script>
    const SWAPPER_API = 'http://localhost:8890';
    
    async function loadSwapperStatus() {
      try {
        const response = await fetch(`${SWAPPER_API}/api/cabinet/swapper/status`);
        const data = await response.json();
        renderSwapperStatus(data);
      } catch (error) {
        console.error('Error loading Swapper status:', error);
      }
    }
    
    function renderSwapperStatus(status) {
      const app = document.getElementById('swapper-app');
      app.innerHTML = `
        <div class="swapper-status-card">
          <h3>Swapper Service</h3>
          <p>Status: ${status.status}</p>
          <p>Mode: ${status.mode}</p>
          ${status.active_model ? `
            <div class="active-model">
              <h4>Active Model: ${status.active_model.name}</h4>
              <p>Uptime: ${status.active_model.uptime_hours.toFixed(2)} hours</p>
            </div>
          ` : '<p>No active model</p>'}
        </div>
      `;
    }
    
    // Load status on page load
    loadSwapperStatus();
    
    // Refresh every 30 seconds
    setInterval(loadSwapperStatus, 30000);
  </script>
</body>
</html>

4.2 Vue.js компонент

Файл: src/components/Swapper/SwapperStatus.vue

<template>
  <div class="swapper-status-card">
    <h3>Swapper Service</h3>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>
      <p>Status: {{ status.status }}</p>
      <p>Mode: {{ status.mode }}</p>
      <div v-if="status.active_model" class="active-model">
        <h4>Active Model: {{ status.active_model.name }}</h4>
        <p>Uptime: {{ status.active_model.uptime_hours.toFixed(2) }} hours</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const status = ref(null);
const loading = ref(true);
const error = ref(null);

const SWAPPER_API = import.meta.env.VITE_SWAPPER_URL || 'http://localhost:8890';

async function fetchStatus() {
  try {
    const response = await fetch(`${SWAPPER_API}/api/cabinet/swapper/status`);
    status.value = await response.json();
    error.value = null;
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
}

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

📋 Крок 5: Інтеграція через Node Registry (опціонально)

Якщо використовується Node Registry Service, можна додати Swapper статус туди:

Файл: services/node-registry/app/main.py

@app.get("/api/nodes/{node_id}/swapper")
async def get_node_swapper_status(node_id: str):
    """Get Swapper Service status for a node"""
    # Determine Swapper URL based on node
    if node_id == "node-2-macbook-m4max":
        swapper_url = "http://localhost:8890"
    elif node_id == "node-1-hetzner-gex44":
        swapper_url = "http://swapper-service:8890"
    else:
        raise HTTPException(404, "Node not found")
    
    async with httpx.AsyncClient() as client:
        response = await client.get(f"{swapper_url}/api/cabinet/swapper/status")
        return response.json()

📋 Крок 6: Додати в Overview сторінку

Додати Swapper статус на головну сторінку Overview:

Файл: src/pages/Admin/Overview.tsx

import { SwapperStatusCard } from '@/services/swapper-service/cabinet-integration';

export default function OverviewPage() {
  return (
    <div className="overview-page">
      <h1>DAO Overview</h1>
      
      {/* Existing overview content */}
      
      {/* Add Swapper status widget */}
      <div className="swapper-widget">
        <SwapperStatusCard />
      </div>
    </div>
  );
}

🧪 Тестування інтеграції

1. Перевірити API доступність

# З браузера або curl
curl http://localhost:8890/api/cabinet/swapper/status

2. Перевірити CORS (якщо frontend на іншому порту)

Якщо frontend на іншому порту (наприклад, 3000), переконайтеся що CORS налаштовано:

Файл: services/swapper-service/app/main.py

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "https://daarion.city"],  # ← Додати ваші домени
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

3. Перевірити в браузері

  1. Відкрити кабінет
  2. Перейти на /admin/swapper
  3. Перевірити що дані завантажуються
  4. Перевірити що кнопки Load/Unload працюють

🐛 Troubleshooting

Проблема: API не доступний

Рішення:

# Перевірити чи Swapper запущений
curl http://localhost:8890/health

# Перевірити логи
docker logs swapper-service
# або
tail -f /tmp/swapper.log

Проблема: CORS помилка

Рішення: Додати ваш домен в allow_origins в app/main.py

Проблема: Компоненти не відображаються

Рішення:

  1. Перевірити що файли скопійовані
  2. Перевірити імпорти
  3. Перевірити консоль браузера на помилки

Чеклист інтеграції

  • Swapper Service запущений
  • API доступний (/health працює)
  • Компоненти скопійовані в проект
  • Маршрут додано в роутер
  • Sidebar оновлено
  • API URL налаштовано
  • CORS налаштовано (якщо потрібно)
  • Тестування в браузері пройдено

Last Updated: 2025-11-22
Status: Готово до інтеграції
Next: Виконати кроки вище для Node #1 та Node #2