Skip to content

Commit d93f7d2

Browse files
committed
Reroute faq; update llm docs
1 parent d34b7c3 commit d93f7d2

8 files changed

Lines changed: 462 additions & 130 deletions

File tree

app/faq/faq-items.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { LAST_INDEX_REFRESH } from "@/utils/constants";
2+
3+
export const updateFrequencyAnswer = (date: string) =>
4+
`We refresh the metadata index on a regular schedule to stay in sync with NCBI and EBI. The last full refresh was on ${date}. New datasets appear within a few days of their public release.`;
5+
6+
export const faqItems = [
7+
{
8+
id: "data-sources",
9+
question: "Where does seqout fetch its datasets from?",
10+
answer:
11+
"We maintain a local mirror of all publicly available datasets on NCBI's FTP servers. This includes all SRA datasets and GEO datasets. We also index ArrayExpress and ENA metadata from EBI, GSA (CNCB-NGDC Genome Sequence Archive) metadata from CNCB-NGDC in China, and DDBJ metadata from Japan, both its Sequence Read Archive (DRA) and its Genomic Expression Archive (GEA). We do not own or modify the original data.",
12+
},
13+
{
14+
id: "download-data",
15+
question: "Does seqout download sequencing data?",
16+
answer:
17+
"No. seqout only indexes and serves metadata. It does not download or host raw sequencing files such as FASTQ or BAM. Project pages do provide bash scripts for downloading FASTQ/SRA files from NCBI, AWS S3, and Google Cloud Storage.",
18+
},
19+
{
20+
id: "difference",
21+
question: "How is seqout different from browsing NCBI directly?",
22+
answer:
23+
"seqout combines GEO, SRA, ENA, DRA, GEA, GSA & ArrayExpress metadata into one interface with relevance-ranked search and consolidated tabular views. NCBI spreads this across multiple pages. seqout also adds enriched metadata, similarity graphs, citation counts, and download scripts.",
24+
},
25+
{
26+
id: "scale",
27+
question: "Is seqout suitable for large-scale searches?",
28+
answer:
29+
"Yes. The backend handles low-latency queries over millions of records. You can filter and compare across studies without waiting.",
30+
},
31+
{
32+
id: "audience",
33+
question: "Who is seqout intended for?",
34+
answer:
35+
"We built seqout for researchers who explore public sequencing metadata and want faster, more structured ways to find datasets.",
36+
},
37+
{
38+
id: "update-frequency",
39+
question: "How often is seqout updated?",
40+
answer: updateFrequencyAnswer(LAST_INDEX_REFRESH),
41+
},
42+
{
43+
id: "api",
44+
question: "Can I use seqout programmatically?",
45+
answer:
46+
"Yes. seqout offers a free REST API with no authentication required. All endpoints return JSON and support cursor-based pagination. Rate limits are 60 requests/minute for most endpoints, 30/minute for search, and 10/minute for bulk operations. See the API Reference for full documentation.",
47+
},
48+
{
49+
id: "enriched-metadata",
50+
question: "What is enriched metadata?",
51+
answer:
52+
"For many projects, we run small language models (SLMs) over free-text sample descriptions to extract structured fields like tissue, cell type, disease, sex, and age. The extractions may contain errors, so treat them as a starting point rather than ground truth. Enriched columns appear in the sample table with a purple AI badge.",
53+
},
54+
{
55+
id: "mcp",
56+
question: "What is the MCP server?",
57+
answer:
58+
"seqout exposes a remote Model Context Protocol (MCP) server. LLM clients like Claude Desktop can connect to it and search datasets through chat. The URL is https://seqout.org/api/mcp. See the MCP page for setup instructions.",
59+
},
60+
{
61+
id: "similarity",
62+
question: "How does the similarity graph work?",
63+
answer:
64+
"We embed each project into a vector space based on its metadata and precompute nearest-neighbor relationships. The similarity graph renders these as an interactive 3D force-directed layout. You can filter by organism and click through clusters of related studies.",
65+
},
66+
{
67+
id: "accession-map",
68+
question: "What is the 2D accession map?",
69+
answer:
70+
"The Map page shows a 2D embedding of roughly 1 million datasets, where proximity reflects metadata similarity. You can zoom, pan, filter by country, and click individual points to navigate to project pages. The browser loads data in a binary format for fast rendering.",
71+
},
72+
{
73+
id: "cite",
74+
question: "How do I cite seqout?",
75+
answer: "Aniruddha Mukherjee and Saket Choudhary. seqout.org.",
76+
},
77+
{
78+
id: "open-source",
79+
question: "Is seqout open source?",
80+
answer:
81+
"Yes. The frontend source code lives on GitHub at github.com/saketlab/seqout.",
82+
},
83+
{
84+
id: "browsers",
85+
question: "What browsers are supported?",
86+
answer:
87+
"Chrome, Firefox, Safari, and Edge all work. The 3D similarity graph and deck.gl maps require WebGL.",
88+
},
89+
];

