mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
9aa16d4715
* move schema folder * fix tests * fix vercel build
334 lines
9.1 KiB
TypeScript
334 lines
9.1 KiB
TypeScript
import { Column, asc, count, inArray, like, sql } from 'drizzle-orm';
|
|
import { and, desc, eq, isNull, not, or } from 'drizzle-orm/expressions';
|
|
|
|
import { appEnv } from '@/config/app';
|
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
import { serverDB } from '@/database/server/core/db';
|
|
import { parseAgentConfig } from '@/server/globalConfig/parseDefaultAgent';
|
|
import { ChatSessionList, LobeAgentSession } from '@/types/session';
|
|
import { merge } from '@/utils/merge';
|
|
|
|
import {
|
|
AgentItem,
|
|
NewAgent,
|
|
NewSession,
|
|
SessionItem,
|
|
agents,
|
|
agentsToSessions,
|
|
sessionGroups,
|
|
sessions,
|
|
} from '../../schemas';
|
|
import { idGenerator } from '@/database/utils/idGenerator';
|
|
|
|
export class SessionModel {
|
|
private userId: string;
|
|
|
|
constructor(userId: string) {
|
|
this.userId = userId;
|
|
}
|
|
// **************** Query *************** //
|
|
|
|
async query({ current = 0, pageSize = 9999 } = {}) {
|
|
const offset = current * pageSize;
|
|
|
|
return serverDB.query.sessions.findMany({
|
|
limit: pageSize,
|
|
offset,
|
|
orderBy: [desc(sessions.updatedAt)],
|
|
where: and(eq(sessions.userId, this.userId), not(eq(sessions.slug, INBOX_SESSION_ID))),
|
|
with: { agentsToSessions: { columns: {}, with: { agent: true } }, group: true },
|
|
});
|
|
}
|
|
|
|
async queryWithGroups(): Promise<ChatSessionList> {
|
|
// 查询所有会话
|
|
const result = await this.query();
|
|
|
|
const groups = await serverDB.query.sessionGroups.findMany({
|
|
orderBy: [asc(sessionGroups.sort), desc(sessionGroups.createdAt)],
|
|
where: eq(sessions.userId, this.userId),
|
|
});
|
|
|
|
return {
|
|
sessionGroups: groups as unknown as ChatSessionList['sessionGroups'],
|
|
sessions: result.map((item) => this.mapSessionItem(item as any)),
|
|
};
|
|
}
|
|
|
|
async queryByKeyword(keyword: string) {
|
|
if (!keyword) return [];
|
|
|
|
const keywordLowerCase = keyword.toLowerCase();
|
|
|
|
const data = await this.findSessionsByKeywords({ keyword: keywordLowerCase });
|
|
|
|
return data.map((item) => this.mapSessionItem(item as any));
|
|
}
|
|
|
|
async findByIdOrSlug(
|
|
idOrSlug: string,
|
|
): Promise<(SessionItem & { agent: AgentItem }) | undefined> {
|
|
const result = await serverDB.query.sessions.findFirst({
|
|
where: and(
|
|
or(eq(sessions.id, idOrSlug), eq(sessions.slug, idOrSlug)),
|
|
eq(sessions.userId, this.userId),
|
|
),
|
|
with: { agentsToSessions: { columns: {}, with: { agent: true } }, group: true },
|
|
});
|
|
|
|
if (!result) return;
|
|
|
|
return { ...result, agent: (result?.agentsToSessions?.[0] as any)?.agent } as any;
|
|
}
|
|
|
|
async count() {
|
|
const result = await serverDB
|
|
.select({
|
|
count: count(),
|
|
})
|
|
.from(sessions)
|
|
.where(eq(sessions.userId, this.userId))
|
|
.execute();
|
|
|
|
return result[0].count;
|
|
}
|
|
|
|
// **************** Create *************** //
|
|
|
|
async create({
|
|
id = idGenerator('sessions'),
|
|
type = 'agent',
|
|
session = {},
|
|
config = {},
|
|
slug,
|
|
}: {
|
|
config?: Partial<NewAgent>;
|
|
id?: string;
|
|
session?: Partial<NewSession>;
|
|
slug?: string;
|
|
type: 'agent' | 'group';
|
|
}): Promise<SessionItem> {
|
|
return serverDB.transaction(async (trx) => {
|
|
const newAgents = await trx
|
|
.insert(agents)
|
|
.values({
|
|
...config,
|
|
createdAt: new Date(),
|
|
id: idGenerator('agents'),
|
|
updatedAt: new Date(),
|
|
userId: this.userId,
|
|
})
|
|
.returning();
|
|
|
|
const result = await trx
|
|
.insert(sessions)
|
|
.values({
|
|
...session,
|
|
createdAt: new Date(),
|
|
id,
|
|
slug,
|
|
type,
|
|
updatedAt: new Date(),
|
|
userId: this.userId,
|
|
})
|
|
.returning();
|
|
|
|
await trx.insert(agentsToSessions).values({
|
|
agentId: newAgents[0].id,
|
|
sessionId: id,
|
|
});
|
|
|
|
return result[0];
|
|
});
|
|
}
|
|
|
|
async createInbox() {
|
|
const item = await serverDB.query.sessions.findFirst({
|
|
where: and(eq(sessions.userId, this.userId), eq(sessions.slug, INBOX_SESSION_ID)),
|
|
});
|
|
if (item) return;
|
|
|
|
const serverAgentConfig = parseAgentConfig(appEnv.DEFAULT_AGENT_CONFIG) || {};
|
|
|
|
return await this.create({
|
|
config: merge(DEFAULT_AGENT_CONFIG, serverAgentConfig),
|
|
slug: INBOX_SESSION_ID,
|
|
type: 'agent',
|
|
});
|
|
}
|
|
|
|
async batchCreate(newSessions: NewSession[]) {
|
|
const sessionsToInsert = newSessions.map((s) => {
|
|
return {
|
|
...s,
|
|
id: this.genId(),
|
|
userId: this.userId,
|
|
};
|
|
});
|
|
|
|
return serverDB.insert(sessions).values(sessionsToInsert);
|
|
}
|
|
|
|
async duplicate(id: string, newTitle?: string) {
|
|
const result = await this.findByIdOrSlug(id);
|
|
|
|
if (!result) return;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars,unused-imports/no-unused-vars
|
|
const { agent, clientId, ...session } = result;
|
|
const sessionId = this.genId();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { id: _, slug: __, ...config } = agent;
|
|
|
|
return this.create({
|
|
config: config,
|
|
id: sessionId,
|
|
session: {
|
|
...session,
|
|
title: newTitle || session.title,
|
|
},
|
|
type: 'agent',
|
|
});
|
|
}
|
|
|
|
// **************** Delete *************** //
|
|
|
|
/**
|
|
* Delete a session, also delete all messages and topics associated with it.
|
|
*/
|
|
async delete(id: string) {
|
|
return serverDB
|
|
.delete(sessions)
|
|
.where(and(eq(sessions.id, id), eq(sessions.userId, this.userId)));
|
|
}
|
|
|
|
/**
|
|
* Batch delete sessions, also delete all messages and topics associated with them.
|
|
*/
|
|
async batchDelete(ids: string[]) {
|
|
return serverDB
|
|
.delete(sessions)
|
|
.where(and(inArray(sessions.id, ids), eq(sessions.userId, this.userId)));
|
|
}
|
|
|
|
async deleteAll() {
|
|
return serverDB.delete(sessions).where(eq(sessions.userId, this.userId));
|
|
}
|
|
// **************** Update *************** //
|
|
|
|
async update(id: string, data: Partial<SessionItem>) {
|
|
return serverDB
|
|
.update(sessions)
|
|
.set(data)
|
|
.where(and(eq(sessions.id, id), eq(sessions.userId, this.userId)))
|
|
.returning();
|
|
}
|
|
|
|
async updateConfig(id: string, data: Partial<AgentItem>) {
|
|
return serverDB
|
|
.update(agents)
|
|
.set(data)
|
|
.where(and(eq(agents.id, id), eq(agents.userId, this.userId)));
|
|
}
|
|
|
|
// **************** Helper *************** //
|
|
|
|
private genId = () => idGenerator('sessions');
|
|
|
|
private mapSessionItem = ({
|
|
agentsToSessions,
|
|
title,
|
|
backgroundColor,
|
|
description,
|
|
avatar,
|
|
groupId,
|
|
...res
|
|
}: SessionItem & { agentsToSessions?: { agent: AgentItem }[] }): LobeAgentSession => {
|
|
// TODO: 未来这里需要更好的实现方案,目前只取第一个
|
|
const agent = agentsToSessions?.[0]?.agent;
|
|
return {
|
|
...res,
|
|
group: groupId,
|
|
meta: {
|
|
avatar: agent?.avatar ?? avatar ?? undefined,
|
|
backgroundColor: agent?.backgroundColor ?? backgroundColor ?? undefined,
|
|
description: agent?.description ?? description ?? undefined,
|
|
tags: agent?.tags ?? undefined,
|
|
title: agent?.title ?? title ?? undefined,
|
|
},
|
|
model: agent?.model,
|
|
} as any;
|
|
};
|
|
|
|
async findSessions(params: {
|
|
current?: number;
|
|
group?: string;
|
|
keyword?: string;
|
|
pageSize?: number;
|
|
pinned?: boolean;
|
|
}) {
|
|
const { pinned, keyword, group, pageSize = 9999, current = 0 } = params;
|
|
|
|
const offset = current * pageSize;
|
|
return serverDB.query.sessions.findMany({
|
|
limit: pageSize,
|
|
offset,
|
|
orderBy: [desc(sessions.updatedAt)],
|
|
where: and(
|
|
eq(sessions.userId, this.userId),
|
|
pinned !== undefined ? eq(sessions.pinned, pinned) : eq(sessions.userId, this.userId),
|
|
keyword
|
|
? or(
|
|
like(
|
|
sql`lower(${sessions.title})` as unknown as Column,
|
|
`%${keyword.toLowerCase()}%`,
|
|
),
|
|
like(
|
|
sql`lower(${sessions.description})` as unknown as Column,
|
|
`%${keyword.toLowerCase()}%`,
|
|
),
|
|
)
|
|
: eq(sessions.userId, this.userId),
|
|
group ? eq(sessions.groupId, group) : isNull(sessions.groupId),
|
|
),
|
|
|
|
with: { agentsToSessions: { columns: {}, with: { agent: true } }, group: true },
|
|
});
|
|
}
|
|
|
|
async findSessionsByKeywords(params: {
|
|
current?: number;
|
|
keyword: string;
|
|
pageSize?: number;
|
|
}) {
|
|
const { keyword, pageSize = 9999, current = 0 } = params;
|
|
const offset = current * pageSize;
|
|
const results = await serverDB.query.agents.findMany({
|
|
limit: pageSize,
|
|
offset,
|
|
orderBy: [desc(agents.updatedAt)],
|
|
where: and(
|
|
eq(agents.userId, this.userId),
|
|
or(
|
|
like(
|
|
sql`lower(${agents.title})` as unknown as Column,
|
|
`%${keyword.toLowerCase()}%`,
|
|
),
|
|
like(
|
|
sql`lower(${agents.description})` as unknown as Column,
|
|
`%${keyword.toLowerCase()}%`,
|
|
),
|
|
)
|
|
),
|
|
with: { agentsToSessions: { columns: {}, with: { session: true } } },
|
|
});
|
|
try {
|
|
// @ts-expect-error
|
|
return results.map((item) => item.agentsToSessions[0].session);
|
|
} catch {}
|
|
return []
|
|
}
|
|
}
|