diff --git a/src/services/bot/config.ts b/src/services/bot/config.ts index 25b5a6a..e925a6c 100644 --- a/src/services/bot/config.ts +++ b/src/services/bot/config.ts @@ -1,57 +1,113 @@ import { Room, User } from "@prisma/client"; import { readJSON, writeJSON } from "../../utils/io"; +import { deepClone, removeEmpty } from "../../utils/base"; +import { UserCRUD } from "../db/user"; +import { RoomCRUD, getRoomID } from "../db/room"; import { DeepPartial } from "../../utils/type"; -import { deepClone } from "../../utils/base"; -import { diff } from "../../utils/diff"; -export type IBotConfig = DeepPartial<{ +const kDefaultMaster = { + name: "用户", + profile: "", +}; + +const kDefaultBot = { + name: "小爱同学", + profile: "", +}; + +interface IBotIndex { + botId: string; + masterId: string; +} + +export interface IBotConfig { bot: User; master: User; room: Room; -}>; +} class _BotConfig { - config?: IBotConfig; + private botIndex?: IBotIndex; - private _config_path = ".bot.json"; + private _index_path = ".bot.json"; - async get() { - if (!this.config) { - this.config = await readJSON(this._config_path); + private async _getIndex(): Promise { + if (!this.botIndex) { + this.botIndex = await readJSON(this._index_path); } - return this.config; + return this.botIndex; } - async update(config: IBotConfig) { - let currentConfig: any = await this.get(); - const oldConfig = deepClone(currentConfig ?? {}); - if (!currentConfig) { - currentConfig = { - master: { - name: "用户", - profile: "", - }, - bot: { - name: "小爱同学", - profile: "", - }, - }; - } - for (const key of ["bot", "master", "room"]) { - currentConfig[key] = { - ...currentConfig[key], - ...(config as any)[key], - }; - } - const diffs = diff(currentConfig, oldConfig); - const diffKeys = diffs.map((e) => e.path[0]); - if (diffKeys.length > 0) { - const success = await writeJSON(this._config_path, currentConfig); - if (success) { - return { config: currentConfig, diffs: diffKeys }; + async get(): Promise { + const index = await this._getIndex(); + if (!index) { + // create db records + const bot = await UserCRUD.addOrUpdate(kDefaultBot); + if (!bot) { + console.error("❌ create bot failed"); + return undefined; } + const master = await UserCRUD.addOrUpdate(kDefaultMaster); + if (!master) { + console.error("❌ create master failed"); + return undefined; + } + const defaultRoomName = `${master.name}和${bot.name}的私聊`; + const room = await RoomCRUD.addOrUpdate({ + id: getRoomID([bot, master]), + name: defaultRoomName, + description: defaultRoomName, + }); + if (!room) { + console.error("❌ create room failed"); + return undefined; + } + this.botIndex = { + botId: bot.id, + masterId: master.id, + }; + await writeJSON(this._index_path, this.botIndex); } - return { config: oldConfig }; + const bot = await UserCRUD.get(this.botIndex!.botId); + if (!bot) { + console.error("❌ find bot failed"); + return undefined; + } + const master = await UserCRUD.get(this.botIndex!.masterId); + if (!master) { + console.error("❌ find master failed"); + return undefined; + } + const room = await RoomCRUD.get(getRoomID([bot, master])); + if (!room) { + console.error("❌ find room failed"); + return undefined; + } + return { bot, master, room }; + } + + async update( + config: DeepPartial + ): Promise { + let currentConfig = await this.get(); + if (!currentConfig) { + return undefined; + } + const oldConfig = deepClone(currentConfig); + for (const key in currentConfig) { + const _key = key as keyof IBotConfig; + currentConfig[_key] = { + ...currentConfig[_key], + ...removeEmpty(config[_key]), + updatedAt: undefined, // reset update date + } as any; + } + let { bot, master, room } = currentConfig; + bot = (await UserCRUD.addOrUpdate(currentConfig.bot)) ?? oldConfig.bot; + master = + (await UserCRUD.addOrUpdate(currentConfig.master)) ?? oldConfig.master; + room = (await RoomCRUD.addOrUpdate(currentConfig.room)) ?? oldConfig.room; + return { bot, master, room }; } } diff --git a/src/services/bot/conversation.ts b/src/services/bot/conversation.ts index 09ac6fe..ac5704b 100644 --- a/src/services/bot/conversation.ts +++ b/src/services/bot/conversation.ts @@ -1,39 +1,28 @@ -import { Message, Prisma, Room, User } from "@prisma/client"; -import { UserCRUD } from "../db/user"; -import { RoomCRUD, getRoomID } from "../db/room"; +import { Message, Prisma, User } from "@prisma/client"; import { MemoryManager } from "./memory"; import { MessageCRUD } from "../db/message"; import { BotConfig, IBotConfig } from "./config"; -import { jsonEncode } from "../../utils/base"; +import { DeepPartial } from "../../utils/type"; export class ConversationManager { - private config: IBotConfig; - constructor(config: IBotConfig) { + private config: DeepPartial; + constructor(config: DeepPartial) { this.config = config; } - async getMemory() { - await this.loadOrUpdateConfig(); - if (!this.isReady) { - return undefined; + async get(): Promise> { + const config = await this.update(); + if (!config) { + return {}; } - return this.memory; + return { + ...config, + memory: new MemoryManager(config.room), + }; } - async getRoom() { - const { room } = await this.loadOrUpdateConfig(); - if (!this.isReady) { - return undefined; - } - return room as Room; - } - - async getUser(key: "bot" | "master") { - const config = await this.loadOrUpdateConfig(); - if (!this.isReady) { - return undefined; - } - return config[key] as User; + async update(config?: DeepPartial) { + return BotConfig.update(config ?? this.config); } async getMessages(options?: { @@ -47,44 +36,15 @@ export class ConversationManager { */ order?: "asc" | "desc"; }) { - const room = await this.getRoom(); - if (!this.isReady) { + const { room } = await this.get(); + if (!room) { return []; } return MessageCRUD.gets({ room, ...options }); } async onMessage(message: Message) { - const memory = await this.getMemory(); + const { memory } = await this.get(); return memory?.addMessage2Memory(message); } - - private memory?: MemoryManager; - - get isReady() { - return !!this.memory; - } - - async loadOrUpdateConfig() { - const { config, diffs } = await BotConfig.update(this.config); - if (!config.bot?.id || diffs?.includes("bot")) { - config.bot = await UserCRUD.addOrUpdate(config.bot); - } - if (!config.master?.id || diffs?.includes("master")) { - config.master = await UserCRUD.addOrUpdate(config.master); - } - if (!config.room?.id || diffs?.includes("room")) { - const defaultRoomName = `${config.master.name}和${config.bot.name}的私聊`; - config.room = await RoomCRUD.addOrUpdate({ - id: getRoomID([config.bot.id, config.master.id]), - name: config.room?.name ?? defaultRoomName, - description: config.room?.description ?? defaultRoomName, - }); - } - const { config: newConfig } = await BotConfig.update(config); - if (!this.memory && config.bot && config.master && config.room) { - this.memory = new MemoryManager(config.room); - } - return newConfig as IBotConfig; - } } diff --git a/src/services/db/memory-long-term.ts b/src/services/db/memory-long-term.ts index 388c91d..b135823 100644 --- a/src/services/db/memory-long-term.ts +++ b/src/services/db/memory-long-term.ts @@ -18,6 +18,13 @@ class _LongTermMemoryCRUD { }); } + async get(id: number) { + return kPrisma.longTermMemory.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get long term memory failed", id, e); + return undefined; + }); + } + async gets(options?: { room?: Room; owner?: User; diff --git a/src/services/db/memory-short-term.ts b/src/services/db/memory-short-term.ts index f9b6326..93dc37c 100644 --- a/src/services/db/memory-short-term.ts +++ b/src/services/db/memory-short-term.ts @@ -18,6 +18,13 @@ class _ShortTermMemoryCRUD { }); } + async get(id: number) { + return kPrisma.shortTermMemory.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get short term memory failed", id, e); + return undefined; + }); + } + async gets(options?: { room?: Room; owner?: User; diff --git a/src/services/db/memory.ts b/src/services/db/memory.ts index 35d1405..bd1e43b 100644 --- a/src/services/db/memory.ts +++ b/src/services/db/memory.ts @@ -18,6 +18,13 @@ class _MemoryCRUD { }); } + async get(id: number) { + return kPrisma.memory.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get memory failed", id, e); + return undefined; + }); + } + async gets(options?: { room?: Room; owner?: User; diff --git a/src/services/db/message.ts b/src/services/db/message.ts index fe15507..3bc1204 100644 --- a/src/services/db/message.ts +++ b/src/services/db/message.ts @@ -18,6 +18,13 @@ class _MessageCRUD { }); } + async get(id: number) { + return kPrisma.message.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get message failed", id, e); + return undefined; + }); + } + async gets(options?: { room?: Room; sender?: User; diff --git a/src/services/db/room.ts b/src/services/db/room.ts index f6ebf58..b9d3a09 100644 --- a/src/services/db/room.ts +++ b/src/services/db/room.ts @@ -1,8 +1,11 @@ import { Prisma, Room, User } from "@prisma/client"; import { k404, kPrisma } from "."; -export function getRoomID(users: string[]) { - return users.sort().join("_"); +export function getRoomID(users: User[]) { + return users + .map((e) => e.id) + .sort() + .join("_"); } class _RoomCRUD { @@ -24,6 +27,13 @@ class _RoomCRUD { }); } + async get(id: string) { + return kPrisma.room.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get room failed", id, e); + return undefined; + }); + } + async gets(options?: { user?: User; take?: number; diff --git a/src/services/db/user.ts b/src/services/db/user.ts index a8d018d..a5133f4 100644 --- a/src/services/db/user.ts +++ b/src/services/db/user.ts @@ -9,6 +9,13 @@ class _UserCRUD { }); } + async get(id: string) { + return kPrisma.user.findFirst({ where: { id } }).catch((e) => { + console.error("❌ get user failed", id, e); + return undefined; + }); + } + async gets(options?: { take?: number; skip?: number; @@ -23,13 +30,14 @@ class _UserCRUD { take = 10, skip = 0, cursorId, - include = { members: true }, + include = { rooms: true }, order = "desc", } = options ?? {}; const users = await kPrisma.user .findMany({ take, skip, + include, cursor: { id: cursorId }, orderBy: { createdAt: order }, }) diff --git a/src/utils/base.ts b/src/utils/base.ts index e1997b2..bee1631 100644 --- a/src/utils/base.ts +++ b/src/utils/base.ts @@ -110,6 +110,9 @@ export function withDefault(e: any, defaultValue: T): T { } export function removeEmpty(data: T): T { + if (!data) { + return data; + } if (Array.isArray(data)) { return data.filter((e) => e != undefined) as any; } diff --git a/tests/db/index.ts b/tests/db/index.ts index f585136..d179c26 100644 --- a/tests/db/index.ts +++ b/tests/db/index.ts @@ -17,7 +17,7 @@ export async function testDB() { description: "王黎的客厅,小爱同学放在角落里", }, }); - const { room } = await manager.loadOrUpdateConfig(); + const { room } = await manager.get(); assert(room, "❌ load config failed"); println("✅ hello world!"); }