app/faq/page.tsx

Lines changed: 1 addition & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { faqItems, updateFrequencyAnswer } from "@/app/faq/faq-items";
12
import SearchBar from "@/components/search-bar";
23
import SectionAnchor from "@/components/section-anchor";
34
import { LAST_INDEX_REFRESH, SERVER_API_BASE } from "@/utils/constants";
@@ -17,94 +18,6 @@ export const metadata: Metadata = {
1718
},
1819
};
1920

20-
const updateFrequencyAnswer = (date: string) =>
21-
`We refresh the metadata index on a regular schedule to stay in sync with NCBI and EBI. The last full refresh was on ${date}. New datasets appear within a few days of their public release.`;
22-
23-
const faqItems = [
24-
{
25-
id: "data-sources",
26-
question: "Where does seqout fetch its datasets from?",
27-
answer:
28-
"We maintain a local mirror of all publicly available datasets on NCBI's FTP servers. This includes all SRA datasets and GEO datasets. We also index ArrayExpress and ENA metadata from EBI, GSA (CNCB-NGDC Genome Sequence Archive) metadata from CNCB-NGDC in China, and DDBJ metadata from Japan, both its Sequence Read Archive (DRA) and its Genomic Expression Archive (GEA). We do not own or modify the original data.",
29-
},
30-
{
31-
id: "download-data",
32-
question: "Does seqout download sequencing data?",
33-
answer:
34-
"No. seqout only indexes and serves metadata. It does not download or host raw sequencing files such as FASTQ or BAM. Project pages do provide bash scripts for downloading FASTQ/SRA files from NCBI, AWS S3, and Google Cloud Storage.",
35-
},
36-
{
37-
id: "difference",
38-
question: "How is seqout different from browsing NCBI directly?",
39-
answer:
40-
"seqout combines GEO, SRA, ENA, DRA, GEA, GSA & ArrayExpress metadata into one interface with relevance-ranked search and consolidated tabular views. NCBI spreads this across multiple pages. seqout also adds enriched metadata, similarity graphs, citation counts, and download scripts.",
41-
},
42-
{
43-
id: "scale",
44-
question: "Is seqout suitable for large-scale searches?",
45-
answer:
46-
"Yes. The backend handles low-latency queries over millions of records. You can filter and compare across studies without waiting.",
47-
},
48-
{
49-
id: "audience",
50-
question: "Who is seqout intended for?",
51-
answer:
52-
"We built seqout for researchers who explore public sequencing metadata and want faster, more structured ways to find datasets.",
53-
},
54-
{
55-
id: "update-frequency",
56-
question: "How often is seqout updated?",
57-
answer: updateFrequencyAnswer(LAST_INDEX_REFRESH),
58-
},
59-
{
60-
id: "api",
61-
question: "Can I use seqout programmatically?",
62-
answer:
63-
"Yes. seqout offers a free REST API with no authentication required. All endpoints return JSON and support cursor-based pagination. Rate limits are 60 requests/minute for most endpoints, 30/minute for search, and 10/minute for bulk operations. See the API Reference for full documentation.",
64-
},
65-
{
66-
id: "enriched-metadata",
67-
question: "What is enriched metadata?",
68-
answer:
69-
"For many projects, we run small language models (SLMs) over free-text sample descriptions to extract structured fields like tissue, cell type, disease, sex, and age. The extractions may contain errors, so treat them as a starting point rather than ground truth. Enriched columns appear in the sample table with a purple AI badge.",
70-
},
71-
{
72-
id: "mcp",
73-
question: "What is the MCP server?",
74-
answer:
75-
"seqout exposes a remote Model Context Protocol (MCP) server. LLM clients like Claude Desktop can connect to it and search datasets through chat. The URL is https://seqout.org/api/mcp. See the MCP page for setup instructions.",
76-
},
77-
{
78-
id: "similarity",
79-
question: "How does the similarity graph work?",
80-
answer:
81-
"We embed each project into a vector space based on its metadata and precompute nearest-neighbor relationships. The similarity graph renders these as an interactive 3D force-directed layout. You can filter by organism and click through clusters of related studies.",
82-
},
83-
{
84-
id: "accession-map",
85-
question: "What is the 2D accession map?",
86-
answer:
87-
"The Map page shows a 2D embedding of roughly 1 million datasets, where proximity reflects metadata similarity. You can zoom, pan, filter by country, and click individual points to navigate to project pages. The browser loads data in a binary format for fast rendering.",
88-
},
89-
{
90-
id: "cite",
91-
question: "How do I cite seqout?",
92-
answer: "Aniruddha Mukherjee and Saket Choudhary. seqout.org.",
93-
},
94-
{
95-
id: "open-source",
96-
question: "Is seqout open source?",
97-
answer:
98-
"Yes. The frontend source code lives on GitHub at github.com/saketlab/seqout.",
99-
},
100-
{
101-
id: "browsers",
102-
question: "What browsers are supported?",
103-
answer:
104-
"Chrome, Firefox, Safari, and Edge all work. The 3D similarity graph and deck.gl maps require WebGL.",
105-
},
106-
];
107-
10821
const buildFaqJsonLd = (items: typeof faqItems) => ({
10922
"@context": "https://schema.org",
11023
"@type": "FAQPage",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { readFileSync } from "node:fs";
2+
import { describe, expect, it } from "vitest";
3+
import { extractHowSearchWorks } from "./jsx-prose";
4+
5+
const guide = extractHowSearchWorks(
6+
readFileSync("app/howsearchworks/page.tsx", "utf8"),
7+
);
8+
9+
describe("extractHowSearchWorks", () => {
10+
it("extracts every section of the live page", () => {
11+
for (const heading of [
12+
"### Write plain keywords",
13+
"### Structured search",
14+
"### How results are ranked",
15+
"### Where the synonyms come from",
16+
"### How synonym expansion is bounded",
17+
]) {
18+
expect(guide).toContain(heading);
19+
}
20+
});
21+
22+
it("keeps operators, examples and formulae", () => {
23+
expect(guide).toContain("`liver NOT tumor`");
24+
expect(guide).toContain("Good: `crispr screen liver`");
25+
expect(guide).toContain("N_{\\max} = 100");
26+
});
27+
28+
it("leaves no JSX behind", () => {
29+
expect(guide).not.toMatch(/<\/?[A-Za-z]/);
30+
expect(guide).not.toContain('{" "}');
31+
expect(guide).not.toContain("%%TIPS%%");
32+
expect(guide).not.toContain("export default");
33+
});
34+
35+
it("puts each list item on one line", () => {
36+
expect(guide).not.toMatch(/^-\s*$/m);
37+
});
38+
39+
it("returns null rather than mangled text for an unrelated page", () => {
40+
expect(extractHowSearchWorks("export default function X() {\n return (<p>hi</p>);\n}")).toBeNull();
41+
});
42+
});

app/llms-full.txt/jsx-prose.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const ENTITIES: Record<string, string> = {
2+
"&quot;": '"',
3+
"&ndash;": "–",
4+
"&mdash;": "—",
5+
"&amp;": "&",
6+
"&lt;": "<",
7+
"&gt;": ">",
8+
};
9+
10+
const MIN_LENGTH = 2000;
11+
12+
function jsxToText(src: string): string {
13+
return (
14+
src
15+
.replace(
16+
/<Examples\s+good="([^"]*)"\s+avoid="([^"]*)"\s*\/>/g,
17+
(_m, good, avoid) => `\n\nGood: \`${good}\`\n\nAvoid: \`${avoid}\``,
18+
)
19+
.replace(/<Q\s+q=\{`([^`]*)`\}\s*\/>/g, (_m, q) => `\`${q}\``)
20+
.replace(/<Q\s+q="([^"]*)"\s*\/>/g, (_m, q) => `\`${q}\``)
21+
.replace(/<Tex\s+inline\s+tex="([^"]*)"\s*\/>/g, (_m, t) => `$${t}$`)
22+
.replace(/<Tex\s+tex="([^"]*)"\s*\/>/g, (_m, t) => `\n\n$$${t}$$\n\n`)
23+
.replace(/<Code>([\s\S]*?)<\/Code>/g, (_m, c) => `\`${c.trim()}\``)
24+
.replace(
25+
/<Heading[^>]*>([\s\S]*?)<\/Heading>/g,
26+
(_m, h) => `\n\n### ${h.trim()}\n`,
27+
)
28+
.replace(
29+
/<li>([\s\S]*?)<\/li>/g,
30+
(_m, inner) =>
31+
`\n\n- ${inner
32+
.replace(/<\/?Text[^>]*>/g, " ")
33+
.replace(/\s+/g, " ")
34+
.trim()}`,
35+
)
36+
.replace(/<\/(?:Text|p|ul|Flex)>/g, "\n\n")
37+
.replace(/<[^>]*>/g, "")
38+
.replace(/\{"\s*"\}/g, " ")
39+
.replace(/&\w+;/g, (e) => ENTITIES[e] ?? e)
40+
.replace(/^[ \t]+/gm, "")
41+
.replace(/[ \t]+$/gm, "")
42+
.replace(/[ \t]{2,}/g, " ")
43+
.replace(/\(\s+(`)/g, "($1")
44+
.replace(/(`)\s+\)/g, "$1)")
45+
.replace(/\n{3,}/g, "\n\n")
46+
.replace(/([^\n])\n(?!\n)/g, "$1 ")
47+
.trim()
48+
);
49+
}
50+
51+
function tipSections(src: string): string {
52+
const start = src.indexOf("const tips");
53+
if (start === -1) return "";
54+
const end = src.indexOf("\n];", start);
55+
if (end === -1) return "";
56+
57+
const sections: string[] = [];
58+
const entry = /title:\s*"([^"]*)",\s*body:\s*\(\s*<>([\s\S]*?)<\/>\s*\),/g;
59+
for (const m of src.slice(start, end).matchAll(entry)) {
60+
sections.push(`### ${m[1]}\n\n${jsxToText(m[2])}`);
61+
}
62+
return sections.join("\n\n");
63+
}
64+
65+
export function extractHowSearchWorks(src: string): string | null {
66+
const component = src.indexOf("export default function");
67+
if (component === -1) return null;
68+
69+
const open = src.indexOf("return (", component);
70+
const close = src.lastIndexOf(");");
71+
if (open === -1 || close <= open) return null;
72+
const body = src.slice(open + "return (".length, close);
73+
74+
const text = jsxToText(body.replace(/\{tips\.map\([\s\S]*?\)\}/, "\n%%TIPS%%\n"))
75+
.replace("%%TIPS%%", tipSections(src))
76+
.replace(/^### .*\n+/, "");
77+
78+
return text.length < MIN_LENGTH ? null : text;
79+
}

0 commit comments

Comments
 (0)