feat: add is_chat field to board memory and task_id to approvals, update pagination and response models
This commit is contained in:
93
Makefile
Normal file
93
Makefile
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
SHELL := /usr/bin/env bash
|
||||||
|
.SHELLFLAGS := -euo pipefail -c
|
||||||
|
|
||||||
|
BACKEND_DIR := backend
|
||||||
|
FRONTEND_DIR := frontend
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Show available targets
|
||||||
|
@grep -E '^[a-zA-Z0-9_.-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " %-26s %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
.PHONY: setup
|
||||||
|
setup: backend-sync frontend-sync ## Install/sync backend + frontend deps
|
||||||
|
|
||||||
|
.PHONY: backend-sync
|
||||||
|
backend-sync: ## uv sync backend deps (includes dev extra)
|
||||||
|
cd $(BACKEND_DIR) && uv sync --extra dev
|
||||||
|
|
||||||
|
.PHONY: frontend-sync
|
||||||
|
frontend-sync: ## npm install frontend deps
|
||||||
|
cd $(FRONTEND_DIR) && npm install
|
||||||
|
|
||||||
|
.PHONY: format
|
||||||
|
format: backend-format frontend-format ## Format backend + frontend
|
||||||
|
|
||||||
|
.PHONY: backend-format
|
||||||
|
backend-format: ## Format backend (isort + black)
|
||||||
|
cd $(BACKEND_DIR) && uv run isort .
|
||||||
|
cd $(BACKEND_DIR) && uv run black .
|
||||||
|
|
||||||
|
.PHONY: frontend-format
|
||||||
|
frontend-format: ## Format frontend (prettier)
|
||||||
|
cd $(FRONTEND_DIR) && npx prettier --write "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}"
|
||||||
|
|
||||||
|
.PHONY: format-check
|
||||||
|
format-check: backend-format-check frontend-format-check ## Check formatting (no changes)
|
||||||
|
|
||||||
|
.PHONY: backend-format-check
|
||||||
|
backend-format-check: ## Check backend formatting (isort + black)
|
||||||
|
cd $(BACKEND_DIR) && uv run isort . --check-only --diff
|
||||||
|
cd $(BACKEND_DIR) && uv run black . --check --diff
|
||||||
|
|
||||||
|
.PHONY: frontend-format-check
|
||||||
|
frontend-format-check: ## Check frontend formatting (prettier)
|
||||||
|
cd $(FRONTEND_DIR) && npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}"
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: backend-lint frontend-lint ## Lint backend + frontend
|
||||||
|
|
||||||
|
.PHONY: backend-lint
|
||||||
|
backend-lint: ## Lint backend (flake8)
|
||||||
|
cd $(BACKEND_DIR) && uv run flake8 --config .flake8
|
||||||
|
|
||||||
|
.PHONY: frontend-lint
|
||||||
|
frontend-lint: ## Lint frontend (eslint)
|
||||||
|
cd $(FRONTEND_DIR) && npm run lint
|
||||||
|
|
||||||
|
.PHONY: typecheck
|
||||||
|
typecheck: backend-typecheck frontend-typecheck ## Typecheck backend + frontend
|
||||||
|
|
||||||
|
.PHONY: backend-typecheck
|
||||||
|
backend-typecheck: ## Typecheck backend (mypy --strict)
|
||||||
|
cd $(BACKEND_DIR) && uv run mypy
|
||||||
|
|
||||||
|
.PHONY: frontend-typecheck
|
||||||
|
frontend-typecheck: ## Typecheck frontend (tsc)
|
||||||
|
cd $(FRONTEND_DIR) && npx tsc -p tsconfig.json --noEmit
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: backend-test ## Run tests
|
||||||
|
|
||||||
|
.PHONY: backend-test
|
||||||
|
backend-test: ## Backend tests (pytest)
|
||||||
|
cd $(BACKEND_DIR) && uv run pytest
|
||||||
|
|
||||||
|
.PHONY: backend-migrate
|
||||||
|
backend-migrate: ## Apply backend DB migrations (alembic upgrade head)
|
||||||
|
cd $(BACKEND_DIR) && uv run alembic upgrade head
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: frontend-build ## Build artifacts
|
||||||
|
|
||||||
|
.PHONY: frontend-build
|
||||||
|
frontend-build: ## Build frontend (next build)
|
||||||
|
cd $(FRONTEND_DIR) && npm run build
|
||||||
|
|
||||||
|
.PHONY: api-gen
|
||||||
|
api-gen: ## Regenerate TS API client (requires backend running at 127.0.0.1:8000)
|
||||||
|
cd $(FRONTEND_DIR) && npm run api:gen
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: lint typecheck test build ## Run lint + typecheck + tests + build
|
||||||
4
backend/.gitignore
vendored
4
backend/.gitignore
vendored
@@ -3,3 +3,7 @@ __pycache__/
|
|||||||
.venv/
|
.venv/
|
||||||
.env
|
.env
|
||||||
.runlogs/
|
.runlogs/
|
||||||
|
|
||||||
|
# Generated on demand from uv.lock (single source of truth is pyproject.toml + uv.lock).
|
||||||
|
requirements.txt
|
||||||
|
requirements-dev.txt
|
||||||
|
|||||||
101
backend/alembic/versions/1d844b04ee06_add_approvals_task_id.py
Normal file
101
backend/alembic/versions/1d844b04ee06_add_approvals_task_id.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
"""add approvals task_id
|
||||||
|
|
||||||
|
Revision ID: 1d844b04ee06
|
||||||
|
Revises: a5aab244d32d
|
||||||
|
Create Date: 2026-02-06 17:26:43.336466
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "1d844b04ee06"
|
||||||
|
down_revision = "a5aab244d32d"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# This migration may run in databases where the column/index/constraint were created via
|
||||||
|
# SQLModel `create_all()` (or a previous hotfix). Make it idempotent to avoid blocking
|
||||||
|
# upgrades in dev environments.
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = sa.inspect(bind)
|
||||||
|
|
||||||
|
approval_cols = {c["name"] for c in inspector.get_columns("approvals")}
|
||||||
|
if "task_id" not in approval_cols:
|
||||||
|
op.add_column("approvals", sa.Column("task_id", sa.Uuid(), nullable=True))
|
||||||
|
|
||||||
|
approval_index_names = {i["name"] for i in inspector.get_indexes("approvals")}
|
||||||
|
if "ix_approvals_task_id" not in approval_index_names:
|
||||||
|
op.create_index("ix_approvals_task_id", "approvals", ["task_id"], unique=False)
|
||||||
|
|
||||||
|
# Backfill from legacy JSON payload keys when they contain a valid UUID.
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
WITH src AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
COALESCE(
|
||||||
|
payload->>'task_id',
|
||||||
|
payload->>'taskId',
|
||||||
|
payload->>'taskID'
|
||||||
|
) AS task_id_str
|
||||||
|
FROM approvals
|
||||||
|
WHERE task_id IS NULL
|
||||||
|
),
|
||||||
|
valid AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
task_id_str::uuid AS task_id
|
||||||
|
FROM src
|
||||||
|
WHERE task_id_str ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
||||||
|
),
|
||||||
|
existing AS (
|
||||||
|
SELECT v.id, v.task_id
|
||||||
|
FROM valid AS v
|
||||||
|
JOIN tasks AS t ON t.id = v.task_id
|
||||||
|
)
|
||||||
|
UPDATE approvals AS a
|
||||||
|
SET task_id = existing.task_id
|
||||||
|
FROM existing
|
||||||
|
WHERE a.id = existing.id;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Avoid FK failures if any approvals point at deleted tasks.
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE approvals AS a
|
||||||
|
SET task_id = NULL
|
||||||
|
WHERE task_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM tasks AS t WHERE t.id = a.task_id
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
approval_fks = inspector.get_foreign_keys("approvals")
|
||||||
|
has_task_fk = any(
|
||||||
|
(fk.get("referred_table") == "tasks" and "task_id" in (fk.get("constrained_columns") or []))
|
||||||
|
for fk in approval_fks
|
||||||
|
)
|
||||||
|
if not has_task_fk:
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_approvals_task_id_tasks",
|
||||||
|
"approvals",
|
||||||
|
"tasks",
|
||||||
|
["task_id"],
|
||||||
|
["id"],
|
||||||
|
ondelete="SET NULL",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_constraint("fk_approvals_task_id_tasks", "approvals", type_="foreignkey")
|
||||||
|
op.drop_index("ix_approvals_task_id", table_name="approvals")
|
||||||
|
op.drop_column("approvals", "task_id")
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""add board memory is_chat
|
||||||
|
|
||||||
|
Revision ID: a5aab244d32d
|
||||||
|
Revises: 3b9b2f1a6c2d
|
||||||
|
Create Date: 2026-02-06 17:57:02.110572
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "a5aab244d32d"
|
||||||
|
down_revision = "3b9b2f1a6c2d"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Idempotent: the column/indexes might already exist if the table was created via
|
||||||
|
# SQLModel `create_all()`.
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = sa.inspect(bind)
|
||||||
|
|
||||||
|
memory_cols = {c["name"] for c in inspector.get_columns("board_memory")}
|
||||||
|
if "is_chat" not in memory_cols:
|
||||||
|
op.add_column(
|
||||||
|
"board_memory",
|
||||||
|
sa.Column("is_chat", sa.Boolean(), server_default=sa.text("false"), nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
memory_index_names = {i["name"] for i in inspector.get_indexes("board_memory")}
|
||||||
|
if "ix_board_memory_is_chat" not in memory_index_names:
|
||||||
|
op.create_index("ix_board_memory_is_chat", "board_memory", ["is_chat"], unique=False)
|
||||||
|
if "ix_board_memory_board_id_is_chat_created_at" not in memory_index_names:
|
||||||
|
op.create_index(
|
||||||
|
"ix_board_memory_board_id_is_chat_created_at",
|
||||||
|
"board_memory",
|
||||||
|
["board_id", "is_chat", "created_at"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Backfill from existing tags arrays.
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE board_memory
|
||||||
|
SET is_chat = TRUE
|
||||||
|
WHERE tags IS NOT NULL
|
||||||
|
AND tags::jsonb @> '["chat"]'::jsonb;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_index("ix_board_memory_board_id_is_chat_created_at", table_name="board_memory")
|
||||||
|
op.drop_index("ix_board_memory_is_chat", table_name="board_memory")
|
||||||
|
op.drop_column("board_memory", "is_chat")
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from app.api.deps import ActorContext, require_admin_or_agent
|
from app.api.deps import ActorContext, require_admin_or_agent
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.models.activity_events import ActivityEvent
|
from app.models.activity_events import ActivityEvent
|
||||||
from app.schemas.activity_events import ActivityEventRead
|
from app.schemas.activity_events import ActivityEventRead
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
|
||||||
router = APIRouter(prefix="/activity", tags=["activity"])
|
router = APIRouter(prefix="/activity", tags=["activity"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[ActivityEventRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[ActivityEventRead])
|
||||||
async def list_activity(
|
async def list_activity(
|
||||||
limit: int = Query(50, ge=1, le=200),
|
|
||||||
offset: int = Query(0, ge=0),
|
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[ActivityEvent]:
|
) -> DefaultLimitOffsetPage[ActivityEventRead]:
|
||||||
statement = select(ActivityEvent)
|
statement = select(ActivityEvent)
|
||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
statement = statement.where(ActivityEvent.agent_id == actor.agent.id)
|
statement = statement.where(ActivityEvent.agent_id == actor.agent.id)
|
||||||
statement = statement.order_by(desc(col(ActivityEvent.created_at))).offset(offset).limit(limit)
|
statement = statement.order_by(desc(col(ActivityEvent.created_at)))
|
||||||
return list(await session.exec(statement))
|
return await paginate(session, statement)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import Any, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||||
from sqlmodel import select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from app.api import agents as agents_api
|
from app.api import agents as agents_api
|
||||||
@@ -13,6 +15,7 @@ from app.api import board_onboarding as onboarding_api
|
|||||||
from app.api import tasks as tasks_api
|
from app.api import tasks as tasks_api
|
||||||
from app.api.deps import ActorContext, get_board_or_404, get_task_or_404
|
from app.api.deps import ActorContext, get_board_or_404, get_task_or_404
|
||||||
from app.core.agent_auth import AgentAuthContext, get_agent_auth_context
|
from app.core.agent_auth import AgentAuthContext, get_agent_auth_context
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||||
@@ -30,6 +33,7 @@ from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
|
|||||||
from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboardingRead
|
from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboardingRead
|
||||||
from app.schemas.boards import BoardRead
|
from app.schemas.boards import BoardRead
|
||||||
from app.schemas.common import OkResponse
|
from app.schemas.common import OkResponse
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
||||||
from app.services.activity_log import record_activity
|
from app.services.activity_log import record_activity
|
||||||
|
|
||||||
@@ -54,15 +58,16 @@ async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientC
|
|||||||
return GatewayClientConfig(url=gateway.url, token=gateway.token)
|
return GatewayClientConfig(url=gateway.url, token=gateway.token)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards", response_model=list[BoardRead])
|
@router.get("/boards", response_model=DefaultLimitOffsetPage[BoardRead])
|
||||||
async def list_boards(
|
async def list_boards(
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[Board]:
|
) -> DefaultLimitOffsetPage[BoardRead]:
|
||||||
|
statement = select(Board)
|
||||||
if agent_ctx.agent.board_id:
|
if agent_ctx.agent.board_id:
|
||||||
board = await session.get(Board, agent_ctx.agent.board_id)
|
statement = statement.where(col(Board.id) == agent_ctx.agent.board_id)
|
||||||
return [board] if board else []
|
statement = statement.order_by(col(Board.created_at).desc())
|
||||||
return list(await session.exec(select(Board)))
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards/{board_id}", response_model=BoardRead)
|
@router.get("/boards/{board_id}", response_model=BoardRead)
|
||||||
@@ -74,13 +79,12 @@ def get_board(
|
|||||||
return board
|
return board
|
||||||
|
|
||||||
|
|
||||||
@router.get("/agents", response_model=list[AgentRead])
|
@router.get("/agents", response_model=DefaultLimitOffsetPage[AgentRead])
|
||||||
async def list_agents(
|
async def list_agents(
|
||||||
board_id: UUID | None = Query(default=None),
|
board_id: UUID | None = Query(default=None),
|
||||||
limit: int | None = Query(default=None, ge=1, le=200),
|
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[AgentRead]:
|
) -> DefaultLimitOffsetPage[AgentRead]:
|
||||||
statement = select(Agent)
|
statement = select(Agent)
|
||||||
if agent_ctx.agent.board_id:
|
if agent_ctx.agent.board_id:
|
||||||
if board_id and board_id != agent_ctx.agent.board_id:
|
if board_id and board_id != agent_ctx.agent.board_id:
|
||||||
@@ -88,32 +92,33 @@ async def list_agents(
|
|||||||
statement = statement.where(Agent.board_id == agent_ctx.agent.board_id)
|
statement = statement.where(Agent.board_id == agent_ctx.agent.board_id)
|
||||||
elif board_id:
|
elif board_id:
|
||||||
statement = statement.where(Agent.board_id == board_id)
|
statement = statement.where(Agent.board_id == board_id)
|
||||||
if limit is not None:
|
|
||||||
statement = statement.limit(limit)
|
|
||||||
agents = list(await session.exec(statement))
|
|
||||||
main_session_keys = await agents_api._get_gateway_main_session_keys(session)
|
main_session_keys = await agents_api._get_gateway_main_session_keys(session)
|
||||||
return [
|
statement = statement.order_by(col(Agent.created_at).desc())
|
||||||
agents_api._to_agent_read(agents_api._with_computed_status(agent), main_session_keys)
|
|
||||||
for agent in agents
|
def _transform(items: Sequence[Any]) -> Sequence[Any]:
|
||||||
]
|
agents = cast(Sequence[Agent], items)
|
||||||
|
return [
|
||||||
|
agents_api._to_agent_read(agents_api._with_computed_status(agent), main_session_keys)
|
||||||
|
for agent in agents
|
||||||
|
]
|
||||||
|
|
||||||
|
return await paginate(session, statement, transformer=_transform)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards/{board_id}/tasks", response_model=list[TaskRead])
|
@router.get("/boards/{board_id}/tasks", response_model=DefaultLimitOffsetPage[TaskRead])
|
||||||
async def list_tasks(
|
async def list_tasks(
|
||||||
status_filter: str | None = Query(default=None, alias="status"),
|
status_filter: str | None = Query(default=None, alias="status"),
|
||||||
assigned_agent_id: UUID | None = None,
|
assigned_agent_id: UUID | None = None,
|
||||||
unassigned: bool | None = None,
|
unassigned: bool | None = None,
|
||||||
limit: int | None = Query(default=None, ge=1, le=200),
|
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[Task]:
|
) -> DefaultLimitOffsetPage[TaskRead]:
|
||||||
_guard_board_access(agent_ctx, board)
|
_guard_board_access(agent_ctx, board)
|
||||||
return await tasks_api.list_tasks(
|
return await tasks_api.list_tasks(
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
assigned_agent_id=assigned_agent_id,
|
assigned_agent_id=assigned_agent_id,
|
||||||
unassigned=unassigned,
|
unassigned=unassigned,
|
||||||
limit=limit,
|
|
||||||
board=board,
|
board=board,
|
||||||
session=session,
|
session=session,
|
||||||
actor=_actor(agent_ctx),
|
actor=_actor(agent_ctx),
|
||||||
@@ -185,12 +190,15 @@ async def update_task(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards/{board_id}/tasks/{task_id}/comments", response_model=list[TaskCommentRead])
|
@router.get(
|
||||||
|
"/boards/{board_id}/tasks/{task_id}/comments",
|
||||||
|
response_model=DefaultLimitOffsetPage[TaskCommentRead],
|
||||||
|
)
|
||||||
async def list_task_comments(
|
async def list_task_comments(
|
||||||
task: Task = Depends(get_task_or_404),
|
task: Task = Depends(get_task_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[ActivityEvent]:
|
) -> DefaultLimitOffsetPage[TaskCommentRead]:
|
||||||
if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id:
|
if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
return await tasks_api.list_task_comments(
|
return await tasks_api.list_task_comments(
|
||||||
@@ -217,18 +225,14 @@ async def create_task_comment(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards/{board_id}/memory", response_model=list[BoardMemoryRead])
|
@router.get("/boards/{board_id}/memory", response_model=DefaultLimitOffsetPage[BoardMemoryRead])
|
||||||
async def list_board_memory(
|
async def list_board_memory(
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
|
||||||
offset: int = Query(default=0, ge=0),
|
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[BoardMemory]:
|
) -> DefaultLimitOffsetPage[BoardMemoryRead]:
|
||||||
_guard_board_access(agent_ctx, board)
|
_guard_board_access(agent_ctx, board)
|
||||||
return await board_memory_api.list_board_memory(
|
return await board_memory_api.list_board_memory(
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
board=board,
|
board=board,
|
||||||
session=session,
|
session=session,
|
||||||
actor=_actor(agent_ctx),
|
actor=_actor(agent_ctx),
|
||||||
@@ -251,13 +255,16 @@ async def create_board_memory(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/boards/{board_id}/approvals", response_model=list[ApprovalRead])
|
@router.get(
|
||||||
|
"/boards/{board_id}/approvals",
|
||||||
|
response_model=DefaultLimitOffsetPage[ApprovalRead],
|
||||||
|
)
|
||||||
async def list_approvals(
|
async def list_approvals(
|
||||||
status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
|
status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
|
||||||
) -> list[Approval]:
|
) -> DefaultLimitOffsetPage[ApprovalRead]:
|
||||||
_guard_board_access(agent_ctx, board)
|
_guard_board_access(agent_ctx, board)
|
||||||
return await approvals_api.list_approvals(
|
return await approvals_api.list_approvals(
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator, Sequence
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from typing import Any, cast
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||||
@@ -17,6 +18,7 @@ from app.api.deps import ActorContext, require_admin_auth, require_admin_or_agen
|
|||||||
from app.core.agent_tokens import generate_agent_token, hash_agent_token
|
from app.core.agent_tokens import generate_agent_token, hash_agent_token
|
||||||
from app.core.auth import AuthContext
|
from app.core.auth import AuthContext
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import async_session_maker, get_session
|
from app.db.session import async_session_maker, get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||||
@@ -33,6 +35,7 @@ from app.schemas.agents import (
|
|||||||
AgentRead,
|
AgentRead,
|
||||||
AgentUpdate,
|
AgentUpdate,
|
||||||
)
|
)
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
from app.services.activity_log import record_activity
|
from app.services.activity_log import record_activity
|
||||||
from app.services.agent_provisioning import (
|
from app.services.agent_provisioning import (
|
||||||
DEFAULT_HEARTBEAT_CONFIG,
|
DEFAULT_HEARTBEAT_CONFIG,
|
||||||
@@ -231,14 +234,28 @@ async def _send_wakeup_message(
|
|||||||
await send_message(message, session_key=session_key, config=config, deliver=True)
|
await send_message(message, session_key=session_key, config=config, deliver=True)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[AgentRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[AgentRead])
|
||||||
async def list_agents(
|
async def list_agents(
|
||||||
|
board_id: UUID | None = Query(default=None),
|
||||||
|
gateway_id: UUID | None = Query(default=None),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
auth: AuthContext = Depends(require_admin_auth),
|
auth: AuthContext = Depends(require_admin_auth),
|
||||||
) -> list[AgentRead]:
|
) -> DefaultLimitOffsetPage[AgentRead]:
|
||||||
agents = list(await session.exec(select(Agent)))
|
|
||||||
main_session_keys = await _get_gateway_main_session_keys(session)
|
main_session_keys = await _get_gateway_main_session_keys(session)
|
||||||
return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents]
|
statement = select(Agent)
|
||||||
|
if board_id is not None:
|
||||||
|
statement = statement.where(col(Agent.board_id) == board_id)
|
||||||
|
if gateway_id is not None:
|
||||||
|
statement = statement.join(Board, col(Agent.board_id) == col(Board.id)).where(
|
||||||
|
col(Board.gateway_id) == gateway_id
|
||||||
|
)
|
||||||
|
statement = statement.order_by(col(Agent.created_at).desc())
|
||||||
|
|
||||||
|
def _transform(items: Sequence[Any]) -> Sequence[Any]:
|
||||||
|
agents = cast(Sequence[Agent], items)
|
||||||
|
return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents]
|
||||||
|
|
||||||
|
return await paginate(session, statement, transformer=_transform)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stream")
|
@router.get("/stream")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from datetime import datetime, timezone
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||||
from sqlalchemy import asc, or_
|
from sqlalchemy import asc, case, func, or_
|
||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from sse_starlette.sse import EventSourceResponse
|
from sse_starlette.sse import EventSourceResponse
|
||||||
@@ -15,13 +15,32 @@ from sse_starlette.sse import EventSourceResponse
|
|||||||
from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent
|
from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent
|
||||||
from app.core.auth import AuthContext
|
from app.core.auth import AuthContext
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import async_session_maker, get_session
|
from app.db.session import async_session_maker, get_session
|
||||||
from app.models.approvals import Approval
|
from app.models.approvals import Approval
|
||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate
|
from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
|
||||||
router = APIRouter(prefix="/boards/{board_id}/approvals", tags=["approvals"])
|
router = APIRouter(prefix="/boards/{board_id}/approvals", tags=["approvals"])
|
||||||
|
|
||||||
|
TASK_ID_KEYS: tuple[str, ...] = ("task_id", "taskId", "taskID")
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_task_id(payload: dict[str, object] | None) -> UUID | None:
|
||||||
|
if not payload:
|
||||||
|
return None
|
||||||
|
for key in TASK_ID_KEYS:
|
||||||
|
value = payload.get(key)
|
||||||
|
if isinstance(value, UUID):
|
||||||
|
return value
|
||||||
|
if isinstance(value, str):
|
||||||
|
try:
|
||||||
|
return UUID(value)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _parse_since(value: str | None) -> datetime | None:
|
def _parse_since(value: str | None) -> datetime | None:
|
||||||
if not value:
|
if not value:
|
||||||
@@ -66,13 +85,13 @@ async def _fetch_approval_events(
|
|||||||
return list(await session.exec(statement))
|
return list(await session.exec(statement))
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[ApprovalRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[ApprovalRead])
|
||||||
async def list_approvals(
|
async def list_approvals(
|
||||||
status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
|
status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[Approval]:
|
) -> DefaultLimitOffsetPage[ApprovalRead]:
|
||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
if actor.agent.board_id and actor.agent.board_id != board.id:
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
@@ -80,7 +99,7 @@ async def list_approvals(
|
|||||||
if status_filter:
|
if status_filter:
|
||||||
statement = statement.where(col(Approval.status) == status_filter)
|
statement = statement.where(col(Approval.status) == status_filter)
|
||||||
statement = statement.order_by(col(Approval.created_at).desc())
|
statement = statement.order_by(col(Approval.created_at).desc())
|
||||||
return list(await session.exec(statement))
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stream")
|
@router.get("/stream")
|
||||||
@@ -103,11 +122,53 @@ async def stream_approvals(
|
|||||||
break
|
break
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
approvals = await _fetch_approval_events(session, board.id, last_seen)
|
approvals = await _fetch_approval_events(session, board.id, last_seen)
|
||||||
|
pending_approvals_count = int(
|
||||||
|
(
|
||||||
|
await session.exec(
|
||||||
|
select(func.count(col(Approval.id)))
|
||||||
|
.where(col(Approval.board_id) == board.id)
|
||||||
|
.where(col(Approval.status) == "pending")
|
||||||
|
)
|
||||||
|
).one()
|
||||||
|
)
|
||||||
|
task_ids = {approval.task_id for approval in approvals if approval.task_id is not None}
|
||||||
|
counts_by_task_id: dict[UUID, tuple[int, int]] = {}
|
||||||
|
if task_ids:
|
||||||
|
rows = list(
|
||||||
|
await session.exec(
|
||||||
|
select(
|
||||||
|
col(Approval.task_id),
|
||||||
|
func.count(col(Approval.id)).label("total"),
|
||||||
|
func.sum(
|
||||||
|
case((col(Approval.status) == "pending", 1), else_=0)
|
||||||
|
).label("pending"),
|
||||||
|
)
|
||||||
|
.where(col(Approval.board_id) == board.id)
|
||||||
|
.where(col(Approval.task_id).in_(task_ids))
|
||||||
|
.group_by(col(Approval.task_id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for task_id, total, pending in rows:
|
||||||
|
if task_id is None:
|
||||||
|
continue
|
||||||
|
counts_by_task_id[task_id] = (int(total or 0), int(pending or 0))
|
||||||
for approval in approvals:
|
for approval in approvals:
|
||||||
updated_at = _approval_updated_at(approval)
|
updated_at = _approval_updated_at(approval)
|
||||||
if updated_at > last_seen:
|
if updated_at > last_seen:
|
||||||
last_seen = updated_at
|
last_seen = updated_at
|
||||||
payload = {"approval": _serialize_approval(approval)}
|
payload: dict[str, object] = {
|
||||||
|
"approval": _serialize_approval(approval),
|
||||||
|
"pending_approvals_count": pending_approvals_count,
|
||||||
|
}
|
||||||
|
if approval.task_id is not None:
|
||||||
|
counts = counts_by_task_id.get(approval.task_id)
|
||||||
|
if counts is not None:
|
||||||
|
total, pending = counts
|
||||||
|
payload["task_counts"] = {
|
||||||
|
"task_id": str(approval.task_id),
|
||||||
|
"approvals_count": total,
|
||||||
|
"approvals_pending_count": pending,
|
||||||
|
}
|
||||||
yield {"event": "approval", "data": json.dumps(payload)}
|
yield {"event": "approval", "data": json.dumps(payload)}
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
@@ -124,8 +185,10 @@ async def create_approval(
|
|||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
if actor.agent.board_id and actor.agent.board_id != board.id:
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
|
task_id = payload.task_id or _extract_task_id(payload.payload)
|
||||||
approval = Approval(
|
approval = Approval(
|
||||||
board_id=board.id,
|
board_id=board.id,
|
||||||
|
task_id=task_id,
|
||||||
agent_id=payload.agent_id,
|
agent_id=payload.agent_id,
|
||||||
action_type=payload.action_type,
|
action_type=payload.action_type,
|
||||||
payload=payload.payload,
|
payload=payload.payload,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from datetime import datetime, timezone
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from sse_starlette.sse import EventSourceResponse
|
from sse_starlette.sse import EventSourceResponse
|
||||||
@@ -15,6 +16,7 @@ from sse_starlette.sse import EventSourceResponse
|
|||||||
from app.api.deps import ActorContext, get_board_or_404, require_admin_or_agent
|
from app.api.deps import ActorContext, get_board_or_404, require_admin_or_agent
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import async_session_maker, get_session
|
from app.db.session import async_session_maker, get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||||
@@ -23,6 +25,7 @@ from app.models.board_memory import BoardMemory
|
|||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
from app.models.gateways import Gateway
|
from app.models.gateways import Gateway
|
||||||
from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
|
from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
|
||||||
router = APIRouter(prefix="/boards/{board_id}/memory", tags=["board-memory"])
|
router = APIRouter(prefix="/boards/{board_id}/memory", tags=["board-memory"])
|
||||||
|
|
||||||
@@ -90,11 +93,19 @@ async def _fetch_memory_events(
|
|||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
board_id: UUID,
|
board_id: UUID,
|
||||||
since: datetime,
|
since: datetime,
|
||||||
|
is_chat: bool | None = None,
|
||||||
) -> list[BoardMemory]:
|
) -> list[BoardMemory]:
|
||||||
statement = (
|
statement = (
|
||||||
select(BoardMemory)
|
select(BoardMemory)
|
||||||
.where(col(BoardMemory.board_id) == board_id)
|
.where(col(BoardMemory.board_id) == board_id)
|
||||||
.where(col(BoardMemory.created_at) >= since)
|
# Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
|
||||||
|
# satisfy the NonEmptyStr response schema.
|
||||||
|
.where(func.length(func.trim(col(BoardMemory.content))) > 0)
|
||||||
|
)
|
||||||
|
if is_chat is not None:
|
||||||
|
statement = statement.where(col(BoardMemory.is_chat) == is_chat)
|
||||||
|
statement = (
|
||||||
|
statement.where(col(BoardMemory.created_at) >= since)
|
||||||
.order_by(col(BoardMemory.created_at))
|
.order_by(col(BoardMemory.created_at))
|
||||||
)
|
)
|
||||||
return list(await session.exec(statement))
|
return list(await session.exec(statement))
|
||||||
@@ -159,25 +170,27 @@ async def _notify_chat_targets(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[BoardMemoryRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[BoardMemoryRead])
|
||||||
async def list_board_memory(
|
async def list_board_memory(
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
is_chat: bool | None = Query(default=None),
|
||||||
offset: int = Query(default=0, ge=0),
|
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[BoardMemory]:
|
) -> DefaultLimitOffsetPage[BoardMemoryRead]:
|
||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
if actor.agent.board_id and actor.agent.board_id != board.id:
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
statement = (
|
statement = (
|
||||||
select(BoardMemory)
|
select(BoardMemory)
|
||||||
.where(col(BoardMemory.board_id) == board.id)
|
.where(col(BoardMemory.board_id) == board.id)
|
||||||
.order_by(col(BoardMemory.created_at).desc())
|
# Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
|
||||||
.offset(offset)
|
# satisfy the NonEmptyStr response schema.
|
||||||
.limit(limit)
|
.where(func.length(func.trim(col(BoardMemory.content))) > 0)
|
||||||
)
|
)
|
||||||
return list(await session.exec(statement))
|
if is_chat is not None:
|
||||||
|
statement = statement.where(col(BoardMemory.is_chat) == is_chat)
|
||||||
|
statement = statement.order_by(col(BoardMemory.created_at).desc())
|
||||||
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stream")
|
@router.get("/stream")
|
||||||
@@ -186,6 +199,7 @@ async def stream_board_memory(
|
|||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
since: str | None = Query(default=None),
|
since: str | None = Query(default=None),
|
||||||
|
is_chat: bool | None = Query(default=None),
|
||||||
) -> EventSourceResponse:
|
) -> EventSourceResponse:
|
||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
if actor.agent.board_id and actor.agent.board_id != board.id:
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
@@ -199,7 +213,12 @@ async def stream_board_memory(
|
|||||||
if await request.is_disconnected():
|
if await request.is_disconnected():
|
||||||
break
|
break
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
memories = await _fetch_memory_events(session, board.id, last_seen)
|
memories = await _fetch_memory_events(
|
||||||
|
session,
|
||||||
|
board.id,
|
||||||
|
last_seen,
|
||||||
|
is_chat=is_chat,
|
||||||
|
)
|
||||||
for memory in memories:
|
for memory in memories:
|
||||||
if memory.created_at > last_seen:
|
if memory.created_at > last_seen:
|
||||||
last_seen = memory.created_at
|
last_seen = memory.created_at
|
||||||
@@ -231,6 +250,7 @@ async def create_board_memory(
|
|||||||
board_id=board.id,
|
board_id=board.id,
|
||||||
content=payload.content,
|
content=payload.content,
|
||||||
tags=payload.tags,
|
tags=payload.tags,
|
||||||
|
is_chat=is_chat,
|
||||||
source=source,
|
source=source,
|
||||||
)
|
)
|
||||||
session.add(memory)
|
session.add(memory)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from uuid import uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||||
from sqlalchemy import delete
|
from sqlalchemy import delete
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, req
|
|||||||
from app.core.auth import AuthContext
|
from app.core.auth import AuthContext
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
from app.db import crud
|
from app.db import crud
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import (
|
from app.integrations.openclaw_gateway import (
|
||||||
@@ -31,6 +33,9 @@ from app.models.task_fingerprints import TaskFingerprint
|
|||||||
from app.models.tasks import Task
|
from app.models.tasks import Task
|
||||||
from app.schemas.common import OkResponse
|
from app.schemas.common import OkResponse
|
||||||
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
|
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
from app.schemas.view_models import BoardSnapshot
|
||||||
|
from app.services.board_snapshot import build_board_snapshot
|
||||||
|
|
||||||
router = APIRouter(prefix="/boards", tags=["boards"])
|
router = APIRouter(prefix="/boards", tags=["boards"])
|
||||||
|
|
||||||
@@ -149,12 +154,17 @@ async def _cleanup_agent_on_gateway(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[BoardRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[BoardRead])
|
||||||
async def list_boards(
|
async def list_boards(
|
||||||
|
gateway_id: UUID | None = Query(default=None),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[Board]:
|
) -> DefaultLimitOffsetPage[BoardRead]:
|
||||||
return list(await session.exec(select(Board)))
|
statement = select(Board)
|
||||||
|
if gateway_id is not None:
|
||||||
|
statement = statement.where(col(Board.gateway_id) == gateway_id)
|
||||||
|
statement = statement.order_by(func.lower(col(Board.name)).asc(), col(Board.created_at).desc())
|
||||||
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=BoardRead)
|
@router.post("", response_model=BoardRead)
|
||||||
@@ -175,6 +185,18 @@ def get_board(
|
|||||||
return board
|
return board
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{board_id}/snapshot", response_model=BoardSnapshot)
|
||||||
|
async def get_board_snapshot(
|
||||||
|
board: Board = Depends(get_board_or_404),
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
|
) -> BoardSnapshot:
|
||||||
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
|
return await build_board_snapshot(session, board)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{board_id}", response_model=BoardRead)
|
@router.patch("/{board_id}", response_model=BoardRead)
|
||||||
async def update_board(
|
async def update_board(
|
||||||
payload: BoardUpdate,
|
payload: BoardUpdate,
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlmodel import select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from app.core.agent_tokens import generate_agent_token, hash_agent_token
|
from app.core.agent_tokens import generate_agent_token, hash_agent_token
|
||||||
from app.core.auth import AuthContext, get_auth_context
|
from app.core.auth import AuthContext, get_auth_context
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||||
@@ -17,6 +17,7 @@ from app.models.agents import Agent
|
|||||||
from app.models.gateways import Gateway
|
from app.models.gateways import Gateway
|
||||||
from app.schemas.common import OkResponse
|
from app.schemas.common import OkResponse
|
||||||
from app.schemas.gateways import GatewayCreate, GatewayRead, GatewayUpdate
|
from app.schemas.gateways import GatewayCreate, GatewayRead, GatewayUpdate
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, provision_main_agent
|
from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, provision_main_agent
|
||||||
|
|
||||||
router = APIRouter(prefix="/gateways", tags=["gateways"])
|
router = APIRouter(prefix="/gateways", tags=["gateways"])
|
||||||
@@ -362,12 +363,13 @@ async def _send_skyll_disable_message(gateway: Gateway) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[GatewayRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[GatewayRead])
|
||||||
async def list_gateways(
|
async def list_gateways(
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
auth: AuthContext = Depends(get_auth_context),
|
auth: AuthContext = Depends(get_auth_context),
|
||||||
) -> list[Gateway]:
|
) -> DefaultLimitOffsetPage[GatewayRead]:
|
||||||
return list(await session.exec(select(Gateway)))
|
statement = select(Gateway).order_by(col(Gateway.created_at).desc())
|
||||||
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=GatewayRead)
|
@router.post("", response_model=GatewayRead)
|
||||||
|
|||||||
@@ -25,16 +25,19 @@ from app.api.deps import (
|
|||||||
)
|
)
|
||||||
from app.core.auth import AuthContext
|
from app.core.auth import AuthContext
|
||||||
from app.core.time import utcnow
|
from app.core.time import utcnow
|
||||||
|
from app.db.pagination import paginate
|
||||||
from app.db.session import async_session_maker, get_session
|
from app.db.session import async_session_maker, get_session
|
||||||
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
|
||||||
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
|
||||||
from app.models.activity_events import ActivityEvent
|
from app.models.activity_events import ActivityEvent
|
||||||
from app.models.agents import Agent
|
from app.models.agents import Agent
|
||||||
|
from app.models.approvals import Approval
|
||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
from app.models.gateways import Gateway
|
from app.models.gateways import Gateway
|
||||||
from app.models.task_fingerprints import TaskFingerprint
|
from app.models.task_fingerprints import TaskFingerprint
|
||||||
from app.models.tasks import Task
|
from app.models.tasks import Task
|
||||||
from app.schemas.common import OkResponse
|
from app.schemas.common import OkResponse
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
||||||
from app.services.activity_log import record_activity
|
from app.services.activity_log import record_activity
|
||||||
|
|
||||||
@@ -410,16 +413,18 @@ async def stream_tasks(
|
|||||||
return EventSourceResponse(event_generator(), ping=15)
|
return EventSourceResponse(event_generator(), ping=15)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[TaskRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[TaskRead])
|
||||||
async def list_tasks(
|
async def list_tasks(
|
||||||
status_filter: str | None = Query(default=None, alias="status"),
|
status_filter: str | None = Query(default=None, alias="status"),
|
||||||
assigned_agent_id: UUID | None = None,
|
assigned_agent_id: UUID | None = None,
|
||||||
unassigned: bool | None = None,
|
unassigned: bool | None = None,
|
||||||
limit: int | None = Query(default=None, ge=1, le=200),
|
|
||||||
board: Board = Depends(get_board_or_404),
|
board: Board = Depends(get_board_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[Task]:
|
) -> DefaultLimitOffsetPage[TaskRead]:
|
||||||
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
|
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
statement = select(Task).where(Task.board_id == board.id)
|
statement = select(Task).where(Task.board_id == board.id)
|
||||||
if status_filter:
|
if status_filter:
|
||||||
statuses = [s.strip() for s in status_filter.split(",") if s.strip()]
|
statuses = [s.strip() for s in status_filter.split(",") if s.strip()]
|
||||||
@@ -434,9 +439,8 @@ async def list_tasks(
|
|||||||
statement = statement.where(col(Task.assigned_agent_id) == assigned_agent_id)
|
statement = statement.where(col(Task.assigned_agent_id) == assigned_agent_id)
|
||||||
if unassigned:
|
if unassigned:
|
||||||
statement = statement.where(col(Task.assigned_agent_id).is_(None))
|
statement = statement.where(col(Task.assigned_agent_id).is_(None))
|
||||||
if limit is not None:
|
statement = statement.order_by(col(Task.created_at).desc())
|
||||||
statement = statement.limit(limit)
|
return await paginate(session, statement)
|
||||||
return list(await session.exec(statement))
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=TaskRead)
|
@router.post("", response_model=TaskRead)
|
||||||
@@ -661,17 +665,18 @@ async def delete_task(
|
|||||||
) -> OkResponse:
|
) -> OkResponse:
|
||||||
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id))
|
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id))
|
||||||
await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id))
|
await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id))
|
||||||
|
await session.execute(delete(Approval).where(col(Approval.task_id) == task.id))
|
||||||
await session.delete(task)
|
await session.delete(task)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return OkResponse()
|
return OkResponse()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{task_id}/comments", response_model=list[TaskCommentRead])
|
@router.get("/{task_id}/comments", response_model=DefaultLimitOffsetPage[TaskCommentRead])
|
||||||
async def list_task_comments(
|
async def list_task_comments(
|
||||||
task: Task = Depends(get_task_or_404),
|
task: Task = Depends(get_task_or_404),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
actor: ActorContext = Depends(require_admin_or_agent),
|
actor: ActorContext = Depends(require_admin_or_agent),
|
||||||
) -> list[ActivityEvent]:
|
) -> DefaultLimitOffsetPage[TaskCommentRead]:
|
||||||
if actor.actor_type == "agent" and actor.agent:
|
if actor.actor_type == "agent" and actor.agent:
|
||||||
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
@@ -681,7 +686,7 @@ async def list_task_comments(
|
|||||||
.where(col(ActivityEvent.event_type) == "task.comment")
|
.where(col(ActivityEvent.event_type) == "task.comment")
|
||||||
.order_by(asc(col(ActivityEvent.created_at)))
|
.order_by(asc(col(ActivityEvent.created_at)))
|
||||||
)
|
)
|
||||||
return list(await session.exec(statement))
|
return await paginate(session, statement)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{task_id}/comments", response_model=TaskCommentRead)
|
@router.post("/{task_id}/comments", response_model=TaskCommentRead)
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from pydantic import model_validator
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
BACKEND_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
DEFAULT_ENV_FILE = BACKEND_ROOT / ".env"
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_file=".env",
|
# Load `backend/.env` regardless of current working directory.
|
||||||
|
# (Important when running uvicorn from repo root or via a process manager.)
|
||||||
|
env_file=[DEFAULT_ENV_FILE, ".env"],
|
||||||
env_file_encoding="utf-8",
|
env_file_encoding="utf-8",
|
||||||
extra="ignore",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
@@ -30,5 +39,13 @@ class Settings(BaseSettings):
|
|||||||
log_format: str = "text"
|
log_format: str = "text"
|
||||||
log_use_utc: bool = False
|
log_use_utc: bool = False
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def _defaults(self) -> Self:
|
||||||
|
# In dev, default to applying Alembic migrations at startup to avoid schema drift
|
||||||
|
# (e.g. missing newly-added columns).
|
||||||
|
if "db_auto_migrate" not in self.model_fields_set and self.environment == "dev":
|
||||||
|
self.db_auto_migrate = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ def utcnow() -> datetime:
|
|||||||
|
|
||||||
# Keep naive UTC values for compatibility with existing DB schema/queries.
|
# Keep naive UTC values for compatibility with existing DB schema/queries.
|
||||||
return datetime.now(UTC).replace(tzinfo=None)
|
return datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
|
||||||
|
|||||||
28
backend/app/db/pagination.py
Normal file
28
backend/app/db/pagination.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Awaitable, Callable, Sequence
|
||||||
|
from typing import Any, TypeVar, cast
|
||||||
|
|
||||||
|
from fastapi_pagination.ext.sqlalchemy import paginate as _paginate
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
from sqlmodel.sql.expression import Select, SelectOfScalar
|
||||||
|
|
||||||
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
Transformer = Callable[[Sequence[Any]], Sequence[Any] | Awaitable[Sequence[Any]]]
|
||||||
|
|
||||||
|
|
||||||
|
async def paginate(
|
||||||
|
session: AsyncSession,
|
||||||
|
statement: Select[Any] | SelectOfScalar[Any],
|
||||||
|
*,
|
||||||
|
transformer: Transformer | None = None,
|
||||||
|
) -> DefaultLimitOffsetPage[T]:
|
||||||
|
# fastapi-pagination is not fully typed (it returns Any), but response_model validation
|
||||||
|
# ensures runtime correctness. Centralize casts here to keep strict mypy clean.
|
||||||
|
return cast(
|
||||||
|
DefaultLimitOffsetPage[T],
|
||||||
|
await _paginate(session, statement, transformer=transformer),
|
||||||
|
)
|
||||||
@@ -5,6 +5,7 @@ from contextlib import asynccontextmanager
|
|||||||
|
|
||||||
from fastapi import APIRouter, FastAPI
|
from fastapi import APIRouter, FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi_pagination import add_pagination
|
||||||
|
|
||||||
from app.api.activity import router as activity_router
|
from app.api.activity import router as activity_router
|
||||||
from app.api.agent import router as agent_router
|
from app.api.agent import router as agent_router
|
||||||
@@ -75,3 +76,5 @@ api_v1.include_router(approvals_router)
|
|||||||
api_v1.include_router(tasks_router)
|
api_v1.include_router(tasks_router)
|
||||||
api_v1.include_router(users_router)
|
api_v1.include_router(users_router)
|
||||||
app.include_router(api_v1)
|
app.include_router(api_v1)
|
||||||
|
|
||||||
|
add_pagination(app)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class Approval(SQLModel, table=True):
|
|||||||
|
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||||
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
||||||
|
task_id: UUID | None = Field(default=None, foreign_key="tasks.id", index=True)
|
||||||
agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True)
|
agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True)
|
||||||
action_type: str
|
action_type: str
|
||||||
payload: dict[str, object] | None = Field(default=None, sa_column=Column(JSON))
|
payload: dict[str, object] | None = Field(default=None, sa_column=Column(JSON))
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ class BoardMemory(SQLModel, table=True):
|
|||||||
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
||||||
content: str
|
content: str
|
||||||
tags: list[str] | None = Field(default=None, sa_column=Column(JSON))
|
tags: list[str] | None = Field(default=None, sa_column=Column(JSON))
|
||||||
|
is_chat: bool = Field(default=False, index=True)
|
||||||
source: str | None = None
|
source: str | None = None
|
||||||
created_at: datetime = Field(default_factory=utcnow)
|
created_at: datetime = Field(default_factory=utcnow)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ ApprovalStatus = Literal["pending", "approved", "rejected"]
|
|||||||
|
|
||||||
class ApprovalBase(SQLModel):
|
class ApprovalBase(SQLModel):
|
||||||
action_type: str
|
action_type: str
|
||||||
|
task_id: UUID | None = None
|
||||||
payload: dict[str, object] | None = None
|
payload: dict[str, object] | None = None
|
||||||
confidence: int
|
confidence: int
|
||||||
rubric_scores: dict[str, int] | None = None
|
rubric_scores: dict[str, int] | None = None
|
||||||
|
|||||||
@@ -9,12 +9,18 @@ from app.schemas.common import NonEmptyStr
|
|||||||
|
|
||||||
|
|
||||||
class BoardMemoryCreate(SQLModel):
|
class BoardMemoryCreate(SQLModel):
|
||||||
|
# For writes, reject blank/whitespace-only content.
|
||||||
content: NonEmptyStr
|
content: NonEmptyStr
|
||||||
tags: list[str] | None = None
|
tags: list[str] | None = None
|
||||||
source: str | None = None
|
source: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class BoardMemoryRead(BoardMemoryCreate):
|
class BoardMemoryRead(SQLModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
board_id: UUID
|
board_id: UUID
|
||||||
|
# For reads, allow legacy rows that may have empty content (avoid response validation 500s).
|
||||||
|
content: str
|
||||||
|
tags: list[str] | None = None
|
||||||
|
source: str | None = None
|
||||||
|
is_chat: bool = False
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|||||||
21
backend/app/schemas/pagination.py
Normal file
21
backend/app/schemas/pagination.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
from fastapi import Query
|
||||||
|
from fastapi_pagination.customization import CustomizedPage, UseParamsFields
|
||||||
|
from fastapi_pagination.limit_offset import LimitOffsetPage
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
# Project-wide default pagination response model.
|
||||||
|
# - Keep `limit` / `offset` naming (matches existing API conventions).
|
||||||
|
# - Cap list endpoints to 200 items per request (matches prior route-level constraints).
|
||||||
|
DefaultLimitOffsetPage = CustomizedPage[
|
||||||
|
LimitOffsetPage[T],
|
||||||
|
UseParamsFields(
|
||||||
|
limit=Query(200, ge=1, le=200),
|
||||||
|
offset=Query(0, ge=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
backend/app/schemas/view_models.py
Normal file
24
backend/app/schemas/view_models.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from app.schemas.agents import AgentRead
|
||||||
|
from app.schemas.approvals import ApprovalRead
|
||||||
|
from app.schemas.board_memory import BoardMemoryRead
|
||||||
|
from app.schemas.boards import BoardRead
|
||||||
|
from app.schemas.tasks import TaskRead
|
||||||
|
|
||||||
|
|
||||||
|
class TaskCardRead(TaskRead):
|
||||||
|
assignee: str | None = None
|
||||||
|
approvals_count: int = 0
|
||||||
|
approvals_pending_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class BoardSnapshot(SQLModel):
|
||||||
|
board: BoardRead
|
||||||
|
tasks: list[TaskCardRead]
|
||||||
|
agents: list[AgentRead]
|
||||||
|
approvals: list[ApprovalRead]
|
||||||
|
chat_messages: list[BoardMemoryRead]
|
||||||
|
pending_approvals_count: int = 0
|
||||||
158
backend/app/services/board_snapshot.py
Normal file
158
backend/app/services/board_snapshot.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from sqlalchemy import case, func
|
||||||
|
from sqlmodel import col, select
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
from app.core.time import utcnow
|
||||||
|
from app.models.agents import Agent
|
||||||
|
from app.models.approvals import Approval
|
||||||
|
from app.models.board_memory import BoardMemory
|
||||||
|
from app.models.boards import Board
|
||||||
|
from app.models.gateways import Gateway
|
||||||
|
from app.models.tasks import Task
|
||||||
|
from app.schemas.agents import AgentRead
|
||||||
|
from app.schemas.approvals import ApprovalRead
|
||||||
|
from app.schemas.board_memory import BoardMemoryRead
|
||||||
|
from app.schemas.boards import BoardRead
|
||||||
|
from app.schemas.view_models import BoardSnapshot, TaskCardRead
|
||||||
|
|
||||||
|
OFFLINE_AFTER = timedelta(minutes=10)
|
||||||
|
|
||||||
|
|
||||||
|
def _computed_agent_status(agent: Agent) -> str:
|
||||||
|
now = utcnow()
|
||||||
|
if agent.status in {"deleting", "updating"}:
|
||||||
|
return agent.status
|
||||||
|
if agent.last_seen_at is None:
|
||||||
|
return "provisioning"
|
||||||
|
if now - agent.last_seen_at > OFFLINE_AFTER:
|
||||||
|
return "offline"
|
||||||
|
return agent.status
|
||||||
|
|
||||||
|
|
||||||
|
async def _gateway_main_session_keys(session: AsyncSession) -> set[str]:
|
||||||
|
keys = (await session.exec(select(Gateway.main_session_key))).all()
|
||||||
|
return {key for key in keys if key}
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_to_read(agent: Agent, main_session_keys: set[str]) -> AgentRead:
|
||||||
|
model = AgentRead.model_validate(agent, from_attributes=True)
|
||||||
|
computed_status = _computed_agent_status(agent)
|
||||||
|
is_gateway_main = bool(agent.openclaw_session_id and agent.openclaw_session_id in main_session_keys)
|
||||||
|
return model.model_copy(update={"status": computed_status, "is_gateway_main": is_gateway_main})
|
||||||
|
|
||||||
|
|
||||||
|
def _memory_to_read(memory: BoardMemory) -> BoardMemoryRead:
|
||||||
|
return BoardMemoryRead.model_validate(memory, from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _approval_to_read(approval: Approval) -> ApprovalRead:
|
||||||
|
return ApprovalRead.model_validate(approval, from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _task_to_card(
|
||||||
|
task: Task,
|
||||||
|
*,
|
||||||
|
agent_name_by_id: dict[UUID, str],
|
||||||
|
counts_by_task_id: dict[UUID, tuple[int, int]],
|
||||||
|
) -> TaskCardRead:
|
||||||
|
card = TaskCardRead.model_validate(task, from_attributes=True)
|
||||||
|
approvals_count, approvals_pending_count = counts_by_task_id.get(task.id, (0, 0))
|
||||||
|
assignee = (
|
||||||
|
agent_name_by_id.get(task.assigned_agent_id) if task.assigned_agent_id is not None else None
|
||||||
|
)
|
||||||
|
return card.model_copy(
|
||||||
|
update={
|
||||||
|
"assignee": assignee,
|
||||||
|
"approvals_count": approvals_count,
|
||||||
|
"approvals_pending_count": approvals_pending_count,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def build_board_snapshot(session: AsyncSession, board: Board) -> BoardSnapshot:
|
||||||
|
board_read = BoardRead.model_validate(board, from_attributes=True)
|
||||||
|
|
||||||
|
tasks = list(
|
||||||
|
await session.exec(
|
||||||
|
select(Task).where(col(Task.board_id) == board.id).order_by(col(Task.created_at).desc())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
main_session_keys = await _gateway_main_session_keys(session)
|
||||||
|
agents = list(
|
||||||
|
await session.exec(
|
||||||
|
select(Agent).where(col(Agent.board_id) == board.id).order_by(col(Agent.created_at).desc())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
agent_reads = [_agent_to_read(agent, main_session_keys) for agent in agents]
|
||||||
|
agent_name_by_id = {agent.id: agent.name for agent in agents}
|
||||||
|
|
||||||
|
pending_approvals_count = int(
|
||||||
|
(await session.exec(
|
||||||
|
select(func.count(col(Approval.id)))
|
||||||
|
.where(col(Approval.board_id) == board.id)
|
||||||
|
.where(col(Approval.status) == "pending")
|
||||||
|
)).one()
|
||||||
|
)
|
||||||
|
|
||||||
|
approvals = list(
|
||||||
|
await session.exec(
|
||||||
|
select(Approval)
|
||||||
|
.where(col(Approval.board_id) == board.id)
|
||||||
|
.order_by(col(Approval.created_at).desc())
|
||||||
|
.limit(200)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
approval_reads = [_approval_to_read(approval) for approval in approvals]
|
||||||
|
|
||||||
|
counts_by_task_id: dict[UUID, tuple[int, int]] = {}
|
||||||
|
rows = list(
|
||||||
|
await session.exec(
|
||||||
|
select(
|
||||||
|
col(Approval.task_id),
|
||||||
|
func.count(col(Approval.id)).label("total"),
|
||||||
|
func.sum(case((col(Approval.status) == "pending", 1), else_=0)).label("pending"),
|
||||||
|
)
|
||||||
|
.where(col(Approval.board_id) == board.id)
|
||||||
|
.where(col(Approval.task_id).is_not(None))
|
||||||
|
.group_by(col(Approval.task_id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for task_id, total, pending in rows:
|
||||||
|
if task_id is None:
|
||||||
|
continue
|
||||||
|
counts_by_task_id[task_id] = (int(total or 0), int(pending or 0))
|
||||||
|
|
||||||
|
task_cards = [
|
||||||
|
_task_to_card(task, agent_name_by_id=agent_name_by_id, counts_by_task_id=counts_by_task_id)
|
||||||
|
for task in tasks
|
||||||
|
]
|
||||||
|
|
||||||
|
chat_messages = list(
|
||||||
|
await session.exec(
|
||||||
|
select(BoardMemory)
|
||||||
|
.where(col(BoardMemory.board_id) == board.id)
|
||||||
|
.where(col(BoardMemory.is_chat).is_(True))
|
||||||
|
# Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
|
||||||
|
# satisfy the NonEmptyStr response schema.
|
||||||
|
.where(func.length(func.trim(col(BoardMemory.content))) > 0)
|
||||||
|
.order_by(col(BoardMemory.created_at).desc())
|
||||||
|
.limit(200)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
chat_messages.sort(key=lambda item: item.created_at)
|
||||||
|
chat_reads = [_memory_to_read(memory) for memory in chat_messages]
|
||||||
|
|
||||||
|
return BoardSnapshot(
|
||||||
|
board=board_read,
|
||||||
|
tasks=task_cards,
|
||||||
|
agents=agent_reads,
|
||||||
|
approvals=approval_reads,
|
||||||
|
chat_messages=chat_reads,
|
||||||
|
pending_approvals_count=pending_approvals_count,
|
||||||
|
)
|
||||||
@@ -12,7 +12,7 @@ name = "openclaw-agency-backend"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi==0.115.4",
|
"fastapi==0.128.0",
|
||||||
"uvicorn[standard]==0.30.6",
|
"uvicorn[standard]==0.30.6",
|
||||||
"sqlmodel==0.0.22",
|
"sqlmodel==0.0.22",
|
||||||
"sqlalchemy==2.0.34",
|
"sqlalchemy==2.0.34",
|
||||||
@@ -25,7 +25,8 @@ dependencies = [
|
|||||||
"redis==5.1.1",
|
"redis==5.1.1",
|
||||||
"fastapi-clerk-auth==0.0.9",
|
"fastapi-clerk-auth==0.0.9",
|
||||||
"sse-starlette==2.1.3",
|
"sse-starlette==2.1.3",
|
||||||
"jinja2"
|
"jinja2==3.1.6",
|
||||||
|
"fastapi-pagination==0.15.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
pytest==8.3.3
|
|
||||||
pytest-asyncio==0.24.0
|
|
||||||
ruff==0.6.9
|
|
||||||
mypy==1.11.2
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
fastapi==0.115.4
|
|
||||||
uvicorn[standard]==0.30.6
|
|
||||||
sqlmodel==0.0.22
|
|
||||||
sqlalchemy==2.0.34
|
|
||||||
alembic==1.13.2
|
|
||||||
psycopg[binary]==3.2.1
|
|
||||||
pydantic-settings==2.5.2
|
|
||||||
python-dotenv==1.0.1
|
|
||||||
websockets==12.0
|
|
||||||
rq==1.16.2
|
|
||||||
redis==5.1.1
|
|
||||||
fastapi-clerk-auth==0.0.9
|
|
||||||
sse-starlette==2.1.3
|
|
||||||
jinja2==3.1.6
|
|
||||||
40
backend/uv.lock
generated
40
backend/uv.lock
generated
@@ -16,6 +16,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990, upload-time = "2024-06-26T15:46:21.088Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990, upload-time = "2024-06-26T15:46:21.088Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-doc"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -177,16 +186,17 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.115.4"
|
version = "0.128.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "annotated-doc" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "starlette" },
|
{ name = "starlette" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/db/5781f19bd30745885e0737ff3fdd4e63e7bc691710f9da691128bb0dc73b/fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349", size = 300737, upload-time = "2024-10-27T22:02:04.678Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/f6/af0d1f58f86002be0cf1e2665cdd6f7a4a71cdc8a7a9438cdc9e3b5375fe/fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742", size = 94732, upload-time = "2024-10-27T22:02:00.974Z" },
|
{ url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -203,6 +213,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/79/4e/058ecbe4fa0d470c3979f1272c0199cc47afb0ed935edb07b55441be8994/fastapi_clerk_auth-0.0.9-py3-none-any.whl", hash = "sha256:f9a47cfc65a2562c144a798ce0022a288799dac1149001b5a109865d578b2647", size = 6464, upload-time = "2025-11-11T06:12:35.655Z" },
|
{ url = "https://files.pythonhosted.org/packages/79/4e/058ecbe4fa0d470c3979f1272c0199cc47afb0ed935edb07b55441be8994/fastapi_clerk_auth-0.0.9-py3-none-any.whl", hash = "sha256:f9a47cfc65a2562c144a798ce0022a288799dac1149001b5a109865d578b2647", size = 6464, upload-time = "2025-11-11T06:12:35.655Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi-pagination"
|
||||||
|
version = "0.15.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "fastapi" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/48/4b/057fea634912ba285e71fa9b65594c9cc90f589ad25ccbdc7549202c12a2/fastapi_pagination-0.15.9.tar.gz", hash = "sha256:e24b0419a6077a75f38970ada2a57e277845fb177cc2da7300374ee8be32e8b5", size = 574790, upload-time = "2026-02-02T18:29:20.863Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/7e/7d1a66b618309ef3d1d969ccab187a8c09444e6eb8d3abcd3bc12f145578/fastapi_pagination-0.15.9-py3-none-any.whl", hash = "sha256:21f5ab465fb75e21a3454234603071711b679b1973ba2428e7a63db2221abc09", size = 60100, upload-time = "2026-02-02T18:29:19.363Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flake8"
|
name = "flake8"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
@@ -226,7 +250,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" },
|
{ url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" },
|
{ url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" },
|
{ url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" },
|
{ url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" },
|
{ url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" },
|
||||||
@@ -235,7 +258,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" },
|
{ url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" },
|
||||||
@@ -244,7 +266,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
|
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
|
||||||
@@ -253,7 +274,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
|
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
|
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
|
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
|
||||||
@@ -456,6 +476,7 @@ dependencies = [
|
|||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "fastapi-clerk-auth" },
|
{ name = "fastapi-clerk-auth" },
|
||||||
|
{ name = "fastapi-pagination" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "psycopg", extra = ["binary"] },
|
{ name = "psycopg", extra = ["binary"] },
|
||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
@@ -484,11 +505,12 @@ dev = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "alembic", specifier = "==1.13.2" },
|
{ name = "alembic", specifier = "==1.13.2" },
|
||||||
{ name = "black", marker = "extra == 'dev'", specifier = "==24.10.0" },
|
{ name = "black", marker = "extra == 'dev'", specifier = "==24.10.0" },
|
||||||
{ name = "fastapi", specifier = "==0.115.4" },
|
{ name = "fastapi", specifier = "==0.128.0" },
|
||||||
{ name = "fastapi-clerk-auth", specifier = "==0.0.9" },
|
{ name = "fastapi-clerk-auth", specifier = "==0.0.9" },
|
||||||
|
{ name = "fastapi-pagination", specifier = "==0.15.9" },
|
||||||
{ name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" },
|
{ name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" },
|
||||||
{ name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" },
|
{ name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2", specifier = "==3.1.6" },
|
||||||
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.2" },
|
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.2" },
|
||||||
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.1" },
|
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.1" },
|
||||||
{ name = "pydantic-settings", specifier = "==2.5.2" },
|
{ name = "pydantic-settings", specifier = "==2.5.2" },
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import type {
|
|||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ActivityEventRead,
|
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedActivityEventRead,
|
||||||
ListActivityApiV1ActivityGetParams,
|
ListActivityApiV1ActivityGetParams,
|
||||||
} from ".././model";
|
} from ".././model";
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Activity
|
* @summary List Activity
|
||||||
*/
|
*/
|
||||||
export type listActivityApiV1ActivityGetResponse200 = {
|
export type listActivityApiV1ActivityGetResponse200 = {
|
||||||
data: ActivityEventRead[];
|
data: LimitOffsetPageTypeVarCustomizedActivityEventRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,17 @@ import type {
|
|||||||
BoardOnboardingRead,
|
BoardOnboardingRead,
|
||||||
BoardRead,
|
BoardRead,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedAgentRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedApprovalRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedBoardMemoryRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedBoardRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedTaskCommentRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedTaskRead,
|
||||||
ListAgentsApiV1AgentAgentsGetParams,
|
ListAgentsApiV1AgentAgentsGetParams,
|
||||||
ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams,
|
ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams,
|
||||||
ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams,
|
ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams,
|
||||||
|
ListBoardsApiV1AgentBoardsGetParams,
|
||||||
|
ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
ListTasksApiV1AgentBoardsBoardIdTasksGetParams,
|
ListTasksApiV1AgentBoardsBoardIdTasksGetParams,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
TaskCommentCreate,
|
TaskCommentCreate,
|
||||||
@@ -54,7 +62,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Boards
|
* @summary List Boards
|
||||||
*/
|
*/
|
||||||
export type listBoardsApiV1AgentBoardsGetResponse200 = {
|
export type listBoardsApiV1AgentBoardsGetResponse200 = {
|
||||||
data: BoardRead[];
|
data: LimitOffsetPageTypeVarCustomizedBoardRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,15 +84,30 @@ export type listBoardsApiV1AgentBoardsGetResponse =
|
|||||||
| listBoardsApiV1AgentBoardsGetResponseSuccess
|
| listBoardsApiV1AgentBoardsGetResponseSuccess
|
||||||
| listBoardsApiV1AgentBoardsGetResponseError;
|
| listBoardsApiV1AgentBoardsGetResponseError;
|
||||||
|
|
||||||
export const getListBoardsApiV1AgentBoardsGetUrl = () => {
|
export const getListBoardsApiV1AgentBoardsGetUrl = (
|
||||||
return `/api/v1/agent/boards`;
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
|
) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/agent/boards?${stringifiedParams}`
|
||||||
|
: `/api/v1/agent/boards`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listBoardsApiV1AgentBoardsGet = async (
|
export const listBoardsApiV1AgentBoardsGet = async (
|
||||||
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listBoardsApiV1AgentBoardsGetResponse> => {
|
): Promise<listBoardsApiV1AgentBoardsGetResponse> => {
|
||||||
return customFetch<listBoardsApiV1AgentBoardsGetResponse>(
|
return customFetch<listBoardsApiV1AgentBoardsGetResponse>(
|
||||||
getListBoardsApiV1AgentBoardsGetUrl(),
|
getListBoardsApiV1AgentBoardsGetUrl(params),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -92,32 +115,37 @@ export const listBoardsApiV1AgentBoardsGet = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListBoardsApiV1AgentBoardsGetQueryKey = () => {
|
export const getListBoardsApiV1AgentBoardsGetQueryKey = (
|
||||||
return [`/api/v1/agent/boards`] as const;
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
|
) => {
|
||||||
|
return [`/api/v1/agent/boards`, ...(params ? [params] : [])] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListBoardsApiV1AgentBoardsGetQueryOptions = <
|
export const getListBoardsApiV1AgentBoardsGetQueryOptions = <
|
||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(options?: {
|
>(
|
||||||
query?: Partial<
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
UseQueryOptions<
|
options?: {
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
query?: Partial<
|
||||||
TError,
|
UseQueryOptions<
|
||||||
TData
|
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
>
|
TError,
|
||||||
>;
|
TData
|
||||||
request?: SecondParameter<typeof customFetch>;
|
>
|
||||||
}) => {
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getListBoardsApiV1AgentBoardsGetQueryKey();
|
queryOptions?.queryKey ?? getListBoardsApiV1AgentBoardsGetQueryKey(params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>
|
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>
|
||||||
> = ({ signal }) =>
|
> = ({ signal }) =>
|
||||||
listBoardsApiV1AgentBoardsGet({ signal, ...requestOptions });
|
listBoardsApiV1AgentBoardsGet(params, { signal, ...requestOptions });
|
||||||
|
|
||||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
@@ -135,6 +163,7 @@ export function useListBoardsApiV1AgentBoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params: undefined | ListBoardsApiV1AgentBoardsGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -161,6 +190,7 @@ export function useListBoardsApiV1AgentBoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -187,6 +217,7 @@ export function useListBoardsApiV1AgentBoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -209,6 +240,7 @@ export function useListBoardsApiV1AgentBoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1AgentBoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -223,7 +255,10 @@ export function useListBoardsApiV1AgentBoardsGet<
|
|||||||
): UseQueryResult<TData, TError> & {
|
): UseQueryResult<TData, TError> & {
|
||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
} {
|
} {
|
||||||
const queryOptions = getListBoardsApiV1AgentBoardsGetQueryOptions(options);
|
const queryOptions = getListBoardsApiV1AgentBoardsGetQueryOptions(
|
||||||
|
params,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
TData,
|
TData,
|
||||||
@@ -439,7 +474,7 @@ export function useGetBoardApiV1AgentBoardsBoardIdGet<
|
|||||||
* @summary List Agents
|
* @summary List Agents
|
||||||
*/
|
*/
|
||||||
export type listAgentsApiV1AgentAgentsGetResponse200 = {
|
export type listAgentsApiV1AgentAgentsGetResponse200 = {
|
||||||
data: AgentRead[];
|
data: LimitOffsetPageTypeVarCustomizedAgentRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -766,7 +801,7 @@ export const useCreateAgentApiV1AgentAgentsPost = <
|
|||||||
* @summary List Tasks
|
* @summary List Tasks
|
||||||
*/
|
*/
|
||||||
export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 = {
|
export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 = {
|
||||||
data: TaskRead[];
|
data: LimitOffsetPageTypeVarCustomizedTaskRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1265,7 +1300,7 @@ export const useUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch = <
|
|||||||
*/
|
*/
|
||||||
export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 =
|
export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 =
|
||||||
{
|
{
|
||||||
data: TaskCommentRead[];
|
data: LimitOffsetPageTypeVarCustomizedTaskCommentRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1290,20 +1325,41 @@ export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetRespons
|
|||||||
| listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError;
|
| listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError;
|
||||||
|
|
||||||
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl =
|
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl =
|
||||||
(boardId: string, taskId: string) => {
|
(
|
||||||
return `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`;
|
boardId: string,
|
||||||
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
|
) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(
|
||||||
|
key,
|
||||||
|
value === null ? "null" : value.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments?${stringifiedParams}`
|
||||||
|
: `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet =
|
export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet =
|
||||||
async (
|
async (
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse> => {
|
): Promise<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse> => {
|
||||||
return customFetch<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse>(
|
return customFetch<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse>(
|
||||||
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl(
|
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
@@ -1313,9 +1369,14 @@ export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey =
|
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey =
|
||||||
(boardId: string, taskId: string) => {
|
(
|
||||||
|
boardId: string,
|
||||||
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
|
) => {
|
||||||
return [
|
return [
|
||||||
`/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`,
|
`/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`,
|
||||||
|
...(params ? [params] : []),
|
||||||
] as const;
|
] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1330,6 +1391,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1352,6 +1414,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
|
|||||||
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey(
|
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
@@ -1364,6 +1427,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
|
|||||||
listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet(
|
listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
{ signal, ...requestOptions },
|
{ signal, ...requestOptions },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1404,6 +1468,9 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params:
|
||||||
|
| undefined
|
||||||
|
| ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1448,6 +1515,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1492,6 +1560,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1524,6 +1593,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1546,6 +1616,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
|
|||||||
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
|
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1720,7 +1791,7 @@ export const useCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost
|
|||||||
* @summary List Board Memory
|
* @summary List Board Memory
|
||||||
*/
|
*/
|
||||||
export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 = {
|
export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 = {
|
||||||
data: BoardMemoryRead[];
|
data: LimitOffsetPageTypeVarCustomizedBoardMemoryRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2121,7 +2192,7 @@ export const useCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPost = <
|
|||||||
* @summary List Approvals
|
* @summary List Approvals
|
||||||
*/
|
*/
|
||||||
export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 = {
|
export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 = {
|
||||||
data: ApprovalRead[];
|
data: LimitOffsetPageTypeVarCustomizedApprovalRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import type {
|
|||||||
AgentRead,
|
AgentRead,
|
||||||
AgentUpdate,
|
AgentUpdate,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedAgentRead,
|
||||||
|
ListAgentsApiV1AgentsGetParams,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
StreamAgentsApiV1AgentsStreamGetParams,
|
StreamAgentsApiV1AgentsStreamGetParams,
|
||||||
UpdateAgentApiV1AgentsAgentIdPatchParams,
|
UpdateAgentApiV1AgentsAgentIdPatchParams,
|
||||||
@@ -40,26 +42,52 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Agents
|
* @summary List Agents
|
||||||
*/
|
*/
|
||||||
export type listAgentsApiV1AgentsGetResponse200 = {
|
export type listAgentsApiV1AgentsGetResponse200 = {
|
||||||
data: AgentRead[];
|
data: LimitOffsetPageTypeVarCustomizedAgentRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type listAgentsApiV1AgentsGetResponse422 = {
|
||||||
|
data: HTTPValidationError;
|
||||||
|
status: 422;
|
||||||
|
};
|
||||||
|
|
||||||
export type listAgentsApiV1AgentsGetResponseSuccess =
|
export type listAgentsApiV1AgentsGetResponseSuccess =
|
||||||
listAgentsApiV1AgentsGetResponse200 & {
|
listAgentsApiV1AgentsGetResponse200 & {
|
||||||
headers: Headers;
|
headers: Headers;
|
||||||
};
|
};
|
||||||
export type listAgentsApiV1AgentsGetResponse =
|
export type listAgentsApiV1AgentsGetResponseError =
|
||||||
listAgentsApiV1AgentsGetResponseSuccess;
|
listAgentsApiV1AgentsGetResponse422 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
export const getListAgentsApiV1AgentsGetUrl = () => {
|
export type listAgentsApiV1AgentsGetResponse =
|
||||||
return `/api/v1/agents`;
|
| listAgentsApiV1AgentsGetResponseSuccess
|
||||||
|
| listAgentsApiV1AgentsGetResponseError;
|
||||||
|
|
||||||
|
export const getListAgentsApiV1AgentsGetUrl = (
|
||||||
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
|
) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/agents?${stringifiedParams}`
|
||||||
|
: `/api/v1/agents`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listAgentsApiV1AgentsGet = async (
|
export const listAgentsApiV1AgentsGet = async (
|
||||||
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listAgentsApiV1AgentsGetResponse> => {
|
): Promise<listAgentsApiV1AgentsGetResponse> => {
|
||||||
return customFetch<listAgentsApiV1AgentsGetResponse>(
|
return customFetch<listAgentsApiV1AgentsGetResponse>(
|
||||||
getListAgentsApiV1AgentsGetUrl(),
|
getListAgentsApiV1AgentsGetUrl(params),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -67,31 +95,37 @@ export const listAgentsApiV1AgentsGet = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListAgentsApiV1AgentsGetQueryKey = () => {
|
export const getListAgentsApiV1AgentsGetQueryKey = (
|
||||||
return [`/api/v1/agents`] as const;
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
|
) => {
|
||||||
|
return [`/api/v1/agents`, ...(params ? [params] : [])] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListAgentsApiV1AgentsGetQueryOptions = <
|
export const getListAgentsApiV1AgentsGetQueryOptions = <
|
||||||
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(options?: {
|
>(
|
||||||
query?: Partial<
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
UseQueryOptions<
|
options?: {
|
||||||
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
query?: Partial<
|
||||||
TError,
|
UseQueryOptions<
|
||||||
TData
|
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
>
|
TError,
|
||||||
>;
|
TData
|
||||||
request?: SecondParameter<typeof customFetch>;
|
>
|
||||||
}) => {
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getListAgentsApiV1AgentsGetQueryKey();
|
queryOptions?.queryKey ?? getListAgentsApiV1AgentsGetQueryKey(params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
|
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
|
||||||
> = ({ signal }) => listAgentsApiV1AgentsGet({ signal, ...requestOptions });
|
> = ({ signal }) =>
|
||||||
|
listAgentsApiV1AgentsGet(params, { signal, ...requestOptions });
|
||||||
|
|
||||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
@@ -103,12 +137,13 @@ export const getListAgentsApiV1AgentsGetQueryOptions = <
|
|||||||
export type ListAgentsApiV1AgentsGetQueryResult = NonNullable<
|
export type ListAgentsApiV1AgentsGetQueryResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
|
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
|
||||||
>;
|
>;
|
||||||
export type ListAgentsApiV1AgentsGetQueryError = unknown;
|
export type ListAgentsApiV1AgentsGetQueryError = HTTPValidationError;
|
||||||
|
|
||||||
export function useListAgentsApiV1AgentsGet<
|
export function useListAgentsApiV1AgentsGet<
|
||||||
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params: undefined | ListAgentsApiV1AgentsGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -133,8 +168,9 @@ export function useListAgentsApiV1AgentsGet<
|
|||||||
};
|
};
|
||||||
export function useListAgentsApiV1AgentsGet<
|
export function useListAgentsApiV1AgentsGet<
|
||||||
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -159,8 +195,9 @@ export function useListAgentsApiV1AgentsGet<
|
|||||||
};
|
};
|
||||||
export function useListAgentsApiV1AgentsGet<
|
export function useListAgentsApiV1AgentsGet<
|
||||||
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -181,8 +218,9 @@ export function useListAgentsApiV1AgentsGet<
|
|||||||
|
|
||||||
export function useListAgentsApiV1AgentsGet<
|
export function useListAgentsApiV1AgentsGet<
|
||||||
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListAgentsApiV1AgentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -197,7 +235,7 @@ export function useListAgentsApiV1AgentsGet<
|
|||||||
): UseQueryResult<TData, TError> & {
|
): UseQueryResult<TData, TError> & {
|
||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
} {
|
} {
|
||||||
const queryOptions = getListAgentsApiV1AgentsGetQueryOptions(options);
|
const queryOptions = getListAgentsApiV1AgentsGetQueryOptions(params, options);
|
||||||
|
|
||||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
TData,
|
TData,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import type {
|
|||||||
ApprovalRead,
|
ApprovalRead,
|
||||||
ApprovalUpdate,
|
ApprovalUpdate,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedApprovalRead,
|
||||||
ListApprovalsApiV1BoardsBoardIdApprovalsGetParams,
|
ListApprovalsApiV1BoardsBoardIdApprovalsGetParams,
|
||||||
StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams,
|
StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams,
|
||||||
} from ".././model";
|
} from ".././model";
|
||||||
@@ -37,7 +38,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Approvals
|
* @summary List Approvals
|
||||||
*/
|
*/
|
||||||
export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 = {
|
export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 = {
|
||||||
data: ApprovalRead[];
|
data: LimitOffsetPageTypeVarCustomizedApprovalRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
BoardMemoryCreate,
|
BoardMemoryCreate,
|
||||||
BoardMemoryRead,
|
BoardMemoryRead,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedBoardMemoryRead,
|
||||||
ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams,
|
ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams,
|
||||||
StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams,
|
StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams,
|
||||||
} from ".././model";
|
} from ".././model";
|
||||||
@@ -36,7 +37,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Board Memory
|
* @summary List Board Memory
|
||||||
*/
|
*/
|
||||||
export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 = {
|
export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 = {
|
||||||
data: BoardMemoryRead[];
|
data: LimitOffsetPageTypeVarCustomizedBoardMemoryRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ import type {
|
|||||||
import type {
|
import type {
|
||||||
BoardCreate,
|
BoardCreate,
|
||||||
BoardRead,
|
BoardRead,
|
||||||
|
BoardSnapshot,
|
||||||
BoardUpdate,
|
BoardUpdate,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedBoardRead,
|
||||||
|
ListBoardsApiV1BoardsGetParams,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
} from ".././model";
|
} from ".././model";
|
||||||
|
|
||||||
@@ -36,7 +39,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|||||||
* @summary List Boards
|
* @summary List Boards
|
||||||
*/
|
*/
|
||||||
export type listBoardsApiV1BoardsGetResponse200 = {
|
export type listBoardsApiV1BoardsGetResponse200 = {
|
||||||
data: BoardRead[];
|
data: LimitOffsetPageTypeVarCustomizedBoardRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,15 +61,30 @@ export type listBoardsApiV1BoardsGetResponse =
|
|||||||
| listBoardsApiV1BoardsGetResponseSuccess
|
| listBoardsApiV1BoardsGetResponseSuccess
|
||||||
| listBoardsApiV1BoardsGetResponseError;
|
| listBoardsApiV1BoardsGetResponseError;
|
||||||
|
|
||||||
export const getListBoardsApiV1BoardsGetUrl = () => {
|
export const getListBoardsApiV1BoardsGetUrl = (
|
||||||
return `/api/v1/boards`;
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
|
) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/boards?${stringifiedParams}`
|
||||||
|
: `/api/v1/boards`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listBoardsApiV1BoardsGet = async (
|
export const listBoardsApiV1BoardsGet = async (
|
||||||
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listBoardsApiV1BoardsGetResponse> => {
|
): Promise<listBoardsApiV1BoardsGetResponse> => {
|
||||||
return customFetch<listBoardsApiV1BoardsGetResponse>(
|
return customFetch<listBoardsApiV1BoardsGetResponse>(
|
||||||
getListBoardsApiV1BoardsGetUrl(),
|
getListBoardsApiV1BoardsGetUrl(params),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -74,31 +92,37 @@ export const listBoardsApiV1BoardsGet = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListBoardsApiV1BoardsGetQueryKey = () => {
|
export const getListBoardsApiV1BoardsGetQueryKey = (
|
||||||
return [`/api/v1/boards`] as const;
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
|
) => {
|
||||||
|
return [`/api/v1/boards`, ...(params ? [params] : [])] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListBoardsApiV1BoardsGetQueryOptions = <
|
export const getListBoardsApiV1BoardsGetQueryOptions = <
|
||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(options?: {
|
>(
|
||||||
query?: Partial<
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
UseQueryOptions<
|
options?: {
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
query?: Partial<
|
||||||
TError,
|
UseQueryOptions<
|
||||||
TData
|
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
>
|
TError,
|
||||||
>;
|
TData
|
||||||
request?: SecondParameter<typeof customFetch>;
|
>
|
||||||
}) => {
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getListBoardsApiV1BoardsGetQueryKey();
|
queryOptions?.queryKey ?? getListBoardsApiV1BoardsGetQueryKey(params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>
|
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>
|
||||||
> = ({ signal }) => listBoardsApiV1BoardsGet({ signal, ...requestOptions });
|
> = ({ signal }) =>
|
||||||
|
listBoardsApiV1BoardsGet(params, { signal, ...requestOptions });
|
||||||
|
|
||||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
@@ -116,6 +140,7 @@ export function useListBoardsApiV1BoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params: undefined | ListBoardsApiV1BoardsGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -142,6 +167,7 @@ export function useListBoardsApiV1BoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -168,6 +194,7 @@ export function useListBoardsApiV1BoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -190,6 +217,7 @@ export function useListBoardsApiV1BoardsGet<
|
|||||||
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
|
||||||
TError = HTTPValidationError,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListBoardsApiV1BoardsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -204,7 +232,7 @@ export function useListBoardsApiV1BoardsGet<
|
|||||||
): UseQueryResult<TData, TError> & {
|
): UseQueryResult<TData, TError> & {
|
||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
} {
|
} {
|
||||||
const queryOptions = getListBoardsApiV1BoardsGetQueryOptions(options);
|
const queryOptions = getListBoardsApiV1BoardsGetQueryOptions(params, options);
|
||||||
|
|
||||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
TData,
|
TData,
|
||||||
@@ -765,3 +793,240 @@ export const useDeleteBoardApiV1BoardsBoardIdDelete = <
|
|||||||
queryClient,
|
queryClient,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @summary Get Board Snapshot
|
||||||
|
*/
|
||||||
|
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse200 = {
|
||||||
|
data: BoardSnapshot;
|
||||||
|
status: 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse422 = {
|
||||||
|
data: HTTPValidationError;
|
||||||
|
status: 422;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseSuccess =
|
||||||
|
getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse200 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseError =
|
||||||
|
getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse422 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse =
|
||||||
|
| getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseSuccess
|
||||||
|
| getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseError;
|
||||||
|
|
||||||
|
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetUrl = (
|
||||||
|
boardId: string,
|
||||||
|
) => {
|
||||||
|
return `/api/v1/boards/${boardId}/snapshot`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBoardSnapshotApiV1BoardsBoardIdSnapshotGet = async (
|
||||||
|
boardId: string,
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse> => {
|
||||||
|
return customFetch<getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse>(
|
||||||
|
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetUrl(boardId),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryKey = (
|
||||||
|
boardId: string,
|
||||||
|
) => {
|
||||||
|
return [`/api/v1/boards/${boardId}/snapshot`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryOptions = <
|
||||||
|
TData = Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
>(
|
||||||
|
boardId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey =
|
||||||
|
queryOptions?.queryKey ??
|
||||||
|
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryKey(boardId);
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<
|
||||||
|
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>
|
||||||
|
> = ({ signal }) =>
|
||||||
|
getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(boardId, {
|
||||||
|
signal,
|
||||||
|
...requestOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
queryKey,
|
||||||
|
queryFn,
|
||||||
|
enabled: !!boardId,
|
||||||
|
...queryOptions,
|
||||||
|
} as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryResult =
|
||||||
|
NonNullable<
|
||||||
|
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>
|
||||||
|
>;
|
||||||
|
export type GetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryError =
|
||||||
|
HTTPValidationError;
|
||||||
|
|
||||||
|
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
|
||||||
|
TData = Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
>(
|
||||||
|
boardId: string,
|
||||||
|
options: {
|
||||||
|
query: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
> &
|
||||||
|
Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): DefinedUseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
|
||||||
|
TData = Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
>(
|
||||||
|
boardId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
> &
|
||||||
|
Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
|
||||||
|
TData = Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
>(
|
||||||
|
boardId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @summary Get Board Snapshot
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
|
||||||
|
TData = Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
>(
|
||||||
|
boardId: string,
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
} {
|
||||||
|
const queryOptions =
|
||||||
|
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryOptions(
|
||||||
|
boardId,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
|
TData,
|
||||||
|
TError
|
||||||
|
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
|
||||||
|
return { ...query, queryKey: queryOptions.queryKey };
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ import type {
|
|||||||
GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams,
|
GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams,
|
||||||
GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams,
|
GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedGatewayRead,
|
||||||
ListGatewaySessionsApiV1GatewaysSessionsGetParams,
|
ListGatewaySessionsApiV1GatewaysSessionsGetParams,
|
||||||
|
ListGatewaysApiV1GatewaysGetParams,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams,
|
SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams,
|
||||||
} from ".././model";
|
} from ".././model";
|
||||||
@@ -1451,26 +1453,52 @@ export function useGatewayCommandsApiV1GatewaysCommandsGet<
|
|||||||
* @summary List Gateways
|
* @summary List Gateways
|
||||||
*/
|
*/
|
||||||
export type listGatewaysApiV1GatewaysGetResponse200 = {
|
export type listGatewaysApiV1GatewaysGetResponse200 = {
|
||||||
data: GatewayRead[];
|
data: LimitOffsetPageTypeVarCustomizedGatewayRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type listGatewaysApiV1GatewaysGetResponse422 = {
|
||||||
|
data: HTTPValidationError;
|
||||||
|
status: 422;
|
||||||
|
};
|
||||||
|
|
||||||
export type listGatewaysApiV1GatewaysGetResponseSuccess =
|
export type listGatewaysApiV1GatewaysGetResponseSuccess =
|
||||||
listGatewaysApiV1GatewaysGetResponse200 & {
|
listGatewaysApiV1GatewaysGetResponse200 & {
|
||||||
headers: Headers;
|
headers: Headers;
|
||||||
};
|
};
|
||||||
export type listGatewaysApiV1GatewaysGetResponse =
|
export type listGatewaysApiV1GatewaysGetResponseError =
|
||||||
listGatewaysApiV1GatewaysGetResponseSuccess;
|
listGatewaysApiV1GatewaysGetResponse422 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
export const getListGatewaysApiV1GatewaysGetUrl = () => {
|
export type listGatewaysApiV1GatewaysGetResponse =
|
||||||
return `/api/v1/gateways`;
|
| listGatewaysApiV1GatewaysGetResponseSuccess
|
||||||
|
| listGatewaysApiV1GatewaysGetResponseError;
|
||||||
|
|
||||||
|
export const getListGatewaysApiV1GatewaysGetUrl = (
|
||||||
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
|
) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/gateways?${stringifiedParams}`
|
||||||
|
: `/api/v1/gateways`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listGatewaysApiV1GatewaysGet = async (
|
export const listGatewaysApiV1GatewaysGet = async (
|
||||||
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listGatewaysApiV1GatewaysGetResponse> => {
|
): Promise<listGatewaysApiV1GatewaysGetResponse> => {
|
||||||
return customFetch<listGatewaysApiV1GatewaysGetResponse>(
|
return customFetch<listGatewaysApiV1GatewaysGetResponse>(
|
||||||
getListGatewaysApiV1GatewaysGetUrl(),
|
getListGatewaysApiV1GatewaysGetUrl(params),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -1478,32 +1506,37 @@ export const listGatewaysApiV1GatewaysGet = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListGatewaysApiV1GatewaysGetQueryKey = () => {
|
export const getListGatewaysApiV1GatewaysGetQueryKey = (
|
||||||
return [`/api/v1/gateways`] as const;
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
|
) => {
|
||||||
|
return [`/api/v1/gateways`, ...(params ? [params] : [])] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListGatewaysApiV1GatewaysGetQueryOptions = <
|
export const getListGatewaysApiV1GatewaysGetQueryOptions = <
|
||||||
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(options?: {
|
>(
|
||||||
query?: Partial<
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
UseQueryOptions<
|
options?: {
|
||||||
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
query?: Partial<
|
||||||
TError,
|
UseQueryOptions<
|
||||||
TData
|
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
>
|
TError,
|
||||||
>;
|
TData
|
||||||
request?: SecondParameter<typeof customFetch>;
|
>
|
||||||
}) => {
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
queryOptions?.queryKey ?? getListGatewaysApiV1GatewaysGetQueryKey();
|
queryOptions?.queryKey ?? getListGatewaysApiV1GatewaysGetQueryKey(params);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
|
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
|
||||||
> = ({ signal }) =>
|
> = ({ signal }) =>
|
||||||
listGatewaysApiV1GatewaysGet({ signal, ...requestOptions });
|
listGatewaysApiV1GatewaysGet(params, { signal, ...requestOptions });
|
||||||
|
|
||||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
@@ -1515,12 +1548,13 @@ export const getListGatewaysApiV1GatewaysGetQueryOptions = <
|
|||||||
export type ListGatewaysApiV1GatewaysGetQueryResult = NonNullable<
|
export type ListGatewaysApiV1GatewaysGetQueryResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
|
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
|
||||||
>;
|
>;
|
||||||
export type ListGatewaysApiV1GatewaysGetQueryError = unknown;
|
export type ListGatewaysApiV1GatewaysGetQueryError = HTTPValidationError;
|
||||||
|
|
||||||
export function useListGatewaysApiV1GatewaysGet<
|
export function useListGatewaysApiV1GatewaysGet<
|
||||||
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params: undefined | ListGatewaysApiV1GatewaysGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1545,8 +1579,9 @@ export function useListGatewaysApiV1GatewaysGet<
|
|||||||
};
|
};
|
||||||
export function useListGatewaysApiV1GatewaysGet<
|
export function useListGatewaysApiV1GatewaysGet<
|
||||||
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1571,8 +1606,9 @@ export function useListGatewaysApiV1GatewaysGet<
|
|||||||
};
|
};
|
||||||
export function useListGatewaysApiV1GatewaysGet<
|
export function useListGatewaysApiV1GatewaysGet<
|
||||||
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1593,8 +1629,9 @@ export function useListGatewaysApiV1GatewaysGet<
|
|||||||
|
|
||||||
export function useListGatewaysApiV1GatewaysGet<
|
export function useListGatewaysApiV1GatewaysGet<
|
||||||
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
|
||||||
TError = unknown,
|
TError = HTTPValidationError,
|
||||||
>(
|
>(
|
||||||
|
params?: ListGatewaysApiV1GatewaysGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1609,7 +1646,10 @@ export function useListGatewaysApiV1GatewaysGet<
|
|||||||
): UseQueryResult<TData, TError> & {
|
): UseQueryResult<TData, TError> & {
|
||||||
queryKey: DataTag<QueryKey, TData, TError>;
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
} {
|
} {
|
||||||
const queryOptions = getListGatewaysApiV1GatewaysGetQueryOptions(options);
|
const queryOptions = getListGatewaysApiV1GatewaysGetQueryOptions(
|
||||||
|
params,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
TData,
|
TData,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { ApprovalCreateStatus } from "./approvalCreateStatus";
|
|||||||
|
|
||||||
export interface ApprovalCreate {
|
export interface ApprovalCreate {
|
||||||
action_type: string;
|
action_type: string;
|
||||||
|
task_id?: string | null;
|
||||||
payload?: ApprovalCreatePayload;
|
payload?: ApprovalCreatePayload;
|
||||||
confidence: number;
|
confidence: number;
|
||||||
rubric_scores?: ApprovalCreateRubricScores;
|
rubric_scores?: ApprovalCreateRubricScores;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { ApprovalReadStatus } from "./approvalReadStatus";
|
|||||||
|
|
||||||
export interface ApprovalRead {
|
export interface ApprovalRead {
|
||||||
action_type: string;
|
action_type: string;
|
||||||
|
task_id?: string | null;
|
||||||
payload?: ApprovalReadPayload;
|
payload?: ApprovalReadPayload;
|
||||||
confidence: number;
|
confidence: number;
|
||||||
rubric_scores?: ApprovalReadRubricScores;
|
rubric_scores?: ApprovalReadRubricScores;
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ export interface BoardMemoryRead {
|
|||||||
source?: string | null;
|
source?: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
board_id: string;
|
board_id: string;
|
||||||
|
is_chat?: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
20
frontend/src/api/generated/model/boardSnapshot.ts
Normal file
20
frontend/src/api/generated/model/boardSnapshot.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { AgentRead } from "./agentRead";
|
||||||
|
import type { ApprovalRead } from "./approvalRead";
|
||||||
|
import type { BoardMemoryRead } from "./boardMemoryRead";
|
||||||
|
import type { BoardRead } from "./boardRead";
|
||||||
|
import type { TaskCardRead } from "./taskCardRead";
|
||||||
|
|
||||||
|
export interface BoardSnapshot {
|
||||||
|
board: BoardRead;
|
||||||
|
tasks: TaskCardRead[];
|
||||||
|
agents: AgentRead[];
|
||||||
|
approvals: ApprovalRead[];
|
||||||
|
chat_messages: BoardMemoryRead[];
|
||||||
|
pending_approvals_count?: number;
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ export * from "./boardOnboardingReadMessages";
|
|||||||
export * from "./boardOnboardingStart";
|
export * from "./boardOnboardingStart";
|
||||||
export * from "./boardRead";
|
export * from "./boardRead";
|
||||||
export * from "./boardReadSuccessMetrics";
|
export * from "./boardReadSuccessMetrics";
|
||||||
|
export * from "./boardSnapshot";
|
||||||
export * from "./boardUpdate";
|
export * from "./boardUpdate";
|
||||||
export * from "./boardUpdateSuccessMetrics";
|
export * from "./boardUpdateSuccessMetrics";
|
||||||
export * from "./confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200";
|
export * from "./confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200";
|
||||||
@@ -90,15 +91,29 @@ export * from "./getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams
|
|||||||
export * from "./healthHealthGet200";
|
export * from "./healthHealthGet200";
|
||||||
export * from "./healthzHealthzGet200";
|
export * from "./healthzHealthzGet200";
|
||||||
export * from "./hTTPValidationError";
|
export * from "./hTTPValidationError";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedActivityEventRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedAgentRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedApprovalRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedBoardMemoryRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedBoardRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedGatewayRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedTaskCommentRead";
|
||||||
|
export * from "./limitOffsetPageTypeVarCustomizedTaskRead";
|
||||||
export * from "./listActivityApiV1ActivityGetParams";
|
export * from "./listActivityApiV1ActivityGetParams";
|
||||||
export * from "./listAgentsApiV1AgentAgentsGetParams";
|
export * from "./listAgentsApiV1AgentAgentsGetParams";
|
||||||
|
export * from "./listAgentsApiV1AgentsGetParams";
|
||||||
export * from "./listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams";
|
export * from "./listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams";
|
||||||
export * from "./listApprovalsApiV1BoardsBoardIdApprovalsGetParams";
|
export * from "./listApprovalsApiV1BoardsBoardIdApprovalsGetParams";
|
||||||
export * from "./listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams";
|
export * from "./listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams";
|
||||||
export * from "./listBoardMemoryApiV1BoardsBoardIdMemoryGetParams";
|
export * from "./listBoardMemoryApiV1BoardsBoardIdMemoryGetParams";
|
||||||
|
export * from "./listBoardsApiV1AgentBoardsGetParams";
|
||||||
|
export * from "./listBoardsApiV1BoardsGetParams";
|
||||||
|
export * from "./listGatewaysApiV1GatewaysGetParams";
|
||||||
export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams";
|
export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams";
|
||||||
export * from "./listSessionsApiV1GatewaySessionsGet200";
|
export * from "./listSessionsApiV1GatewaySessionsGet200";
|
||||||
export * from "./listSessionsApiV1GatewaySessionsGetParams";
|
export * from "./listSessionsApiV1GatewaySessionsGetParams";
|
||||||
|
export * from "./listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams";
|
||||||
|
export * from "./listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams";
|
||||||
export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams";
|
export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams";
|
||||||
export * from "./listTasksApiV1BoardsBoardIdTasksGetParams";
|
export * from "./listTasksApiV1BoardsBoardIdTasksGetParams";
|
||||||
export * from "./okResponse";
|
export * from "./okResponse";
|
||||||
@@ -111,6 +126,8 @@ export * from "./streamAgentsApiV1AgentsStreamGetParams";
|
|||||||
export * from "./streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams";
|
export * from "./streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams";
|
||||||
export * from "./streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams";
|
export * from "./streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams";
|
||||||
export * from "./streamTasksApiV1BoardsBoardIdTasksStreamGetParams";
|
export * from "./streamTasksApiV1BoardsBoardIdTasksStreamGetParams";
|
||||||
|
export * from "./taskCardRead";
|
||||||
|
export * from "./taskCardReadStatus";
|
||||||
export * from "./taskCommentCreate";
|
export * from "./taskCommentCreate";
|
||||||
export * from "./taskCommentRead";
|
export * from "./taskCommentRead";
|
||||||
export * from "./taskCreate";
|
export * from "./taskCreate";
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ActivityEventRead } from "./activityEventRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedActivityEventRead {
|
||||||
|
items: ActivityEventRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { AgentRead } from "./agentRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedAgentRead {
|
||||||
|
items: AgentRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ApprovalRead } from "./approvalRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedApprovalRead {
|
||||||
|
items: ApprovalRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { BoardMemoryRead } from "./boardMemoryRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedBoardMemoryRead {
|
||||||
|
items: BoardMemoryRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { BoardRead } from "./boardRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedBoardRead {
|
||||||
|
items: BoardRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { GatewayRead } from "./gatewayRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedGatewayRead {
|
||||||
|
items: GatewayRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { TaskCommentRead } from "./taskCommentRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedTaskCommentRead {
|
||||||
|
items: TaskCommentRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { TaskRead } from "./taskRead";
|
||||||
|
|
||||||
|
export interface LimitOffsetPageTypeVarCustomizedTaskRead {
|
||||||
|
items: TaskRead[];
|
||||||
|
/** @minimum 0 */
|
||||||
|
total: number;
|
||||||
|
/** @minimum 1 */
|
||||||
|
limit: number;
|
||||||
|
/** @minimum 0 */
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
@@ -7,5 +7,13 @@
|
|||||||
|
|
||||||
export type ListAgentsApiV1AgentAgentsGetParams = {
|
export type ListAgentsApiV1AgentAgentsGetParams = {
|
||||||
board_id?: string | null;
|
board_id?: string | null;
|
||||||
limit?: number | null;
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListAgentsApiV1AgentsGetParams = {
|
||||||
|
board_id?: string | null;
|
||||||
|
gateway_id?: string | null;
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -7,4 +7,13 @@
|
|||||||
|
|
||||||
export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams = {
|
export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams = {
|
||||||
status?: "pending" | "approved" | "rejected" | null;
|
status?: "pending" | "approved" | "rejected" | null;
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,4 +7,13 @@
|
|||||||
|
|
||||||
export type ListApprovalsApiV1BoardsBoardIdApprovalsGetParams = {
|
export type ListApprovalsApiV1BoardsBoardIdApprovalsGetParams = {
|
||||||
status?: "pending" | "approved" | "rejected" | null;
|
status?: "pending" | "approved" | "rejected" | null;
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams = {
|
export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams = {
|
||||||
|
is_chat?: boolean | null;
|
||||||
/**
|
/**
|
||||||
* @minimum 1
|
* @minimum 1
|
||||||
* @maximum 200
|
* @maximum 200
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListBoardsApiV1AgentBoardsGetParams = {
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListBoardsApiV1BoardsGetParams = {
|
||||||
|
gateway_id?: string | null;
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListGatewaysApiV1GatewaysGetParams = {
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams = {
|
||||||
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -9,5 +9,13 @@ export type ListTasksApiV1AgentBoardsBoardIdTasksGetParams = {
|
|||||||
status?: string | null;
|
status?: string | null;
|
||||||
assigned_agent_id?: string | null;
|
assigned_agent_id?: string | null;
|
||||||
unassigned?: boolean | null;
|
unassigned?: boolean | null;
|
||||||
limit?: number | null;
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,5 +9,13 @@ export type ListTasksApiV1BoardsBoardIdTasksGetParams = {
|
|||||||
status?: string | null;
|
status?: string | null;
|
||||||
assigned_agent_id?: string | null;
|
assigned_agent_id?: string | null;
|
||||||
unassigned?: boolean | null;
|
unassigned?: boolean | null;
|
||||||
limit?: number | null;
|
/**
|
||||||
|
* @minimum 1
|
||||||
|
* @maximum 200
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
/**
|
||||||
|
* @minimum 0
|
||||||
|
*/
|
||||||
|
offset?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,4 +7,5 @@
|
|||||||
|
|
||||||
export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams = {
|
export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams = {
|
||||||
since?: string | null;
|
since?: string | null;
|
||||||
|
is_chat?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|||||||
25
frontend/src/api/generated/model/taskCardRead.ts
Normal file
25
frontend/src/api/generated/model/taskCardRead.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { TaskCardReadStatus } from "./taskCardReadStatus";
|
||||||
|
|
||||||
|
export interface TaskCardRead {
|
||||||
|
title: string;
|
||||||
|
description?: string | null;
|
||||||
|
status?: TaskCardReadStatus;
|
||||||
|
priority?: string;
|
||||||
|
due_at?: string | null;
|
||||||
|
assigned_agent_id?: string | null;
|
||||||
|
id: string;
|
||||||
|
board_id: string | null;
|
||||||
|
created_by_user_id: string | null;
|
||||||
|
in_progress_at: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
assignee?: string | null;
|
||||||
|
approvals_count?: number;
|
||||||
|
approvals_pending_count?: number;
|
||||||
|
}
|
||||||
16
frontend/src/api/generated/model/taskCardReadStatus.ts
Normal file
16
frontend/src/api/generated/model/taskCardReadStatus.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Mission Control API
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type TaskCardReadStatus =
|
||||||
|
(typeof TaskCardReadStatus)[keyof typeof TaskCardReadStatus];
|
||||||
|
|
||||||
|
export const TaskCardReadStatus = {
|
||||||
|
inbox: "inbox",
|
||||||
|
in_progress: "in_progress",
|
||||||
|
review: "review",
|
||||||
|
done: "done",
|
||||||
|
} as const;
|
||||||
@@ -22,6 +22,9 @@ import type {
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
|
LimitOffsetPageTypeVarCustomizedTaskCommentRead,
|
||||||
|
LimitOffsetPageTypeVarCustomizedTaskRead,
|
||||||
|
ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
ListTasksApiV1BoardsBoardIdTasksGetParams,
|
ListTasksApiV1BoardsBoardIdTasksGetParams,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
StreamTasksApiV1BoardsBoardIdTasksStreamGetParams,
|
StreamTasksApiV1BoardsBoardIdTasksStreamGetParams,
|
||||||
@@ -292,7 +295,7 @@ export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet<
|
|||||||
* @summary List Tasks
|
* @summary List Tasks
|
||||||
*/
|
*/
|
||||||
export type listTasksApiV1BoardsBoardIdTasksGetResponse200 = {
|
export type listTasksApiV1BoardsBoardIdTasksGetResponse200 = {
|
||||||
data: TaskRead[];
|
data: LimitOffsetPageTypeVarCustomizedTaskRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -900,7 +903,7 @@ export const useDeleteTaskApiV1BoardsBoardIdTasksTaskIdDelete = <
|
|||||||
*/
|
*/
|
||||||
export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse200 =
|
export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse200 =
|
||||||
{
|
{
|
||||||
data: TaskCommentRead[];
|
data: LimitOffsetPageTypeVarCustomizedTaskCommentRead;
|
||||||
status: 200;
|
status: 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -926,19 +929,34 @@ export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse =
|
|||||||
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl = (
|
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl = (
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
) => {
|
) => {
|
||||||
return `/api/v1/boards/${boardId}/tasks/${taskId}/comments`;
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0
|
||||||
|
? `/api/v1/boards/${boardId}/tasks/${taskId}/comments?${stringifiedParams}`
|
||||||
|
: `/api/v1/boards/${boardId}/tasks/${taskId}/comments`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async (
|
export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async (
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
): Promise<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse> => {
|
): Promise<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse> => {
|
||||||
return customFetch<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse>(
|
return customFetch<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse>(
|
||||||
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl(
|
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
@@ -948,8 +966,15 @@ export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey =
|
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey =
|
||||||
(boardId: string, taskId: string) => {
|
(
|
||||||
return [`/api/v1/boards/${boardId}/tasks/${taskId}/comments`] as const;
|
boardId: string,
|
||||||
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
|
) => {
|
||||||
|
return [
|
||||||
|
`/api/v1/boards/${boardId}/tasks/${taskId}/comments`,
|
||||||
|
...(params ? [params] : []),
|
||||||
|
] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions =
|
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions =
|
||||||
@@ -963,6 +988,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -985,6 +1011,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
|
|||||||
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey(
|
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryFn: QueryFunction<
|
const queryFn: QueryFunction<
|
||||||
@@ -997,6 +1024,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
|
|||||||
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet(
|
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
{ signal, ...requestOptions },
|
{ signal, ...requestOptions },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1035,6 +1063,9 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params:
|
||||||
|
| undefined
|
||||||
|
| ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options: {
|
options: {
|
||||||
query: Partial<
|
query: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1077,6 +1108,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1119,6 +1151,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1149,6 +1182,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
|
|||||||
>(
|
>(
|
||||||
boardId: string,
|
boardId: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
|
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
|
||||||
options?: {
|
options?: {
|
||||||
query?: Partial<
|
query?: Partial<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -1171,6 +1205,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
|
|||||||
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
|
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
|
||||||
boardId,
|
boardId,
|
||||||
taskId,
|
taskId,
|
||||||
|
params,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default function EditAgentPage() {
|
|||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||||
listBoardsApiV1BoardsGetResponse,
|
listBoardsApiV1BoardsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
@@ -148,7 +148,8 @@ export default function EditAgentPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : [];
|
const boards =
|
||||||
|
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
|
||||||
const loadedAgent: AgentRead | null =
|
const loadedAgent: AgentRead | null =
|
||||||
agentQuery.data?.status === 200 ? agentQuery.data.data : null;
|
agentQuery.data?.status === 200 ? agentQuery.data.data : null;
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function AgentDetailPage() {
|
|||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||||
listBoardsApiV1BoardsGetResponse,
|
listBoardsApiV1BoardsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchInterval: 60_000,
|
refetchInterval: 60_000,
|
||||||
@@ -118,9 +118,11 @@ export default function AgentDetailPage() {
|
|||||||
const agent: AgentRead | null =
|
const agent: AgentRead | null =
|
||||||
agentQuery.data?.status === 200 ? agentQuery.data.data : null;
|
agentQuery.data?.status === 200 ? agentQuery.data.data : null;
|
||||||
const events: ActivityEventRead[] =
|
const events: ActivityEventRead[] =
|
||||||
activityQuery.data?.status === 200 ? activityQuery.data.data : [];
|
activityQuery.data?.status === 200
|
||||||
|
? activityQuery.data.data.items ?? []
|
||||||
|
: [];
|
||||||
const boards: BoardRead[] =
|
const boards: BoardRead[] =
|
||||||
boardsQuery.data?.status === 200 ? boardsQuery.data.data : [];
|
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
|
||||||
|
|
||||||
const agentEvents = useMemo(() => {
|
const agentEvents = useMemo(() => {
|
||||||
if (!agent) return [];
|
if (!agent) return [];
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default function NewAgentPage() {
|
|||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||||
listBoardsApiV1BoardsGetResponse,
|
listBoardsApiV1BoardsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
@@ -111,7 +111,8 @@ export default function NewAgentPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : [];
|
const boards =
|
||||||
|
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
|
||||||
const displayBoardId = boardId || boards[0]?.id || "";
|
const displayBoardId = boardId || boards[0]?.id || "";
|
||||||
const isLoading = boardsQuery.isLoading || createAgentMutation.isPending;
|
const isLoading = boardsQuery.isLoading || createAgentMutation.isPending;
|
||||||
const errorMessage = error ?? boardsQuery.error?.message ?? null;
|
const errorMessage = error ?? boardsQuery.error?.message ?? null;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default function AgentsPage() {
|
|||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||||
listBoardsApiV1BoardsGetResponse,
|
listBoardsApiV1BoardsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchInterval: 30_000,
|
refetchInterval: 30_000,
|
||||||
@@ -109,7 +109,7 @@ export default function AgentsPage() {
|
|||||||
const agentsQuery = useListAgentsApiV1AgentsGet<
|
const agentsQuery = useListAgentsApiV1AgentsGet<
|
||||||
listAgentsApiV1AgentsGetResponse,
|
listAgentsApiV1AgentsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchInterval: 15_000,
|
refetchInterval: 15_000,
|
||||||
@@ -118,10 +118,15 @@ export default function AgentsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const boards = useMemo(
|
const boards = useMemo(
|
||||||
() => (boardsQuery.data?.status === 200 ? boardsQuery.data.data : []),
|
() =>
|
||||||
|
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [],
|
||||||
[boardsQuery.data]
|
[boardsQuery.data]
|
||||||
);
|
);
|
||||||
const agents = useMemo(() => agentsQuery.data?.data ?? [], [agentsQuery.data]);
|
const agents = useMemo(
|
||||||
|
() =>
|
||||||
|
agentsQuery.data?.status === 200 ? agentsQuery.data.data.items ?? [] : [],
|
||||||
|
[agentsQuery.data]
|
||||||
|
);
|
||||||
|
|
||||||
const deleteMutation = useDeleteAgentApiV1AgentsAgentIdDelete<
|
const deleteMutation = useDeleteAgentApiV1AgentsAgentIdDelete<
|
||||||
ApiError,
|
ApiError,
|
||||||
@@ -133,10 +138,18 @@ export default function AgentsPage() {
|
|||||||
await queryClient.cancelQueries({ queryKey: agentsKey });
|
await queryClient.cancelQueries({ queryKey: agentsKey });
|
||||||
const previous =
|
const previous =
|
||||||
queryClient.getQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey);
|
queryClient.getQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey);
|
||||||
if (previous) {
|
if (previous && previous.status === 200) {
|
||||||
|
const nextItems = previous.data.items.filter(
|
||||||
|
(agent) => agent.id !== agentId
|
||||||
|
);
|
||||||
|
const removedCount = previous.data.items.length - nextItems.length;
|
||||||
queryClient.setQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey, {
|
queryClient.setQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey, {
|
||||||
...previous,
|
...previous,
|
||||||
data: previous.data.filter((agent) => agent.id !== agentId),
|
data: {
|
||||||
|
...previous.data,
|
||||||
|
items: nextItems,
|
||||||
|
total: Math.max(0, previous.data.total - removedCount),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { previous };
|
return { previous };
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import {
|
|||||||
useListGatewaysApiV1GatewaysGet,
|
useListGatewaysApiV1GatewaysGet,
|
||||||
} from "@/api/generated/gateways/gateways";
|
} from "@/api/generated/gateways/gateways";
|
||||||
import type { BoardRead, BoardUpdate } from "@/api/generated/model";
|
import type { BoardRead, BoardUpdate } from "@/api/generated/model";
|
||||||
import { BoardApprovalsPanel } from "@/components/BoardApprovalsPanel";
|
|
||||||
import { BoardGoalPanel } from "@/components/BoardGoalPanel";
|
|
||||||
import { BoardOnboardingChat } from "@/components/BoardOnboardingChat";
|
import { BoardOnboardingChat } from "@/components/BoardOnboardingChat";
|
||||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
@@ -72,7 +70,7 @@ export default function EditBoardPage() {
|
|||||||
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
||||||
listGatewaysApiV1GatewaysGetResponse,
|
listGatewaysApiV1GatewaysGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
@@ -105,7 +103,9 @@ export default function EditBoardPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const gateways =
|
const gateways =
|
||||||
gatewaysQuery.data?.status === 200 ? gatewaysQuery.data.data : [];
|
gatewaysQuery.data?.status === 200
|
||||||
|
? gatewaysQuery.data.data.items ?? []
|
||||||
|
: [];
|
||||||
const loadedBoard: BoardRead | null =
|
const loadedBoard: BoardRead | null =
|
||||||
boardQuery.data?.status === 200 ? boardQuery.data.data : null;
|
boardQuery.data?.status === 200 ? boardQuery.data.data : null;
|
||||||
const baseBoard = board ?? loadedBoard;
|
const baseBoard = board ?? loadedBoard;
|
||||||
@@ -224,135 +224,149 @@ export default function EditBoardPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
|
<div className="space-y-6">
|
||||||
<div className="space-y-6">
|
<form
|
||||||
<BoardGoalPanel
|
onSubmit={handleSubmit}
|
||||||
board={baseBoard}
|
className="space-y-6 rounded-xl border border-slate-200 bg-white p-6 shadow-sm"
|
||||||
onStartOnboarding={() => setIsOnboardingOpen(true)}
|
>
|
||||||
/>
|
{resolvedBoardType !== "general" &&
|
||||||
<form
|
baseBoard &&
|
||||||
onSubmit={handleSubmit}
|
!(baseBoard.goal_confirmed ?? false) ? (
|
||||||
className="space-y-6 rounded-xl border border-slate-200 bg-white p-6 shadow-sm"
|
<div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3">
|
||||||
>
|
<div className="min-w-0">
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<p className="text-sm font-semibold text-amber-900">
|
||||||
<div className="space-y-2">
|
Goal needs confirmation
|
||||||
<label className="text-sm font-medium text-slate-900">
|
</p>
|
||||||
Board name <span className="text-red-500">*</span>
|
<p className="mt-1 text-xs text-amber-800/80">
|
||||||
</label>
|
Start onboarding to draft an objective and success
|
||||||
<Input
|
metrics.
|
||||||
value={resolvedName}
|
</p>
|
||||||
onChange={(event) => setName(event.target.value)}
|
|
||||||
placeholder="Board name"
|
|
||||||
disabled={isLoading || !baseBoard}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-slate-900">
|
|
||||||
Gateway <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<SearchableSelect
|
|
||||||
ariaLabel="Select gateway"
|
|
||||||
value={displayGatewayId}
|
|
||||||
onValueChange={setGatewayId}
|
|
||||||
options={gatewayOptions}
|
|
||||||
placeholder="Select gateway"
|
|
||||||
searchPlaceholder="Search gateways..."
|
|
||||||
emptyMessage="No gateways found."
|
|
||||||
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
||||||
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
|
||||||
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-slate-900">
|
|
||||||
Board type
|
|
||||||
</label>
|
|
||||||
<Select value={resolvedBoardType} onValueChange={setBoardType}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select board type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="goal">Goal</SelectItem>
|
|
||||||
<SelectItem value="general">General</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-slate-900">
|
|
||||||
Target date
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="date"
|
|
||||||
value={resolvedTargetDate}
|
|
||||||
onChange={(event) => setTargetDate(event.target.value)}
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-slate-900">
|
|
||||||
Objective
|
|
||||||
</label>
|
|
||||||
<Textarea
|
|
||||||
value={resolvedObjective}
|
|
||||||
onChange={(event) => setObjective(event.target.value)}
|
|
||||||
placeholder="What should this board achieve?"
|
|
||||||
className="min-h-[120px]"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-slate-900">
|
|
||||||
Success metrics (JSON)
|
|
||||||
</label>
|
|
||||||
<Textarea
|
|
||||||
value={resolvedSuccessMetrics}
|
|
||||||
onChange={(event) => setSuccessMetrics(event.target.value)}
|
|
||||||
placeholder='e.g. { "target": "Launch by week 2" }'
|
|
||||||
className="min-h-[140px] font-mono text-xs"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-slate-500">
|
|
||||||
Add key outcomes so the lead agent can measure progress.
|
|
||||||
</p>
|
|
||||||
{metricsError ? (
|
|
||||||
<p className="text-xs text-red-500">{metricsError}</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{gateways.length === 0 ? (
|
|
||||||
<div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
|
||||||
<p>No gateways available. Create one in Gateways to continue.</p>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{errorMessage ? (
|
|
||||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="secondary"
|
||||||
onClick={() => router.push(`/boards/${boardId}`)}
|
onClick={() => setIsOnboardingOpen(true)}
|
||||||
disabled={isLoading}
|
disabled={isLoading || !baseBoard}
|
||||||
>
|
>
|
||||||
Cancel
|
Start onboarding
|
||||||
</Button>
|
|
||||||
<Button type="submit" disabled={isLoading || !baseBoard || !isFormReady}>
|
|
||||||
{isLoading ? "Saving…" : "Save changes"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
) : null}
|
||||||
</div>
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-6">
|
<div className="space-y-2">
|
||||||
{boardId ? <BoardApprovalsPanel boardId={boardId} /> : null}
|
<label className="text-sm font-medium text-slate-900">
|
||||||
</div>
|
Board name <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={resolvedName}
|
||||||
|
onChange={(event) => setName(event.target.value)}
|
||||||
|
placeholder="Board name"
|
||||||
|
disabled={isLoading || !baseBoard}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-900">
|
||||||
|
Gateway <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<SearchableSelect
|
||||||
|
ariaLabel="Select gateway"
|
||||||
|
value={displayGatewayId}
|
||||||
|
onValueChange={setGatewayId}
|
||||||
|
options={gatewayOptions}
|
||||||
|
placeholder="Select gateway"
|
||||||
|
searchPlaceholder="Search gateways..."
|
||||||
|
emptyMessage="No gateways found."
|
||||||
|
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
||||||
|
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
||||||
|
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-900">
|
||||||
|
Board type
|
||||||
|
</label>
|
||||||
|
<Select value={resolvedBoardType} onValueChange={setBoardType}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select board type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="goal">Goal</SelectItem>
|
||||||
|
<SelectItem value="general">General</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-900">
|
||||||
|
Target date
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={resolvedTargetDate}
|
||||||
|
onChange={(event) => setTargetDate(event.target.value)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-900">
|
||||||
|
Objective
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={resolvedObjective}
|
||||||
|
onChange={(event) => setObjective(event.target.value)}
|
||||||
|
placeholder="What should this board achieve?"
|
||||||
|
className="min-h-[120px]"
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-900">
|
||||||
|
Success metrics (JSON)
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={resolvedSuccessMetrics}
|
||||||
|
onChange={(event) => setSuccessMetrics(event.target.value)}
|
||||||
|
placeholder='e.g. { "target": "Launch by week 2" }'
|
||||||
|
className="min-h-[140px] font-mono text-xs"
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-slate-500">
|
||||||
|
Add key outcomes so the lead agent can measure progress.
|
||||||
|
</p>
|
||||||
|
{metricsError ? (
|
||||||
|
<p className="text-xs text-red-500">{metricsError}</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{gateways.length === 0 ? (
|
||||||
|
<div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||||||
|
<p>No gateways available. Create one in Gateways to continue.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{errorMessage ? (
|
||||||
|
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => router.push(`/boards/${boardId}`)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={isLoading || !baseBoard || !isFormReady}>
|
||||||
|
{isLoading ? "Saving…" : "Save changes"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -28,16 +28,14 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { listAgentsApiV1AgentsGet, streamAgentsApiV1AgentsStreamGet } from "@/api/generated/agents/agents";
|
import { streamAgentsApiV1AgentsStreamGet } from "@/api/generated/agents/agents";
|
||||||
import {
|
import {
|
||||||
listApprovalsApiV1BoardsBoardIdApprovalsGet,
|
|
||||||
streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet,
|
streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet,
|
||||||
updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
|
updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
|
||||||
} from "@/api/generated/approvals/approvals";
|
} from "@/api/generated/approvals/approvals";
|
||||||
import { getBoardApiV1BoardsBoardIdGet } from "@/api/generated/boards/boards";
|
import { getBoardSnapshotApiV1BoardsBoardIdSnapshotGet } from "@/api/generated/boards/boards";
|
||||||
import {
|
import {
|
||||||
createBoardMemoryApiV1BoardsBoardIdMemoryPost,
|
createBoardMemoryApiV1BoardsBoardIdMemoryPost,
|
||||||
listBoardMemoryApiV1BoardsBoardIdMemoryGet,
|
|
||||||
streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet,
|
streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet,
|
||||||
} from "@/api/generated/board-memory/board-memory";
|
} from "@/api/generated/board-memory/board-memory";
|
||||||
import {
|
import {
|
||||||
@@ -45,7 +43,6 @@ import {
|
|||||||
createTaskCommentApiV1BoardsBoardIdTasksTaskIdCommentsPost,
|
createTaskCommentApiV1BoardsBoardIdTasksTaskIdCommentsPost,
|
||||||
deleteTaskApiV1BoardsBoardIdTasksTaskIdDelete,
|
deleteTaskApiV1BoardsBoardIdTasksTaskIdDelete,
|
||||||
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet,
|
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet,
|
||||||
listTasksApiV1BoardsBoardIdTasksGet,
|
|
||||||
streamTasksApiV1BoardsBoardIdTasksStreamGet,
|
streamTasksApiV1BoardsBoardIdTasksStreamGet,
|
||||||
updateTaskApiV1BoardsBoardIdTasksTaskIdPatch,
|
updateTaskApiV1BoardsBoardIdTasksTaskIdPatch,
|
||||||
} from "@/api/generated/tasks/tasks";
|
} from "@/api/generated/tasks/tasks";
|
||||||
@@ -54,6 +51,7 @@ import type {
|
|||||||
ApprovalRead,
|
ApprovalRead,
|
||||||
BoardMemoryRead,
|
BoardMemoryRead,
|
||||||
BoardRead,
|
BoardRead,
|
||||||
|
TaskCardRead,
|
||||||
TaskCommentRead,
|
TaskCommentRead,
|
||||||
TaskRead,
|
TaskRead,
|
||||||
} from "@/api/generated/model";
|
} from "@/api/generated/model";
|
||||||
@@ -61,13 +59,16 @@ import { cn } from "@/lib/utils";
|
|||||||
|
|
||||||
type Board = BoardRead;
|
type Board = BoardRead;
|
||||||
|
|
||||||
type TaskStatus = Exclude<TaskRead["status"], undefined>;
|
type TaskStatus = Exclude<TaskCardRead["status"], undefined>;
|
||||||
|
|
||||||
type Task = TaskRead & {
|
type Task = Omit<
|
||||||
|
TaskCardRead,
|
||||||
|
"status" | "priority" | "approvals_count" | "approvals_pending_count"
|
||||||
|
> & {
|
||||||
status: TaskStatus;
|
status: TaskStatus;
|
||||||
priority: string;
|
priority: string;
|
||||||
approvalsCount?: number;
|
approvals_count: number;
|
||||||
approvalsPendingCount?: number;
|
approvals_pending_count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Agent = AgentRead & { status: string };
|
type Agent = AgentRead & { status: string };
|
||||||
@@ -78,10 +79,12 @@ type Approval = ApprovalRead & { status: string };
|
|||||||
|
|
||||||
type BoardChatMessage = BoardMemoryRead;
|
type BoardChatMessage = BoardMemoryRead;
|
||||||
|
|
||||||
const normalizeTask = (task: TaskRead): Task => ({
|
const normalizeTask = (task: TaskCardRead): Task => ({
|
||||||
...task,
|
...task,
|
||||||
status: task.status ?? "inbox",
|
status: task.status ?? "inbox",
|
||||||
priority: task.priority ?? "medium",
|
priority: task.priority ?? "medium",
|
||||||
|
approvals_count: task.approvals_count ?? 0,
|
||||||
|
approvals_pending_count: task.approvals_pending_count ?? 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeAgent = (agent: AgentRead): Agent => ({
|
const normalizeAgent = (agent: AgentRead): Agent => ({
|
||||||
@@ -94,15 +97,6 @@ const normalizeApproval = (approval: ApprovalRead): Approval => ({
|
|||||||
status: approval.status ?? "pending",
|
status: approval.status ?? "pending",
|
||||||
});
|
});
|
||||||
|
|
||||||
const approvalTaskId = (approval: Approval) => {
|
|
||||||
const payload = approval.payload ?? {};
|
|
||||||
return (
|
|
||||||
(payload as Record<string, unknown>).task_id ??
|
|
||||||
(payload as Record<string, unknown>).taskId ??
|
|
||||||
(payload as Record<string, unknown>).taskID
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const priorities = [
|
const priorities = [
|
||||||
{ value: "low", label: "Low" },
|
{ value: "low", label: "Low" },
|
||||||
{ value: "medium", label: "Medium" },
|
{ value: "medium", label: "Medium" },
|
||||||
@@ -244,31 +238,38 @@ export default function BoardDetailPage() {
|
|||||||
const loadBoard = async () => {
|
const loadBoard = async () => {
|
||||||
if (!isSignedIn || !boardId) return;
|
if (!isSignedIn || !boardId) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setIsApprovalsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setApprovalsError(null);
|
||||||
|
setChatError(null);
|
||||||
try {
|
try {
|
||||||
const [boardResult, tasksResult, agentsResult] = await Promise.all([
|
const snapshotResult = await getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(
|
||||||
getBoardApiV1BoardsBoardIdGet(boardId),
|
boardId,
|
||||||
listTasksApiV1BoardsBoardIdTasksGet(boardId),
|
);
|
||||||
listAgentsApiV1AgentsGet(),
|
if (snapshotResult.status !== 200) {
|
||||||
]);
|
throw new Error("Unable to load board snapshot.");
|
||||||
|
}
|
||||||
if (boardResult.status !== 200) throw new Error("Unable to load board.");
|
const snapshot = snapshotResult.data;
|
||||||
if (tasksResult.status !== 200) throw new Error("Unable to load tasks.");
|
setBoard(snapshot.board);
|
||||||
|
setTasks((snapshot.tasks ?? []).map(normalizeTask));
|
||||||
setBoard(boardResult.data);
|
setAgents((snapshot.agents ?? []).map(normalizeAgent));
|
||||||
setTasks(tasksResult.data.map(normalizeTask));
|
setApprovals((snapshot.approvals ?? []).map(normalizeApproval));
|
||||||
setAgents(agentsResult.data.map(normalizeAgent));
|
setChatMessages(snapshot.chat_messages ?? []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Something went wrong.");
|
const message = err instanceof Error ? err.message : "Something went wrong.";
|
||||||
|
setError(message);
|
||||||
|
setApprovalsError(message);
|
||||||
|
setChatError(message);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setIsApprovalsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadBoard();
|
loadBoard();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [boardId, isSignedIn]);
|
}, [board, boardId, isSignedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
tasksRef.current = tasks;
|
tasksRef.current = tasks;
|
||||||
@@ -294,54 +295,6 @@ export default function BoardDetailPage() {
|
|||||||
return () => window.clearTimeout(timeout);
|
return () => window.clearTimeout(timeout);
|
||||||
}, [chatMessages, isChatOpen]);
|
}, [chatMessages, isChatOpen]);
|
||||||
|
|
||||||
const loadApprovals = useCallback(async () => {
|
|
||||||
if (!isSignedIn || !boardId) return;
|
|
||||||
setIsApprovalsLoading(true);
|
|
||||||
setApprovalsError(null);
|
|
||||||
try {
|
|
||||||
const result = await listApprovalsApiV1BoardsBoardIdApprovalsGet(boardId);
|
|
||||||
if (result.status !== 200) throw new Error("Unable to load approvals.");
|
|
||||||
setApprovals(result.data.map(normalizeApproval));
|
|
||||||
} catch (err) {
|
|
||||||
setApprovalsError(
|
|
||||||
err instanceof Error ? err.message : "Unable to load approvals.",
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsApprovalsLoading(false);
|
|
||||||
}
|
|
||||||
}, [boardId, isSignedIn]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadApprovals();
|
|
||||||
}, [boardId, isSignedIn, loadApprovals]);
|
|
||||||
|
|
||||||
const loadBoardChat = useCallback(async () => {
|
|
||||||
if (!isSignedIn || !boardId) return;
|
|
||||||
setChatError(null);
|
|
||||||
try {
|
|
||||||
const result = await listBoardMemoryApiV1BoardsBoardIdMemoryGet(boardId, {
|
|
||||||
limit: 200,
|
|
||||||
});
|
|
||||||
if (result.status !== 200) throw new Error("Unable to load board chat.");
|
|
||||||
const data = result.data;
|
|
||||||
const chatOnly = data.filter((item) => item.tags?.includes("chat"));
|
|
||||||
const ordered = chatOnly.sort((a, b) => {
|
|
||||||
const aTime = new Date(a.created_at).getTime();
|
|
||||||
const bTime = new Date(b.created_at).getTime();
|
|
||||||
return aTime - bTime;
|
|
||||||
});
|
|
||||||
setChatMessages(ordered);
|
|
||||||
} catch (err) {
|
|
||||||
setChatError(
|
|
||||||
err instanceof Error ? err.message : "Unable to load board chat.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [boardId, isSignedIn]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadBoardChat();
|
|
||||||
}, [boardId, isSignedIn, loadBoardChat]);
|
|
||||||
|
|
||||||
const latestChatTimestamp = (items: BoardChatMessage[]) => {
|
const latestChatTimestamp = (items: BoardChatMessage[]) => {
|
||||||
if (!items.length) return undefined;
|
if (!items.length) return undefined;
|
||||||
const latest = items.reduce((max, item) => {
|
const latest = items.reduce((max, item) => {
|
||||||
@@ -353,17 +306,18 @@ export default function BoardDetailPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSignedIn || !boardId) return;
|
if (!isSignedIn || !boardId || !board) return;
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
try {
|
try {
|
||||||
const since = latestChatTimestamp(chatMessagesRef.current);
|
const since = latestChatTimestamp(chatMessagesRef.current);
|
||||||
|
const params = { is_chat: true, ...(since ? { since } : {}) };
|
||||||
const streamResult =
|
const streamResult =
|
||||||
await streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet(
|
await streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet(
|
||||||
boardId,
|
boardId,
|
||||||
since ? { since } : undefined,
|
params,
|
||||||
{
|
{
|
||||||
headers: { Accept: "text/event-stream" },
|
headers: { Accept: "text/event-stream" },
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
@@ -439,7 +393,7 @@ export default function BoardDetailPage() {
|
|||||||
}, [boardId, isSignedIn]);
|
}, [boardId, isSignedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSignedIn || !boardId) return;
|
if (!isSignedIn || !boardId || !board) return;
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
@@ -487,7 +441,15 @@ export default function BoardDetailPage() {
|
|||||||
}
|
}
|
||||||
if (eventType === "approval" && data) {
|
if (eventType === "approval" && data) {
|
||||||
try {
|
try {
|
||||||
const payload = JSON.parse(data) as { approval?: ApprovalRead };
|
const payload = JSON.parse(data) as {
|
||||||
|
approval?: ApprovalRead;
|
||||||
|
task_counts?: {
|
||||||
|
task_id?: string;
|
||||||
|
approvals_count?: number;
|
||||||
|
approvals_pending_count?: number;
|
||||||
|
};
|
||||||
|
pending_approvals_count?: number;
|
||||||
|
};
|
||||||
if (payload.approval) {
|
if (payload.approval) {
|
||||||
const normalized = normalizeApproval(payload.approval);
|
const normalized = normalizeApproval(payload.approval);
|
||||||
setApprovals((prev) => {
|
setApprovals((prev) => {
|
||||||
@@ -505,6 +467,25 @@ export default function BoardDetailPage() {
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.task_counts?.task_id) {
|
||||||
|
const taskId = payload.task_counts.task_id;
|
||||||
|
setTasks((prev) => {
|
||||||
|
const index = prev.findIndex((task) => task.id === taskId);
|
||||||
|
if (index === -1) return prev;
|
||||||
|
const next = [...prev];
|
||||||
|
const current = next[index];
|
||||||
|
next[index] = {
|
||||||
|
...current,
|
||||||
|
approvals_count:
|
||||||
|
payload.task_counts?.approvals_count ??
|
||||||
|
current.approvals_count,
|
||||||
|
approvals_pending_count:
|
||||||
|
payload.task_counts?.approvals_pending_count ??
|
||||||
|
current.approvals_pending_count,
|
||||||
|
};
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore malformed payloads.
|
// Ignore malformed payloads.
|
||||||
}
|
}
|
||||||
@@ -524,7 +505,7 @@ export default function BoardDetailPage() {
|
|||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
}, [boardId, isSignedIn]);
|
}, [board, boardId, isSignedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedTask) {
|
if (!selectedTask) {
|
||||||
@@ -610,14 +591,37 @@ export default function BoardDetailPage() {
|
|||||||
return [...prev, payload.comment as TaskComment];
|
return [...prev, payload.comment as TaskComment];
|
||||||
});
|
});
|
||||||
} else if (payload.task) {
|
} else if (payload.task) {
|
||||||
const normalizedTask = normalizeTask(payload.task);
|
|
||||||
setTasks((prev) => {
|
setTasks((prev) => {
|
||||||
const index = prev.findIndex((item) => item.id === normalizedTask.id);
|
const index = prev.findIndex((item) => item.id === payload.task?.id);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return [normalizedTask, ...prev];
|
const assignee = payload.task?.assigned_agent_id
|
||||||
|
? agentsRef.current.find(
|
||||||
|
(agent) => agent.id === payload.task?.assigned_agent_id,
|
||||||
|
)?.name ?? null
|
||||||
|
: null;
|
||||||
|
const created = normalizeTask({
|
||||||
|
...payload.task,
|
||||||
|
assignee,
|
||||||
|
approvals_count: 0,
|
||||||
|
approvals_pending_count: 0,
|
||||||
|
} as TaskCardRead);
|
||||||
|
return [created, ...prev];
|
||||||
}
|
}
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
next[index] = { ...next[index], ...normalizedTask };
|
const existing = next[index];
|
||||||
|
const assignee = payload.task?.assigned_agent_id
|
||||||
|
? agentsRef.current.find(
|
||||||
|
(agent) => agent.id === payload.task?.assigned_agent_id,
|
||||||
|
)?.name ?? null
|
||||||
|
: null;
|
||||||
|
const updated = normalizeTask({
|
||||||
|
...existing,
|
||||||
|
...payload.task,
|
||||||
|
assignee,
|
||||||
|
approvals_count: existing.approvals_count,
|
||||||
|
approvals_pending_count: existing.approvals_pending_count,
|
||||||
|
} as TaskCardRead);
|
||||||
|
next[index] = { ...existing, ...updated };
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -727,7 +731,7 @@ export default function BoardDetailPage() {
|
|||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
}, [boardId, isSignedIn]);
|
}, [board, boardId, isSignedIn]);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setTitle("");
|
setTitle("");
|
||||||
@@ -754,7 +758,14 @@ export default function BoardDetailPage() {
|
|||||||
});
|
});
|
||||||
if (result.status !== 200) throw new Error("Unable to create task.");
|
if (result.status !== 200) throw new Error("Unable to create task.");
|
||||||
|
|
||||||
const created = normalizeTask(result.data);
|
const created = normalizeTask({
|
||||||
|
...result.data,
|
||||||
|
assignee: result.data.assigned_agent_id
|
||||||
|
? assigneeById.get(result.data.assigned_agent_id) ?? null
|
||||||
|
: null,
|
||||||
|
approvals_count: 0,
|
||||||
|
approvals_pending_count: 0,
|
||||||
|
} as TaskCardRead);
|
||||||
setTasks((prev) => [created, ...prev]);
|
setTasks((prev) => [created, ...prev]);
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
resetForm();
|
resetForm();
|
||||||
@@ -829,49 +840,9 @@ export default function BoardDetailPage() {
|
|||||||
});
|
});
|
||||||
}, [liveFeed]);
|
}, [liveFeed]);
|
||||||
|
|
||||||
const pendingApprovalsByTaskId = useMemo(() => {
|
|
||||||
const map = new Map<string, number>();
|
|
||||||
approvals
|
|
||||||
.filter((approval) => approval.status === "pending")
|
|
||||||
.forEach((approval) => {
|
|
||||||
const taskId = approvalTaskId(approval);
|
|
||||||
if (!taskId || typeof taskId !== "string") return;
|
|
||||||
map.set(taskId, (map.get(taskId) ?? 0) + 1);
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [approvals]);
|
|
||||||
|
|
||||||
const totalApprovalsByTaskId = useMemo(() => {
|
|
||||||
const map = new Map<string, number>();
|
|
||||||
approvals.forEach((approval) => {
|
|
||||||
const taskId = approvalTaskId(approval);
|
|
||||||
if (!taskId || typeof taskId !== "string") return;
|
|
||||||
map.set(taskId, (map.get(taskId) ?? 0) + 1);
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [approvals]);
|
|
||||||
|
|
||||||
const displayTasks = useMemo(
|
|
||||||
() =>
|
|
||||||
tasks.map((task) => ({
|
|
||||||
...task,
|
|
||||||
assignee: task.assigned_agent_id
|
|
||||||
? assigneeById.get(task.assigned_agent_id)
|
|
||||||
: undefined,
|
|
||||||
approvalsCount: totalApprovalsByTaskId.get(task.id) ?? 0,
|
|
||||||
approvalsPendingCount: pendingApprovalsByTaskId.get(task.id) ?? 0,
|
|
||||||
})),
|
|
||||||
[tasks, assigneeById, pendingApprovalsByTaskId, totalApprovalsByTaskId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const boardAgents = useMemo(
|
|
||||||
() => agents.filter((agent) => !boardId || agent.board_id === boardId),
|
|
||||||
[agents, boardId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const assignableAgents = useMemo(
|
const assignableAgents = useMemo(
|
||||||
() => boardAgents.filter((agent) => !agent.is_board_lead),
|
() => agents.filter((agent) => !agent.is_board_lead),
|
||||||
[boardAgents],
|
[agents],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasTaskChanges = useMemo(() => {
|
const hasTaskChanges = useMemo(() => {
|
||||||
@@ -912,10 +883,7 @@ export default function BoardDetailPage() {
|
|||||||
const taskApprovals = useMemo(() => {
|
const taskApprovals = useMemo(() => {
|
||||||
if (!selectedTask) return [];
|
if (!selectedTask) return [];
|
||||||
const taskId = selectedTask.id;
|
const taskId = selectedTask.id;
|
||||||
return approvals.filter((approval) => {
|
return approvals.filter((approval) => approval.task_id === taskId);
|
||||||
const payloadTaskId = approvalTaskId(approval);
|
|
||||||
return payloadTaskId === taskId;
|
|
||||||
});
|
|
||||||
}, [approvals, selectedTask]);
|
}, [approvals, selectedTask]);
|
||||||
|
|
||||||
const workingAgentIds = useMemo(() => {
|
const workingAgentIds = useMemo(() => {
|
||||||
@@ -935,12 +903,12 @@ export default function BoardDetailPage() {
|
|||||||
if (agent.status === "provisioning") return 2;
|
if (agent.status === "provisioning") return 2;
|
||||||
return 3;
|
return 3;
|
||||||
};
|
};
|
||||||
return [...boardAgents].sort((a, b) => {
|
return [...agents].sort((a, b) => {
|
||||||
const diff = rank(a) - rank(b);
|
const diff = rank(a) - rank(b);
|
||||||
if (diff !== 0) return diff;
|
if (diff !== 0) return diff;
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
}, [boardAgents, workingAgentIds]);
|
}, [agents, workingAgentIds]);
|
||||||
|
|
||||||
const loadComments = async (taskId: string) => {
|
const loadComments = async (taskId: string) => {
|
||||||
if (!isSignedIn || !boardId) return;
|
if (!isSignedIn || !boardId) return;
|
||||||
@@ -953,7 +921,7 @@ export default function BoardDetailPage() {
|
|||||||
taskId,
|
taskId,
|
||||||
);
|
);
|
||||||
if (result.status !== 200) throw new Error("Unable to load comments.");
|
if (result.status !== 200) throw new Error("Unable to load comments.");
|
||||||
setComments(result.data);
|
setComments(result.data.items ?? []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setCommentsError(err instanceof Error ? err.message : "Something went wrong.");
|
setCommentsError(err instanceof Error ? err.message : "Something went wrong.");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1059,9 +1027,20 @@ export default function BoardDetailPage() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result.status !== 200) throw new Error("Unable to update task.");
|
if (result.status !== 200) throw new Error("Unable to update task.");
|
||||||
const updated = normalizeTask(result.data);
|
const previous =
|
||||||
|
tasksRef.current.find((task) => task.id === selectedTask.id) ??
|
||||||
|
selectedTask;
|
||||||
|
const updated = normalizeTask({
|
||||||
|
...previous,
|
||||||
|
...result.data,
|
||||||
|
assignee: result.data.assigned_agent_id
|
||||||
|
? assigneeById.get(result.data.assigned_agent_id) ?? null
|
||||||
|
: null,
|
||||||
|
approvals_count: previous.approvals_count,
|
||||||
|
approvals_pending_count: previous.approvals_pending_count,
|
||||||
|
} as TaskCardRead);
|
||||||
setTasks((prev) =>
|
setTasks((prev) =>
|
||||||
prev.map((task) => (task.id === updated.id ? updated : task)),
|
prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
|
||||||
);
|
);
|
||||||
setSelectedTask(updated);
|
setSelectedTask(updated);
|
||||||
if (closeOnSuccess) {
|
if (closeOnSuccess) {
|
||||||
@@ -1119,6 +1098,7 @@ export default function BoardDetailPage() {
|
|||||||
status,
|
status,
|
||||||
assigned_agent_id:
|
assigned_agent_id:
|
||||||
status === "inbox" ? null : task.assigned_agent_id,
|
status === "inbox" ? null : task.assigned_agent_id,
|
||||||
|
assignee: status === "inbox" ? null : task.assignee,
|
||||||
}
|
}
|
||||||
: task,
|
: task,
|
||||||
),
|
),
|
||||||
@@ -1130,9 +1110,17 @@ export default function BoardDetailPage() {
|
|||||||
{ status },
|
{ status },
|
||||||
);
|
);
|
||||||
if (result.status !== 200) throw new Error("Unable to move task.");
|
if (result.status !== 200) throw new Error("Unable to move task.");
|
||||||
const updated = normalizeTask(result.data);
|
const updated = normalizeTask({
|
||||||
|
...currentTask,
|
||||||
|
...result.data,
|
||||||
|
assignee: result.data.assigned_agent_id
|
||||||
|
? assigneeById.get(result.data.assigned_agent_id) ?? null
|
||||||
|
: null,
|
||||||
|
approvals_count: currentTask.approvals_count,
|
||||||
|
approvals_pending_count: currentTask.approvals_pending_count,
|
||||||
|
} as TaskCardRead);
|
||||||
setTasks((prev) =>
|
setTasks((prev) =>
|
||||||
prev.map((task) => (task.id === updated.id ? updated : task)),
|
prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setTasks(previousTasks);
|
setTasks(previousTasks);
|
||||||
@@ -1262,6 +1250,7 @@ export default function BoardDetailPage() {
|
|||||||
const approvalRows = (approval: Approval) => {
|
const approvalRows = (approval: Approval) => {
|
||||||
const payload = approval.payload ?? {};
|
const payload = approval.payload ?? {};
|
||||||
const taskId =
|
const taskId =
|
||||||
|
approval.task_id ??
|
||||||
approvalPayloadValue(payload, "task_id") ??
|
approvalPayloadValue(payload, "task_id") ??
|
||||||
approvalPayloadValue(payload, "taskId") ??
|
approvalPayloadValue(payload, "taskId") ??
|
||||||
approvalPayloadValue(payload, "taskID");
|
approvalPayloadValue(payload, "taskID");
|
||||||
@@ -1499,7 +1488,7 @@ export default function BoardDetailPage() {
|
|||||||
<>
|
<>
|
||||||
{viewMode === "board" ? (
|
{viewMode === "board" ? (
|
||||||
<TaskBoard
|
<TaskBoard
|
||||||
tasks={displayTasks}
|
tasks={tasks}
|
||||||
onTaskSelect={openComments}
|
onTaskSelect={openComments}
|
||||||
onTaskMove={handleTaskMove}
|
onTaskMove={handleTaskMove}
|
||||||
/>
|
/>
|
||||||
@@ -1512,7 +1501,7 @@ export default function BoardDetailPage() {
|
|||||||
All tasks
|
All tasks
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
{displayTasks.length} tasks in this board
|
{tasks.length} tasks in this board
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -1526,12 +1515,12 @@ export default function BoardDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-slate-100">
|
<div className="divide-y divide-slate-100">
|
||||||
{displayTasks.length === 0 ? (
|
{tasks.length === 0 ? (
|
||||||
<div className="px-5 py-8 text-sm text-slate-500">
|
<div className="px-5 py-8 text-sm text-slate-500">
|
||||||
No tasks yet. Create your first task to get started.
|
No tasks yet. Create your first task to get started.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
displayTasks.map((task) => (
|
tasks.map((task) => (
|
||||||
<button
|
<button
|
||||||
key={task.id}
|
key={task.id}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1553,10 +1542,10 @@ export default function BoardDetailPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500">
|
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500">
|
||||||
{task.approvalsPendingCount ? (
|
{task.approvals_pending_count ? (
|
||||||
<span className="inline-flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wide text-amber-700">
|
<span className="inline-flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wide text-amber-700">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-amber-500" />
|
<span className="h-1.5 w-1.5 rounded-full bg-amber-500" />
|
||||||
Approval needed · {task.approvalsPendingCount}
|
Approval needed · {task.approvals_pending_count}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function NewBoardPage() {
|
|||||||
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
||||||
listGatewaysApiV1GatewaysGetResponse,
|
listGatewaysApiV1GatewaysGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
@@ -59,7 +59,9 @@ export default function NewBoardPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const gateways =
|
const gateways =
|
||||||
gatewaysQuery.data?.status === 200 ? gatewaysQuery.data.data : [];
|
gatewaysQuery.data?.status === 200
|
||||||
|
? gatewaysQuery.data.data.items ?? []
|
||||||
|
: [];
|
||||||
const displayGatewayId = gatewayId || gateways[0]?.id || "";
|
const displayGatewayId = gatewayId || gateways[0]?.id || "";
|
||||||
const isLoading = gatewaysQuery.isLoading || createBoardMutation.isPending;
|
const isLoading = gatewaysQuery.isLoading || createBoardMutation.isPending;
|
||||||
const errorMessage = error ?? gatewaysQuery.error?.message ?? null;
|
const errorMessage = error ?? gatewaysQuery.error?.message ?? null;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function BoardsPage() {
|
|||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
const boardsQuery = useListBoardsApiV1BoardsGet<
|
||||||
listBoardsApiV1BoardsGetResponse,
|
listBoardsApiV1BoardsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchInterval: 30_000,
|
refetchInterval: 30_000,
|
||||||
@@ -62,15 +62,11 @@ export default function BoardsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const boards = useMemo(
|
const boards = useMemo(
|
||||||
() => (boardsQuery.data?.status === 200 ? boardsQuery.data.data : []),
|
() =>
|
||||||
|
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [],
|
||||||
[boardsQuery.data]
|
[boardsQuery.data]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedBoards = useMemo(
|
|
||||||
() => [...boards].sort((a, b) => a.name.localeCompare(b.name)),
|
|
||||||
[boards]
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteMutation = useDeleteBoardApiV1BoardsBoardIdDelete<
|
const deleteMutation = useDeleteBoardApiV1BoardsBoardIdDelete<
|
||||||
ApiError,
|
ApiError,
|
||||||
{ previous?: listBoardsApiV1BoardsGetResponse }
|
{ previous?: listBoardsApiV1BoardsGetResponse }
|
||||||
@@ -82,9 +78,17 @@ export default function BoardsPage() {
|
|||||||
const previous =
|
const previous =
|
||||||
queryClient.getQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey);
|
queryClient.getQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey);
|
||||||
if (previous && previous.status === 200) {
|
if (previous && previous.status === 200) {
|
||||||
|
const nextItems = previous.data.items.filter(
|
||||||
|
(board) => board.id !== boardId
|
||||||
|
);
|
||||||
|
const removedCount = previous.data.items.length - nextItems.length;
|
||||||
queryClient.setQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey, {
|
queryClient.setQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey, {
|
||||||
...previous,
|
...previous,
|
||||||
data: previous.data.filter((board) => board.id !== boardId),
|
data: {
|
||||||
|
...previous.data,
|
||||||
|
items: nextItems,
|
||||||
|
total: Math.max(0, previous.data.total - removedCount),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { previous };
|
return { previous };
|
||||||
@@ -159,7 +163,7 @@ export default function BoardsPage() {
|
|||||||
|
|
||||||
// eslint-disable-next-line react-hooks/incompatible-library
|
// eslint-disable-next-line react-hooks/incompatible-library
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: sortedBoards,
|
data: boards,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
});
|
});
|
||||||
@@ -191,11 +195,11 @@ export default function BoardsPage() {
|
|||||||
Boards
|
Boards
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-slate-500">
|
<p className="mt-1 text-sm text-slate-500">
|
||||||
Manage boards and task workflows. {sortedBoards.length} board
|
Manage boards and task workflows. {boards.length} board
|
||||||
{sortedBoards.length === 1 ? "" : "s"} total.
|
{boards.length === 1 ? "" : "s"} total.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{sortedBoards.length > 0 ? (
|
{boards.length > 0 ? (
|
||||||
<Link
|
<Link
|
||||||
href="/boards/new"
|
href="/boards/new"
|
||||||
className={buttonVariants({ size: "md", variant: "primary" })}
|
className={buttonVariants({ size: "md", variant: "primary" })}
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ import {
|
|||||||
type listAgentsApiV1AgentsGetResponse,
|
type listAgentsApiV1AgentsGetResponse,
|
||||||
useListAgentsApiV1AgentsGet,
|
useListAgentsApiV1AgentsGet,
|
||||||
} from "@/api/generated/agents/agents";
|
} from "@/api/generated/agents/agents";
|
||||||
import {
|
|
||||||
type listBoardsApiV1BoardsGetResponse,
|
|
||||||
useListBoardsApiV1BoardsGet,
|
|
||||||
} from "@/api/generated/boards/boards";
|
|
||||||
import type { AgentRead } from "@/api/generated/model";
|
|
||||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -65,22 +60,12 @@ export default function GatewayDetailPage() {
|
|||||||
const gateway =
|
const gateway =
|
||||||
gatewayQuery.data?.status === 200 ? gatewayQuery.data.data : null;
|
gatewayQuery.data?.status === 200 ? gatewayQuery.data.data : null;
|
||||||
|
|
||||||
const boardsQuery = useListBoardsApiV1BoardsGet<
|
|
||||||
listBoardsApiV1BoardsGetResponse,
|
|
||||||
ApiError
|
|
||||||
>({
|
|
||||||
query: {
|
|
||||||
enabled: Boolean(isSignedIn),
|
|
||||||
refetchInterval: 30_000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const agentsQuery = useListAgentsApiV1AgentsGet<
|
const agentsQuery = useListAgentsApiV1AgentsGet<
|
||||||
listAgentsApiV1AgentsGetResponse,
|
listAgentsApiV1AgentsGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(gatewayId ? { gateway_id: gatewayId } : undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn && gatewayId),
|
||||||
refetchInterval: 15_000,
|
refetchInterval: 15_000,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -103,22 +88,11 @@ export default function GatewayDetailPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const agents = useMemo(() => {
|
const agents = useMemo(
|
||||||
const allAgents = agentsQuery.data?.data ?? [];
|
() =>
|
||||||
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : [];
|
agentsQuery.data?.status === 200 ? agentsQuery.data.data.items ?? [] : [],
|
||||||
if (!gatewayId) {
|
[agentsQuery.data],
|
||||||
return allAgents;
|
);
|
||||||
}
|
|
||||||
const boardIds = new Set(
|
|
||||||
boards.filter((board) => board.gateway_id === gatewayId).map((board) => board.id),
|
|
||||||
);
|
|
||||||
if (boardIds.size === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return allAgents.filter(
|
|
||||||
(agent): agent is AgentRead => Boolean(agent.board_id && boardIds.has(agent.board_id)),
|
|
||||||
);
|
|
||||||
}, [agentsQuery.data, boardsQuery.data, gatewayId]);
|
|
||||||
|
|
||||||
const status =
|
const status =
|
||||||
statusQuery.data?.status === 200 ? statusQuery.data.data : null;
|
statusQuery.data?.status === 200 ? statusQuery.data.data : null;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function GatewaysPage() {
|
|||||||
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
|
||||||
listGatewaysApiV1GatewaysGetResponse,
|
listGatewaysApiV1GatewaysGetResponse,
|
||||||
ApiError
|
ApiError
|
||||||
>({
|
>(undefined, {
|
||||||
query: {
|
query: {
|
||||||
enabled: Boolean(isSignedIn),
|
enabled: Boolean(isSignedIn),
|
||||||
refetchInterval: 30_000,
|
refetchInterval: 30_000,
|
||||||
@@ -73,7 +73,13 @@ export default function GatewaysPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const gateways = useMemo(() => gatewaysQuery.data?.data ?? [], [gatewaysQuery.data]);
|
const gateways = useMemo(
|
||||||
|
() =>
|
||||||
|
gatewaysQuery.data?.status === 200
|
||||||
|
? gatewaysQuery.data.data.items ?? []
|
||||||
|
: [],
|
||||||
|
[gatewaysQuery.data]
|
||||||
|
);
|
||||||
const sortedGateways = useMemo(() => [...gateways], [gateways]);
|
const sortedGateways = useMemo(() => [...gateways], [gateways]);
|
||||||
|
|
||||||
const deleteMutation = useDeleteGatewayApiV1GatewaysGatewayIdDelete<
|
const deleteMutation = useDeleteGatewayApiV1GatewaysGatewayIdDelete<
|
||||||
@@ -86,10 +92,18 @@ export default function GatewaysPage() {
|
|||||||
await queryClient.cancelQueries({ queryKey: gatewaysKey });
|
await queryClient.cancelQueries({ queryKey: gatewaysKey });
|
||||||
const previous =
|
const previous =
|
||||||
queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey);
|
queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey);
|
||||||
if (previous) {
|
if (previous && previous.status === 200) {
|
||||||
|
const nextItems = previous.data.items.filter(
|
||||||
|
(gateway) => gateway.id !== gatewayId
|
||||||
|
);
|
||||||
|
const removedCount = previous.data.items.length - nextItems.length;
|
||||||
queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey, {
|
queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey, {
|
||||||
...previous,
|
...previous,
|
||||||
data: previous.data.filter((gateway) => gateway.id !== gatewayId),
|
data: {
|
||||||
|
...previous.data,
|
||||||
|
items: nextItems,
|
||||||
|
total: Math.max(0, previous.data.total - removedCount),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { previous };
|
return { previous };
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ const payloadValue = (payload: Approval["payload"], key: string) => {
|
|||||||
const approvalSummary = (approval: Approval) => {
|
const approvalSummary = (approval: Approval) => {
|
||||||
const payload = approval.payload ?? {};
|
const payload = approval.payload ?? {};
|
||||||
const taskId =
|
const taskId =
|
||||||
|
approval.task_id ??
|
||||||
payloadValue(payload, "task_id") ??
|
payloadValue(payload, "task_id") ??
|
||||||
payloadValue(payload, "taskId") ??
|
payloadValue(payload, "taskId") ??
|
||||||
payloadValue(payload, "taskID");
|
payloadValue(payload, "taskID");
|
||||||
@@ -223,7 +224,7 @@ export function BoardApprovalsPanel({
|
|||||||
const raw = usingExternal
|
const raw = usingExternal
|
||||||
? externalApprovals ?? []
|
? externalApprovals ?? []
|
||||||
: approvalsQuery.data?.status === 200
|
: approvalsQuery.data?.status === 200
|
||||||
? approvalsQuery.data.data
|
? approvalsQuery.data.data.items ?? []
|
||||||
: [];
|
: [];
|
||||||
return raw.map(normalizeApproval);
|
return raw.map(normalizeApproval);
|
||||||
}, [approvalsQuery.data, externalApprovals, usingExternal]);
|
}, [approvalsQuery.data, externalApprovals, usingExternal]);
|
||||||
@@ -266,9 +267,12 @@ export function BoardApprovalsPanel({
|
|||||||
if (!previous || previous.status !== 200) return previous;
|
if (!previous || previous.status !== 200) return previous;
|
||||||
return {
|
return {
|
||||||
...previous,
|
...previous,
|
||||||
data: previous.data.map((item) =>
|
data: {
|
||||||
item.id === approvalId ? result.data : item,
|
...previous.data,
|
||||||
),
|
items: previous.data.items.map((item) =>
|
||||||
|
item.id === approvalId ? result.data : item,
|
||||||
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ type Task = {
|
|||||||
description?: string | null;
|
description?: string | null;
|
||||||
due_at?: string | null;
|
due_at?: string | null;
|
||||||
assigned_agent_id?: string | null;
|
assigned_agent_id?: string | null;
|
||||||
assignee?: string;
|
assignee?: string | null;
|
||||||
approvalsPendingCount?: number;
|
approvals_pending_count?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TaskBoardProps = {
|
type TaskBoardProps = {
|
||||||
@@ -187,9 +187,9 @@ export function TaskBoard({
|
|||||||
key={task.id}
|
key={task.id}
|
||||||
title={task.title}
|
title={task.title}
|
||||||
priority={task.priority}
|
priority={task.priority}
|
||||||
assignee={task.assignee}
|
assignee={task.assignee ?? undefined}
|
||||||
due={formatDueDate(task.due_at)}
|
due={formatDueDate(task.due_at)}
|
||||||
approvalsPendingCount={task.approvalsPendingCount}
|
approvalsPendingCount={task.approvals_pending_count}
|
||||||
onClick={() => onTaskSelect?.(task)}
|
onClick={() => onTaskSelect?.(task)}
|
||||||
draggable
|
draggable
|
||||||
isDragging={draggingId === task.id}
|
isDragging={draggingId === task.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user