proteinea / src /lib /sample-artifacts.ts
Mahmoud Eljendy
feat: Antibody Studio — AI-native antibody design workspace by Proteinea
30cc31a
/**
* Sample artifact builders — emitted by the demo engine at the end of a plan
* execution (plus on-demand for phylo/MSA/structure prompts).
*
* Two shapes:
* 1) Synthetic JSON/CSV artifacts embedded as `data:` URLs so downloads work
* fully client-side without a backend round-trip. This mirrors the existing
* test fixture at `.data/tasks/sess_b2c176cf8e6b.json`.
* 2) Public sample files (`/sample-data/*.{nwk,fasta,pdb}`) referenced by URL.
* Agent A owns those viewers; we just point at them with realistic sizes.
*/
import type { Artifact, PlanProposal, TraceEntry } from "./types";
// ---- utilities -------------------------------------------------------------
function encodeBase64(s: string): string {
if (typeof window === "undefined") {
return Buffer.from(s, "utf8").toString("base64");
}
return btoa(unescape(encodeURIComponent(s)));
}
function dataUrl(mime: string, content: string): string {
return `data:${mime};base64,${encodeBase64(content)}`;
}
function newId(): string {
return "art-" + Math.random().toString(36).slice(2, 10);
}
function utf8Bytes(s: string): number {
if (typeof TextEncoder !== "undefined") {
return new TextEncoder().encode(s).length;
}
return Buffer.byteLength(s, "utf8");
}
// ---- synthetic artifact builders ------------------------------------------
export interface DesignBriefInput {
target: string;
indication?: string;
mechanism?: string;
format?: string;
epitope?: string;
affinity?: string;
species?: string;
developability?: string;
route?: string;
competition?: string;
seed?: string;
scale?: string;
}
export function buildDesignBriefJson(brief: DesignBriefInput): string {
const payload = {
schema: "proteinea.design_brief/v1",
generated_at: new Date().toISOString(),
target: brief.target,
indication: brief.indication || null,
mechanism: brief.mechanism || null,
format: brief.format || "VHH",
epitope: brief.epitope || "no preference",
affinity_goal: brief.affinity || null,
species_cross_reactivity: brief.species || null,
developability: brief.developability || null,
route_of_administration: brief.route || null,
competitive_landscape: brief.competition || null,
starting_point: brief.seed || "de novo",
design_count: parseInt(brief.scale || "50", 10) || 50,
};
return JSON.stringify(payload, null, 2);
}
export function buildPipelinePlanJson(plan: PlanProposal): string {
const payload = {
schema: "proteinea.pipeline_plan/v1",
generated_at: new Date().toISOString(),
title: plan.title,
steps: plan.steps.map((s, i) => ({
index: i + 1,
label: s.label,
tools: s.tools,
})),
};
return JSON.stringify(payload, null, 2);
}
export function buildExecutionTraceJson(trace: TraceEntry[]): string {
const payload = {
schema: "proteinea.execution_trace/v1",
generated_at: new Date().toISOString(),
event_count: trace.length,
events: trace,
};
return JSON.stringify(payload, null, 2);
}
export function buildTopDesignsCsv(n = 10): string {
const aa = "ACDEFGHIKLMNPQRSTVWY";
const rows: string[] = [
"rank,design_id,cdr_h3,length,iPAE,pLDDT,affinity_nM,consensus_score",
];
for (let i = 0; i < n; i++) {
const id = `d${String(i + 1).padStart(3, "0")}`;
const len = 14 + Math.floor(Math.random() * 8);
let cdr = "";
for (let j = 0; j < len; j++) cdr += aa[Math.floor(Math.random() * aa.length)];
const iPAE = (5 + Math.random() * 5).toFixed(2);
const pLDDT = (82 + Math.random() * 12).toFixed(1);
const kd = (0.3 + Math.random() * 40).toFixed(2);
const score = (0.55 + Math.random() * 0.4).toFixed(3);
rows.push(`${i + 1},${id},${cdr},${len},${iPAE},${pLDDT},${kd},${score}`);
}
return rows.join("\n");
}
// ---- artifact constructors -------------------------------------------------
export function makeJsonArtifact(name: string, content: string): Artifact {
return {
id: newId(),
name,
bytes: utf8Bytes(content),
mime: "application/json",
url: dataUrl("application/json", content),
createdAt: Date.now(),
};
}
export function makeCsvArtifact(name: string, content: string): Artifact {
return {
id: newId(),
name,
bytes: utf8Bytes(content),
mime: "text/csv",
url: dataUrl("text/csv", content),
createdAt: Date.now(),
};
}
// ---- public sample-data artifacts (Agent A owns the files) -----------------
export function makePhyloTreeArtifact(): Artifact {
return {
id: newId(),
name: "spike.nwk",
bytes: 4096,
mime: "text/x-nh",
url: "/sample-data/spike.nwk",
createdAt: Date.now(),
};
}
export function makeMsaArtifact(): Artifact {
return {
id: newId(),
name: "igg-fc.fasta",
bytes: 8192,
mime: "text/x-fasta",
url: "/sample-data/igg-fc.fasta",
createdAt: Date.now(),
};
}
export function makeStructureArtifact(): Artifact {
return {
id: newId(),
name: "her2-1n8z-mini.pdb",
bytes: 24576,
mime: "chemical/x-pdb",
url: "/sample-data/her2-1n8z-mini.pdb",
createdAt: Date.now(),
};
}
/**
* Classify a raw user prompt for which sample-data viewer(s) to surface.
* Keyword-based; matches are non-exclusive.
*/
export function detectSampleDataHints(msg: string): {
phylo: boolean;
msa: boolean;
structure: boolean;
} {
const m = msg.toLowerCase();
return {
phylo:
m.includes("phylogen") ||
m.includes("tree") ||
m.includes(".nwk") ||
m.includes("newick") ||
m.includes("clade"),
msa:
m.includes("msa") ||
m.includes("alignment") ||
m.includes("multiple sequence") ||
m.includes("fasta") ||
m.includes("conservation"),
structure:
m.includes("structure") ||
m.includes("pdb") ||
m.includes("crystal") ||
m.includes("cryo-em") ||
m.includes("cryoem") ||
m.includes("3d structure"),
};
}
// ---- KB topic catalog ------------------------------------------------------
export interface KbTopic {
name: string;
ms: number;
}
/**
* Deterministic-ish KB topics we "load" before planning. Timings are realistic
* (60-300ms) and slightly randomized so no two plan previews look identical.
*/
export function buildKbTopics(): KbTopic[] {
const jitter = (base: number, spread: number) =>
Math.round(base + (Math.random() - 0.5) * spread);
return [
{ name: "antibody design best practices", ms: jitter(150, 60) },
{ name: "benchmark catalog", ms: jitter(80, 40) },
{ name: "target intelligence", ms: jitter(120, 60) },
{ name: "developability heuristics", ms: jitter(95, 40) },
];
}