Devin AI 7ecb6b8962 feat: apply Noto Serif font and heading styles to DOCX export
Add document-level styles so the downloaded .docx has:
- Noto Serif font throughout (matching the web UI)
- Sized headings: H1 20pt, H2 16pt, H3 14pt, H4 12pt
- 11pt body text with 1.15× line spacing
- Consistent color (#1f1b16) and spacing
- Styled hyperlinks (blue, underlined)
- List paragraph font inheritance

Co-Authored-By: alex <alex@semipublic.co>
2026-05-20 18:00:13 +00:00
2026-05-11 22:27:54 -04:00
2026-05-11 22:27:54 -04:00
2026-05-11 22:27:54 -04:00
2026-05-11 22:27:54 -04:00
2026-05-11 22:27:54 -04:00
2026-05-11 22:27:54 -04:00

PMC Funder Discovery Tool

A React + Vite frontend (Tailwind, Noto Serif) that collects public media station details and streams an Anthropic Managed Agent response back to the browser through a Netlify Edge Function proxy.

Setup

npm install

Add your Anthropic credentials (in .env for local dev, in the Netlify UI for production):

ANTHROPIC_API_KEY=sk-ant-...
# Optional - if you've provisioned a Managed Agent in the Anthropic console:
ANTHROPIC_AGENT_ID=agent_...
# Optional - override the model:
ANTHROPIC_MODEL=claude-sonnet-4-5

Run locally

npm run dev   # netlify dev (proxies Vite + functions on :8888)

Open http://localhost:8888.

How streaming works

  1. The browser POSTs the form to /api/agent-proxy.
  2. A Netlify Edge Function (Deno runtime) creates an Anthropic Managed Agent session, opens the upstream SSE event stream, and sends the user message. It tails the stream and re-opens it (with an events.list backfill, deduped by event id) whenever it drops mid session, until the session reports a terminal session.status_* event or a 20-minute wall-clock budget is hit.
  3. Downstream to the browser the function emits newline-delimited JSON (application/x-ndjson) — text, status, heartbeat, segment_end, done, error — one object per line.
  4. The React app reads response.body.getReader(), splits on \n, and parses each line. text lines append to the brief; status lines drive a separate “what the agent is doing now” banner.
  5. A Thinking flag stays true until the first chunk arrives, then flips to a streaming state with a pulsing cursor.

Why segmented streaming

Netlify Edge Functions empirically cap a single streaming response at ~60 s wall-clock (Netlify's own canonical SSE example cuts at the same mark, even though the docs imply "indefinite" streaming). The Anthropic Managed Agent session itself is fine for several minutes; the limit is per-HTTP-response. So the proxy segments the stream:

  • Just before ~54 s the function writes one final NDJSON line — {"type":"segment_end","sessionId":"sess_…","lastEventId":"evt_…","startedAt":…} — and closes the response.
  • The React side sees segment_end, immediately POSTs to /api/agent-proxy again with {sessionId, lastEventId, startedAt} in the body.
  • The function recognises this as a resume payload: it does not create a new session or re-send the user message. It just reopens the live SSE stream, backfills via GET /v1/sessions/{id}/events?after_id=lastEventId (deduped by event.id), and keeps tailing.

The 20-minute overall budget is enforced via Date.now() - startedAt so it spans across all segments. The previous v2 Node Function (on AWS Lambda) was killed at ~27 s, well before any reconnect could fire — moving to Edge Functions extended that to ~60 s per response, and the segment loop extends it the rest of the way.

Files

S
Description
No description provided
Readme 147 KiB
Languages
JavaScript 48.4%
TypeScript 46.1%
CSS 4.4%
HTML 1.1%