@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fbraza/pi-cite",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Pi extension with PubMed and literature search tools.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -138,7 +138,7 @@ export async function searchLiterature(
138
138
  emitProgress(onUpdate, text, { events: [...events] });
139
139
  };
140
140
 
141
- emitEvent("Starting literature search...");
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(`Searching PubMed q1: ${params.pubmed_query}`);
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(`PubMed q1 found ${pubmed.count} candidate papers.`);
179
+ emitEvent(`Found ${pubmed.count} PubMed ${pubmed.count === 1 ? "paper" : "papers"}.`);
181
180
 
182
181
  events.push({ phase: "dedupe" });
183
- emitEvent("Deduplicating literature results...");
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} merged papers.`);
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 MAX_FINAL_MERGED_PAPERS = 20;
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; papers: CompactPaperForDisplay[] };
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 paper.source ?? "—";
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 formatMergedLine(
161
+ export function formatPaperPreviewLine(
159
162
  paper: CompactPaperForDisplay,
160
163
  index: number,
161
164
  theme?: ThemeLike,
162
165
  ): string {
163
- const title = truncateText(paper.title, 72);
164
- const source = color(theme, "success", `(${paper.source})`);
165
- return ` ${color(theme, "success", "+")} ${index + 1}. ${title} ${source}`;
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 pubmed = details?.providers?.pubmed;
245
- const pubmedText = pubmed?.searched ? `PubMed: ${pubmed.count}` : "PubMed: ";
246
- const count = details?.count ?? details?.papers?.length ?? 0;
247
- return `${color(theme, "success", "✓")} ${color(theme, "toolTitle", "literature_search")} ${color(theme, "success", pubmedText)} | merged: ${count}`;
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(transcript || color(theme, "warning", "Searching literature..."));
245
+ return terminalText(renderLiteratureStreamingStatus(details, theme));
259
246
  }
260
247
  if (!options.expanded) {
261
248
  return terminalText(renderCollapsedLiteratureResult(details, theme));
262
249
  }
263
- if (transcript) return terminalText(transcript);
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(