feat: implement TTS, Document processing, and Memory Service /facts API
- TTS: xtts-v2 integration with voice cloning support
- Document: docling integration for PDF/DOCX/PPTX processing
- Memory Service: added /facts/upsert, /facts/{key}, /facts endpoints
- Added required dependencies (TTS, docling)
This commit is contained in:
126
src/components/image-gen/ImageGenStatusCard.tsx
Normal file
126
src/components/image-gen/ImageGenStatusCard.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||
|
||||
interface ImageGenHealth {
|
||||
status: string;
|
||||
model_loaded: boolean;
|
||||
model_id: string;
|
||||
device: string;
|
||||
dtype: string;
|
||||
}
|
||||
|
||||
interface ImageGenInfo {
|
||||
model_id: string;
|
||||
device: string;
|
||||
dtype: string;
|
||||
pipeline_loaded: boolean;
|
||||
load_error?: string | null;
|
||||
}
|
||||
|
||||
const getImageGenUrl = (nodeId?: string): string => {
|
||||
if (!nodeId) {
|
||||
return import.meta.env.VITE_IMAGE_GEN_URL || 'http://localhost:8892';
|
||||
}
|
||||
if (nodeId === 'node-1' || nodeId === 'node-1-hetzner-gex44' || nodeId.includes('node-1')) {
|
||||
return import.meta.env.VITE_IMAGE_GEN_NODE1_URL || 'http://144.76.224.179:8892';
|
||||
}
|
||||
if (nodeId === 'node-3' || nodeId.includes('node-3')) {
|
||||
return import.meta.env.VITE_IMAGE_GEN_NODE3_URL || 'http://80.77.35.151:8892';
|
||||
}
|
||||
return import.meta.env.VITE_IMAGE_GEN_URL || 'http://localhost:8892';
|
||||
};
|
||||
|
||||
export function ImageGenStatusCard({ nodeId }: { nodeId?: string }) {
|
||||
const [health, setHealth] = useState<ImageGenHealth | null>(null);
|
||||
const [info, setInfo] = useState<ImageGenInfo | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const imageGenUrl = getImageGenUrl(nodeId);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const healthRes = await fetch(`${imageGenUrl}/health`, { mode: 'cors' });
|
||||
if (!healthRes.ok) {
|
||||
throw new Error(`Health check failed: ${healthRes.status}`);
|
||||
}
|
||||
const healthData = (await healthRes.json()) as ImageGenHealth;
|
||||
setHealth(healthData);
|
||||
|
||||
const infoRes = await fetch(`${imageGenUrl}/info`, { mode: 'cors' });
|
||||
if (infoRes.ok) {
|
||||
const infoData = (await infoRes.json()) as ImageGenInfo;
|
||||
setInfo(infoData);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
const interval = setInterval(fetchData, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [imageGenUrl]);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-sm text-gray-500">Завантаження статусу Image Gen...</div>;
|
||||
}
|
||||
|
||||
if (error || !health) {
|
||||
return (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-red-900 mb-1">Image Gen Service недоступний</h4>
|
||||
<p className="text-sm text-red-700 mb-2">{error || 'Немає даних'}</p>
|
||||
<div className="text-xs text-red-600">
|
||||
<p><strong>URL:</strong> {imageGenUrl}</p>
|
||||
<p><strong>Node ID:</strong> {nodeId || 'N/A'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">🎨 Image Gen (FLUX)</h3>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-semibold ${
|
||||
health.status === 'ok' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{health.status === 'ok' ? 'Healthy' : 'Error'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
|
||||
<div>
|
||||
<div className="text-gray-500">Модель</div>
|
||||
<div className="font-semibold text-gray-900">{info?.model_id || health.model_id}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500">Device / Dtype</div>
|
||||
<div className="font-semibold text-gray-900">{health.device} / {health.dtype}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-600" />
|
||||
<span>{health.model_loaded ? 'Модель завантажена' : 'Модель ще вантажиться'}</span>
|
||||
</div>
|
||||
{info?.load_error && (
|
||||
<div className="text-red-600">{info.load_error}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-xs text-gray-500">
|
||||
Endpoint: <code className="text-xs">{imageGenUrl}</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -205,6 +205,8 @@ export function DagiMonitorPage() {
|
||||
{ name: 'NATS JetStream', url: 'http://144.76.224.179:8222/varz', type: 'message-broker', port: 4222, description: 'Message broker for async communication' },
|
||||
{ name: 'Swapper Node1', url: 'http://144.76.224.179:8890/health', type: 'service', port: 8890, description: 'LLM routing service on Node1' },
|
||||
{ name: 'Swapper Node2', url: 'http://localhost:8890/health', type: 'service', port: 8890, description: 'LLM routing service on Node2' },
|
||||
{ name: 'Image Gen Node1', url: 'http://144.76.224.179:8892/health', type: 'image-gen', port: 8892, description: 'FLUX image generation on Node1' },
|
||||
{ name: 'Image Gen Node3', url: 'http://80.77.35.151:8892/health', type: 'image-gen', port: 8892, description: 'FLUX image generation on Node3' },
|
||||
{ name: 'DAGI Router Node1', url: 'http://144.76.224.179:9102/health', type: 'router', port: 9102, description: 'DAGI Router on Node1' },
|
||||
{ name: 'DAGI Router Node2', url: 'http://localhost:9102/health', type: 'router', port: 9102, description: 'DAGI Router on Node2' },
|
||||
{ name: 'Main API', url: `${API_BASE_URL}/health`, type: 'api', description: 'Main MicroDAO API' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, Server, Activity, Cpu, HardDrive, Network, Users, Settings, BarChart3, Plug, RefreshCw, CheckCircle2, XCircle, AlertCircle, Filter, Play, Loader2, Wrench, Download, Bot, Database, AlertTriangle, PlusCircle, Boxes, Shield } from 'lucide-react';
|
||||
import { ArrowLeft, Server, Activity, Cpu, HardDrive, Network, Users, Settings, BarChart3, Plug, RefreshCw, CheckCircle2, XCircle, AlertCircle, Filter, Play, Loader2, Wrench, Download, Bot, Database, AlertTriangle, PlusCircle, Boxes, Shield, Zap } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiGet } from '../api/client';
|
||||
@@ -8,6 +8,7 @@ import { getNode1Agents, type Node1Agent } from '../api/node1Agents';
|
||||
import { deployAgentToNode2, deployAllAgentsToNode2, checkNode2AgentsDeployment } from '../api/node2Deployment';
|
||||
import { SwapperStatusCard, SwapperMetricsSummary } from '../components/swapper/SwapperComponents';
|
||||
import { SwapperDetailedMetrics } from '../components/swapper/SwapperDetailedMetrics';
|
||||
import { ImageGenStatusCard } from '../components/image-gen/ImageGenStatusCard';
|
||||
import { getNodeInventory, type NodeInventory } from '../api/nodeInventory';
|
||||
import { NodeMonitorChat } from '../components/monitor/NodeMonitorChat';
|
||||
import '../styles/swapper.css';
|
||||
@@ -66,6 +67,14 @@ interface NodeDetails {
|
||||
const GRAFANA_URL = import.meta.env.VITE_GRAFANA_URL || 'http://localhost:3000';
|
||||
const PROMETHEUS_URL = import.meta.env.VITE_PROMETHEUS_URL || 'http://localhost:9090';
|
||||
|
||||
const formatServiceName = (name: string) => {
|
||||
return name
|
||||
.replace('dagi-', '')
|
||||
.replace('swapper-service', 'Swapper Service')
|
||||
.replace('image-gen-service', 'Image Gen Service')
|
||||
.replace(/-/g, ' ');
|
||||
};
|
||||
|
||||
export function NodeCabinetPage() {
|
||||
const { nodeId } = useParams<{ nodeId: string }>();
|
||||
const navigate = useNavigate();
|
||||
@@ -151,7 +160,7 @@ export function NodeCabinetPage() {
|
||||
const port = portStr.includes(':') ? portStr.split(':')[0] : portStr;
|
||||
const portNum = parseInt(port) || 0;
|
||||
services.push({
|
||||
name: container.name.replace('dagi-', '').replace('swapper-service', 'Swapper Service').replace(/-/g, ' '),
|
||||
name: formatServiceName(container.name),
|
||||
status: 'running',
|
||||
port: portNum,
|
||||
url: isNode1
|
||||
@@ -166,7 +175,7 @@ export function NodeCabinetPage() {
|
||||
const port = portStr.includes(':') ? portStr.split(':')[0] : portStr;
|
||||
const portNum = parseInt(port) || 0;
|
||||
services.push({
|
||||
name: container.name.replace('dagi-', '').replace(/-/g, ' '),
|
||||
name: formatServiceName(container.name),
|
||||
status: 'running',
|
||||
port: portNum,
|
||||
url: isNode1
|
||||
@@ -181,7 +190,7 @@ export function NodeCabinetPage() {
|
||||
const port = portStr.includes(':') ? portStr.split(':')[0] : portStr;
|
||||
const portNum = parseInt(port) || 0;
|
||||
services.push({
|
||||
name: container.name.replace('dagi-', '').replace(/-/g, ' '),
|
||||
name: formatServiceName(container.name),
|
||||
status: container.state === 'restarting' ? 'restarting' : 'unhealthy',
|
||||
port: portNum,
|
||||
url: isNode1
|
||||
@@ -193,6 +202,7 @@ export function NodeCabinetPage() {
|
||||
// Fallback дані
|
||||
services.push(
|
||||
{ name: 'Swapper Service', status: 'running', port: 8890, url: isNode1 ? 'http://144.76.224.179:8890' : 'http://192.168.1.244:8890' },
|
||||
{ name: 'Image Gen Service', status: 'running', port: 8892, url: isNode1 ? 'http://144.76.224.179:8892' : 'http://80.77.35.151:8892' },
|
||||
{ name: 'Node Registry', status: 'running', port: 9205, url: isNode1 ? 'http://144.76.224.179:9205' : 'http://192.168.1.244:9205' },
|
||||
{ name: 'NATS JetStream', status: 'running', port: 4222, url: 'nats://localhost:4222' }
|
||||
);
|
||||
@@ -604,6 +614,7 @@ export function NodeCabinetPage() {
|
||||
{ name: 'Node Registry', icon: Database },
|
||||
{ name: 'NATS JetStream', icon: Network },
|
||||
{ name: 'Swapper Service', icon: RefreshCw },
|
||||
{ name: 'Image Gen', icon: Zap },
|
||||
{ name: 'Ollama', icon: Bot },
|
||||
].map((service) => {
|
||||
const s = nodeDetails.services?.find(s => s.name.includes(service.name) || (service.name === 'Ollama' && s.name.includes('ollama')));
|
||||
@@ -945,6 +956,19 @@ export function NodeCabinetPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Image Generation Service */}
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<h2 className="text-xl font-semibold text-gray-900">🎨 Image Gen (FLUX)</h2>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Генерація зображень для {nodeDetails.node_name} (FLUX.2 Klein 4B Base)
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<ImageGenStatusCard nodeId={nodeId} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Інші сервіси ноди */}
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
|
||||
Reference in New Issue
Block a user