misc: only save session info to local file

This commit is contained in:
WJG 2024-01-31 22:21:58 +08:00
parent dc8324610e
commit 8606cf47d5
No known key found for this signature in database
GPG Key ID: 258474EF8590014A
10 changed files with 164 additions and 99 deletions

View File

@ -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<IBotIndex | undefined> {
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<IBotConfig | undefined> {
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<IBotConfig>
): Promise<IBotConfig | undefined> {
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 };
}
}

View File

@ -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<IBotConfig>;
constructor(config: DeepPartial<IBotConfig>) {
this.config = config;
}
async getMemory() {
await this.loadOrUpdateConfig();
if (!this.isReady) {
return undefined;
async get(): Promise<Partial<IBotConfig & { memory: MemoryManager }>> {
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<IBotConfig>) {
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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 },
})

View File

@ -110,6 +110,9 @@ export function withDefault<T = any>(e: any, defaultValue: T): T {
}
export function removeEmpty<T = any>(data: T): T {
if (!data) {
return data;
}
if (Array.isArray(data)) {
return data.filter((e) => e != undefined) as any;
}

View File

@ -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");
}