Sidebar chat#5587
Draft
alisman wants to merge 17 commits into
Draft
Conversation
| ✕ | ||
| </button> | ||
| <img | ||
| src={lastScreenshot} |
| export function getChatServerBase(): string { | ||
| const host = | ||
| typeof window !== 'undefined' ? window.location.hostname : ''; | ||
| if (host.endsWith('cbioportal.org') || host.endsWith('.netlify.app')) { |
| return ( | ||
| url.includes('/api/chat/') || | ||
| url.includes('cbioportal-frontend-sidebar.vercel.app') || | ||
| url.includes('tailf02841.ts.net:5174') |
| ✕ | ||
| </button> | ||
| <img | ||
| src={lastScreenshot} |
✅ Deploy Preview for cbioportalfrontend ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “Study Chat” sidebar integrated into the Results View. It embeds an isolated iframe-based chat UI, captures viewport screenshots for context, and adds “alteration beacons” overlays driven by a new chat-sidebar backend that fetches/uses paper text and calls Anthropic models.
Changes:
- Add
ChatSidebarhost component (+ styles) that embeds the sidebar iframe and responds to screenshot requests. - Add
AlterationBeaconsoverlay that fetches paper-grounded highlights and places animated beacons on matching legend/genes/tabs. - Add new
packages/chat-sidebar(Vite/React) iframe app andpackages/chat-sidebar-server(Express dev + Vercel functions) backend; update rspack devServer/proxy and addhtml2canvas.
Reviewed changes
Copilot reviewed 29 out of 32 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared/components/chatSidebar/screenshot.ts | Implements viewport capture + network/view readiness helpers |
| src/shared/components/chatSidebar/ChatSidebar.tsx | Host-side launcher/panel + iframe integration + screenshot messaging |
| src/shared/components/chatSidebar/ChatSidebar.scss | Styles for launcher button, panel iframe, and loader chip |
| src/shared/components/chatSidebar/chatServerBase.ts | Chooses iframe/backend base URL (prod vs dev) |
| src/shared/components/chatSidebar/AlterationBeacons.tsx | Fetches highlight data and renders/pulses overlay beacons + tooltip |
| src/pages/resultsView/ResultsViewPage.tsx | Mounts ChatSidebar on Results View |
| rspack.config.js | Copies chat-sidebar dist (optional), changes dev host/publicPath, adds /api/chat proxy |
| packages/chat-sidebar/vite.config.ts | Vite config for iframe app (relative base, HTTPS dev, API proxy) |
| packages/chat-sidebar/tsconfig.json | TS config for iframe app |
| packages/chat-sidebar/src/styles.css | Iframe UI styling |
| packages/chat-sidebar/src/main.tsx | Iframe app bootstrap |
| packages/chat-sidebar/src/cbioportal.ts | Minimal cBioPortal API helper for study metadata |
| packages/chat-sidebar/src/App.tsx | Iframe chat UI, preset prompts, screenshot request, /api/chat calls |
| packages/chat-sidebar/pnpm-lock.yaml | Adds per-package pnpm lockfile |
| packages/chat-sidebar/package.json | Iframe app package definition + scripts/deps |
| packages/chat-sidebar/index.html | Iframe HTML entry |
| packages/chat-sidebar/.gitignore | Ignores build/dev/certs artifacts |
| packages/chat-sidebar-server/vercel.json | Vercel build/install configuration and function maxDuration |
| packages/chat-sidebar-server/tsconfig.json | TS config for backend |
| packages/chat-sidebar-server/src/server.ts | Express dev server wrapping shared core logic |
| packages/chat-sidebar-server/src/paper.ts | Fetches/derives paper context via PMC BioC + PubMed fallback |
| packages/chat-sidebar-server/src/cors.ts | CORS helper for Vercel functions |
| packages/chat-sidebar-server/src/core.ts | Shared Claude prompting, cost calculation, highlights JSON schema |
| packages/chat-sidebar-server/package.json | Backend package scripts/deps (Anthropic SDK, Express, Vercel) |
| packages/chat-sidebar-server/api/chat/suggest.ts | Vercel function handler for suggest endpoint |
| packages/chat-sidebar-server/api/chat/highlights.ts | Vercel function handler for highlights endpoint |
| packages/chat-sidebar-server/api/chat/health.ts | Vercel function health endpoint |
| packages/chat-sidebar-server/.vercelignore | Excludes local artifacts from Vercel build context |
| packages/chat-sidebar-server/.gitignore | Ignores local env/build artifacts |
| package.json | Adds html2canvas dependency |
| .gitignore | Ignores .claude/ and .vercel directories |
Files not reviewed (1)
- packages/chat-sidebar/pnpm-lock.yaml: Language not supported
Comment on lines
+33
to
+36
| (function installNetworkPatch() { | ||
| const w = window as any; | ||
| if (typeof window === 'undefined' || w[PATCH_FLAG]) return; | ||
| w[PATCH_FLAG] = true; |
Comment on lines
+62
to
+78
| onMessage = async (e: MessageEvent) => { | ||
| // The iframe asks for a screenshot before each preset request so the | ||
| // model sees what the user is actually looking at. | ||
| if ( | ||
| e.source !== this.iframeRef.current?.contentWindow || | ||
| e.data?.type !== 'chat-sidebar:requestScreenshot' | ||
| ) { | ||
| return; | ||
| } | ||
| const requestId = e.data.requestId; | ||
| await waitForNetworkIdle(1000); | ||
| await waitForViewReady(); | ||
| const dataUrl = await captureViewport(); | ||
| this.iframeRef.current?.contentWindow?.postMessage( | ||
| { type: 'chat-sidebar:screenshot', requestId, dataUrl }, | ||
| '*' | ||
| ); |
| { | ||
| context: ['/api/chat'], | ||
| target: 'http://127.0.0.1:4000', | ||
| pathRewrite: { '^/api/chat': '' }, |
Comment on lines
+6
to
+12
| export function getChatServerBase(): string { | ||
| const host = | ||
| typeof window !== 'undefined' ? window.location.hostname : ''; | ||
| if (host.endsWith('cbioportal.org') || host.endsWith('.netlify.app')) { | ||
| return 'https://cbioportal-frontend-sidebar.vercel.app'; | ||
| } | ||
| return 'https://vps-870e202d.tailf02841.ts.net:5174'; |
Comment on lines
+3
to
+17
| // The highlights and suggest endpoints are invoked cross-origin from the | ||
| // cBioPortal host page (e.g. cbioportal.org) as well as same-origin from the | ||
| // iframe. Echo the Origin header for any caller — these endpoints take only | ||
| // a studyId, so there's nothing user-specific to leak; the ANTHROPIC_API_KEY | ||
| // stays server-side. | ||
| export function applyCors(req: VercelRequest, res: VercelResponse): boolean { | ||
| const origin = (req.headers.origin as string) || '*'; | ||
| res.setHeader('Access-Control-Allow-Origin', origin); | ||
| res.setHeader('Vary', 'Origin'); | ||
| res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); | ||
| res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); | ||
| if (req.method === 'OPTIONS') { | ||
| res.status(204).end(); | ||
| return true; | ||
| } |
Comment on lines
+207
to
+214
| // Continuously reposition placed beacons. Cheap for ~10 elements. | ||
| private startRepositionLoop() { | ||
| const tick = () => { | ||
| this.repositionAll(); | ||
| this.rafId = requestAnimationFrame(tick); | ||
| }; | ||
| this.rafId = requestAnimationFrame(tick); | ||
| } |
Comment on lines
+884
to
+898
| const studyIds = this.resultsViewPageStore.studyIds.result; | ||
| return ( | ||
| <PageLayout | ||
| noMargin={true} | ||
| hideFooter={true} | ||
| className={'subhead-dark'} | ||
| > | ||
| {this.pageContent} | ||
| </PageLayout> | ||
| <> | ||
| <PageLayout | ||
| noMargin={true} | ||
| hideFooter={true} | ||
| className={'subhead-dark'} | ||
| > | ||
| {this.pageContent} | ||
| </PageLayout> | ||
| <ChatSidebar | ||
| studyId={studyIds && studyIds[0]} | ||
| genes={this.resultsViewPageStore.hugoGeneSymbols} | ||
| tab={this.resultsViewPageStore.tabId} | ||
| /> |
Comment on lines
+1
to
+64
| lockfileVersion: '9.0' | ||
|
|
||
| settings: | ||
| autoInstallPeers: true | ||
| excludeLinksFromLockfile: false | ||
|
|
||
| importers: | ||
|
|
||
| .: | ||
| dependencies: | ||
| react: | ||
| specifier: ^19.0.0 | ||
| version: 19.2.6 | ||
| react-dom: | ||
| specifier: ^19.0.0 | ||
| version: 19.2.6(react@19.2.6) | ||
| devDependencies: | ||
| '@types/react': | ||
| specifier: ^19.0.0 | ||
| version: 19.2.14 | ||
| '@types/react-dom': | ||
| specifier: ^19.0.0 | ||
| version: 19.2.3(@types/react@19.2.14) | ||
| '@vitejs/plugin-react': | ||
| specifier: ^4.3.4 | ||
| version: 4.7.0(vite@6.4.2) | ||
| typescript: | ||
| specifier: ^5.7.2 | ||
| version: 5.9.3 | ||
| vite: | ||
| specifier: ^6.0.7 | ||
| version: 6.4.2 | ||
|
|
||
| packages: | ||
|
|
||
| '@babel/code-frame@7.29.0': | ||
| resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/compat-data@7.29.3': | ||
| resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/core@7.29.0': | ||
| resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/generator@7.29.1': | ||
| resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/helper-compilation-targets@7.28.6': | ||
| resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/helper-globals@7.28.0': | ||
| resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/helper-module-imports@7.28.6': | ||
| resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} | ||
| engines: {node: '>=6.9.0'} | ||
|
|
||
| '@babel/helper-module-transforms@7.28.6': |
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "$schema": "https://openapi.vercel.sh/vercel.json", | |||
| "installCommand": "cd ../.. && pnpm install --no-frozen-lockfile", | |||
Comment on lines
+68
to
+80
| // Per-process paper cache. Persistent on the Express server; per-warm-Lambda | ||
| // on Vercel (each warm function reuses; cold starts fetch fresh from PMC). | ||
| const paperCache = new Map<string, PaperContext>(); | ||
|
|
||
| export async function getPaperContext( | ||
| studyId: string | ||
| ): Promise<PaperContext> { | ||
| const cached = paperCache.get(studyId); | ||
| if (cached) return cached; | ||
| const ctx = await fetchPaperForStudy(studyId); | ||
| paperCache.set(studyId, ctx); | ||
| return ctx; | ||
| } |
- packages/chat-sidebar: React 19 + Vite iframe app, served via rspack's CopyPlugin at /chat-sidebar/index.html. Button-triggered "Suggest insight" flow that renders a paper-grounded observation plus per-call cost. - packages/chat-sidebar-server: Node + Express backend (port 4000) that fetches the open study's primary publication (PMC full text via the BioC API, fallback to PubMed abstract) and prompts Claude with a verbatim-quote grounding contract. Prompt-caches the paper text. - rspack.config.js: dev-server proxy /api/chat/* -> :4000; bind to HOST env so the dev server is reachable over LAN/Tailscale. - src/shared/components/chatSidebar: launcher + iframe wrapper mounted in ResultsViewPage. Drops credentials:include from study fetch (was breaking CORS against the public cBioPortal CORS=* policy). API key lives in packages/chat-sidebar-server/.env (gitignored). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- chat-sidebar-server: new /highlights endpoint returns a structured list of
paper-grounded items via Anthropic structured outputs. Three beacon types
in one schema:
* alteration -> oncoprint legend label (Amplification, Missense, ...)
* gene -> oncoprint gene track title (TP53, ESR1, ...)
* tab -> results-view tab (Survival, Mutual Exclusivity, ...)
Each carries note + verbatim quote + importance, grounded in the paper.
Prompt-cached on the paper text with 1h ephemeral TTL; /suggest uses the
same TTL. Cost breakdown now reports cacheWrite5m vs cacheWrite1h.
- AlterationBeacons (new): single React component mounted by ChatSidebar.
Fetches /api/chat/highlights, watches the DOM via MutationObserver, and
injects absolutely-positioned HTML beacon dots overlaying the matched
targets (SVG <text> for oncoprint, <a class="tabAnchor_*"> for tabs).
Pulse animation runs on the rAF reposition loop directly so it cannot be
defeated by stylesheet/SMIL/CSS-keyframes edge cases. Tab beacons use a
Range over the link text so they snug up to the last character, not the
padded edge of the <a> or <li>.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract Express handler logic into shared core.ts so the serverless functions in api/chat/* and the local dev server stay in sync. Add vercel.json to bundle the chat-sidebar iframe into public/ and expose suggest/highlights/health endpoints. Point ChatSidebar's iframe at the deployed Vercel URL so cBioPortal embeds the hosted app and same-origin API calls reach the functions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iframe URL is now picked at runtime: prod Vercel deployment when the host is cbioportal.org, otherwise a local Vite dev server reachable over the tailnet at vps-870e202d.tailf02841.ts.net:5174. Vite serves HTTPS using a Tailscale-issued cert (loaded from packages/chat-sidebar/ certs/, gitignored) and proxies /api/* to the local Express server on :4000 so dev mirrors the same-origin assumption the iframe code makes in prod. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sidebar opens by default with the queried genes/active tab passed through ChatSidebar -> iframe URL. Auto-runs the "Key finding" preset on mount; "Cohort" and "Limitations" presets are one click away. Preset prompts explicitly tell Claude to stay relevant to the listed genes and tab. - Move the study link into the header (paper URL on click), drop the separate study-meta block and the source-pill banner. - Make /api/chat/* reachable cross-origin from the host page: shared CORS helper on Vercel functions, Vite dev server CORS opened with reflected origin, /api routes on the Express dev server prefixed to match production paths. - Centralize the chat-server base URL (Vercel prod vs. tailnet dev) so both the iframe and AlterationBeacons resolve the same host. - Keep iframe state across open/close by hiding the panel via [hidden] instead of unmounting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Host page captures the current viewport via html2canvas (excluding the sidebar itself), downscaled to a max 1024px long side, and ships the PNG to the iframe over postMessage. The iframe attaches it to the suggest POST; the backend forwards it as an image content block in the user turn so Claude can reference what the user is actually seeing — specific genes/tracks, legend buckets, plot patterns. Also adds a shimmer animation behind the "thinking…" bubble while a preset is in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a free-form chat input at the bottom of the sidebar that sends the user's question with the same paper/gene/tab/screenshot context as the preset buttons. The textarea expands on focus and collapses on submit; the user's last prompt is rendered as a user bubble above the reply. - Move the open/close toggle to the top-right of the panel when open, back to a circular bottom-right launcher when closed. Skip mounting AlterationBeacons while the sidebar is closed. Persist open/closed in localStorage so it survives reloads. - Wait for any [data-test=LoadingIndicator] (e.g. oncoprint paint) to clear before capturing the screenshot, and rasterize the oncoprint via oncoprintjs.toCanvas() into the html2canvas onclone hook so the WebGL surface no longer screenshots as black. - Add a "view screenshot" debug link next to the cost line that opens a modal with the exact image sent to Claude. - Switch the highlights model to Sonnet 4.6 (~50% cheaper); keep Opus 4.7 for suggest. computeCost takes the model so each call's price reflects what was actually used. - Surface a "Loading beacons…" pill in the bottom-left while the highlights fetch is in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… previews to Vercel Monkey-patch fetch + XHR once at module load to track in-flight host-page requests (skipping our own /api/chat/* traffic). Before each screenshot, wait for 1s of continuous network idle, then for any DOM LoadingIndicator to clear, then snap — so tabs like Mutations that finish data loading after the initial paint don't get captured half-empty. Also extend the prod-vs-dev chat-server base check to recognize *.netlify.app hosts so deploy previews use the Vercel deployment instead of the developer's tailnet host. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… closed
Highlights endpoint now takes an `inventory: { alterations, genes, tabs }`
captured from the host DOM (legend labels, queried genes, present
`.tabAnchor_*` nodes). The system prompt instructs Claude to emit
highlights only for items in the inventory — anything outside has no DOM
target and would be silently dropped, so spending output budget on it is
waste. Wait for network idle before snapshotting the inventory so the
listing reflects what the user actually sees, not a half-rendered shell.
Re-fetch highlights when the queried gene set changes, not just when the
study does. Toggle `body.chat-sidebar-closed` so beacons + loader hide
via CSS when the user closes the sidebar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… with per-call pricing (#5591) * Route Claude calls through Vercel AI Gateway via the AI SDK Replace direct @anthropic-ai/sdk usage in chat-sidebar-server with `ai@^6` + `zod`. LLM calls now go through the Vercel AI Gateway with plain "anthropic/claude-opus-4.7" / "anthropic/claude-sonnet-4.6" slugs (versions use dots, not hyphens), authenticated via the VERCEL_OIDC_TOKEN that Vercel injects on deploy and `vercel env pull` writes to .env.local for local dev. Why the switch: - Unified observability and spend tracking in one Vercel dashboard. - Provider-agnostic — we can fail over or A/B different models by changing a single string instead of swapping SDKs. - Zero markup; AI SDK preserves Anthropic prompt caching (verified: the 1h cache_control breakpoint on the paper-text system prompt still produces cache_creation_input_tokens on the first call and cache_read_input_tokens on every subsequent call within the TTL). Mechanics: - runSuggest: generateText(...) with the system role inside `messages` so the providerOptions.anthropic.cacheControl breakpoint can attach to it. The previous .stream().finalMessage() pattern was internal-only — the server only ever returned res.json(result) — so non-streaming is fine. - runHighlights: generateObject(...) with a Zod schema replaces the hand-built JSON Schema + JSON.parse path; same cache breakpoint. - computeCost is unchanged. It reads from providerMetadata.anthropic.usage, which preserves Anthropic's native snake_case shape including the cache_creation.ephemeral_1h_input_tokens split. - Image content uses AI SDK's {type:'image', image: dataUrl} shape; the data-URL is parsed for mediaType automatically. - Server startup now requires VERCEL_OIDC_TOKEN (or AI_GATEWAY_API_KEY) instead of ANTHROPIC_API_KEY; .env.local is layered on top of .env so the local order matches what Vercel does in deployed envs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Let users switch models from the sidebar; price per call via Gateway pricing The sidebar header now exposes a model dropdown wired to a curated shortlist (Anthropic Opus/Sonnet/Haiku, OpenAI GPT-5.4 + mini, Gemini 3 Pro Preview, Grok 4.1 fast-reasoning, DeepSeek V4 Pro). Selection persists in localStorage and ships with each /api/chat/suggest call. Backend changes: - New src/pricing.ts: lazy-load gateway.getAvailableModels() once, expose getShortlistedModels() for the dropdown and computeCostFromUsage(usage, model) keyed on Gateway-listed pricing. Hand-maintained PRICES table is gone — the Gateway is the authoritative source. - runSuggest / runHighlights now accept an optional `model` Gateway slug and default to SUGGEST_MODEL / HIGHLIGHTS_MODEL when absent. - Cost computation switches to the AI-SDK-normalized result.usage shape (inputTokens, outputTokens, inputTokenDetails.cacheReadTokens/ cacheWriteTokens) so it works across providers, not just Anthropic. One known under-count: the Gateway lists Anthropic's 5m-TTL write rate (1.25x input) as `cacheCreationInputTokens`; our 1h-TTL writes actually bill at 2x. Cache writes are one-time per session, so the cost-display delta is small; documented inline. - New GET /api/chat/models endpoint (Express + Vercel function) returns the shortlist with name + per-token pricing for the iframe's dropdown. - suggest / highlights endpoints accept `model` in the request body. Frontend (packages/chat-sidebar): - App.tsx fetches /api/chat/models on mount, renders a <select> in the header, persists choice in localStorage, sends `model` with each suggest request. Per-option title shows in/out/cached per-million-token prices. - Cost line already showed cost.total and cost.model — works unchanged once the backend swaps models per request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several follow-ups on top of the AI Gateway refactor, all driven by use: Model selection propagates to beacons. The iframe owns the model dropdown and persists it in localStorage; on mount and on every change it posts `chat-sidebar:modelChanged` to the host. ChatSidebar mirrors that into an @observable and passes it down to AlterationBeacons, which now re-fetches /api/chat/highlights whenever the model changes. Previously the dropdown only affected the iframe's runSuggest calls; beacons silently kept using the server default (sonnet-4.6). Auto-refire suggest on model switch. Changing the dropdown immediately re-runs whatever the user last asked — preset or free-form prompt — with the new model, so swapping providers gives an instant A/B comparison without re-clicking or re-typing. Eligibility gating. The host page renders the sidebar with an explanatory message (and skips beacons entirely) when either: (a) the user has more than one study queried — grounding requires a single primary publication (b) the single study has no PMC full text — we won't ground on just an abstract Backed by a new lightweight `GET /api/chat/paper-status?studyId=X` endpoint that re-uses the in-process paper cache, so the check is fast on warm path. Renders "Checking paper availability…" while the status fetch is in flight to avoid flashing the iframe and yanking it. Beacon-cost receipt. The bottom-left "Loading beacons…" pill stays visible after load and becomes a quiet receipt: `Beacons · model · $X`. On failure it turns red with the error in the tooltip — previously the chip vanished silently when highlights failed. Token-budget bump. Reasoning models (DeepSeek V4 Pro and o-series) were exhausting the 4096 maxOutputTokens cap entirely on internal reasoning and emitting zero text, causing generateObject to throw NoObjectGenerated. Bumped runHighlights to 16384 and runSuggest to 4096. Non-reasoning models only consume what they need, so the floor doesn't inflate normal-case cost. Layout + theming. Close button is back in the panel's top-right (small, subtle gray ✕), model dropdown sits just to its left with a `|` divider between them — all three elements share a 22px row at top:8 so they align cleanly. Sidebar/launcher/iframe accents now use cBioPortal blue #3786C2 instead of the prior #2563eb. Cost computation. Switched from a hand-maintained PRICES table to gateway.getAvailableModels()-provided per-token rates (lazy-loaded once per process) so costs are correct for every model the catalog returns, including non-Anthropic providers whose discount structures differ. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… inventory Refactor the host-side beacon code into a framework-neutral capability surface and stand up a Model Context Protocol server over postMessage, so any allowlisted in-browser agent (the sidebar iframe today; other agents tomorrow) can read the current view/URL/inventory, screenshot, annotate, and URL-navigate through one discoverable surface instead of reverse-engineering the DOM. - inventory.ts: scrapeInventory() + legend/tab constants, extracted from AlterationBeacons.buildInventory(). - BeaconEngine.ts: framework-neutral beacon placement, rAF pulse/reposition loop, MutationObserver rescan, and tooltip — lifted from AlterationBeacons. - PortalContext.ts: typed capability interface + HostPortalContext + the ViewSource adapter over ResultsViewURLWrapper. Navigation routes through urlWrapper.updateURL (soft, session-prop-aware), never window.location. - portalMcpServer.ts: JSON-RPC-over-postMessage MCP shim with an origin allowlist, read/act capability scoping, and a writableParams allowlist. - AlterationBeacons.tsx: slimmed to a thin wrapper (fetch highlights -> map to beacons -> engine.setBeacons), sharing one beacon implementation. - ChatSidebar/ResultsViewPage: build the ViewSource from store.urlWrapper and start the server, gated behind localStorage chat-sidebar:mcp=1 so default behavior is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
coadread_tcga_pub (and any cold study) intermittently reported "no full-text paper" even though PMC full text exists. Root cause: the sidebar fires /paper-status, /highlights, and /suggest for the same study at mount, and getPaperContext had no in-flight dedup — all three raced into the NCBI chain at once, a self-inflicted burst that trips NCBI's 3-req/s/IP limit. NCBI's throttle response is HTTP 200 with an empty linkset (not a 429), so elink silently yields no PMCID and the code falls through to the abstract. That degraded result was then cached permanently per warm Lambda, freezing the study. - core.ts: coalesce concurrent getPaperContext calls per study onto one Promise so the mount-time burst becomes a single fetch; cache 'pmc' results for good but give 'abstract'/'none' a 5-minute TTL so a transient miss self-heals. - paper.ts: identify the client to NCBI (tool/email, plus api_key from NCBI_API_KEY when set, lifting the limit 3 -> 10 req/s); add fetchWithRetry with a per-attempt 8s timeout and backoff retries on socket errors / 429 / 5xx for elink, BioC, and the abstract fetch. Set NCBI_API_KEY in the Vercel env for durable headroom against shared-egress throttling. Verified coadread_tcga_pub resolves to source=pmc (PMC3401966). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The paper full-text hop still failed on Vercel even after the rate-limit fixes: every study resolved to abstract. Instrumenting each NCBI hop from Vercel's egress showed why — NCBI tarpits eutils elink.fcgi from cloud IPs (it hangs ~8s and times out), while efetch on the same host responds in ~130ms. PMID->PMCID depended solely on elink, so PMC never resolved. Switch that hop to NCBI's ID Converter (pmc.ncbi.nlm.nih.gov/tools/idconv), which returns the PMCID in ~50ms from Vercel and shares the host family of the BioC full-text service (also fast from Vercel). efetch stays on eutils for the abstract fallback. Verified on production: coadread_tcga_pub, brca_tcga_pub, gbm_tcga_pub all return source=pmc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the resource/tool catalogs into an exported getServerSpec() and commit the generated mcp.json next to the module, so the surface can be read without a live postMessage connection — for docs, tooling, or an agent reading ahead. A spec test asserts mcp.json stays in sync with getServerSpec(), and MCP.md documents both ways to obtain the spec (static file + runtime initialize/list handshake). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy the committed mcp.json into the build output via CopyRspackPlugin so the in-page MCP surface is discoverable at https://<portal-origin>/.well-known/mcp.json — the same origin where the postMessage MCP server runs. Source of truth stays the committed file (kept in sync with getServerSpec() by portalMcpServer.spec.ts); this just emits it as a static asset alongside the other copied JSON. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add portalWebMcp.ts, which registers the same PortalContext capability surface as native WebMCP tools via the browser's document.modelContext API (W3C draft), reusing getServerSpec() as the catalog. This makes the tools discoverable by standard in-browser agents — the MCP-B bridge extension and the model-context tool inspector today (Chrome Canary behind enable-webmcp-testing), Gemini-in- Chrome / Edge Copilot as their consumption ships — without our bespoke postMessage transport. - Feature-detects document.modelContext (falls back to the deprecated navigator.modelContext); a no-op where the API is absent, and each registerTool call is wrapped so experimental API drift can't break page load. - Registered on the host document alongside PortalMcpServer, behind the same opt-in flag, sharing PortalContext and the writableParams allowlist for navigate. Unregisters via AbortSignal on unmount. - portalWebMcp.spec.ts covers tool registration, execute routing to PortalContext, readOnly filtering, and writableParams enforcement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix cBioPortal/cbioportal# (see https://help.github.com/en/articles/closing-issues-using-keywords)
Describe changes proposed in this pull request:
Checks
Any screenshots or GIFs?
If this is a new visual feature please add a before/after screenshot or gif
here with e.g. Giphy CAPTURE or Peek
Notify reviewers
Read our Pull request merging
policy. It can help to figure out who worked on the
file before you. Please use
git blame <filename>to determine thatand notify them either through slack or by assigning them as a reviewer on the PR
Need help on this PR? Tag
@codesmithwith what you need.