97 lines
2.4 KiB
TypeScript
97 lines
2.4 KiB
TypeScript
import type { SearchResult, WebResult } from "./types.js";
|
|
|
|
const MAX_SNIPPET_LENGTH = 240;
|
|
|
|
function truncateSnippet(snippet: string): string {
|
|
const normalized = snippet.replace(/\s+/g, " ").trim();
|
|
if (normalized.length <= MAX_SNIPPET_LENGTH) {
|
|
return normalized;
|
|
}
|
|
|
|
return `${normalized.slice(0, MAX_SNIPPET_LENGTH - 3)}...`;
|
|
}
|
|
|
|
function humanizeAge(timestamp?: string): string {
|
|
if (!timestamp) {
|
|
return "unknown";
|
|
}
|
|
|
|
const parsed = Date.parse(timestamp);
|
|
if (Number.isNaN(parsed)) {
|
|
return "unknown";
|
|
}
|
|
|
|
const diffMs = Math.max(0, Date.now() - parsed);
|
|
const diffSeconds = Math.floor(diffMs / 1000);
|
|
|
|
if (diffSeconds < 60) {
|
|
return "just now";
|
|
}
|
|
|
|
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
if (diffMinutes < 60) {
|
|
return `${diffMinutes}m ago`;
|
|
}
|
|
|
|
const diffHours = Math.floor(diffMinutes / 60);
|
|
if (diffHours < 24) {
|
|
return `${diffHours}h ago`;
|
|
}
|
|
|
|
const diffDays = Math.floor(diffHours / 24);
|
|
return `${diffDays}d ago`;
|
|
}
|
|
|
|
function formatSource(source: WebResult, index: number): string {
|
|
const title = source.name?.trim() || "Untitled source";
|
|
const age = humanizeAge(source.timestamp);
|
|
const lines: string[] = [`[${index + 1}] ${title} (${age})`];
|
|
|
|
if (source.url?.trim()) {
|
|
lines.push(` ${source.url.trim()}`);
|
|
}
|
|
|
|
if (source.snippet?.trim()) {
|
|
lines.push(` ${truncateSnippet(source.snippet)}`);
|
|
}
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
/** Format a SearchResult into LLM-friendly text with ## Answer, ## Sources, ## Meta sections. */
|
|
export function formatForLLM(result: SearchResult, limit?: number): string {
|
|
const sourceLimit =
|
|
typeof limit === "number" && Number.isFinite(limit)
|
|
? Math.max(0, Math.floor(limit))
|
|
: result.sources.length;
|
|
|
|
const limitedSources = result.sources.slice(0, sourceLimit);
|
|
|
|
const sourceSection =
|
|
limitedSources.length === 0
|
|
? "0 sources\n(no sources returned)"
|
|
: `${limitedSources.length} sources\n${limitedSources
|
|
.map((source, index) => formatSource(source, index))
|
|
.join("\n\n")}`;
|
|
|
|
const metaLines = [
|
|
"Provider: perplexity (oauth)",
|
|
`Model: ${result.displayModel ?? "unknown"}`,
|
|
];
|
|
|
|
if (result.uuid) {
|
|
metaLines.push(`Request ID: ${result.uuid}`);
|
|
}
|
|
|
|
return [
|
|
"## Answer",
|
|
result.answer.trim() || "No answer returned.",
|
|
"",
|
|
"## Sources",
|
|
sourceSection,
|
|
"",
|
|
"## Meta",
|
|
...metaLines,
|
|
].join("\n");
|
|
}
|