Backend and integration layer for our Hackathon solution to the AI Brainstorm Canvas case.
The core idea was simple: AI should not live in a separate tab. It should act inside the same canvas as the team, see the current board state, use meeting context, and propose spatial edits that users can approve, reject, or refine.
Current frontend deployment: https://hacknu.167.99.251.164.nip.io
Frontend source: https://github.com/bekzhanak/hacknu-front
This repository is the backend side of the system plus the Google Meet transcript extension:
hacknu-back/: FastAPI service, planner, storage integration, DB models, migrationsmeet-extension/: Chrome extension that captures Google Meet captions and sends them to the backendinfra/: deployment compose file and deploy scriptdocker-compose.yml: local backend + Postgres stack
The browser canvas itself lives in the separate frontend repo. The full product is the combination of:
- React + tldraw + Liveblocks frontend
- This FastAPI backend
- PostgreSQL
- Google Meet caption ingestion
- Optional Higgsfield media generation
The brief asked for an AI brainstorming agent that lives inside a canvas, not a chatbot with a whiteboard attached in the background.
Our answer was to make AI behave like a spatial collaborator:
- it reads the current canvas from Liveblocks
- it reads meeting context from captured transcripts
- it proposes actual canvas operations, not just text
- it appears on the board through pending changes
- humans stay in control through approve, reject, and edit actions
This keeps the agent present in the session without requiring fake real-time cursor movement.
Users in browser
|
v
React + tldraw frontend
|- writes user canvas changes directly to Liveblocks
|- calls backend for autocomplete, chat-agent runs, approvals, transcript-backed queries
|- renders pending AI changes as ghost/spatial suggestions
|
+-----------------------> Liveblocks Cloud
| |- canonical shared canvas state
| |- pendingChanges
| |- agent registry / room metadata
| |- presence
|
+-----------------------> FastAPI backend (this repo)
|- reads room storage through Liveblocks REST API
|- calls LLM planner
|- normalizes safe tldraw operations
|- stores agents / messages / transcript records in Postgres
|- writes pending changes back into Liveblocks
|- optionally generates media via Higgsfield
Google Meet
|
v
Chrome extension
|
v
POST /rooms/{room_id}/transcript
Two decisions drive the whole system:
The frontend writes user edits straight to Liveblocks, and the backend reads and patches that same room through the Liveblocks REST API. That means the agent always reasons over the same canvas state the users are seeing.
The planner asks the model for high-level draft operations such as:
- add a note
- add a geo shape
- connect two shapes
- update a label
Then hacknu-back/app/operations.py compiles those into concrete tldraw-compatible objects, applies backend styling defaults, resolves references, avoids bad arrow duplication, and keeps suggestions sane relative to the viewport and existing layout. This makes the agent more reliable and keeps prompt complexity manageable.
- Frontend detects user activity on the canvas, waits for idle, and calls
POST /complete. - Backend fetches room storage from Liveblocks.
- Backend loads rejected history and recent meeting context.
- Planner generates a small, contextual suggestion.
- Backend compiles and normalizes operations.
- Backend stores the suggestion in Postgres and writes it to Liveblocks
pendingChanges. - Frontend renders the suggestion on the board as a pending spatial change.
- Users approve, reject, or edit it.
This is how the AI feels present without constantly interrupting.
- User sends a prompt to an agent via
POST /agent/{agent_id}/run. - Backend loads:
- current room storage
- that agent's chat history
- recent transcript context
- prior rejected changes
- In
generatemode, the backend produces pending canvas changes. - In
querymode, the backend answers questions about the canvas and returns referenced shape IDs.
This makes the agent usable both as a collaborator and as a contextual explainer.
Pending changes can be:
approve: commit intoshapesreject: remove from Liveblocks and store as rejected historyedit: treat human feedback as a revision request and generate a better replacement
This was important for the brief because the AI needed to participate without taking over the board.
The Chrome extension polls Google Meet captions, batches them, and posts them to the backend. The backend stores transcript chunks in meeting_transcripts, deduplicates progressive caption fragments, and builds a cached summary plus recent raw lines for the planner.
That gives the agent access to the conversation, not only the visible canvas.
The backend exposes Higgsfield-backed endpoints for:
- text-to-image:
POST /media/generate - image-to-video:
POST /media/generate/video - status polling:
GET /media/status/{request_id}
For image generation, the backend first uses an LLM to decide whether the selected canvas content is visually suitable, crafts a stronger image prompt, then inserts the generated result into Liveblocks as canvas media data.
hacknu-back/app/main.py: app setup, CORS, router mounting, health endpointhacknu-back/app/routes.py: agent, autocomplete, transcript, and approval flowshacknu-back/app/generate_routes.py: media generation endpoints
hacknu-back/app/planner.py: prompt construction, LLM calls, query answeringhacknu-back/app/operations.py: converts semantic draft ops into concrete tldraw-safe operationshacknu-back/app/shapes.py: backend shape schemas and defaults
hacknu-back/app/liveblocks.py: Liveblocks REST client for storage and presencehacknu-back/app/transcript.py: transcript ingestion, deduplication, summarization cachehacknu-back/app/higgsfield.py: Higgsfield client with polling helpers
hacknu-back/app/models.py:Agent,AgentChange,ChatMessage,MeetingTranscripthacknu-back/app/database.py: async SQLAlchemy engine/sessionhacknu-back/alembic/: schema migrations
The frontend and backend coordinate through shared room storage. The main keys used by the product are:
shapes: committed canvas objectspendingChanges: AI suggestions awaiting approvalagents: agent registry mirrored into room storageagentChats: frontend-managed collaborative chat UI statemeta: room metadata and autocomplete lease
Generated media may also create Liveblocks asset records so image shapes can render on the board.
agents: agent registry per roomagent_changes: pending / approved / rejected AI changeschat_messages: persisted agent conversations and change timeline itemsmeeting_transcripts: captured meeting caption chunks
Postgres is used for memory, auditability, and replayable chat history. Liveblocks is used for shared real-time canvas state.
POST /completePOST /complete/actionGET /agents/{room_id}POST /agents/{room_id}POST /agent/{agent_id}/runPOST /agent/{agent_id}/actionGET /agent/{agent_id}/messages
POST /rooms/{room_id}/transcriptGET /rooms/{room_id}/transcriptDELETE /rooms/{room_id}/transcript
POST /media/generatePOST /media/generate/videoGET /media/status/{request_id}
GET /healthGET /docs
.
├── README.md
├── docker-compose.yml
├── FRONTEND_STORAGE_GUIDE.md
├── agent.md
├── hacknu-back/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── alembic/
│ ├── app/
│ └── tests/
├── infra/
│ ├── docker-compose.yml
│ ├── deploy.sh
│ └── README.md
└── meet-extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
└── popup.js
- Docker / Docker Compose
- Node.js for the separate frontend repo
- A Liveblocks project
- At least one LLM provider key:
- OpenAI via
OPENAI_API_KEY - or Gemini via
GEMINI_API
- OpenAI via
- Optional Higgsfield keys for media generation
cp .env.example .envFill in the application values you need:
OPENAI_API_KEY=
GEMINI_API=
LIVEBLOCKS_SECRET_KEY=
HIGGSFIELD_API_KEY_ID=
HIGGSFIELD_API_KEY_SECRET=
AGENT_PROVIDER=openai
AGENT_MODEL=gpt-5.4
AI_DEBUG_PRINTS=truedocker compose up -d --buildUseful URLs:
- backend:
http://localhost:8000 - health:
http://localhost:8000/health - docs:
http://localhost:8000/docs
Migrations run automatically when the backend container starts.
In the separate frontend repo:
git clone https://github.com/bekzhanak/hacknu-front
cd hacknu-front
npm install
cp .env.example .env
npm run devAt minimum, point the frontend at:
VITE_LIVEBLOCKS_PUBLIC_KEYVITE_BRAINSTORM_API_BASE_URL=http://localhost:8000
Load meet-extension/ as an unpacked Chrome extension, then in the popup set:
roomId: the Liveblocks room used by the frontendbackendUrl: your backend base URL, for examplehttp://localhost:8000
Turn on Google Meet captions, then start capture. The extension will batch transcript chunks into the backend.
For server deployment, this repo includes:
infra/docker-compose.ymlinfra/deploy.sh
The deploy flow syncs the backend source to a remote machine, copies a filtered .env, then starts the production compose stack remotely.
bash infra/deploy.shDeploy-specific variables live in the same repo-root .env, for example:
DO_HOST=
DO_USER=root
SSH_KEY=~/.ssh/id_rsa
SSH_PUBLIC_KEY=
REMOTE_DIR=/root/hacknu-back
APP_PORT=9000More detail is in infra/README.md.
Backend tests live under hacknu-back/tests/.
cd hacknu-back
python -m unittest discover -s testsThe current test suite focuses on:
- shape / schema normalization
- planner and route helper behavior
- approval and pending-change flows
The brief was not asking for a prettier whiteboard. It was asking for a new interaction model where AI feels like it is already in the room.
This system pushes in that direction by grounding the agent in three sources at once:
- the shared canvas state
- the live conversation transcript
- the human approval loop
That combination is what turns the agent from a disconnected chatbot into a canvas participant.
This was built as a hackathon demo for a single collaborative session, not as a production platform.
Intentional tradeoffs:
- strong reliance on Liveblocks as the room-state boundary
- best-effort transcript capture through DOM polling of Google Meet captions
- approval-gated AI edits instead of fully autonomous canvas mutation
- simple Docker-based deployment
Those tradeoffs were acceptable for the brief because the goal was to make AI participation feel real in a demo-length session.