fix: NODE1_REPAIR - healthchecks, dependencies, SSR env, telegram gateway

TASK_PHASE_NODE1_REPAIR:
- Fix daarion-web SSR: use CITY_API_BASE_URL instead of 127.0.0.1
- Fix auth API routes: use AUTH_API_URL env var
- Add wget to Dockerfiles for healthchecks (stt, ocr, web-search, swapper, vector-db, rag)
- Update healthchecks to use wget instead of curl
- Fix vector-db-service: update torch==2.4.0, sentence-transformers==2.6.1
- Fix rag-service: correct haystack imports for v2.x
- Fix telegram-gateway: remove msg.ack() for non-JetStream NATS
- Add /health endpoint to nginx mvp-routes.conf
- Add room_role, is_public, sort_order columns to city_rooms migration
- Add TASK_PHASE_NODE1_REPAIR.md and DEPLOY_NODE1_REPAIR.md docs

Previous tasks included:
- TASK 039-044: Orchestrator rooms, Matrix chat cleanup, CrewAI integration
This commit is contained in:
Apple
2025-11-29 05:17:08 -08:00
parent 0bab4bba08
commit a6e531a098
69 changed files with 4693 additions and 1310 deletions

View File

@@ -1,14 +1,18 @@
"use client";
import Link from "next/link";
import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2 } from "lucide-react";
import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2, Bot, PlusCircle } from "lucide-react";
import { CityRoomSummary } from "@/lib/types/microdao";
import { CityChatWidget } from "@/components/city/CityChatWidget";
import { Button } from "@/components/ui/button";
import { useState } from "react";
interface MicrodaoRoomsSectionProps {
rooms: CityRoomSummary[];
primaryRoomSlug?: string | null;
showAllChats?: boolean;
canManage?: boolean;
onEnsureOrchestratorRoom?: () => Promise<void>;
}
const ROLE_META: Record<string, { label: string; chipClass: string; icon: React.ReactNode }> = {
@@ -42,13 +46,34 @@ const ROLE_META: Record<string, { label: string; chipClass: string; icon: React.
chipClass: "bg-amber-500/10 text-amber-300 border-amber-500/30",
icon: <Gavel className="w-3.5 h-3.5" />,
},
orchestrator_team: {
label: "Orchestrator Team",
chipClass: "bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30",
icon: <Bot className="w-3.5 h-3.5" />,
},
};
export function MicrodaoRoomsSection({
rooms,
primaryRoomSlug,
showAllChats = false
showAllChats = false,
canManage = false,
onEnsureOrchestratorRoom
}: MicrodaoRoomsSectionProps) {
const [isCreatingTeam, setIsCreatingTeam] = useState(false);
const handleCreateTeam = async () => {
if (!onEnsureOrchestratorRoom) return;
setIsCreatingTeam(true);
try {
await onEnsureOrchestratorRoom();
} catch (e) {
console.error("Failed to create team room", e);
} finally {
setIsCreatingTeam(false);
}
};
if (!rooms || rooms.length === 0) {
return (
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4">
@@ -63,14 +88,21 @@ export function MicrodaoRoomsSection({
);
}
// Find primary room
const primary = rooms.find(r => r.slug === primaryRoomSlug)
?? rooms.find(r => r.room_role === 'primary')
?? rooms[0];
// Find special rooms
const teamRoom = rooms.find(r => r.room_role === 'orchestrator_team');
const others = rooms.filter(r => r.id !== primary.id);
// Filter out team room from general list if we show it separately
const generalRooms = rooms.filter(r => r.room_role !== 'orchestrator_team');
// Group by role for mini-map
// Find primary room
const primary = generalRooms.find(r => r.slug === primaryRoomSlug)
?? generalRooms.find(r => r.room_role === 'primary')
?? generalRooms[0];
// Others (excluding primary and team room)
const others = generalRooms.filter(r => r.id !== primary?.id);
// Group by role for mini-map (include all rooms for stats)
const byRole = rooms.reduce((acc, r) => {
const role = r.room_role || 'other';
if (!acc[role]) acc[role] = [];
@@ -79,112 +111,163 @@ export function MicrodaoRoomsSection({
}, {} as Record<string, CityRoomSummary[]>);
// Get meta for primary room
const primaryMeta = primary.room_role ? ROLE_META[primary.room_role] : undefined;
const primaryMeta = primary?.room_role ? ROLE_META[primary.room_role] : undefined;
return (
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
<MessageCircle className="w-5 h-5 text-cyan-400" />
Кімнати MicroDAO
<span className="text-sm font-normal text-slate-500">({rooms.length})</span>
</h2>
{/* Mini-map */}
<div className="flex flex-wrap gap-2">
{Object.entries(byRole).map(([role, list]) => {
const meta = ROLE_META[role];
return (
<div
key={role}
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-[11px] ${
meta ? meta.chipClass : "bg-slate-700/30 text-slate-400 border-slate-700/50"
}`}
>
{meta?.icon || <Hash className="w-3 h-3" />}
<span>{meta?.label ?? (role === 'other' ? 'Other' : role)}</span>
<span className="opacity-60">({list.length})</span>
</div>
);
})}
</div>
</div>
{/* Primary room with inline chat */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg border ${primaryMeta ? primaryMeta.chipClass : "text-cyan-400 bg-cyan-500/10 border-cyan-500/30"}`}>
{primaryMeta?.icon || <Home className="w-4 h-4" />}
</div>
<div>
<div className="text-base font-medium text-slate-100 flex items-center gap-2">
{primary.name}
<span className="text-[10px] uppercase tracking-wider text-emerald-400 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20">
Primary
</span>
</div>
<div className="text-xs text-slate-500">
{primaryMeta?.label || primary.room_role || 'Main Room'}
</div>
</div>
<section className="space-y-6">
{/* Orchestrator Team Chat Section */}
{(teamRoom || canManage) && (
<div className="bg-fuchsia-900/10 border border-fuchsia-500/20 rounded-xl p-4 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-fuchsia-200 flex items-center gap-2">
<Bot className="w-4 h-4 text-fuchsia-400" />
Orchestrator Team Chat
</h3>
{teamRoom && (
<span className="text-[10px] uppercase tracking-wider text-fuchsia-300 bg-fuchsia-500/10 px-2 py-0.5 rounded-full border border-fuchsia-500/20">
Team Room
</span>
)}
</div>
<Link
href={`/city/${primary.slug}`}
className="text-xs text-cyan-400 hover:text-cyan-300 transition-colors px-3 py-1.5 rounded-lg hover:bg-cyan-950/30 border border-transparent hover:border-cyan-500/20"
>
Відкрити окремо
</Link>
{teamRoom ? (
<CityChatWidget roomSlug={teamRoom.slug} hideTitle className="border-fuchsia-500/20" />
) : (
<div className="flex flex-col items-center justify-center py-8 text-center space-y-3 bg-fuchsia-950/30 rounded-lg border border-dashed border-fuchsia-500/30">
<Bot className="w-8 h-8 text-fuchsia-500/50" />
<div>
<p className="text-fuchsia-200 font-medium">Командний чат оркестратора</p>
<p className="text-sm text-fuchsia-400/70 max-w-md mx-auto mt-1">
Створіть закриту кімнату для команди агентів оркестратора (CrewAI integration).
</p>
</div>
<Button
onClick={handleCreateTeam}
disabled={isCreatingTeam}
variant="outline"
size="sm"
className="bg-fuchsia-600/20 border-fuchsia-500/50 hover:bg-fuchsia-600/30 text-fuchsia-200"
>
{isCreatingTeam ? "Створення..." : (
<>
<PlusCircle className="w-4 h-4 mr-2" />
Створити Orchestrator Team Chat
</>
)}
</Button>
</div>
)}
</div>
)}
<CityChatWidget roomSlug={primary.slug} />
</div>
{/* General Rooms Section */}
<div className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
<MessageCircle className="w-5 h-5 text-cyan-400" />
Кімнати MicroDAO
<span className="text-sm font-normal text-slate-500">({rooms.length})</span>
</h2>
{/* Other rooms */}
{others.length > 0 && (
<div className="space-y-3 pt-2">
<div className="text-sm text-slate-400 font-medium px-1">Інші кімнати</div>
<div className="grid gap-3 md:grid-cols-2">
{others.map(room => {
const meta = room.room_role ? ROLE_META[room.room_role] : undefined;
{/* Mini-map */}
<div className="flex flex-wrap gap-2">
{Object.entries(byRole).map(([role, list]) => {
const meta = ROLE_META[role];
return (
<div
key={room.id}
className="bg-slate-900/50 border border-slate-700/30 rounded-xl p-4 space-y-3 hover:border-slate-600/50 transition-colors"
key={role}
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-[11px] ${
meta ? meta.chipClass : "bg-slate-700/30 text-slate-400 border-slate-700/50"
}`}
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-3">
<div className={`p-1.5 rounded-lg border ${meta ? meta.chipClass : "text-slate-400 bg-slate-700/30 border-slate-700/50"}`}>
{meta?.icon || <Hash className="w-3.5 h-3.5" />}
</div>
<div>
<div className="text-sm font-medium text-slate-200">{room.name}</div>
{meta && (
<div className="text-[11px] text-slate-500">
{meta.label}
</div>
)}
</div>
</div>
<Link
href={`/city/${room.slug}`}
className="text-xs text-slate-400 hover:text-cyan-400 transition-colors px-2 py-1"
>
Увійти
</Link>
</div>
{showAllChats && (
<div className="mt-2">
<CityChatWidget roomSlug={room.slug} compact />
</div>
)}
{meta?.icon || <Hash className="w-3 h-3" />}
<span>{meta?.label ?? (role === 'other' ? 'Other' : role)}</span>
<span className="opacity-60">({list.length})</span>
</div>
);
})}
</div>
</div>
)}
{/* Primary room with inline chat (if exists) */}
{primary && (
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg border ${primaryMeta ? primaryMeta.chipClass : "text-cyan-400 bg-cyan-500/10 border-cyan-500/30"}`}>
{primaryMeta?.icon || <Home className="w-4 h-4" />}
</div>
<div>
<div className="text-base font-medium text-slate-100 flex items-center gap-2">
{primary.name}
<span className="text-[10px] uppercase tracking-wider text-emerald-400 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20">
Primary
</span>
</div>
<div className="text-xs text-slate-500">
{primaryMeta?.label || primary.room_role || 'Main Room'}
</div>
</div>
</div>
<Link
href={`/city/${primary.slug}`}
className="text-xs text-cyan-400 hover:text-cyan-300 transition-colors px-3 py-1.5 rounded-lg hover:bg-cyan-950/30 border border-transparent hover:border-cyan-500/20"
>
Відкрити окремо
</Link>
</div>
<CityChatWidget roomSlug={primary.slug} hideTitle />
</div>
)}
{/* Other rooms */}
{others.length > 0 && (
<div className="space-y-3 pt-2">
<div className="text-sm text-slate-400 font-medium px-1">Інші кімнати</div>
<div className="grid gap-3 md:grid-cols-2">
{others.map(room => {
const meta = room.room_role ? ROLE_META[room.room_role] : undefined;
return (
<div
key={room.id}
className="bg-slate-900/50 border border-slate-700/30 rounded-xl p-4 space-y-3 hover:border-slate-600/50 transition-colors"
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-3">
<div className={`p-1.5 rounded-lg border ${meta ? meta.chipClass : "text-slate-400 bg-slate-700/30 border-slate-700/50"}`}>
{meta?.icon || <Hash className="w-3.5 h-3.5" />}
</div>
<div>
<div className="text-sm font-medium text-slate-200">{room.name}</div>
{meta && (
<div className="text-[11px] text-slate-500">
{meta.label}
</div>
)}
</div>
</div>
<Link
href={`/city/${room.slug}`}
className="text-xs text-slate-400 hover:text-cyan-400 transition-colors px-2 py-1"
>
Увійти
</Link>
</div>
{showAllChats && (
<div className="mt-2">
<CityChatWidget roomSlug={room.slug} compact />
</div>
)}
</div>
);
})}
</div>
</div>
)}
</div>
</section>
);
}