65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
import type { UIMessage } from "@/lib/types";
|
|
|
|
export interface PromptAnchor {
|
|
id: string;
|
|
label: string;
|
|
preview: string;
|
|
createdAt: number;
|
|
index: number;
|
|
}
|
|
|
|
export function userPromptAnchors(messages: UIMessage[]): PromptAnchor[] {
|
|
let index = 0;
|
|
return messages.flatMap((message) => {
|
|
if (message.role !== "user") return [];
|
|
const anchor: PromptAnchor = {
|
|
id: message.id,
|
|
label: promptLabel(message.content, index),
|
|
preview: promptPreview(message.content, index),
|
|
createdAt: message.createdAt,
|
|
index,
|
|
};
|
|
index += 1;
|
|
return [anchor];
|
|
});
|
|
}
|
|
|
|
export function promptLabel(content: string, index: number): string {
|
|
const text = content.replace(/\s+/g, " ").trim();
|
|
if (!text) return `Prompt ${index + 1}`;
|
|
return text.length > 80 ? `${text.slice(0, 77)}...` : text;
|
|
}
|
|
|
|
export function promptPreview(content: string, index: number): string {
|
|
const text = content.replace(/\n{3,}/g, "\n\n").trim();
|
|
if (!text) return `Prompt ${index + 1}`;
|
|
return text.length > 320 ? `${text.slice(0, 317)}...` : text;
|
|
}
|
|
|
|
export function jumpToPrompt(scrollEl: HTMLElement | null, promptId: string | undefined): void {
|
|
if (!scrollEl || !promptId) return;
|
|
const target = findPromptElement(scrollEl, promptId);
|
|
if (!target) return;
|
|
scrollEl.scrollTo({
|
|
top: Math.max(0, promptTop(scrollEl, target) - 16),
|
|
behavior: "smooth",
|
|
});
|
|
}
|
|
|
|
export function findPromptElement(scrollEl: HTMLElement, promptId: string): HTMLElement | null {
|
|
const candidates = scrollEl.querySelectorAll<HTMLElement>("[data-user-prompt-id]");
|
|
return Array.from(candidates).find(
|
|
(candidate) => candidate.dataset.userPromptId === promptId,
|
|
) ?? null;
|
|
}
|
|
|
|
export function promptTop(scrollEl: HTMLElement, target: HTMLElement): number {
|
|
const scrollRect = scrollEl.getBoundingClientRect();
|
|
const targetRect = target.getBoundingClientRect();
|
|
const hasLayoutRect = scrollRect.top !== 0 || targetRect.top !== 0;
|
|
if (hasLayoutRect) {
|
|
return targetRect.top - scrollRect.top + scrollEl.scrollTop;
|
|
}
|
|
return target.offsetTop;
|
|
}
|