You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

179 lines
5.7 KiB

import { dbGlobal } from "drizzle-pkg/lib/db";
import { characterSessions, characterMessages } from "drizzle-pkg/lib/schema/character";
import { eq, desc, asc, and, isNull, sql, max } from "drizzle-orm";
import type { CharacterSessionRow, CharacterMessageRow } from "./types";
export async function createCharacterSession(params: {
userId?: number | null;
tempToken?: string | null;
cardId?: number | null;
modelId?: number | null;
expiresAt?: Date | null;
}): Promise<CharacterSessionRow> {
const id = `cs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
const [row] = await dbGlobal
.insert(characterSessions)
.values({
id,
userId: params.userId ?? null,
tempToken: params.tempToken ?? null,
cardId: params.cardId ?? null,
modelId: params.modelId ?? null,
expiresAt: params.expiresAt ?? null,
})
.returning();
return row!;
}
export async function getCharacterSessionById(id: string): Promise<CharacterSessionRow | null> {
const [row] = await dbGlobal
.select()
.from(characterSessions)
.where(and(eq(characterSessions.id, id), isNull(characterSessions.deletedAt)))
.limit(1);
return row ?? null;
}
export async function getCharacterSessionByIdAndUser(
id: string,
userId: number | null,
tempToken: string | null,
): Promise<CharacterSessionRow | null> {
const conditions = [eq(characterSessions.id, id), isNull(characterSessions.deletedAt)];
if (userId) {
conditions.push(eq(characterSessions.userId, userId));
} else if (tempToken) {
conditions.push(eq(characterSessions.tempToken, tempToken));
} else {
return null;
}
const [row] = await dbGlobal
.select()
.from(characterSessions)
.where(and(...conditions))
.limit(1);
return row ?? null;
}
export async function listCharacterSessions(params: {
userId?: number | null;
tempToken?: string | null;
cardId?: number | null;
page?: number;
pageSize?: number;
}): Promise<{ list: CharacterSessionRow[]; total: number; page: number; pageSize: number; totalPages: number }> {
const { page = 1, pageSize = 30 } = params;
const conditions = [isNull(characterSessions.deletedAt)];
if (params.userId) {
conditions.push(eq(characterSessions.userId, params.userId));
} else if (params.tempToken) {
conditions.push(eq(characterSessions.tempToken, params.tempToken));
} else {
return { list: [], total: 0, page, pageSize, totalPages: 0 };
}
if (params.cardId) {
conditions.push(eq(characterSessions.cardId, params.cardId));
}
const where = and(...conditions);
const [countResult] = await dbGlobal
.select({ count: sql<number>`count(*)` })
.from(characterSessions)
.where(where);
const total = countResult?.count ?? 0;
const totalPages = Math.ceil(total / pageSize);
const list = await dbGlobal
.select()
.from(characterSessions)
.where(where)
.orderBy(desc(characterSessions.lastActiveAt))
.limit(pageSize)
.offset((page - 1) * pageSize);
return { list, total, page, pageSize, totalPages };
}
export async function updateCharacterSession(
id: string,
data: {
title?: string;
cardId?: number | null;
modelId?: number | null;
lastActiveAt?: Date;
},
): Promise<void> {
const updates: Record<string, any> = {};
if (data.title !== undefined) updates.title = data.title;
if (data.cardId !== undefined) updates.cardId = data.cardId;
if (data.modelId !== undefined) updates.modelId = data.modelId;
if (data.lastActiveAt !== undefined) updates.lastActiveAt = data.lastActiveAt;
if (Object.keys(updates).length === 0) return;
await dbGlobal.update(characterSessions).set(updates).where(eq(characterSessions.id, id));
}
export async function softDeleteCharacterSession(id: string): Promise<void> {
await dbGlobal
.update(characterSessions)
.set({ deletedAt: new Date() })
.where(eq(characterSessions.id, id));
}
export async function getMessagesByCharacterSession(
sessionId: string,
params?: { limit?: number; latest?: boolean },
): Promise<CharacterMessageRow[]> {
const { limit = 50, latest = false } = params ?? {};
const baseQuery = () =>
dbGlobal.select().from(characterMessages).where(eq(characterMessages.sessionId, sessionId));
if (latest) {
const rows = await baseQuery().orderBy(desc(characterMessages.sortOrder)).limit(limit);
return rows.reverse();
}
return baseQuery().orderBy(asc(characterMessages.sortOrder)).limit(limit);
}
export async function getMaxCharacterSortOrder(sessionId: string): Promise<number> {
const [result] = await dbGlobal
.select({ maxSort: max(characterMessages.sortOrder) })
.from(characterMessages)
.where(eq(characterMessages.sessionId, sessionId));
return result?.maxSort ?? 0;
}
export async function saveCharacterMessage(params: {
sessionId: string;
role: string;
content: string;
sortOrder: number;
}): Promise<CharacterMessageRow> {
const id = `cm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
const [row] = await dbGlobal
.insert(characterMessages)
.values({
id,
sessionId: params.sessionId,
role: params.role,
content: params.content,
sortOrder: params.sortOrder,
})
.returning();
return row!;
}
export async function countCharacterMessagesByRole(sessionId: string, role: string): Promise<number> {
const [result] = await dbGlobal
.select({ cnt: sql<number>`cast(count(*) as integer)` })
.from(characterMessages)
.where(and(eq(characterMessages.sessionId, sessionId), eq(characterMessages.role, role)));
return result?.cnt ?? 0;
}
export async function touchCharacterSession(sessionId: string): Promise<void> {
await dbGlobal
.update(characterSessions)
.set({ lastActiveAt: new Date() })
.where(eq(characterSessions.id, sessionId));
}