@fbraza/pi-cite 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/literature-search.ts +5 -7
- package/src/rendering.ts +48 -71
package/package.json
CHANGED
package/src/literature-search.ts
CHANGED
|
@@ -138,7 +138,7 @@ export async function searchLiterature(
|
|
|
138
138
|
emitProgress(onUpdate, text, { events: [...events] });
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
-
emitEvent("
|
|
141
|
+
emitEvent("Searching PubMed...");
|
|
142
142
|
|
|
143
143
|
events.push({
|
|
144
144
|
phase: "query_start",
|
|
@@ -146,7 +146,7 @@ export async function searchLiterature(
|
|
|
146
146
|
query_index: 1,
|
|
147
147
|
query: params.pubmed_query,
|
|
148
148
|
});
|
|
149
|
-
emitEvent(
|
|
149
|
+
emitEvent("Searching PubMed...");
|
|
150
150
|
|
|
151
151
|
const pubmed = await searchPubmed(
|
|
152
152
|
{
|
|
@@ -175,20 +175,18 @@ export async function searchLiterature(
|
|
|
175
175
|
query_index: 1,
|
|
176
176
|
query: pubmed.query ?? params.pubmed_query,
|
|
177
177
|
count: pubmed.count,
|
|
178
|
-
papers: pubmedDisplayPapers,
|
|
179
178
|
});
|
|
180
|
-
emitEvent(`
|
|
179
|
+
emitEvent(`Found ${pubmed.count} PubMed ${pubmed.count === 1 ? "paper" : "papers"}.`);
|
|
181
180
|
|
|
182
181
|
events.push({ phase: "dedupe" });
|
|
183
|
-
emitEvent("
|
|
182
|
+
emitEvent("Preparing results...");
|
|
184
183
|
|
|
185
184
|
const papers = dedupeLiteraturePapers(pubmed.papers);
|
|
186
185
|
events.push({
|
|
187
186
|
phase: "complete",
|
|
188
187
|
count: papers.length,
|
|
189
|
-
papers: compactPapersForDisplay(papers),
|
|
190
188
|
});
|
|
191
|
-
emitEvent(`Literature search complete: ${papers.length}
|
|
189
|
+
emitEvent(`Literature search complete: ${papers.length} PubMed ${papers.length === 1 ? "paper" : "papers"}.`);
|
|
192
190
|
|
|
193
191
|
return {
|
|
194
192
|
count: papers.length,
|
package/src/rendering.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
2
2
|
import type { PaperRecord } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
export const MAX_STREAMED_PAPERS_PER_QUERY = 5;
|
|
5
|
-
export const
|
|
5
|
+
export const MAX_EXPANDED_PAPER_PREVIEW = 5;
|
|
6
6
|
|
|
7
7
|
type ThemeLike = {
|
|
8
8
|
fg?: (color: string, text: string) => string;
|
|
@@ -32,7 +32,6 @@ export type LiteratureSearchDisplayEvent =
|
|
|
32
32
|
query_index: number;
|
|
33
33
|
query: string;
|
|
34
34
|
count: number;
|
|
35
|
-
papers: CompactPaperForDisplay[];
|
|
36
35
|
}
|
|
37
36
|
| {
|
|
38
37
|
phase: "query_error";
|
|
@@ -42,7 +41,7 @@ export type LiteratureSearchDisplayEvent =
|
|
|
42
41
|
error: string;
|
|
43
42
|
}
|
|
44
43
|
| { phase: "dedupe" }
|
|
45
|
-
| { phase: "complete"; count: number
|
|
44
|
+
| { phase: "complete"; count: number };
|
|
46
45
|
|
|
47
46
|
export type LiteratureSearchDisplaySearch = {
|
|
48
47
|
provider: "pubmed";
|
|
@@ -119,7 +118,7 @@ export function sourceLabel(paper: PaperRecord): string {
|
|
|
119
118
|
.filter(Boolean),
|
|
120
119
|
);
|
|
121
120
|
if (sources.has("pubmed")) return "PM";
|
|
122
|
-
return
|
|
121
|
+
return "—";
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
export function compactPaperForDisplay(paper: PaperRecord): CompactPaperForDisplay {
|
|
@@ -145,6 +144,10 @@ function providerColor(provider: "pubmed"): string {
|
|
|
145
144
|
return "success";
|
|
146
145
|
}
|
|
147
146
|
|
|
147
|
+
function pluralize(count: number, singular: string, plural = `${singular}s`): string {
|
|
148
|
+
return count === 1 ? singular : plural;
|
|
149
|
+
}
|
|
150
|
+
|
|
148
151
|
export function formatFoundLine(
|
|
149
152
|
paper: CompactPaperForDisplay,
|
|
150
153
|
theme?: ThemeLike,
|
|
@@ -155,60 +158,14 @@ export function formatFoundLine(
|
|
|
155
158
|
return ` ${color(theme, "success", "✓ found:")} ${author} ${title} ${color(theme, "muted", id)}`;
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
export function
|
|
161
|
+
export function formatPaperPreviewLine(
|
|
159
162
|
paper: CompactPaperForDisplay,
|
|
160
163
|
index: number,
|
|
161
164
|
theme?: ThemeLike,
|
|
162
165
|
): string {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
return ` ${color(theme, "success",
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function renderEvent(
|
|
169
|
-
event: LiteratureSearchDisplayEvent,
|
|
170
|
-
theme?: ThemeLike,
|
|
171
|
-
): string[] {
|
|
172
|
-
if (event.phase === "start") {
|
|
173
|
-
return [`${color(theme, "accent", "●")} ${color(theme, "toolTitle", "literature_search")} starting`];
|
|
174
|
-
}
|
|
175
|
-
if (event.phase === "query_start") {
|
|
176
|
-
return [
|
|
177
|
-
`${color(theme, providerColor(event.provider), "→")} ${color(theme, providerColor(event.provider), providerLabel(event.provider))} q${event.query_index}: ${event.query}`,
|
|
178
|
-
];
|
|
179
|
-
}
|
|
180
|
-
if (event.phase === "query_results") {
|
|
181
|
-
const lines = event.papers
|
|
182
|
-
.slice(0, MAX_STREAMED_PAPERS_PER_QUERY)
|
|
183
|
-
.map((paper) => formatFoundLine(paper, theme));
|
|
184
|
-
const hidden = event.count - Math.min(event.count, MAX_STREAMED_PAPERS_PER_QUERY);
|
|
185
|
-
if (hidden > 0) lines.push(` ${color(theme, "dim", "…")} ${hidden} more candidate papers`);
|
|
186
|
-
if (event.count === 0) lines.push(` ${color(theme, "muted", "no candidate papers found")}`);
|
|
187
|
-
return lines;
|
|
188
|
-
}
|
|
189
|
-
if (event.phase === "query_error") {
|
|
190
|
-
return [
|
|
191
|
-
` ${color(theme, "error", "! failed:")} ${providerLabel(event.provider)} q${event.query_index}: ${truncateText(event.error, 96)}`,
|
|
192
|
-
];
|
|
193
|
-
}
|
|
194
|
-
if (event.phase === "dedupe") {
|
|
195
|
-
return [`${color(theme, "warning", "→")} deduplicating by DOI / PMID / title-year`];
|
|
196
|
-
}
|
|
197
|
-
const lines = event.papers
|
|
198
|
-
.slice(0, MAX_FINAL_MERGED_PAPERS)
|
|
199
|
-
.map((paper, index) => formatMergedLine(paper, index, theme));
|
|
200
|
-
const hidden = event.count - Math.min(event.count, MAX_FINAL_MERGED_PAPERS);
|
|
201
|
-
if (hidden > 0) lines.push(` ${color(theme, "dim", "…")} ${hidden} more merged papers`);
|
|
202
|
-
lines.push(`${color(theme, "success", "✓")} done: ${event.count} merged papers`);
|
|
203
|
-
return lines;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function renderLiteratureEventTranscript(
|
|
207
|
-
events: LiteratureSearchDisplayEvent[] | undefined,
|
|
208
|
-
theme?: ThemeLike,
|
|
209
|
-
): string {
|
|
210
|
-
if (!events?.length) return "";
|
|
211
|
-
return events.flatMap((event) => renderEvent(event, theme)).join("\n");
|
|
166
|
+
const year = paper.year ? ` ${paper.year}` : "";
|
|
167
|
+
const title = truncateText(paper.title, 88);
|
|
168
|
+
return ` ${color(theme, "success", `${index + 1}.`)} ${paper.first_author}${year} — ${title}`;
|
|
212
169
|
}
|
|
213
170
|
|
|
214
171
|
type RenderOptions = { expanded?: boolean; isPartial?: boolean };
|
|
@@ -223,6 +180,7 @@ type ToolRenderResult<TDetails> = {
|
|
|
223
180
|
type ProviderSearchSummary = {
|
|
224
181
|
searched?: boolean;
|
|
225
182
|
count?: number;
|
|
183
|
+
query?: string;
|
|
226
184
|
};
|
|
227
185
|
|
|
228
186
|
type LiteratureResultDetails = {
|
|
@@ -241,10 +199,40 @@ type ProviderResultDetails = {
|
|
|
241
199
|
};
|
|
242
200
|
|
|
243
201
|
function renderCollapsedLiteratureResult(details: LiteratureResultDetails, theme?: ThemeLike): string {
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
202
|
+
const count = details.count ?? details.papers?.length ?? details.providers?.pubmed?.count;
|
|
203
|
+
const prefix = `${color(theme, "success", "✓")} ${color(theme, "toolTitle", "literature_search")}`;
|
|
204
|
+
if (count === undefined) return `${prefix} PubMed papers`;
|
|
205
|
+
if (count === 0) return `${prefix} no PubMed papers found`;
|
|
206
|
+
return `${prefix} ${count} PubMed ${pluralize(count, "paper")}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function renderLiteratureStreamingStatus(details: LiteratureResultDetails, theme?: ThemeLike): string {
|
|
210
|
+
const event = details.events?.at(-1);
|
|
211
|
+
const prefix = `${color(theme, "accent", "●")} ${color(theme, "toolTitle", "literature_search")}`;
|
|
212
|
+
if (!event || event.phase === "start" || event.phase === "query_start" || event.phase === "dedupe") {
|
|
213
|
+
return `${prefix} searching PubMed…`;
|
|
214
|
+
}
|
|
215
|
+
if (event.phase === "query_error") {
|
|
216
|
+
return `${color(theme, "error", "!")} ${color(theme, "toolTitle", "literature_search")} PubMed failed: ${truncateText(event.error, 96)}`;
|
|
217
|
+
}
|
|
218
|
+
const count = event.count;
|
|
219
|
+
if (count === 0) return `${prefix} no PubMed papers found`;
|
|
220
|
+
return `${prefix} found ${count} PubMed ${pluralize(count, "paper")}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function renderExpandedLiteratureResult(details: LiteratureResultDetails, theme?: ThemeLike): string {
|
|
224
|
+
const papers = compactPapersForDisplay(details.papers ?? []);
|
|
225
|
+
const lines = [renderCollapsedLiteratureResult(details, theme)];
|
|
226
|
+
const query = details.providers?.pubmed?.query;
|
|
227
|
+
if (query) lines.push(`${color(theme, "muted", "query:")} ${truncateText(query, 96)}`);
|
|
228
|
+
lines.push(
|
|
229
|
+
...papers
|
|
230
|
+
.slice(0, MAX_EXPANDED_PAPER_PREVIEW)
|
|
231
|
+
.map((paper, index) => formatPaperPreviewLine(paper, index, theme)),
|
|
232
|
+
);
|
|
233
|
+
const hidden = papers.length - Math.min(papers.length, MAX_EXPANDED_PAPER_PREVIEW);
|
|
234
|
+
if (hidden > 0) lines.push(` ${color(theme, "dim", "…")} ${hidden} more ${pluralize(hidden, "paper")} in tool result`);
|
|
235
|
+
return lines.join("\n");
|
|
248
236
|
}
|
|
249
237
|
|
|
250
238
|
export function renderLiteratureSearchResult(
|
|
@@ -253,24 +241,13 @@ export function renderLiteratureSearchResult(
|
|
|
253
241
|
theme?: ThemeLike,
|
|
254
242
|
): Text {
|
|
255
243
|
const details = result.details ?? {};
|
|
256
|
-
const transcript = renderLiteratureEventTranscript(details.events, theme);
|
|
257
244
|
if (options.isPartial) {
|
|
258
|
-
return terminalText(
|
|
245
|
+
return terminalText(renderLiteratureStreamingStatus(details, theme));
|
|
259
246
|
}
|
|
260
247
|
if (!options.expanded) {
|
|
261
248
|
return terminalText(renderCollapsedLiteratureResult(details, theme));
|
|
262
249
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const papers = compactPapersForDisplay(details.papers ?? []);
|
|
266
|
-
const lines = [
|
|
267
|
-
`${color(theme, "accent", "●")} ${color(theme, "toolTitle", "literature_search")} result`,
|
|
268
|
-
renderCollapsedLiteratureResult(details, theme),
|
|
269
|
-
`${color(theme, "warning", "→")} deduplicating by DOI / PMID / title-year`,
|
|
270
|
-
...papers.slice(0, MAX_FINAL_MERGED_PAPERS).map((paper, index) => formatMergedLine(paper, index, theme)),
|
|
271
|
-
`${color(theme, "success", "✓")} done: ${papers.length} merged papers`,
|
|
272
|
-
];
|
|
273
|
-
return terminalText(lines.join("\n"));
|
|
250
|
+
return terminalText(renderExpandedLiteratureResult(details, theme));
|
|
274
251
|
}
|
|
275
252
|
|
|
276
253
|
export function renderProviderSearchResult(
|