koishi-plugin-satori-ai 1.2.1 → 1.2.4

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,19 +208,28 @@ 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) {
231
+ if (user.userid == "Alice")
232
+ return { content: "测试画像", error: false };
222
233
  const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
223
234
  let keys;
224
235
  let modle;
@@ -234,16 +245,24 @@ var APIClient = class {
234
245
  baseURL = this.config.baseURL;
235
246
  }
236
247
  const payload = this.createAuxiliaryPayload(messages, modle);
237
- for (let i = 0; i < keys.length; i++) {
248
+ const url = `${trimSlash(baseURL)}/chat/completions`;
249
+ const headers = this.createHeaders(keys);
250
+ let content;
251
+ for (let i = 1; i <= this.config.maxRetryTimes; i++) {
238
252
  try {
239
- return await this.tryRequest(baseURL, payload, keys);
253
+ const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
254
+ content = response.choices[0].message.content;
255
+ return { content, error: false };
240
256
  } catch (error) {
241
- if (i == keys.length - 1) {
257
+ if (i == this.config.maxRetryTimes) {
242
258
  return this.handleAPIError(error);
243
259
  }
244
- this.rotateKey();
260
+ await new Promise((resolve2) => setTimeout(resolve2, this.config.retry_delay_time || 5e3));
261
+ logger2.warn(`生成画像时API请求失败(${error}),重试(第${i}次)中...`);
262
+ continue;
245
263
  }
246
264
  }
265
+ return { content: "unreachable", error: true };
247
266
  }
248
267
  // 生成请求体
249
268
  createPayload(messages, model) {
@@ -290,7 +309,7 @@ var APIClient = class {
290
309
  const responseMsg = { role: "assistant", content };
291
310
  if (payload.messages.some((msg) => msg === responseMsg) && content.length > 5) {
292
311
  const lastMsg = payload.messages[payload.messages.length - 1];
293
- payload.messages[payload.messages.length - 1].content = lastMsg.content + "(system:注意不要重复之前的内容)";
312
+ payload.messages[payload.messages.length - 1].content = lastMsg.content + "(注意不要重复之前的内容)";
294
313
  logger2.warn(`返回内容与之前内容相同,重试(第${i}次)中...`);
295
314
  continue;
296
315
  }
@@ -362,9 +381,101 @@ var APIClient = class {
362
381
  var import_koishi3 = require("koishi");
363
382
  var fs2 = __toESM(require("fs"));
364
383
  var path2 = __toESM(require("path"));
384
+
385
+ // src/database.ts
386
+ async function isTargetIdNotExists(ctx, userId) {
387
+ const users = await ctx.database.get("p_system", { userid: userId });
388
+ return users.length === 0;
389
+ }
390
+ __name(isTargetIdNotExists, "isTargetIdNotExists");
391
+ async function createUser(ctx, user) {
392
+ await ctx.database.create("p_system", {
393
+ userid: user.userid || "",
394
+ usersname: user.usersname || "",
395
+ p: user.p || 0,
396
+ favorability: user.favorability || 0,
397
+ userlevel: user.userlevel || 0,
398
+ usage: user.usage || 0,
399
+ lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
400
+ items: user.items || {}
401
+ });
402
+ }
403
+ __name(createUser, "createUser");
404
+ async function updateFavorability(ctx, user, adjustment) {
405
+ if (!user)
406
+ return;
407
+ let newValue;
408
+ if (typeof adjustment === "number")
409
+ newValue = user.favorability + adjustment;
410
+ else
411
+ newValue = user.favorability;
412
+ await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
413
+ }
414
+ __name(updateFavorability, "updateFavorability");
415
+ async function updateUserLevel(ctx, user, level) {
416
+ if (!user)
417
+ return;
418
+ await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
419
+ }
420
+ __name(updateUserLevel, "updateUserLevel");
421
+ async function updateUserUsage(ctx, user, adjustment = 1) {
422
+ if (!user)
423
+ return;
424
+ if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
425
+ user.usage = 0;
426
+ await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
427
+ await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
428
+ return user.usage + adjustment;
429
+ }
430
+ __name(updateUserUsage, "updateUserUsage");
431
+ async function updateUserItems(ctx, user) {
432
+ if (!user)
433
+ return;
434
+ await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
435
+ }
436
+ __name(updateUserItems, "updateUserItems");
437
+ async function getUser(ctx, userId) {
438
+ const users = await ctx.database.get("p_system", { userid: userId });
439
+ return users[0] || null;
440
+ }
441
+ __name(getUser, "getUser");
442
+ async function ensureUserExists(ctx, userId, username) {
443
+ const notExists = await isTargetIdNotExists(ctx, userId);
444
+ if (notExists) {
445
+ await createUser(ctx, {
446
+ userid: userId,
447
+ usersname: username,
448
+ p: 0,
449
+ favorability: 0,
450
+ userlevel: 0,
451
+ usage: 0,
452
+ lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
453
+ items: {}
454
+ });
455
+ }
456
+ return getUser(ctx, userId);
457
+ }
458
+ __name(ensureUserExists, "ensureUserExists");
459
+ function extendDatabase(ctx) {
460
+ ctx.model.extend("p_system", {
461
+ id: "unsigned",
462
+ userid: "string",
463
+ usersname: "string",
464
+ p: "integer",
465
+ favorability: "integer",
466
+ userlevel: "integer",
467
+ usage: "integer",
468
+ lastChatTime: "integer",
469
+ items: "object"
470
+ }, { autoInc: true });
471
+ }
472
+ __name(extendDatabase, "extendDatabase");
473
+
474
+ // src/memory.ts
365
475
  var logger3 = new import_koishi3.Logger("satori-memory");
366
476
  var MemoryManager = class {
367
- constructor(config) {
477
+ constructor(ctx, config) {
478
+ this.ctx = ctx;
368
479
  this.config = config;
369
480
  }
370
481
  static {
@@ -379,7 +490,8 @@ var MemoryManager = class {
379
490
  this.updateChannelMemory(session, prompt, config, response.content);
380
491
  prompt = prompt.replace("我", session.username);
381
492
  const date = ` (对话日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()})`;
382
- if (this.shouldRemember(prompt)) {
493
+ const userFavourbility = await (await getUser(this.ctx, session.userId)).favorability;
494
+ if (this.shouldRemember(prompt, userFavourbility)) {
383
495
  await this.saveLongTermMemory(session, [{
384
496
  role: date,
385
497
  content: prompt
@@ -387,8 +499,8 @@ var MemoryManager = class {
387
499
  }
388
500
  }
389
501
  // 是否应当记忆
390
- shouldRemember(content) {
391
- return (content.length >= this.config.remember_min_length || content.includes("记住")) && !this.config.memory_block_words.some((word) => content.includes(word));
502
+ shouldRemember(content, userFavourbility) {
503
+ return (content.length >= this.config.remember_min_length || content.includes("记住") && userFavourbility >= 50) && !this.config.memory_block_words.some((word) => content.includes(word));
392
504
  }
393
505
  // 更新频道对话
394
506
  async updateChannelDialogue(session, prompt, name) {
@@ -595,95 +707,6 @@ ${matched.map((entry) => entry.content).join("\n")}
595
707
  }
596
708
  };
597
709
 
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
710
  // src/fixed-dialogues.ts
688
711
  var fs3 = __toESM(require("fs"));
689
712
  var path3 = __toESM(require("path"));
@@ -1059,13 +1082,11 @@ var Sat = class extends import_koishi5.Service {
1059
1082
  randnum: import_koishi5.Schema.number().role("slider").min(0).max(1).step(0.01).default(0).description("在群聊中随机触发对话的概率,如需关闭可设置为 0"),
1060
1083
  sentences_divide: import_koishi5.Schema.boolean().default(true).description("是否分句发送"),
1061
1084
  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("触发画像生成的最小对话条数")
1085
+ reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向")
1065
1086
  }).description("对话设置"),
1066
1087
  import_koishi5.Schema.object({
1067
1088
  enable_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度系统(每次对话默认+1好感度)"),
1068
- max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度变化)对话次数上限"),
1089
+ max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度增长)对话次数上限"),
1069
1090
  input_censor_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度审查(通过输入屏蔽词扣除好感)"),
1070
1091
  value_of_input_favorability: import_koishi5.Schema.number().default(15).description("输入触发屏蔽词每次扣除的好感度"),
1071
1092
  output_censor_favorability: import_koishi5.Schema.boolean().default(false).description("通过输出屏蔽词扣除好感,在dataDir中的output_censor.txt修改)"),
@@ -1073,6 +1094,10 @@ var Sat = class extends import_koishi5.Service {
1073
1094
  enable_auxiliary_LLM: import_koishi5.Schema.boolean().default(false).description("是否使用辅助大模型判断好感度增减(量与输入屏蔽词每次扣除的好感度相关,不稳定,慎用)"),
1074
1095
  offset_of_fafavorability: import_koishi5.Schema.number().default(3.5).description("辅助大模型好感度偏移量(越大越容易扣好感度)"),
1075
1096
  visible_favorability: import_koishi5.Schema.boolean().default(true).description("是否开启好感度升降显示"),
1097
+ enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
1098
+ portrait_usage: import_koishi5.Schema.number().default(2).description("每天触发第几次对话后触发画像生成(最小为2)"),
1099
+ max_portrait_dialogues: import_koishi5.Schema.number().default(100).description("画像生成获取的最大对话条数"),
1100
+ portrait_min_favorability: import_koishi5.Schema.number().default(100).description("触发画像生成的最小好感度"),
1076
1101
  prompt_0: import_koishi5.Schema.string().role("textarea").description("厌恶好感补充设定"),
1077
1102
  favorability_div_1: import_koishi5.Schema.number().default(15).description("厌恶-陌生分界线"),
1078
1103
  prompt_1: import_koishi5.Schema.string().role("textarea").description("陌生好感补充设定"),
@@ -1090,8 +1115,155 @@ var Sat = class extends import_koishi5.Service {
1090
1115
  ]);
1091
1116
  })(Sat || (Sat = {}));
1092
1117
 
1118
+ // src/userportrait.ts
1119
+ var import_koishi6 = require("koishi");
1120
+ var fs5 = __toESM(require("fs"));
1121
+ var path5 = __toESM(require("path"));
1122
+ var logger5 = new import_koishi6.Logger("satori-portrait");
1123
+ var UserPortraitManager = class {
1124
+ constructor(ctx, config) {
1125
+ this.ctx = ctx;
1126
+ this.config = config;
1127
+ }
1128
+ static {
1129
+ __name(this, "UserPortraitManager");
1130
+ }
1131
+ // 获取用户印象路径
1132
+ getPortraitPath(userId) {
1133
+ return path5.join(this.config.dataDir, "UserPortrait", `${userId}.txt`);
1134
+ }
1135
+ // 检查用户印象文件
1136
+ hasPortrait(userId) {
1137
+ return fs5.existsSync(this.getPortraitPath(userId));
1138
+ }
1139
+ // 读取用户印象
1140
+ readPortrait(userId) {
1141
+ try {
1142
+ return fs5.readFileSync(this.getPortraitPath(userId), "utf-8");
1143
+ } catch {
1144
+ return "";
1145
+ }
1146
+ }
1147
+ // 获取长期记忆对话
1148
+ async getDialogues(user) {
1149
+ const userId = user.userid;
1150
+ const memoryPath = path5.join(this.config.dataDir, "dialogues", `${userId}.txt`);
1151
+ if (!fs5.existsSync(memoryPath))
1152
+ return [];
1153
+ const MemoryContent = await JSON.parse(fs5.readFileSync(memoryPath, "utf-8"));
1154
+ const dialoguesContent = MemoryContent.map((msg) => `${msg.content}${msg.role == "user" ? "(未记录时间)" : msg.role}`);
1155
+ const level = user.userlevel < 5 ? user.userlevel : 4;
1156
+ const usageLimit = this.config.max_usage[level] == 0 ? this.config.max_portrait_dialogues : this.config.max_usage[level];
1157
+ if (dialoguesContent.length >= usageLimit || dialoguesContent.length >= this.config.max_portrait_dialogues) {
1158
+ dialoguesContent.splice(0, dialoguesContent.length - Math.min(usageLimit, this.config.max_portrait_dialogues));
1159
+ }
1160
+ return dialoguesContent;
1161
+ }
1162
+ // 生成提示词模板
1163
+ buildMessage(level, history, existingPortrait) {
1164
+ return [{
1165
+ role: "user",
1166
+ content: `你是一个角色扮演智能体工作流中的一环,请根据以下信息生成用户画像。需包括:
1167
+ 1. 基本信息(性别、年龄、生日等)
1168
+ 2. 生活习惯(作息、爱好等)
1169
+ 3. 性格特征(用几句话描述)
1170
+ 4. 交流偏好(常用语气词、话题倾向)
1171
+ 5. 敏感点(明确表示反感的内容)
1172
+ 6. 对AI的期望(从对话历史中推断)
1173
+ 7. 其他你认为需要记录的信息(如有)
1174
+
1175
+ 你与用户的关系:${level}
1176
+ 历史画像:
1177
+ ${existingPortrait ? "无" : existingPortrait}
1178
+
1179
+ 近期的用户发言记录:
1180
+ ${history}
1181
+
1182
+ 注意事项:
1183
+ ·用户一定是人类,其他情况是角色扮演
1184
+ ·需要综合历史画像和用户发言记录,避免重复信息或遗漏信息,并根据发言修正历史画像内容
1185
+ ·基于事实推断,避免盲目相信或主观臆测
1186
+ ·使用简洁的条目式表达,但内容要全面
1187
+ ·保留不确定性的表述(如"可能"、"似乎"、"用户自称")
1188
+ ·保持中立和客观,避免带有个人情感色彩的描述,不要添加评价或建议
1189
+ ·仅给出画像内容,不要添加额外的描述、建议、评价、注解等
1190
+ ·不使用markdown等标记语言,直接书写即可`
1191
+ }];
1192
+ }
1193
+ // 执行画像生成
1194
+ async generatePortrait(session, user, apiClient) {
1195
+ if (!this.config.enable_favorability)
1196
+ return;
1197
+ if (!this.config.enable_user_portrait)
1198
+ return;
1199
+ if (user.favorability < this.config.portrait_min_favorability)
1200
+ return;
1201
+ const dialogues = await this.getDialogues(user);
1202
+ const existing = this.readPortrait(user.userid);
1203
+ const userlevel = getFavorabilityLevel(user, this.getFavorabilityConfig());
1204
+ const messages = this.buildMessage(userlevel, dialogues.join("\n"), existing);
1205
+ logger5.info(`用户 ${user.userid} 画像生成中...`);
1206
+ logger5.info(`${messages[0].content}`);
1207
+ try {
1208
+ const response = await apiClient.generateUserPortrait(user, messages);
1209
+ if (response && !response.error) {
1210
+ this.savePortrait(user, response.content);
1211
+ if (user.usage > this.config.portrait_usage - 1)
1212
+ session.send("用户画像更新成功。");
1213
+ logger5.success(`用户 ${user.userid} 画像更新成功`);
1214
+ }
1215
+ } catch (error) {
1216
+ logger5.error(`画像生成失败:${error.message}`);
1217
+ }
1218
+ }
1219
+ savePortrait(user, portrait) {
1220
+ const filePath = this.getPortraitPath(user.userid);
1221
+ this.ensurePortraitFile(user.userid);
1222
+ fs5.writeFileSync(filePath, portrait);
1223
+ }
1224
+ ensurePortraitFile(userId) {
1225
+ const filePath = this.getPortraitPath(userId);
1226
+ if (!fs5.existsSync(filePath)) {
1227
+ fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
1228
+ fs5.writeFileSync(filePath, "");
1229
+ }
1230
+ }
1231
+ // 获取用户画像
1232
+ getUserPortrait(session) {
1233
+ if (!this.config.enable_user_portrait)
1234
+ return "";
1235
+ const portrait = this.readPortrait(session.userId);
1236
+ return portrait ? `
1237
+ 以下是当前用户的补充信息:{${portrait}
1238
+ }
1239
+ ` : "";
1240
+ }
1241
+ getFavorabilityConfig() {
1242
+ return {
1243
+ enable_favorability: this.config.enable_favorability,
1244
+ dataDir: this.config.dataDir,
1245
+ max_favorability_perday: this.config.max_favorability_perday,
1246
+ input_censor_favorability: this.config.input_censor_favorability,
1247
+ value_of_input_favorability: this.config.value_of_input_favorability,
1248
+ output_censor_favorability: this.config.output_censor_favorability,
1249
+ value_of_output_favorability: this.config.value_of_output_favorability,
1250
+ enable_auxiliary_LLM: this.config.enable_auxiliary_LLM,
1251
+ offset_of_fafavorability: this.config.offset_of_fafavorability,
1252
+ prompt_0: this.config.prompt_0,
1253
+ favorability_div_1: this.config.favorability_div_1,
1254
+ prompt_1: this.config.prompt_1,
1255
+ favorability_div_2: this.config.favorability_div_2,
1256
+ prompt_2: this.config.prompt_2,
1257
+ favorability_div_3: this.config.favorability_div_3,
1258
+ prompt_3: this.config.prompt_3,
1259
+ favorability_div_4: this.config.favorability_div_4,
1260
+ prompt_4: this.config.prompt_4
1261
+ };
1262
+ }
1263
+ };
1264
+
1093
1265
  // src/index.tsx
1094
- var logger5 = new import_koishi6.Logger("satori-ai");
1266
+ var logger6 = new import_koishi7.Logger("satori-ai");
1095
1267
  var SAT = class extends Sat {
1096
1268
  // 重写构造函数
1097
1269
  constructor(ctx, config) {
@@ -1100,7 +1272,8 @@ var SAT = class extends Sat {
1100
1272
  ctx.i18n.define("zh", require_zh());
1101
1273
  extendDatabase(ctx);
1102
1274
  this.apiClient = new APIClient(ctx, this.getAPIConfig());
1103
- this.memoryManager = new MemoryManager(this.getMemoryConfig());
1275
+ this.memoryManager = new MemoryManager(ctx, this.getMemoryConfig());
1276
+ this.portraitManager = new UserPortraitManager(ctx, config);
1104
1277
  ensureCensorFileExists(this.config.dataDir);
1105
1278
  ctx.middleware(createMiddleware(ctx, this, this.getMiddlewareConfig()));
1106
1279
  this.registerCommands(ctx);
@@ -1110,7 +1283,7 @@ var SAT = class extends Sat {
1110
1283
  }
1111
1284
  apiClient;
1112
1285
  memoryManager;
1113
- //private portraitManager: UserPortraitManager
1286
+ portraitManager;
1114
1287
  ChannelParallelCount = /* @__PURE__ */ new Map();
1115
1288
  onlineUsers = [];
1116
1289
  getAPIConfig() {
@@ -1231,6 +1404,8 @@ var SAT = class extends Sat {
1231
1404
  const response = await this.generateResponse(session, processedPrompt);
1232
1405
  const auxiliaryResult = await this.handleAuxiliaryDialogue(session, processedPrompt, response);
1233
1406
  await this.memoryManager.updateMemories(session, processedPrompt, this.getMemoryConfig(), response);
1407
+ if (user.usage == this.config.portrait_usage - 1)
1408
+ this.portraitManager.generatePortrait(session, user, this.apiClient);
1234
1409
  return await this.formatResponse(session, response.content, auxiliaryResult);
1235
1410
  }
1236
1411
  // 处理辅助判断
@@ -1250,9 +1425,9 @@ var SAT = class extends Sat {
1250
1425
  const messages = generateAuxiliaryPrompt(prompt, response.content, user, this.getFavorabilityConfig());
1251
1426
  const result = await this.apiClient.auxiliaryChat(messages);
1252
1427
  if (result.error) {
1253
- logger5.error(`辅助判断失败:${result.content}`);
1428
+ logger6.error(`辅助判断失败:${result.content}`);
1254
1429
  } else {
1255
- logger5.info(`辅助判断:${result.content}`);
1430
+ logger6.info(`辅助判断:${result.content}`);
1256
1431
  return handleAuxiliaryResult(this.ctx, session, this.getFavorabilityConfig(), result.content);
1257
1432
  }
1258
1433
  }
@@ -1266,7 +1441,7 @@ var SAT = class extends Sat {
1266
1441
  }
1267
1442
  // 前置检查
1268
1443
  performPreChecks(session, prompt) {
1269
- if (!prompt)
1444
+ if (prompt === "")
1270
1445
  return session.text("commands.sat.messages.no-prompt");
1271
1446
  if (prompt.length > this.config.max_tokens)
1272
1447
  return session.text("commands.sat.messages.tooLong");
@@ -1312,7 +1487,7 @@ var SAT = class extends Sat {
1312
1487
  const usage = await updateUserUsage(this.ctx, user, adjustment);
1313
1488
  if (user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on")
1314
1489
  return null;
1315
- const level = user.userlevel || 0;
1490
+ const level = user.userlevel < 5 ? user.userlevel : 4;
1316
1491
  const usageLimit = this.config.max_usage[level] || 0;
1317
1492
  if (usage && usageLimit != 0 && usage > usageLimit) {
1318
1493
  return session.text("commands.sat.messages.exceeds");
@@ -1339,13 +1514,13 @@ var SAT = class extends Sat {
1339
1514
  await inputContentCheck(this.ctx, censored, session.userId, this.getFavorabilityConfig(), session);
1340
1515
  }
1341
1516
  if (this.config.log_ask_response)
1342
- logger5.info(`用户 ${session.username}:${processedPrompt}`);
1517
+ logger6.info(`用户 ${session.username}:${processedPrompt}`);
1343
1518
  return censored;
1344
1519
  }
1345
1520
  // 生成回复
1346
1521
  async generateResponse(session, prompt) {
1347
1522
  if (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
1348
- logger5.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
1523
+ logger6.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
1349
1524
  }
1350
1525
  while (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
1351
1526
  this.updateChannelParallelCount(session, -1);
@@ -1353,11 +1528,11 @@ var SAT = class extends Sat {
1353
1528
  this.updateChannelParallelCount(session, 1);
1354
1529
  }
1355
1530
  const messages = this.buildMessages(session, prompt);
1356
- logger5.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
1531
+ logger6.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
1357
1532
  const user = await getUser(this.ctx, session.userId);
1358
1533
  const response = await this.apiClient.chat(user, await messages);
1359
1534
  if (this.config.log_ask_response)
1360
- logger5.info(`Satori AI:${response.content}`);
1535
+ logger6.info(`Satori AI:${response.content}`);
1361
1536
  return response;
1362
1537
  }
1363
1538
  // 构建消息
@@ -1382,7 +1557,7 @@ var SAT = class extends Sat {
1382
1557
  });
1383
1558
  let payload = messages.map((msg) => msg.role + ":" + msg.content).join("\n");
1384
1559
  if (this.config.log_system_prompt)
1385
- logger5.info(`系统提示:
1560
+ logger6.info(`系统提示:
1386
1561
  ${payload}`);
1387
1562
  return messages;
1388
1563
  }
@@ -1396,6 +1571,7 @@ ${payload}`);
1396
1571
  systemPrompt += channelDialogue;
1397
1572
  systemPrompt += userMemory;
1398
1573
  const user = await getUser(this.ctx, session.userId);
1574
+ systemPrompt += this.portraitManager.getUserPortrait(session);
1399
1575
  const nickName = user.items["情侣合照"]?.metadata?.userNickName;
1400
1576
  systemPrompt += `用户的名字是:${session.username}, id是:${session.userId}`;
1401
1577
  if (nickName)
@@ -1430,7 +1606,7 @@ ${payload}`);
1430
1606
  response = `@${session.username} ` + response;
1431
1607
  }
1432
1608
  if (this.config.sentences_divide && response.length > 10) {
1433
- const sentences = splitSentences(response).map((text) => import_koishi6.h.text(text));
1609
+ const sentences = splitSentences(response).map((text) => import_koishi7.h.text(text));
1434
1610
  for (const sentence of sentences) {
1435
1611
  await session.send(sentence);
1436
1612
  await new Promise((resolve2) => setTimeout(resolve2, this.config.time_interval));
@@ -1458,7 +1634,7 @@ ${payload}`);
1458
1634
  async addCommonSense(session, content) {
1459
1635
  if (!content)
1460
1636
  return session.text("commands.sat.common_sense.messages.no-prompt");
1461
- const filePath = path5.join(this.config.dataDir, "common_sense.txt");
1637
+ const filePath = path6.join(this.config.dataDir, "common_sense.txt");
1462
1638
  await this.memoryManager.saveLongTermMemory(session, [{
1463
1639
  role: "user",
1464
1640
  content
@@ -1470,6 +1646,9 @@ ${payload}`);
1470
1646
  const userId = options.id || session.userId;
1471
1647
  const level = options.level || 1;
1472
1648
  const user = await ensureUserExists(this.ctx, userId, session.username);
1649
+ const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
1650
+ if (enableUserKey)
1651
+ await this.portraitManager.generatePortrait(session, user, this.apiClient);
1473
1652
  await updateUserLevel(this.ctx, user, level);
1474
1653
  return session.text("commands.sat.messages.update_level_succeed", [level]);
1475
1654
  }
@@ -1478,7 +1657,18 @@ ${payload}`);
1478
1657
  const user = await ensureUserExists(this.ctx, session.userId, session.username);
1479
1658
  const userUsage = user.usage || 0;
1480
1659
  const maxUsage = this.config.max_usage[user.userlevel || 0] || 0;
1481
- return session.text("commands.sat.messages.usage", [userUsage, maxUsage]);
1660
+ const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
1661
+ let result = "";
1662
+ result += "用户等级:" + user.userlevel + "\n";
1663
+ if (enableUserKey)
1664
+ result += session.text("commands.sat.messages.usage", [userUsage, "∞"]) + "\n地灵殿通行证生效中\n";
1665
+ else
1666
+ result += session.text("commands.sat.messages.usage", [userUsage, maxUsage]) + "\n";
1667
+ if (this.portraitManager.hasPortrait(user.userid))
1668
+ result += "用户画像生效中\n";
1669
+ else
1670
+ result += "用户画像未生效\n";
1671
+ return result;
1482
1672
  }
1483
1673
  // 随机中间件转接
1484
1674
  async handleRandomMiddleware(session, prompt) {
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,10 @@ 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
+ max_portrait_dialogues: number;
203
+ portrait_usage: number;
204
+ portrait_min_favorability: number;
203
205
  prompt_0: string;
204
206
  favorability_div_1: number;
205
207
  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
- "description": "基于 Satori 适配器的智能对话插件,具备长期记忆、常识管理和好感度系统",
4
- "version": "1.2.1",
3
+ "description": "基于 Satori 适配器的智能对话插件,具备用户画像、长期记忆、常识管理和好感度系统",
4
+ "version": "1.2.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "types": "lib/index.d.ts",