- Node-guardian running on MacBook and updating metrics - NODE2 agents (Atlas, Greeter, Oracle, Builder Bot) assigned to node-2-macbook-m4max - Swapper models displaying correctly (8 models) - DAGI Router agents showing with correct status (3 active, 1 stale) - Router health check using node_cache for remote nodes
479 lines
12 KiB
Markdown
479 lines
12 KiB
Markdown
# TASK_PHASE_MICRODAO_DASHBOARD_v1
|
||
|
||
Статус: PLANNED
|
||
|
||
Пріоритет: High (презентація MVP)
|
||
|
||
## 1. Ціль
|
||
|
||
Зробити повноцінний **кабінет MicroDAO** (спочатку для `DAARION DAO`), який показує:
|
||
|
||
- базові метрики DAO (кімнати, громадяни, агенти);
|
||
|
||
- стрічку активності (новини/апдейти);
|
||
|
||
- кімнати DAO + кнопки створення кімнати;
|
||
|
||
- команду DAO (ключові агенти/громадяни);
|
||
|
||
- підготовлені секції під проєкти/задачі/файли/інтеграції.
|
||
|
||
Архітектурна вимога: **цей самий дашборд** повинен працювати для будь-якого MicroDAO (`slug`-залежний), щоб його можна було фрактально клонувати.
|
||
|
||
---
|
||
|
||
## 2. Поточний стан
|
||
|
||
Є:
|
||
|
||
- /microdao — список MicroDAO (з лого, тегами, pinned-сортуванням).
|
||
|
||
- /microdao/[slug] — сторінка MicroDAO з:
|
||
|
||
- Branding (лого, банер),
|
||
|
||
- visibility (public / platform),
|
||
|
||
- MicroDAO Rooms section,
|
||
|
||
- базовою статистикою (rooms count).
|
||
|
||
- /city — кімнати міста (мапа + список).
|
||
|
||
- /citizens — публічні агенти (громадяни).
|
||
|
||
- /agents — Agent Console (63 агентів, фільтри по нодах, кнопка "Новий агент").
|
||
|
||
- API для кімнат і агентів уже працює.
|
||
|
||
Немає:
|
||
|
||
- окремого **Dashboard API** для MicroDAO;
|
||
|
||
- таблиць для **активності (новин)** и **DAO-level метрик**;
|
||
|
||
- явної прив'язки громадян до конкретного MicroDAO (лише district/room mapping).
|
||
|
||
---
|
||
|
||
## 3. Архітектура рішення
|
||
|
||
### 3.1. База даних (PostgreSQL)
|
||
|
||
#### 3.1.1. Таблиця активності MicroDAO
|
||
|
||
Створити міграцію `migrations/041_microdao_activity.sql`:
|
||
|
||
```sql
|
||
|
||
CREATE TABLE microdao_activity (
|
||
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
||
microdao_slug TEXT NOT NULL REFERENCES city_microdao(slug) ON DELETE CASCADE,
|
||
|
||
kind TEXT NOT NULL CHECK (kind IN ('post', 'event', 'update')),
|
||
|
||
title TEXT,
|
||
|
||
body TEXT NOT NULL,
|
||
|
||
author_agent_id UUID NULL REFERENCES city_agent(id),
|
||
|
||
author_name TEXT NULL,
|
||
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
|
||
);
|
||
|
||
CREATE INDEX idx_microdao_activity_microdao_created_at
|
||
|
||
ON microdao_activity (microdao_slug, created_at DESC);
|
||
|
||
```
|
||
|
||
#### 3.1.2. Розширити `city_microdao` під метрики (опціонально, але корисно)
|
||
|
||
Міграція `migrations/042_microdao_stats.sql`:
|
||
|
||
```sql
|
||
|
||
ALTER TABLE city_microdao
|
||
|
||
ADD COLUMN IF NOT EXISTS citizens_count INTEGER NOT NULL DEFAULT 0,
|
||
|
||
ADD COLUMN IF NOT EXISTS rooms_count INTEGER NOT NULL DEFAULT 0,
|
||
|
||
ADD COLUMN IF NOT EXISTS agents_count INTEGER NOT NULL DEFAULT 0,
|
||
|
||
ADD COLUMN IF NOT EXISTS last_update_at TIMESTAMPTZ;
|
||
|
||
```
|
||
|
||
> На етапі MVP значення можна просто перераховувати на льоту в репозиторії й не оновлювати ці поля — але стовпці потрібні для майбутнього кешування.
|
||
|
||
---
|
||
|
||
### 3.2. Backend: FastAPI (city-service)
|
||
|
||
#### 3.2.1. Repo-рівень (Python)
|
||
|
||
Файл: `services/city-service/repo_city.py`
|
||
|
||
Додати:
|
||
|
||
```python
|
||
|
||
async def get_microdao_dashboard(conn, slug: str) -> MicrodaoDashboard:
|
||
|
||
"""
|
||
|
||
Aggregates:
|
||
|
||
- microdao summary
|
||
|
||
- last 5 activity items
|
||
|
||
- up to 5 rooms
|
||
|
||
- up to 6 citizens (agents) linked to this microdao
|
||
|
||
"""
|
||
|
||
```
|
||
|
||
Використати існуючі репо-методи:
|
||
|
||
* `get_microdao_by_slug`
|
||
|
||
* `get_microdao_rooms(slug)`
|
||
|
||
* `get_agents_by_microdao(slug)` або аналог (якщо немає — додати простий SELECT по district/slug mapping).
|
||
|
||
Нові helper-методи:
|
||
|
||
```python
|
||
|
||
async def get_microdao_activity(conn, slug: str, limit: int = 10) -> list[MicrodaoActivity]:
|
||
|
||
...
|
||
|
||
async def insert_microdao_activity(conn, activity: CreateMicrodaoActivity) -> MicrodaoActivity:
|
||
|
||
...
|
||
|
||
```
|
||
|
||
#### 3.2.2. Pydantic-моделі
|
||
|
||
Файл: `services/city-service/models_city.py`
|
||
|
||
Додати:
|
||
|
||
```python
|
||
|
||
class MicrodaoActivity(BaseModel):
|
||
|
||
id: UUID
|
||
|
||
microdao_slug: str
|
||
|
||
kind: str
|
||
|
||
title: str | None
|
||
|
||
body: str
|
||
|
||
author_agent_id: UUID | None
|
||
|
||
author_name: str | None
|
||
|
||
created_at: datetime
|
||
|
||
class CreateMicrodaoActivity(BaseModel):
|
||
|
||
kind: str = Field(pattern="^(post|event|update)$")
|
||
|
||
title: str | None = None
|
||
|
||
body: str
|
||
|
||
author_agent_id: UUID | None = None
|
||
|
||
author_name: str | None = None
|
||
|
||
class MicrodaoDashboard(BaseModel):
|
||
|
||
microdao: MicrodaoSummary
|
||
|
||
stats: MicrodaoStats
|
||
|
||
recent_activity: list[MicrodaoActivity]
|
||
|
||
rooms: list[RoomSummary]
|
||
|
||
citizens: list[PublicCitizenSummary]
|
||
|
||
```
|
||
|
||
`MicrodaoStats`:
|
||
|
||
```python
|
||
|
||
class MicrodaoStats(BaseModel):
|
||
|
||
rooms_count: int
|
||
|
||
citizens_count: int
|
||
|
||
agents_count: int
|
||
|
||
last_update_at: datetime | None
|
||
|
||
```
|
||
|
||
#### 3.2.3. Routes (FastAPI)
|
||
|
||
Файл: `services/city-service/routes_city.py`
|
||
|
||
Додати endpoints:
|
||
|
||
```python
|
||
|
||
@router.get("/microdao/{slug}/dashboard", response_model=MicrodaoDashboard)
|
||
|
||
async def get_microdao_dashboard(slug: str, conn=Depends(get_conn)):
|
||
|
||
return await repo_city.get_microdao_dashboard(conn, slug)
|
||
|
||
@router.get("/microdao/{slug}/activity", response_model=list[MicrodaoActivity])
|
||
|
||
async def list_microdao_activity(slug: str, limit: int = 20, conn=Depends(get_conn)):
|
||
|
||
return await repo_city.get_microdao_activity(conn, slug, limit)
|
||
|
||
@router.post("/microdao/{slug}/activity", response_model=MicrodaoActivity, status_code=201)
|
||
|
||
async def create_microdao_activity(
|
||
|
||
slug: str,
|
||
|
||
payload: CreateMicrodaoActivity,
|
||
|
||
conn=Depends(get_conn),
|
||
|
||
# TODO: optional auth / orchestrator check
|
||
|
||
):
|
||
|
||
return await repo_city.create_microdao_activity(conn, slug, payload)
|
||
|
||
```
|
||
|
||
Для MVP можна не чіпати авторизацію — припустити, що виклик робить orchestrator через адмін-UI.
|
||
|
||
---
|
||
|
||
### 3.3. Frontend: Next.js (apps/web)
|
||
|
||
#### 3.3.1. API-клієнт
|
||
|
||
Файл: `apps/web/src/lib/api.ts`
|
||
|
||
Додати:
|
||
|
||
```ts
|
||
|
||
export async function fetchMicrodaoDashboard(slug: string): Promise<MicrodaoDashboard> {
|
||
|
||
const res = await fetch(`${CITY_API_BASE_URL}/microdao/${slug}/dashboard`, { cache: "no-store" });
|
||
|
||
if (!res.ok) throw new Error("Failed to load microdao dashboard");
|
||
|
||
return res.json();
|
||
|
||
}
|
||
|
||
export async function fetchMicrodaoActivity(slug: string): Promise<MicrodaoActivity[]> {
|
||
|
||
const res = await fetch(`${CITY_API_BASE_URL}/microdao/${slug}/activity?limit=20`, { cache: "no-store" });
|
||
|
||
if (!res.ok) throw new Error("Failed to load microdao activity");
|
||
|
||
return res.json();
|
||
|
||
}
|
||
|
||
```
|
||
|
||
Типи `MicrodaoDashboard` / `MicrodaoActivity` додати до `apps/web/src/lib/types.ts`.
|
||
|
||
---
|
||
|
||
#### 3.3.2. Структура сторінки `/microdao/[slug]`
|
||
|
||
Файл (вже існує):
|
||
|
||
`apps/web/src/app/microdao/[slug]/page.tsx`
|
||
|
||
Перетворити на **Dashboard Layout**:
|
||
|
||
1. На сервері викликати `fetchMicrodaoDashboard(slug)`.
|
||
|
||
2. Розбити на секції:
|
||
|
||
```tsx
|
||
|
||
<MicrodaoHeaderCard dashboard={dashboard} /> // hero
|
||
|
||
<MicrodaoDashboardGrid>
|
||
|
||
<MicrodaoActivitySection activity={dashboard.recent_activity} />
|
||
|
||
<MicrodaoRoomsSection rooms={dashboard.rooms} slug={slug} />
|
||
|
||
<MicrodaoTeamSection citizens={dashboard.citizens} />
|
||
|
||
<MicrodaoProjectsSection microdao={dashboard.microdao} /> // placeholder
|
||
|
||
<MicrodaoTasksSection microdao={dashboard.microdao} /> // placeholder
|
||
|
||
</MicrodaoDashboardGrid>
|
||
|
||
```
|
||
|
||
Нові компоненти:
|
||
|
||
* `apps/web/src/components/microdao/MicrodaoHeaderCard.tsx`
|
||
|
||
* `apps/web/src/components/microdao/MicrodaoActivitySection.tsx`
|
||
|
||
* `apps/web/src/components/microdao/MicrodaoTeamSection.tsx`
|
||
|
||
* `apps/web/src/components/microdao/MicrodaoProjectsSection.tsx` (stub)
|
||
|
||
* `apps/web/src/components/microdao/MicrodaoTasksSection.tsx` (stub)
|
||
|
||
##### MicrodaoHeaderCard
|
||
|
||
Показати:
|
||
|
||
* Лого (через `normalizeAssetUrl`)
|
||
|
||
* Назву, теги (Platform / Core / Active)
|
||
|
||
* Метрики:
|
||
|
||
* `{stats.rooms_count} Кімнат`
|
||
|
||
* `{stats.citizens_count} Громадян`
|
||
|
||
* `{stats.agents_count} Агентів`
|
||
|
||
* кнопки:
|
||
|
||
* "Поспілкуватися з DAARWIZZ" → `href="/city?room=city-lobby"`
|
||
|
||
* "Створити кімнату" → прокрутка до секції кімнат або модалка.
|
||
|
||
##### MicrodaoActivitySection
|
||
|
||
* Заголовок "Новини DAARION" (або назва DAO).
|
||
|
||
* Список останніх 5–10 записів:
|
||
|
||
* заголовок / перші рядки body,
|
||
|
||
* дата,
|
||
|
||
* автор (агент або author_name).
|
||
|
||
* Якщо немає записів — показати `Empty state: "Поки що немає новин"`.
|
||
|
||
---
|
||
|
||
### 3.4. Зв'язок з існуючими розділами
|
||
|
||
* MicroDAO Rooms: у компоненті `MicrodaoRoomsSection` використати вже існуючу логіку створення/видалення кімнат.
|
||
|
||
* Citizens: `MicrodaoTeamSection` повинна показувати тільки тих громадян, у яких `home_microdao_slug` або district/room mapping вказує на цей MicroDAO. На етапі MVP можна вибрати фіксований набір (наприклад, 6 ключових агентів DAARION) через простий SQL.
|
||
|
||
---
|
||
|
||
## 4. План виконання (для Cursor)
|
||
|
||
### Крок 1. DB міграції
|
||
|
||
1. Створити `migrations/041_microdao_activity.sql` і `042_microdao_stats.sql`.
|
||
|
||
2. Запустити міграції локально й на NODE1 (MVP DB).
|
||
|
||
### Крок 2. Backend
|
||
|
||
1. Оновити `models_city.py` — додати моделі `MicrodaoActivity`, `CreateMicrodaoActivity`, `MicrodaoStats`, `MicrodaoDashboard`.
|
||
|
||
2. Оновити `repo_city.py` — реалізувати aggregation для `get_microdao_dashboard` та CRUD для activity.
|
||
|
||
3. Оновити `routes_city.py` — додати `/microdao/{slug}/dashboard` та `/microdao/{slug}/activity`.
|
||
|
||
### Крок 3. Frontend
|
||
|
||
1. Оновити `apps/web/src/lib/types.ts` та `api.ts` (типи й API-клієнт).
|
||
|
||
2. Переписати `apps/web/src/app/microdao/[slug]/page.tsx`, щоб вона використовувала `fetchMicrodaoDashboard`.
|
||
|
||
3. Створити компоненти:
|
||
|
||
* `MicrodaoHeaderCard.tsx`
|
||
|
||
* `MicrodaoActivitySection.tsx`
|
||
|
||
* `MicrodaoTeamSection.tsx`
|
||
|
||
* (stubs) `MicrodaoProjectsSection.tsx`, `MicrodaoTasksSection.tsx`
|
||
|
||
### Крок 4. DAARION DAO як демо
|
||
|
||
1. Заповнити хоча б **5–10 записів `microdao_activity`** для `slug='daarion'` (через SQL або простий admin-endpoint /seed).
|
||
|
||
2. Прив'язати ключових агентів DAARION до цього MicroDAO (update в таблиці citizens/agents).
|
||
|
||
3. Перевірити сторінку `/microdao/daarion`:
|
||
|
||
* лого + title;
|
||
|
||
* метрики не ламаються (0 — ок, NaN — ні);
|
||
|
||
* відображаються кімнати DAARION;
|
||
|
||
* видно список 6+ громадян DAARION;
|
||
|
||
* CTA "Поспілкуватися з DAARWIZZ" працює.
|
||
|
||
---
|
||
|
||
## 7. Що показувати на презентації
|
||
|
||
1. **МікроDAO список** — DAARION, Energy Union, GreenFood, Soul наверху (pin).
|
||
|
||
2. **DAARION DAO сторінка**:
|
||
|
||
* красивий hero-блок як "панель керування DAO";
|
||
|
||
* блок "Новини DAARION";
|
||
|
||
* блок "Кімнати DAARION";
|
||
|
||
* блок "Команда DAARION".
|
||
|
||
3. **City Rooms** — показати, що одна з кімнат — головний публічний чат (DAARION City Lobby) з DAARWIZZ.
|
||
|
||
4. **Citizens / Agents** — як реєстр агентів, які прив'язані до DAARION.
|
||
|
||
Це вже виглядає як **живий кабінет DAO**, а не просто сайт.
|
||
|
||
---
|
||
|
||
Якщо хочеш, наступним кроком можу згенерувати окремий `TASK_PHASE_MICRODAO_DASHBOARD_v1_CURSOR_PROMPT.md` з готовим промтом для Cursor (щоб він сам крок-за-кроком виконав усе з цього таска).
|
||
|