Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8fcc22d
Add chat sidebar MVP: Claude-grounded paper observations on results view
May 19, 2026
e4e5294
Add paper-grounded beacons over oncoprint and results-view tabs
May 19, 2026
c7db0d6
Deploy chat sidebar to Vercel
alisman May 19, 2026
3ab84cb
Wire chat sidebar dev mode via Tailscale HTTPS
alisman May 19, 2026
e776c22
Refocus sidebar on the current view; add presets, CORS, dev wiring
alisman May 19, 2026
1b7aa04
Send viewport screenshot with each preset request
alisman May 19, 2026
e518dac
Chat input, screenshot fidelity, beacons loader, dev affordances
alisman May 19, 2026
f9f0ca4
Wait for cBioPortal network idle before screenshotting; route Netlify…
alisman May 19, 2026
f490c56
Ground beacons in the page's actual inventory; hide them when sidebar…
alisman May 21, 2026
98f3bbb
Route chat sidebar through Vercel AI Gateway; let users switch models…
alisman May 21, 2026
837eee1
Sidebar UX polish: per-model beacons, eligibility gating, cost receipt
alisman May 21, 2026
0957bf0
Expose the page as a postMessage MCP surface; extract beacon engine +…
alisman Jun 8, 2026
d167a1a
Fix paper full-text dropping to abstract under NCBI rate limits
alisman Jun 8, 2026
17e29fb
Resolve PMID->PMCID via idconv instead of eutils elink
alisman Jun 8, 2026
0bdbfdd
Make the portal MCP surface obtainable as a static spec
alisman Jun 8, 2026
fd0b504
Serve the MCP catalog at /.well-known/mcp.json on the portal origin
alisman Jun 8, 2026
c313fef
Expose the portal surface as native WebMCP tools (document.modelContext)
alisman Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,5 @@ api-e2e/validation.js
.vs/
.nx
.turbo
.claude/
.vercel
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"gl-matrix": "2.3.2",
"history": "4.10.1",
"html-webpack-plugin": "^5.3.2",
"html2canvas": "^1.4.1",
"igv": "^2.11.2",
"imports-loader": "^0.8.0",
"jStat": "^1.7.0",
Expand Down
8 changes: 8 additions & 0 deletions packages/chat-sidebar-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
public
.vercel
.env
.env.local
.env.*.local
*.log
6 changes: 6 additions & 0 deletions packages/chat-sidebar-server/.vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.env
.env.local
.env.*.local
dist
*.log
8 changes: 8 additions & 0 deletions packages/chat-sidebar-server/api/chat/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { MODEL } from '../../src/core.js';
import { applyCors } from '../../src/cors.js';

export default function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
res.json({ ok: true, model: MODEL });
}
32 changes: 32 additions & 0 deletions packages/chat-sidebar-server/api/chat/highlights.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { runHighlights } from '../../src/core.js';
import { applyCors } from '../../src/cors.js';

export const config = {
maxDuration: 60,
};

export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
if (applyCors(req, res)) return;
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method not allowed' });
return;
}
const { studyId, inventory, model } = (req.body ?? {}) as any;
if (!studyId || typeof studyId !== 'string') {
res.status(400).json({ error: 'studyId (string) required' });
return;
}
try {
const result = await runHighlights({ studyId, inventory, model });
res.json(result);
} catch (err: any) {
console.error('highlights failed:', err);
res.status(500).json({
error: err?.message ?? 'Claude call failed',
});
}
}
27 changes: 27 additions & 0 deletions packages/chat-sidebar-server/api/chat/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { getShortlistedModels } from '../../src/pricing.js';
import { applyCors } from '../../src/cors.js';

export const config = {
maxDuration: 30,
};

export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
if (applyCors(req, res)) return;
if (req.method !== 'GET') {
res.status(405).json({ error: 'Method not allowed' });
return;
}
try {
const models = await getShortlistedModels();
res.json({ models });
} catch (err: any) {
console.error('models failed:', err);
res.status(500).json({
error: err?.message ?? 'Could not fetch model catalog',
});
}
}
39 changes: 39 additions & 0 deletions packages/chat-sidebar-server/api/chat/paper-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { getPaperContext } from '../../src/core.js';
import { applyCors } from '../../src/cors.js';

export const config = {
// First call per study still has to fetch PMC; subsequent ones hit the
// in-memory cache and return immediately.
maxDuration: 30,
};

export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
if (applyCors(req, res)) return;
if (req.method !== 'GET') {
res.status(405).json({ error: 'Method not allowed' });
return;
}
const studyId = req.query.studyId;
if (!studyId || typeof studyId !== 'string') {
res.status(400).json({ error: 'studyId (string) required' });
return;
}
try {
const paper = await getPaperContext(studyId);
res.json({
studyId,
source: paper.source,
paperUrl: paper.paperUrl,
studyName: paper.study.name,
});
} catch (err: any) {
console.error('paper-status failed:', err);
res.status(500).json({
error: err?.message ?? 'paper status failed',
});
}
}
41 changes: 41 additions & 0 deletions packages/chat-sidebar-server/api/chat/suggest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { runSuggest } from '../../src/core.js';
import { applyCors } from '../../src/cors.js';

export const config = {
maxDuration: 60, // Pro tier cap; first call on a fresh paper can take 15-30s.
};

export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
if (applyCors(req, res)) return;
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method not allowed' });
return;
}
const { studyId, genes, tab, preset, userPrompt, screenshot, model } =
(req.body ?? {}) as any;
if (!studyId || typeof studyId !== 'string') {
res.status(400).json({ error: 'studyId (string) required' });
return;
}
try {
const result = await runSuggest({
studyId,
genes,
tab,
preset,
userPrompt,
screenshot,
model,
});
res.json(result);
} catch (err: any) {
console.error('suggest failed:', err);
res.status(500).json({
error: err?.message ?? 'Claude call failed',
});
}
}
29 changes: 29 additions & 0 deletions packages/chat-sidebar-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "chat-sidebar-server",
"private": true,
"version": "0.1.0",
"description": "Backend for the cBioPortal chat sidebar. Fetches paper text from PMC/PubMed and proxies Claude API calls.",
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "tsx src/server.ts",
"vercel-dev": "vercel dev --listen 4000",
"vercel-build": "cd ../.. && pnpm --filter chat-sidebar build && rm -rf packages/chat-sidebar-server/public && mkdir -p packages/chat-sidebar-server/public && cp -R packages/chat-sidebar/dist/. packages/chat-sidebar-server/public/"
},
"dependencies": {
"ai": "^6.0.188",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.2",
"zod": "4.4.3"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^22.10.0",
"@vercel/node": "^3.2.0",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vercel": "^39.0.0"
}
}
Loading
Loading