koishi-plugin-satori-ai 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export declare class SAT extends Sat {
4
4
  config: Sat.Config;
5
5
  private apiClient;
6
6
  private memoryManager;
7
+ private portraitManager;
7
8
  private ChannelParallelCount;
8
9
  private onlineUsers;
9
10
  constructor(ctx: Context, config: Sat.Config);
package/lib/index.js CHANGED
@@ -44,8 +44,8 @@ __export(src_exports, {
44
44
  default: () => src_default
45
45
  });
46
46
  module.exports = __toCommonJS(src_exports);
47
- var import_koishi6 = require("koishi");
48
- var path5 = __toESM(require("path"));
47
+ var import_koishi7 = require("koishi");
48
+ var path6 = __toESM(require("path"));
49
49
 
50
50
  // src/api.ts
51
51
  var import_koishi2 = require("koishi");
@@ -131,6 +131,8 @@ function isErrorWithMessage(error) {
131
131
  }
132
132
  __name(isErrorWithMessage, "isErrorWithMessage");
133
133
  function processPrompt(prompt) {
134
+ if (!prompt)
135
+ return "";
134
136
  if (prompt.includes(":poke"))
135
137
  return "戳戳";
136
138
  return prompt.replace(/<[^>]*?name="([^\"]*)"[^>]*>/g, (_, name) => `@${name}`);
@@ -206,16 +208,23 @@ var APIClient = class {
206
208
  // 发送辅助聊天请求
207
209
  async auxiliaryChat(messages) {
208
210
  const AuxiliaryPayload = this.createAuxiliaryPayload(messages, this.config.auxiliary_LLM);
209
- for (let i = 0; i < this.config.keys.length; i++) {
211
+ const url = `${trimSlash(this.config.auxiliary_LLM_URL)}/chat/completions`;
212
+ const headers = this.createHeaders(this.config.auxiliary_LLM_key);
213
+ let content;
214
+ for (let i = 1; i <= this.config.maxRetryTimes; i++) {
210
215
  try {
211
- return await this.tryRequest(this.config.auxiliary_LLM_URL, AuxiliaryPayload, this.config.auxiliary_LLM_key);
216
+ const response = await this.ctx.http.post(url, AuxiliaryPayload, { headers, timeout: 36e5 });
217
+ content = response.choices[0].message.content;
218
+ return { content, error: false };
212
219
  } catch (error) {
213
- if (i == this.config.keys.length - 1) {
220
+ if (i == this.config.maxRetryTimes) {
214
221
  return this.handleAPIError(error);
215
222
  }
216
- this.rotateKey();
223
+ logger2.warn(`辅助模型API请求失败(${error}),重试(第${i}次)中...`);
224
+ continue;
217
225
  }
218
226
  }
227
+ return { content: "unreachable", error: true };
219
228
  }
220
229
  // 发送生成用户画像请求
221
230
  async generateUserPortrait(user, messages) {
@@ -234,16 +243,24 @@ var APIClient = class {
234
243
  baseURL = this.config.baseURL;
235
244
  }
236
245
  const payload = this.createAuxiliaryPayload(messages, modle);
237
- for (let i = 0; i < keys.length; i++) {
246
+ const url = `${trimSlash(baseURL)}/chat/completions`;
247
+ const headers = this.createHeaders(keys);
248
+ let content;
249
+ for (let i = 1; i <= this.config.maxRetryTimes; i++) {
238
250
  try {
239
- return await this.tryRequest(baseURL, payload, keys);
251
+ const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
252
+ content = response.choices[0].message.content;
253
+ return { content, error: false };
240
254
  } catch (error) {
241
- if (i == keys.length - 1) {
255
+ if (i == this.config.maxRetryTimes) {
242
256
  return this.handleAPIError(error);
243
257
  }
244
- this.rotateKey();
258
+ await new Promise((resolve2) => setTimeout(resolve2, this.config.retry_delay_time || 5e3));
259
+ logger2.warn(`生成画像时API请求失败(${error}),重试(第${i}次)中...`);
260
+ continue;
245
261
  }
246
262
  }
263
+ return { content: "unreachable", error: true };
247
264
  }
248
265
  // 生成请求体
249
266
  createPayload(messages, model) {
@@ -362,9 +379,101 @@ var APIClient = class {
362
379
  var import_koishi3 = require("koishi");
363
380
  var fs2 = __toESM(require("fs"));
364
381
  var path2 = __toESM(require("path"));
382
+
383
+ // src/database.ts
384
+ async function isTargetIdNotExists(ctx, userId) {
385
+ const users = await ctx.database.get("p_system", { userid: userId });
386
+ return users.length === 0;
387
+ }
388
+ __name(isTargetIdNotExists, "isTargetIdNotExists");
389
+ async function createUser(ctx, user) {
390
+ await ctx.database.create("p_system", {
391
+ userid: user.userid || "",
392
+ usersname: user.usersname || "",
393
+ p: user.p || 0,
394
+ favorability: user.favorability || 0,
395
+ userlevel: user.userlevel || 0,
396
+ usage: user.usage || 0,
397
+ lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
398
+ items: user.items || {}
399
+ });
400
+ }
401
+ __name(createUser, "createUser");
402
+ async function updateFavorability(ctx, user, adjustment) {
403
+ if (!user)
404
+ return;
405
+ let newValue;
406
+ if (typeof adjustment === "number")
407
+ newValue = user.favorability + adjustment;
408
+ else
409
+ newValue = user.favorability;
410
+ await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
411
+ }
412
+ __name(updateFavorability, "updateFavorability");
413
+ async function updateUserLevel(ctx, user, level) {
414
+ if (!user)
415
+ return;
416
+ await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
417
+ }
418
+ __name(updateUserLevel, "updateUserLevel");
419
+ async function updateUserUsage(ctx, user, adjustment = 1) {
420
+ if (!user)
421
+ return;
422
+ if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
423
+ user.usage = 0;
424
+ await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
425
+ await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
426
+ return user.usage + adjustment;
427
+ }
428
+ __name(updateUserUsage, "updateUserUsage");
429
+ async function updateUserItems(ctx, user) {
430
+ if (!user)
431
+ return;
432
+ await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
433
+ }
434
+ __name(updateUserItems, "updateUserItems");
435
+ async function getUser(ctx, userId) {
436
+ const users = await ctx.database.get("p_system", { userid: userId });
437
+ return users[0] || null;
438
+ }
439
+ __name(getUser, "getUser");
440
+ async function ensureUserExists(ctx, userId, username) {
441
+ const notExists = await isTargetIdNotExists(ctx, userId);
442
+ if (notExists) {
443
+ await createUser(ctx, {
444
+ userid: userId,
445
+ usersname: username,
446
+ p: 0,
447
+ favorability: 0,
448
+ userlevel: 0,
449
+ usage: 0,
450
+ lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
451
+ items: {}
452
+ });
453
+ }
454
+ return getUser(ctx, userId);
455
+ }
456
+ __name(ensureUserExists, "ensureUserExists");
457
+ function extendDatabase(ctx) {
458
+ ctx.model.extend("p_system", {
459
+ id: "unsigned",
460
+ userid: "string",
461
+ usersname: "string",
462
+ p: "integer",
463
+ favorability: "integer",
464
+ userlevel: "integer",
465
+ usage: "integer",
466
+ lastChatTime: "integer",
467
+ items: "object"
468
+ }, { autoInc: true });
469
+ }
470
+ __name(extendDatabase, "extendDatabase");
471
+
472
+ // src/memory.ts
365
473
  var logger3 = new import_koishi3.Logger("satori-memory");
366
474
  var MemoryManager = class {
367
- constructor(config) {
475
+ constructor(ctx, config) {
476
+ this.ctx = ctx;
368
477
  this.config = config;
369
478
  }
370
479
  static {
@@ -379,7 +488,8 @@ var MemoryManager = class {
379
488
  this.updateChannelMemory(session, prompt, config, response.content);
380
489
  prompt = prompt.replace("我", session.username);
381
490
  const date = ` (对话日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()})`;
382
- if (this.shouldRemember(prompt)) {
491
+ const userFavourbility = await (await getUser(this.ctx, session.userId)).favorability;
492
+ if (this.shouldRemember(prompt, userFavourbility)) {
383
493
  await this.saveLongTermMemory(session, [{
384
494
  role: date,
385
495
  content: prompt
@@ -387,8 +497,8 @@ var MemoryManager = class {
387
497
  }
388
498
  }
389
499
  // 是否应当记忆
390
- shouldRemember(content) {
391
- return (content.length >= this.config.remember_min_length || content.includes("记住")) && !this.config.memory_block_words.some((word) => content.includes(word));
500
+ shouldRemember(content, userFavourbility) {
501
+ return (content.length >= this.config.remember_min_length || content.includes("记住") && userFavourbility >= 50) && !this.config.memory_block_words.some((word) => content.includes(word));
392
502
  }
393
503
  // 更新频道对话
394
504
  async updateChannelDialogue(session, prompt, name) {
@@ -595,95 +705,6 @@ ${matched.map((entry) => entry.content).join("\n")}
595
705
  }
596
706
  };
597
707
 
598
- // src/database.ts
599
- async function isTargetIdNotExists(ctx, userId) {
600
- const users = await ctx.database.get("p_system", { userid: userId });
601
- return users.length === 0;
602
- }
603
- __name(isTargetIdNotExists, "isTargetIdNotExists");
604
- async function createUser(ctx, user) {
605
- await ctx.database.create("p_system", {
606
- userid: user.userid || "",
607
- usersname: user.usersname || "",
608
- p: user.p || 0,
609
- favorability: user.favorability || 0,
610
- userlevel: user.userlevel || 0,
611
- usage: user.usage || 0,
612
- lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
613
- items: user.items || {}
614
- });
615
- }
616
- __name(createUser, "createUser");
617
- async function updateFavorability(ctx, user, adjustment) {
618
- if (!user)
619
- return;
620
- let newValue;
621
- if (typeof adjustment === "number")
622
- newValue = user.favorability + adjustment;
623
- else
624
- newValue = user.favorability;
625
- await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
626
- }
627
- __name(updateFavorability, "updateFavorability");
628
- async function updateUserLevel(ctx, user, level) {
629
- if (!user)
630
- return;
631
- await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
632
- }
633
- __name(updateUserLevel, "updateUserLevel");
634
- async function updateUserUsage(ctx, user, adjustment = 1) {
635
- if (!user)
636
- return;
637
- if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
638
- user.usage = 0;
639
- await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
640
- await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
641
- return user.usage + adjustment;
642
- }
643
- __name(updateUserUsage, "updateUserUsage");
644
- async function updateUserItems(ctx, user) {
645
- if (!user)
646
- return;
647
- await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
648
- }
649
- __name(updateUserItems, "updateUserItems");
650
- async function getUser(ctx, userId) {
651
- const users = await ctx.database.get("p_system", { userid: userId });
652
- return users[0] || null;
653
- }
654
- __name(getUser, "getUser");
655
- async function ensureUserExists(ctx, userId, username) {
656
- const notExists = await isTargetIdNotExists(ctx, userId);
657
- if (notExists) {
658
- await createUser(ctx, {
659
- userid: userId,
660
- usersname: username,
661
- p: 0,
662
- favorability: 0,
663
- userlevel: 0,
664
- usage: 0,
665
- lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
666
- items: {}
667
- });
668
- }
669
- return getUser(ctx, userId);
670
- }
671
- __name(ensureUserExists, "ensureUserExists");
672
- function extendDatabase(ctx) {
673
- ctx.model.extend("p_system", {
674
- id: "unsigned",
675
- userid: "string",
676
- usersname: "string",
677
- p: "integer",
678
- favorability: "integer",
679
- userlevel: "integer",
680
- usage: "integer",
681
- lastChatTime: "integer",
682
- items: "object"
683
- }, { autoInc: true });
684
- }
685
- __name(extendDatabase, "extendDatabase");
686
-
687
708
  // src/fixed-dialogues.ts
688
709
  var fs3 = __toESM(require("fs"));
689
710
  var path3 = __toESM(require("path"));
@@ -1059,13 +1080,11 @@ var Sat = class extends import_koishi5.Service {
1059
1080
  randnum: import_koishi5.Schema.number().role("slider").min(0).max(1).step(0.01).default(0).description("在群聊中随机触发对话的概率,如需关闭可设置为 0"),
1060
1081
  sentences_divide: import_koishi5.Schema.boolean().default(true).description("是否分句发送"),
1061
1082
  time_interval: import_koishi5.Schema.number().default(1e3).description("每句话的时间间隔"),
1062
- reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向"),
1063
- enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
1064
- portrait_min_dialogues: import_koishi5.Schema.number().default(100).description("触发画像生成的最小对话条数")
1083
+ reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向")
1065
1084
  }).description("对话设置"),
1066
1085
  import_koishi5.Schema.object({
1067
1086
  enable_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度系统(每次对话默认+1好感度)"),
1068
- max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度变化)对话次数上限"),
1087
+ max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度增长)对话次数上限"),
1069
1088
  input_censor_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度审查(通过输入屏蔽词扣除好感)"),
1070
1089
  value_of_input_favorability: import_koishi5.Schema.number().default(15).description("输入触发屏蔽词每次扣除的好感度"),
1071
1090
  output_censor_favorability: import_koishi5.Schema.boolean().default(false).description("通过输出屏蔽词扣除好感,在dataDir中的output_censor.txt修改)"),
@@ -1073,6 +1092,9 @@ var Sat = class extends import_koishi5.Service {
1073
1092
  enable_auxiliary_LLM: import_koishi5.Schema.boolean().default(false).description("是否使用辅助大模型判断好感度增减(量与输入屏蔽词每次扣除的好感度相关,不稳定,慎用)"),
1074
1093
  offset_of_fafavorability: import_koishi5.Schema.number().default(3.5).description("辅助大模型好感度偏移量(越大越容易扣好感度)"),
1075
1094
  visible_favorability: import_koishi5.Schema.boolean().default(true).description("是否开启好感度升降显示"),
1095
+ enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
1096
+ portrait_usage: import_koishi5.Schema.number().default(2).description("每天触发第几次对话后触发画像生成(最小为2)"),
1097
+ portrait_min_favorability: import_koishi5.Schema.number().default(100).description("触发画像生成的最小好感度"),
1076
1098
  prompt_0: import_koishi5.Schema.string().role("textarea").description("厌恶好感补充设定"),
1077
1099
  favorability_div_1: import_koishi5.Schema.number().default(15).description("厌恶-陌生分界线"),
1078
1100
  prompt_1: import_koishi5.Schema.string().role("textarea").description("陌生好感补充设定"),
@@ -1090,8 +1112,154 @@ var Sat = class extends import_koishi5.Service {
1090
1112
  ]);
1091
1113
  })(Sat || (Sat = {}));
1092
1114
 
1115
+ // src/userportrait.ts
1116
+ var import_koishi6 = require("koishi");
1117
+ var fs5 = __toESM(require("fs"));
1118
+ var path5 = __toESM(require("path"));
1119
+ var logger5 = new import_koishi6.Logger("satori-portrait");
1120
+ var UserPortraitManager = class {
1121
+ constructor(ctx, config) {
1122
+ this.ctx = ctx;
1123
+ this.config = config;
1124
+ }
1125
+ static {
1126
+ __name(this, "UserPortraitManager");
1127
+ }
1128
+ // 获取用户印象路径
1129
+ getPortraitPath(userId) {
1130
+ return path5.join(this.config.dataDir, "UserPortrait", `${userId}.txt`);
1131
+ }
1132
+ // 检查用户印象文件
1133
+ hasPortrait(userId) {
1134
+ return fs5.existsSync(this.getPortraitPath(userId));
1135
+ }
1136
+ // 读取用户印象
1137
+ readPortrait(userId) {
1138
+ try {
1139
+ return fs5.readFileSync(this.getPortraitPath(userId), "utf-8");
1140
+ } catch {
1141
+ return "";
1142
+ }
1143
+ }
1144
+ // 获取长期记忆对话
1145
+ async getDialogues(user) {
1146
+ const userId = user.userid;
1147
+ const memoryPath = path5.join(this.config.dataDir, "dialogues", `${userId}.txt`);
1148
+ if (!fs5.existsSync(memoryPath))
1149
+ return [];
1150
+ const MemoryContent = await JSON.parse(fs5.readFileSync(memoryPath, "utf-8"));
1151
+ const dialoguesContent = MemoryContent.map((msg) => `${msg.content}${msg.role == "user" ? "(未记录时间)" : msg.role}`);
1152
+ const level = user.userlevel < 5 ? user.userlevel : 4;
1153
+ const usageLimit = this.config.max_usage[level] || 0;
1154
+ if (dialoguesContent.length > usageLimit) {
1155
+ dialoguesContent.splice(0, dialoguesContent.length - usageLimit);
1156
+ }
1157
+ return dialoguesContent;
1158
+ }
1159
+ // 生成提示词模板
1160
+ buildMessage(level, history, existingPortrait) {
1161
+ return [{
1162
+ role: "user",
1163
+ content: `你是一个角色扮演智能体工作流中的一环,请根据以下信息生成用户画像。需包括:
1164
+ 1. 基本信息(性别、年龄、生日等)
1165
+ 2. 生活习惯(作息、爱好等)
1166
+ 3. 性格特征(用几句话描述)
1167
+ 4. 交流偏好(常用语气词、话题倾向)
1168
+ 5. 敏感点(明确表示反感的内容)
1169
+ 6. 对AI的期望(从对话历史中推断)
1170
+ 7. 其他你认为需要记录的信息(如有)
1171
+
1172
+ 你与用户的关系:${level}
1173
+ 历史画像:
1174
+ ${existingPortrait ? "无" : existingPortrait}
1175
+
1176
+ 近期的用户发言记录:
1177
+ ${history}
1178
+
1179
+ 注意事项:
1180
+ ·需要综合历史画像和用户发言记录,避免重复信息或遗漏信息
1181
+ ·基于事实推断,避免盲目相信或主观臆测
1182
+ ·使用简洁的条目式表达,但内容要全面
1183
+ ·保留不确定性的表述(如"可能"、"似乎")
1184
+ ·保持中立和客观,避免带有个人情感色彩的描述,不要添加评价或建议
1185
+ ·仅给出画像内容,不要添加额外的描述、建议、评价、注解等
1186
+ ·不必使用标记语言,直接书写即可`
1187
+ }];
1188
+ }
1189
+ // 执行画像生成
1190
+ async generatePortrait(session, user, apiClient) {
1191
+ if (!this.config.enable_favorability)
1192
+ return;
1193
+ if (!this.config.enable_user_portrait)
1194
+ return;
1195
+ if (user.favorability < this.config.portrait_min_favorability)
1196
+ return;
1197
+ const dialogues = await this.getDialogues(user);
1198
+ const existing = this.readPortrait(user.userid);
1199
+ const userlevel = getFavorabilityLevel(user, this.getFavorabilityConfig());
1200
+ const messages = this.buildMessage(userlevel, dialogues.join("\n"), existing);
1201
+ logger5.info(`用户 ${user.userid} 画像生成中...`);
1202
+ logger5.info(`${messages[0].content}`);
1203
+ try {
1204
+ const response = await apiClient.generateUserPortrait(user, messages);
1205
+ if (response && !response.error) {
1206
+ this.savePortrait(user, response.content);
1207
+ if (user.usage > 0)
1208
+ session.send(`用户画像更新成功。`);
1209
+ logger5.success(`用户 ${user.userid} 画像更新成功`);
1210
+ }
1211
+ } catch (error) {
1212
+ logger5.error(`画像生成失败:${error.message}`);
1213
+ }
1214
+ }
1215
+ savePortrait(user, portrait) {
1216
+ const filePath = this.getPortraitPath(user.userid);
1217
+ this.ensurePortraitFile(user.userid);
1218
+ fs5.writeFileSync(filePath, portrait);
1219
+ }
1220
+ ensurePortraitFile(userId) {
1221
+ const filePath = this.getPortraitPath(userId);
1222
+ if (!fs5.existsSync(filePath)) {
1223
+ fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
1224
+ fs5.writeFileSync(filePath, "");
1225
+ }
1226
+ }
1227
+ // 获取用户画像
1228
+ getUserPortrait(session) {
1229
+ if (!this.config.enable_user_portrait)
1230
+ return "";
1231
+ const portrait = this.readPortrait(session.userId);
1232
+ return portrait ? `
1233
+ 以下是当前用户的补充信息:{${portrait}
1234
+ }
1235
+ ` : "";
1236
+ }
1237
+ getFavorabilityConfig() {
1238
+ return {
1239
+ enable_favorability: this.config.enable_favorability,
1240
+ dataDir: this.config.dataDir,
1241
+ max_favorability_perday: this.config.max_favorability_perday,
1242
+ input_censor_favorability: this.config.input_censor_favorability,
1243
+ value_of_input_favorability: this.config.value_of_input_favorability,
1244
+ output_censor_favorability: this.config.output_censor_favorability,
1245
+ value_of_output_favorability: this.config.value_of_output_favorability,
1246
+ enable_auxiliary_LLM: this.config.enable_auxiliary_LLM,
1247
+ offset_of_fafavorability: this.config.offset_of_fafavorability,
1248
+ prompt_0: this.config.prompt_0,
1249
+ favorability_div_1: this.config.favorability_div_1,
1250
+ prompt_1: this.config.prompt_1,
1251
+ favorability_div_2: this.config.favorability_div_2,
1252
+ prompt_2: this.config.prompt_2,
1253
+ favorability_div_3: this.config.favorability_div_3,
1254
+ prompt_3: this.config.prompt_3,
1255
+ favorability_div_4: this.config.favorability_div_4,
1256
+ prompt_4: this.config.prompt_4
1257
+ };
1258
+ }
1259
+ };
1260
+
1093
1261
  // src/index.tsx
1094
- var logger5 = new import_koishi6.Logger("satori-ai");
1262
+ var logger6 = new import_koishi7.Logger("satori-ai");
1095
1263
  var SAT = class extends Sat {
1096
1264
  // 重写构造函数
1097
1265
  constructor(ctx, config) {
@@ -1100,7 +1268,8 @@ var SAT = class extends Sat {
1100
1268
  ctx.i18n.define("zh", require_zh());
1101
1269
  extendDatabase(ctx);
1102
1270
  this.apiClient = new APIClient(ctx, this.getAPIConfig());
1103
- this.memoryManager = new MemoryManager(this.getMemoryConfig());
1271
+ this.memoryManager = new MemoryManager(ctx, this.getMemoryConfig());
1272
+ this.portraitManager = new UserPortraitManager(ctx, config);
1104
1273
  ensureCensorFileExists(this.config.dataDir);
1105
1274
  ctx.middleware(createMiddleware(ctx, this, this.getMiddlewareConfig()));
1106
1275
  this.registerCommands(ctx);
@@ -1110,7 +1279,7 @@ var SAT = class extends Sat {
1110
1279
  }
1111
1280
  apiClient;
1112
1281
  memoryManager;
1113
- //private portraitManager: UserPortraitManager
1282
+ portraitManager;
1114
1283
  ChannelParallelCount = /* @__PURE__ */ new Map();
1115
1284
  onlineUsers = [];
1116
1285
  getAPIConfig() {
@@ -1231,6 +1400,8 @@ var SAT = class extends Sat {
1231
1400
  const response = await this.generateResponse(session, processedPrompt);
1232
1401
  const auxiliaryResult = await this.handleAuxiliaryDialogue(session, processedPrompt, response);
1233
1402
  await this.memoryManager.updateMemories(session, processedPrompt, this.getMemoryConfig(), response);
1403
+ if (user.usage == this.config.portrait_usage - 1)
1404
+ this.portraitManager.generatePortrait(session, user, this.apiClient);
1234
1405
  return await this.formatResponse(session, response.content, auxiliaryResult);
1235
1406
  }
1236
1407
  // 处理辅助判断
@@ -1250,9 +1421,9 @@ var SAT = class extends Sat {
1250
1421
  const messages = generateAuxiliaryPrompt(prompt, response.content, user, this.getFavorabilityConfig());
1251
1422
  const result = await this.apiClient.auxiliaryChat(messages);
1252
1423
  if (result.error) {
1253
- logger5.error(`辅助判断失败:${result.content}`);
1424
+ logger6.error(`辅助判断失败:${result.content}`);
1254
1425
  } else {
1255
- logger5.info(`辅助判断:${result.content}`);
1426
+ logger6.info(`辅助判断:${result.content}`);
1256
1427
  return handleAuxiliaryResult(this.ctx, session, this.getFavorabilityConfig(), result.content);
1257
1428
  }
1258
1429
  }
@@ -1266,7 +1437,7 @@ var SAT = class extends Sat {
1266
1437
  }
1267
1438
  // 前置检查
1268
1439
  performPreChecks(session, prompt) {
1269
- if (!prompt)
1440
+ if (prompt === "")
1270
1441
  return session.text("commands.sat.messages.no-prompt");
1271
1442
  if (prompt.length > this.config.max_tokens)
1272
1443
  return session.text("commands.sat.messages.tooLong");
@@ -1312,7 +1483,7 @@ var SAT = class extends Sat {
1312
1483
  const usage = await updateUserUsage(this.ctx, user, adjustment);
1313
1484
  if (user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on")
1314
1485
  return null;
1315
- const level = user.userlevel || 0;
1486
+ const level = user.userlevel < 5 ? user.userlevel : 4;
1316
1487
  const usageLimit = this.config.max_usage[level] || 0;
1317
1488
  if (usage && usageLimit != 0 && usage > usageLimit) {
1318
1489
  return session.text("commands.sat.messages.exceeds");
@@ -1339,13 +1510,13 @@ var SAT = class extends Sat {
1339
1510
  await inputContentCheck(this.ctx, censored, session.userId, this.getFavorabilityConfig(), session);
1340
1511
  }
1341
1512
  if (this.config.log_ask_response)
1342
- logger5.info(`用户 ${session.username}:${processedPrompt}`);
1513
+ logger6.info(`用户 ${session.username}:${processedPrompt}`);
1343
1514
  return censored;
1344
1515
  }
1345
1516
  // 生成回复
1346
1517
  async generateResponse(session, prompt) {
1347
1518
  if (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
1348
- logger5.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
1519
+ logger6.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
1349
1520
  }
1350
1521
  while (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
1351
1522
  this.updateChannelParallelCount(session, -1);
@@ -1353,11 +1524,11 @@ var SAT = class extends Sat {
1353
1524
  this.updateChannelParallelCount(session, 1);
1354
1525
  }
1355
1526
  const messages = this.buildMessages(session, prompt);
1356
- logger5.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
1527
+ logger6.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
1357
1528
  const user = await getUser(this.ctx, session.userId);
1358
1529
  const response = await this.apiClient.chat(user, await messages);
1359
1530
  if (this.config.log_ask_response)
1360
- logger5.info(`Satori AI:${response.content}`);
1531
+ logger6.info(`Satori AI:${response.content}`);
1361
1532
  return response;
1362
1533
  }
1363
1534
  // 构建消息
@@ -1382,7 +1553,7 @@ var SAT = class extends Sat {
1382
1553
  });
1383
1554
  let payload = messages.map((msg) => msg.role + ":" + msg.content).join("\n");
1384
1555
  if (this.config.log_system_prompt)
1385
- logger5.info(`系统提示:
1556
+ logger6.info(`系统提示:
1386
1557
  ${payload}`);
1387
1558
  return messages;
1388
1559
  }
@@ -1396,6 +1567,7 @@ ${payload}`);
1396
1567
  systemPrompt += channelDialogue;
1397
1568
  systemPrompt += userMemory;
1398
1569
  const user = await getUser(this.ctx, session.userId);
1570
+ systemPrompt += this.portraitManager.getUserPortrait(session);
1399
1571
  const nickName = user.items["情侣合照"]?.metadata?.userNickName;
1400
1572
  systemPrompt += `用户的名字是:${session.username}, id是:${session.userId}`;
1401
1573
  if (nickName)
@@ -1430,7 +1602,7 @@ ${payload}`);
1430
1602
  response = `@${session.username} ` + response;
1431
1603
  }
1432
1604
  if (this.config.sentences_divide && response.length > 10) {
1433
- const sentences = splitSentences(response).map((text) => import_koishi6.h.text(text));
1605
+ const sentences = splitSentences(response).map((text) => import_koishi7.h.text(text));
1434
1606
  for (const sentence of sentences) {
1435
1607
  await session.send(sentence);
1436
1608
  await new Promise((resolve2) => setTimeout(resolve2, this.config.time_interval));
@@ -1458,7 +1630,7 @@ ${payload}`);
1458
1630
  async addCommonSense(session, content) {
1459
1631
  if (!content)
1460
1632
  return session.text("commands.sat.common_sense.messages.no-prompt");
1461
- const filePath = path5.join(this.config.dataDir, "common_sense.txt");
1633
+ const filePath = path6.join(this.config.dataDir, "common_sense.txt");
1462
1634
  await this.memoryManager.saveLongTermMemory(session, [{
1463
1635
  role: "user",
1464
1636
  content
@@ -1471,6 +1643,9 @@ ${payload}`);
1471
1643
  const level = options.level || 1;
1472
1644
  const user = await ensureUserExists(this.ctx, userId, session.username);
1473
1645
  await updateUserLevel(this.ctx, user, level);
1646
+ const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
1647
+ if (enableUserKey)
1648
+ await this.portraitManager.generatePortrait(session, user, this.apiClient);
1474
1649
  return session.text("commands.sat.messages.update_level_succeed", [level]);
1475
1650
  }
1476
1651
  // 处理查询用户使用次数
package/lib/memory.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { Session } from 'koishi';
1
+ import { Session, Context } from 'koishi';
2
2
  import { MemoryEntry, MemoryConfig } from './types';
3
3
  export declare class MemoryManager {
4
+ private ctx;
4
5
  private config;
5
6
  private channelMemories;
6
7
  private channelDialogues;
7
- constructor(config: MemoryConfig);
8
+ constructor(ctx: Context, config: MemoryConfig);
8
9
  updateMemories(session: Session, prompt: string, config: MemoryConfig, response: {
9
10
  content: string;
10
11
  error: boolean;
package/lib/types.d.ts CHANGED
@@ -189,8 +189,6 @@ export declare namespace Sat {
189
189
  time_interval: number;
190
190
  max_parallel_count: number;
191
191
  reply_pointing: boolean;
192
- enable_user_portrait: boolean;
193
- portrait_min_dialogues: number;
194
192
  enable_favorability: boolean;
195
193
  max_favorability_perday: number;
196
194
  input_censor_favorability: boolean;
@@ -200,6 +198,9 @@ export declare namespace Sat {
200
198
  enable_auxiliary_LLM: boolean;
201
199
  offset_of_fafavorability: number;
202
200
  visible_favorability: boolean;
201
+ enable_user_portrait: boolean;
202
+ portrait_usage: number;
203
+ portrait_min_favorability: number;
203
204
  prompt_0: string;
204
205
  favorability_div_1: number;
205
206
  prompt_1: string;
@@ -8,9 +8,11 @@ export declare class UserPortraitManager {
8
8
  private getPortraitPath;
9
9
  hasPortrait(userId: string): boolean;
10
10
  readPortrait(userId: string): string;
11
- private processDialogues;
12
- private buildPrompt;
11
+ private getDialogues;
12
+ private buildMessage;
13
13
  generatePortrait(session: Session, user: User, apiClient: APIClient): Promise<void>;
14
- enhanceSystemPrompt(session: Session): string;
14
+ private savePortrait;
15
+ private ensurePortraitFile;
16
+ getUserPortrait(session: Session): string;
15
17
  private getFavorabilityConfig;
16
18
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-satori-ai",
3
3
  "description": "基于 Satori 适配器的智能对话插件,具备长期记忆、常识管理和好感度系统",
4
- "version": "1.2.1",
4
+ "version": "1.2.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "types": "lib/index.d.ts",