183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
import type { Session, SessionSummary } from '../types';
|
|
|
|
export const DEFAULT_CHAT_TITLE = 'New Chat';
|
|
|
|
function isGeneratedSessionName(name: string): boolean {
|
|
return /^20\d{6}(?:[_-]\d+)?$/.test(name.trim());
|
|
}
|
|
|
|
function isDefaultSessionName(name: string): boolean {
|
|
const trimmed = name.trim();
|
|
return !trimmed || trimmed === DEFAULT_CHAT_TITLE || trimmed === '新对话' || isGeneratedSessionName(trimmed);
|
|
}
|
|
|
|
export function shouldShowNewChatTitle(session: SessionSummary): boolean {
|
|
if (session.recipe) return false;
|
|
return !session.user_set_name && session.message_count === 0 && isDefaultSessionName(session.name);
|
|
}
|
|
|
|
export function sortAndTrim<T extends SessionSummary>(sessions: T[]): T[] {
|
|
return [...sessions]
|
|
.sort((a, b) => {
|
|
const aTime = new Date(a.updated_at ?? a.created_at ?? 0).getTime();
|
|
const bTime = new Date(b.updated_at ?? b.created_at ?? 0).getTime();
|
|
return bTime - aTime;
|
|
});
|
|
}
|
|
|
|
export function hasMeaningfulSessionTitle(session: SessionSummary): boolean {
|
|
if (session.user_set_name) return Boolean(session.name.trim());
|
|
if (session.recipe?.title?.trim()) return true;
|
|
return !isDefaultSessionName(session.name);
|
|
}
|
|
|
|
export function toSessionSummary(session: Session | SessionSummary): SessionSummary {
|
|
return {
|
|
id: session.id,
|
|
name: session.name,
|
|
message_count: session.message_count,
|
|
created_at: session.created_at,
|
|
updated_at: session.updated_at,
|
|
user_set_name: session.user_set_name,
|
|
recipe: session.recipe ?? null,
|
|
};
|
|
}
|
|
|
|
export function mergeSessionRecord(
|
|
existing: SessionSummary | undefined,
|
|
incoming: SessionSummary,
|
|
): SessionSummary {
|
|
if (!existing) return incoming;
|
|
|
|
const keepExistingTitle =
|
|
hasMeaningfulSessionTitle(existing) && !hasMeaningfulSessionTitle(incoming);
|
|
if (!keepExistingTitle) return { ...existing, ...incoming, id: incoming.id };
|
|
|
|
return {
|
|
...existing,
|
|
...incoming,
|
|
id: incoming.id,
|
|
name: existing.name,
|
|
user_set_name: existing.user_set_name,
|
|
recipe: existing.recipe ?? incoming.recipe,
|
|
};
|
|
}
|
|
|
|
export function mergeSessionLists(prev: SessionSummary[], incoming: SessionSummary[]): SessionSummary[] {
|
|
const previousById = new Map(prev.map((item) => [item.id, item]));
|
|
return sortAndTrim(incoming.map((item) => mergeSessionRecord(previousById.get(item.id), item)));
|
|
}
|
|
|
|
export function appendSessionLists(prev: SessionSummary[], incoming: SessionSummary[]): SessionSummary[] {
|
|
const previousById = new Map(prev.map((item) => [item.id, item]));
|
|
const mergedIncoming = incoming.map((item) => mergeSessionRecord(previousById.get(item.id), item));
|
|
const incomingIds = new Set(mergedIncoming.map((item) => item.id));
|
|
return sortAndTrim([
|
|
...prev.filter((item) => !incomingIds.has(item.id)),
|
|
...mergedIncoming,
|
|
]);
|
|
}
|
|
|
|
export function prependUnique(prev: SessionSummary[], session: SessionSummary): SessionSummary[] {
|
|
return sortAndTrim([
|
|
mergeSessionRecord(prev.find((s) => s.id === session.id), session),
|
|
...prev.filter((s) => s.id !== session.id),
|
|
]);
|
|
}
|
|
|
|
export function touchSession(
|
|
sessions: SessionSummary[],
|
|
sessionId: string,
|
|
messageDelta = 0,
|
|
): SessionSummary[] {
|
|
const now = new Date().toISOString();
|
|
return sortAndTrim(
|
|
sessions.map((s) =>
|
|
s.id === sessionId
|
|
? {
|
|
...s,
|
|
updated_at: now,
|
|
message_count: Math.max(0, s.message_count + messageDelta),
|
|
}
|
|
: s,
|
|
),
|
|
);
|
|
}
|
|
|
|
export function getSessionDisplayName(session: SessionSummary): string {
|
|
if (session.user_set_name) return session.name;
|
|
if (session.recipe?.title) return session.recipe.title;
|
|
if (shouldShowNewChatTitle(session)) return DEFAULT_CHAT_TITLE;
|
|
return session.name;
|
|
}
|
|
|
|
function sameLocalDay(a: Date, b: Date): boolean {
|
|
return (
|
|
a.getFullYear() === b.getFullYear() &&
|
|
a.getMonth() === b.getMonth() &&
|
|
a.getDate() === b.getDate()
|
|
);
|
|
}
|
|
|
|
export function getSessionDateKey(session: SessionSummary): string {
|
|
const iso = session.updated_at ?? session.created_at;
|
|
if (!iso) return '';
|
|
const date = new Date(iso);
|
|
if (Number.isNaN(date.getTime())) return '';
|
|
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
}
|
|
|
|
export function formatSessionDateLabel(session: SessionSummary): string {
|
|
const iso = session.updated_at ?? session.created_at;
|
|
if (!iso) return '';
|
|
const date = new Date(iso);
|
|
if (Number.isNaN(date.getTime())) return '';
|
|
|
|
const now = new Date();
|
|
if (sameLocalDay(date, now)) return '今天';
|
|
|
|
const yesterday = new Date(now);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
if (sameLocalDay(date, yesterday)) return '昨天';
|
|
|
|
if (date.getFullYear() === now.getFullYear()) {
|
|
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
}
|
|
|
|
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
|
|
}
|
|
|
|
export type SessionDateGroup = {
|
|
dateKey: string;
|
|
label: string;
|
|
sessions: SessionSummary[];
|
|
};
|
|
|
|
export function groupSessionsByDate(sessions: SessionSummary[]): SessionDateGroup[] {
|
|
const groups: SessionDateGroup[] = [];
|
|
for (const session of sessions) {
|
|
const dateKey = getSessionDateKey(session);
|
|
const label = formatSessionDateLabel(session) || '未知日期';
|
|
const last = groups[groups.length - 1];
|
|
if (last && last.dateKey === dateKey) {
|
|
last.sessions.push(session);
|
|
} else {
|
|
groups.push({ dateKey, label, sessions: [session] });
|
|
}
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
const DEFAULT_SESSION_NAMES = new Set([DEFAULT_CHAT_TITLE, '新对话']);
|
|
|
|
export function getSessionListLabel(session: SessionSummary): string {
|
|
const displayName = getSessionDisplayName(session).trim();
|
|
if (displayName && !DEFAULT_SESSION_NAMES.has(displayName) && !isGeneratedSessionName(displayName)) {
|
|
return displayName;
|
|
}
|
|
if (session.message_count > 0) {
|
|
return `对话 · ${session.message_count} 条消息`;
|
|
}
|
|
return `会话 ${session.id.slice(0, 8)}`;
|
|
}
|