104 lines · 3.0 KB · TypeScript
RawDownload
import type { ProjectId } from "@t3tools/contracts";import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "@t3tools/contracts/settings";import * as Arr from "effect/Array";import * as Order from "effect/Order"; export interface ThreadSortInput {  readonly createdAt: string;  readonly updatedAt: string;  readonly latestUserMessageAt?: string | null;  readonly messages?: ReadonlyArray<{    readonly createdAt: string;    readonly role: string;  }>;} export function toSortableTimestamp(iso: string | undefined): number | null {  if (!iso) return null;  const ms = Date.parse(iso);  return Number.isFinite(ms) ? ms : null;} function getFirstSortableTimestamp(...values: Array<string | null | undefined>): number | null {  for (const value of values) {    const timestamp = toSortableTimestamp(value ?? undefined);    if (timestamp !== null) {      return timestamp;    }  }   return null;} function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {  if (thread.latestUserMessageAt) {    const latestUserMessageTimestamp = toSortableTimestamp(thread.latestUserMessageAt);    if (latestUserMessageTimestamp !== null) {      return latestUserMessageTimestamp;    }  }   let latestUserMessageTimestamp: number | null = null;   for (const message of thread.messages ?? []) {    if (message.role !== "user") continue;    const messageTimestamp = toSortableTimestamp(message.createdAt);    if (messageTimestamp === null) continue;    latestUserMessageTimestamp =      latestUserMessageTimestamp === null        ? messageTimestamp        : Math.max(latestUserMessageTimestamp, messageTimestamp);  }   if (latestUserMessageTimestamp !== null) {    return latestUserMessageTimestamp;  }   return getFirstSortableTimestamp(thread.updatedAt, thread.createdAt) ?? Number.NEGATIVE_INFINITY;} export function getThreadSortTimestamp(  thread: ThreadSortInput,  sortOrder: SidebarThreadSortOrder | Exclude<SidebarProjectSortOrder, "manual">,): number {  if (sortOrder === "created_at") {    return (      getFirstSortableTimestamp(thread.createdAt, thread.updatedAt) ?? Number.NEGATIVE_INFINITY    );  }  return getLatestUserMessageTimestamp(thread);} export function sortThreads<T extends { readonly id: string } & ThreadSortInput>(  threads: readonly T[],  sortOrder: SidebarThreadSortOrder,): T[] {  return Arr.sort(    threads,    Order.mapInput(      Order.Struct({        timestamp: Order.flip(Order.Number),        id: Order.flip(Order.String),      }),      (thread: T) => ({        timestamp: getThreadSortTimestamp(thread, sortOrder),        id: thread.id,      }),    ),  );} export function getLatestThreadForProject<  T extends {    readonly id: string;    readonly projectId: ProjectId;    readonly archivedAt: string | null;  } & ThreadSortInput,>(threads: readonly T[], projectId: ProjectId, sortOrder: SidebarThreadSortOrder): T | null {  return (    sortThreads(      threads.filter((thread) => thread.projectId === projectId && thread.archivedAt === null),      sortOrder,    )[0] ?? null  );}