From 3f808d5fbfb5f6fd7d367a484e2a0b0891117fd2 Mon Sep 17 00:00:00 2001 From: baiyu-yu <135424680+baiyu-yu@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:53:21 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E2=80=9C=E5=9C=A8?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E5=86=85=E6=B7=BB=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=97=B6=E9=97=B4=E2=80=9D=E5=92=8C=E2=80=9D=E6=B4=BB?= =?UTF-8?q?=E8=B7=83=E6=97=B6=E9=97=B4=E6=AE=B5=E9=9A=8F=E6=9C=BA=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E2=80=9C=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue #33 --- src/AI/AI.ts | 190 ++++++++++++++++++++++++++++++++++- src/AI/context.ts | 7 +- src/config/config_message.ts | 5 + src/index.ts | 66 +++++++++++- src/timer.ts | 156 +++++++++++++++++++++++++++- src/utils/utils_message.ts | 28 +++++- 6 files changed, 443 insertions(+), 9 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 3ccbd93..81df73d 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -15,7 +15,9 @@ export interface Privilege { counter: number, timer: number, prob: number, - standby: boolean + standby: boolean, + activeTimeRange: { start: string, end: string } | null, + segments: number, } export class AI { @@ -51,7 +53,9 @@ export class AI { counter: -1, timer: -1, prob: -1, - standby: false + standby: false, + activeTimeRange: null, + segments: 3 }; this.stream = { id: '', @@ -101,6 +105,16 @@ export class AI { } async chat(ctx: seal.MsgContext, msg: seal.Message, reason: string = ''): Promise { + const isKeywordTrigger = reason === "事件触发" || reason === "非指令"; + const isTimerTrigger = reason === "定时任务"; + + if (!isKeywordTrigger && !isTimerTrigger) { + if (!this.isInActiveTimeRange()) { + logger.info(`当前时间不在AI活跃时间段内,${reason}触发不进行回复`); + return; + } + } + logger.info('触发回复:', reason || '未知原因'); const { bucketLimit, fillInterval } = ConfigManager.received; @@ -309,6 +323,178 @@ export class AI { await this.stopCurrentChatStream(); } + isInActiveTimeRange(): boolean { + if (!this.privilege.activeTimeRange) { + return true; + } + + const now = new Date(); + const currentHour = now.getHours(); + const currentMinute = now.getMinutes(); + const currentTimeMinutes = currentHour * 60 + currentMinute; + + const { start, end } = this.privilege.activeTimeRange; + const [startHour, startMinute] = start.split(':').map(Number); + const [endHour, endMinute] = end.split(':').map(Number); + + const startTimeMinutes = startHour * 60 + startMinute; + const endTimeMinutes = endHour * 60 + endMinute; + + if (startTimeMinutes <= endTimeMinutes) { + return currentTimeMinutes >= startTimeMinutes && currentTimeMinutes <= endTimeMinutes; + } else { + return currentTimeMinutes >= startTimeMinutes || currentTimeMinutes <= endTimeMinutes; + } + } + + getActiveTimeSegments(): number[] { + if (!this.privilege.activeTimeRange) { + return []; + } + + const { start, end } = this.privilege.activeTimeRange; + const [startHour, startMinute] = start.split(':').map(Number); + const [endHour, endMinute] = end.split(':').map(Number); + + const startTimeMinutes = startHour * 60 + startMinute; + const endTimeMinutes = endHour * 60 + endMinute; + const segments = this.privilege.segments; + + const seed = this.generateSeed(this.id, start, end, segments); + const timePoints: number[] = []; + + if (startTimeMinutes <= endTimeMinutes) { + const totalMinutes = endTimeMinutes - startTimeMinutes; + const segmentDuration = totalMinutes / segments; + + for (let i = 0; i < segments; i++) { + const segmentStart = startTimeMinutes + i * segmentDuration; + const randomOffset = this.seededRandom(seed + i) * segmentDuration * 0.6 - segmentDuration * 0.3; + const randomTime = segmentStart + segmentDuration / 2 + randomOffset; + timePoints.push(Math.floor(randomTime)); + } + } else { + const totalMinutes = (24 * 60 - startTimeMinutes) + endTimeMinutes; + const segmentDuration = totalMinutes / segments; + + for (let i = 0; i < segments; i++) { + let segmentStart = startTimeMinutes + i * segmentDuration; + + if (segmentStart >= 24 * 60) { + segmentStart -= 24 * 60; + } + + const randomOffset = this.seededRandom(seed + i) * segmentDuration * 0.6 - segmentDuration * 0.3; + let randomTime = segmentStart + segmentDuration / 2 + randomOffset; + + if (randomTime >= 24 * 60) { + randomTime -= 24 * 60; + } + + timePoints.push(Math.floor(randomTime)); + } + } + + return timePoints; + } + + getCurrentSegmentIndex(currentTimeMinutes: number, timePoints: number[]): number { + if (!this.privilege.activeTimeRange) { + return -1; + } + + const { start, end } = this.privilege.activeTimeRange; + const [startHour, startMinute] = start.split(':').map(Number); + const [endHour, endMinute] = end.split(':').map(Number); + const startTimeMinutes = startHour * 60 + startMinute; + const endTimeMinutes = endHour * 60 + endMinute; + const isOvernight = startTimeMinutes > endTimeMinutes; + + for (let i = 0; i < timePoints.length; i++) { + const segStart = (i === 0) ? startTimeMinutes : timePoints[i - 1]; + const segEnd = timePoints[i]; + + if (!isOvernight) { + if (currentTimeMinutes >= segStart && currentTimeMinutes < segEnd) { + return i; + } + } else { + if (segStart <= segEnd) { + if (currentTimeMinutes >= segStart && currentTimeMinutes < segEnd) { + return i; + } + } else { + if (currentTimeMinutes >= segStart || currentTimeMinutes < segEnd) { + return i; + } + } + } + } + + return -1; + } + + hasMessageInCurrentSegment(currentSegmentIndex: number, timePoints: number[]): boolean { + if (!this.privilege.activeTimeRange) { + return false; + } + + const lastUserMessage = [...this.context.messages].reverse() + .find(m => m.role !== "assistant" && m.time); + + if (!lastUserMessage) { + return false; + } + + const lastMessageDate = new Date(lastUserMessage.time * 1000); + const lastMessageMinutes = lastMessageDate.getHours() * 60 + lastMessageDate.getMinutes(); + + const { start, end } = this.privilege.activeTimeRange; + const [startHour, startMinute] = start.split(':').map(Number); + const [endHour, endMinute] = end.split(':').map(Number); + const startTimeMinutes = startHour * 60 + startMinute; + const endTimeMinutes = endHour * 60 + endMinute; + const isOvernight = startTimeMinutes > endTimeMinutes; + + const currentSegmentTime = timePoints[currentSegmentIndex]; + const previousSegmentTime = currentSegmentIndex === 0 + ? this.getActiveTimeRangeStartMinutes() + : timePoints[currentSegmentIndex - 1]; + + if (isOvernight) { + if (previousSegmentTime <= currentSegmentTime) { + return lastMessageMinutes >= previousSegmentTime && lastMessageMinutes < currentSegmentTime; + } else { + return lastMessageMinutes >= previousSegmentTime || lastMessageMinutes < currentSegmentTime; + } + } else { + return lastMessageMinutes >= previousSegmentTime && lastMessageMinutes < currentSegmentTime; + } + } + + private getActiveTimeRangeStartMinutes(): number { + if (!this.privilege.activeTimeRange) { + return 0; + } + const [startHour, startMinute] = this.privilege.activeTimeRange.start.split(':').map(Number); + return startHour * 60 + startMinute; + } + + private generateSeed(id: string, start: string, end: string, segments: number): number { + const str = `${id}-${start}-${end}-${segments}`; + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return Math.abs(hash); + } + + private seededRandom(seed: number): number { + const x = Math.sin(seed) * 10000; + return x - Math.floor(x); + } async stopCurrentChatStream(): Promise { const { id, reply, toolCallStatus } = this.stream; this.stream = { diff --git a/src/AI/context.ts b/src/AI/context.ts index 2846ac9..7e63830 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -17,6 +17,7 @@ export interface Message { contentArray: string[]; msgIdArray: string[]; images: Image[]; + time?: number; } export class Context { @@ -70,6 +71,8 @@ export class Context { const { isShortMemory, shortMemorySummaryRound } = ConfigManager.memory; const messages = this.messages; + const messageTime = msg?.time || Date.now() / 1000; + //处理文本 s = s .replace(/\[CQ:(.*?),(?:qq|id)=(-?\d+)\]/g, (_, p1, p2) => { @@ -117,6 +120,7 @@ export class Context { messages[length - 1].contentArray.push(s); messages[length - 1].msgIdArray.push(msgId); messages[length - 1].images.push(...images); + messages[length - 1].time = messageTime; } else { const message = { role: role, @@ -125,7 +129,8 @@ export class Context { name: name, contentArray: [s], msgIdArray: [msgId], - images: images + images: images, + time: messageTime }; messages.push(message); diff --git a/src/config/config_message.ts b/src/config/config_message.ts index bb460af..e68e3c2 100644 --- a/src/config/config_message.ts +++ b/src/config/config_message.ts @@ -86,6 +86,9 @@ export class MessageConfig { {{#if 展示消息ID}} - <|msg_id:xxx|>表示消息ID,仅用于调用函数时使用,不要在生成的回复中提及或使用 - <|quote:xxx|>表示引用消息,xxx为对应的消息ID +{{/if}} +{{#if 展示时间}} + - <|time:xxxx-xx-xx xx:xx:xx|>表示消息发送时间,不要在生成的回复中提及或使用 {{/if}} - \\f用于分割多条消息 {{#if 接收图片}} @@ -143,6 +146,7 @@ export class MessageConfig { seal.ext.registerBoolConfig(MessageConfig.ext, "是否在消息内添加前缀", true, "可用于辨别不同用户"); seal.ext.registerBoolConfig(MessageConfig.ext, "是否给AI展示数字号码", true, "例如QQ号和群号,能力较弱模型可能会出现幻觉"); seal.ext.registerBoolConfig(MessageConfig.ext, "是否在消息内添加消息ID", false, "可用于撤回等情况"); + seal.ext.registerBoolConfig(MessageConfig.ext, "是否在消息内添加发送时间", false, "将消息发送时间添加到上下文中"); seal.ext.registerBoolConfig(MessageConfig.ext, "是否合并user content", false, "在不支持连续多个role为user的情况下开启,可用于适配deepseek-reasoner"); seal.ext.registerIntConfig(MessageConfig.ext, "存储上下文对话限制轮数", 15, "出现一次user视作一轮"); seal.ext.registerIntConfig(MessageConfig.ext, "上下文插入system message间隔轮数", 0, "需要小于限制轮数的二分之一才能生效,为0时不生效,示例对话不计入轮数"); @@ -156,6 +160,7 @@ export class MessageConfig { isPrefix: seal.ext.getBoolConfig(MessageConfig.ext, "是否在消息内添加前缀"), showNumber: seal.ext.getBoolConfig(MessageConfig.ext, "是否给AI展示数字号码"), showMsgId: seal.ext.getBoolConfig(MessageConfig.ext, "是否在消息内添加消息ID"), + showTime: seal.ext.getBoolConfig(MessageConfig.ext, "是否在消息内添加发送时间"), isMerge: seal.ext.getBoolConfig(MessageConfig.ext, "是否合并user content"), maxRounds: seal.ext.getIntConfig(MessageConfig.ext, "存储上下文对话限制轮数"), insertCount: seal.ext.getIntConfig(MessageConfig.ext, "上下文插入system message间隔轮数") diff --git a/src/index.ts b/src/index.ts index f46fa9f..4aa8582 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { transformTextToArray } from "./utils/utils_string"; import { checkUpdate } from "./utils/utils_update"; import { get_chart_url } from "./service"; import { TimerManager } from "./timer"; +import { registerEventHandlers } from "./events"; function main() { ConfigManager.registerConfig(); @@ -16,6 +17,7 @@ function main() { AIManager.getUsageMap(); ToolManager.registerTool(); TimerManager.init(); + registerEventHandlers(); const ext = ConfigManager.ext; @@ -145,7 +147,8 @@ function main() { 计数器模式(c): ${pr.counter > -1 ? `${pr.counter}条` : '关闭'} 计时器模式(t): ${pr.timer > -1 ? `${pr.timer}秒` : '关闭'} 概率模式(p): ${pr.prob > -1 ? `${pr.prob}%` : '关闭'} -待机模式: ${pr.standby ? '开启' : '关闭'}`); +待机模式: ${pr.standby ? '开启' : '关闭'} +活跃时间段: ${pr.activeTimeRange ? `${pr.activeTimeRange.start}至${pr.activeTimeRange.end}` : '未设置'}`); return ret; } case 'ctxn': { @@ -179,6 +182,8 @@ function main() { 单位/秒,默认60秒 【p】概率模式,每条消息按概率触发 单位/%,默认10% +【s】活跃时间段或时间段分割数量 +格式为"开始时间-结束时间"(如"09:00-18:00")或数字(如"5"表示分割5个时间段) 【.ai on --t --p=42】使用示例`); return ret; @@ -209,11 +214,57 @@ function main() { text += `\n概率模式:${pr.prob}%`; break; } + case 's': + case 'schedule': + if (exist && kwarg.value) { + const timeRange = kwarg.value.trim(); + const timePattern = /^(\d{1,2}:\d{2})-(\d{1,2}:\d{2})$/; + const match = timeRange.match(timePattern); + + if (match) { + const [startTime, endTime] = match.slice(1); + const validateTime = (timeStr: string): boolean => { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59; + }; + + if (validateTime(startTime) && validateTime(endTime)) { + const [startH, startM] = startTime.split(':').map(Number); + const [endH, endM] = endTime.split(':').map(Number); + pr.activeTimeRange = { + start: startH + ':' + startM, + end: endH + ':' + endM, + }; + TimerManager.addSegmentTimer(ctx, msg, ai); + text += `\n活跃时间段:${startTime}至${endTime}`; + } else { + text += `\n活跃时间段格式错误,应为HH:MM格式`; + } + } else if (!isNaN(parseInt(timeRange))) { + const segments = parseInt(timeRange); + if (segments > 0 && segments <= 10) { + pr.segments = segments; + text += `\n时间段分割数量:${segments}个`; + } else { + text += `\n时间段分割数量必须在1-10之间`; + } + } else { + text += `\n活跃时间段格式错误,应为"开始时间-结束时间"或数字`; + } + } else { + pr.activeTimeRange = null; + text += `\n已清除活跃时间段限制`; + } + break; } }); pr.standby = true; + if (pr.activeTimeRange) { + TimerManager.addSegmentTimer(ctx, msg, ai); + } + seal.replyToSender(ctx, msg, text); AIManager.saveAI(id); return ret; @@ -250,6 +301,8 @@ function main() { pr.prob = -1; pr.standby = false; + TimerManager.removeSegmentTimer(id); + ai.resetState(); seal.replyToSender(ctx, msg, 'AI已关闭'); @@ -280,6 +333,14 @@ function main() { text += `\n概率模式`; break; } + case 's': + case 'schedule': { + pr.activeTimeRange = null; + pr.segments = 3; + TimerManager.removeSegmentTimer(id); + text += `\n活跃时间段`; + break; + } } }); @@ -1428,6 +1489,9 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { if (pattern && pattern.test(message)) { const fmtCondition = parseInt(seal.format(ctx, `{${triggerCondition}}`)); if (fmtCondition === 1) { + if (!ai.isInActiveTimeRange()) { + ai.context.addSystemUserMessage("睡眠中", "当前是你的睡眠时间,但触发了关键词", []); + } return ai.handleReceipt(ctx, msg, ai, message, CQTypes) .then(() => ai.chat(ctx, msg, '非指令')); } diff --git a/src/timer.ts b/src/timer.ts index 13f8515..9e5f0f0 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -14,8 +14,20 @@ export interface TimerInfo { content: string }; +export interface SegmentTimerInfo { + id: string, + messageType: 'private' | 'group', + uid: string, + gid: string, + epId: string, + timestamp: number, + setTime: string, + lastTriggeredSegment: number +}; + export class TimerManager { static timerQueue: TimerInfo[] = []; + static segmentTimerQueue: SegmentTimerInfo[] = []; static isTaskRunning = false; static intervalId: number | null = null; @@ -34,6 +46,21 @@ export class TimerManager { ConfigManager.ext.storageSet(`timerQueue`, JSON.stringify(this.timerQueue)); } + static getSegmentTimerQueue() { + try { + JSON.parse(ConfigManager.ext.storageGet(`segmentTimerQueue`) || '[]') + .forEach((item: any) => { + this.segmentTimerQueue.push(item); + }); + } catch (e) { + logger.error('在获取segmentTimerQueue时出错', e); + } + } + + static saveSegmentTimerQueue() { + ConfigManager.ext.storageSet(`segmentTimerQueue`, JSON.stringify(this.segmentTimerQueue)); + } + static addTimer(ctx: seal.MsgContext, msg: seal.Message, ai: AI, t: number, content: string) { this.timerQueue.push({ id: ai.id, @@ -54,6 +81,43 @@ export class TimerManager { } } + static addSegmentTimer(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { + const existingIndex = this.segmentTimerQueue.findIndex(timer => timer.id === ai.id); + + const timerInfo = { + id: ai.id, + messageType: msg.messageType, + uid: ctx.player.userId, + gid: ctx.group.groupId, + epId: ctx.endPoint.userId, + timestamp: Math.floor(Date.now() / 1000), + setTime: new Date().toLocaleString(), + lastTriggeredSegment: -1 + }; + + if (existingIndex >= 0) { + timerInfo.lastTriggeredSegment = this.segmentTimerQueue[existingIndex].lastTriggeredSegment; + this.segmentTimerQueue[existingIndex] = timerInfo; + } else { + this.segmentTimerQueue.push(timerInfo); + } + + this.saveSegmentTimerQueue(); + + if (!this.intervalId) { + logger.info('时间段检查任务启动'); + this.executeTask(); + } + } + + static removeSegmentTimer(aiId: string) { + const index = this.segmentTimerQueue.findIndex(timer => timer.id === aiId); + if (index >= 0) { + this.segmentTimerQueue.splice(index, 1); + this.saveSegmentTimerQueue(); + } + } + static async task() { try { if (this.isTaskRunning) { @@ -83,8 +147,12 @@ export class TimerManager { 提示内容:${content}`; await ai.context.addSystemUserMessage("定时器触发提示", s, []); + + if (!ai.isInActiveTimeRange()) { + await ai.context.addSystemUserMessage("睡眠中", "当前是你的睡眠时间,但定时任务触发了", []); + } + await ai.chat(ctx, msg, '定时任务'); - changed = true; await new Promise(resolve => setTimeout(resolve, 2000)); } @@ -94,14 +162,97 @@ export class TimerManager { this.saveTimerQueue(); } + await this.checkSegmentTimers(); + this.isTaskRunning = false; } catch (e) { logger.error(`定时任务处理出错,错误信息:${e.message}`); } } + static async checkSegmentTimers() { + const remainingTimers: SegmentTimerInfo[] = []; + let changed = false; + const now = new Date(); + const currentHour = now.getHours(); + const currentMinute = now.getMinutes(); + const currentTimeMinutes = currentHour * 60 + currentMinute; + + for (const timer of this.segmentTimerQueue) { + const ai = AIManager.getAI(timer.id); + if (!ai) { + changed = true; + continue; + } + + if (!ai.privilege.standby || !ai.privilege.activeTimeRange) { + changed = true; + continue; + } + + if (!ai.isInActiveTimeRange()) { + remainingTimers.push(timer); + continue; + } + + const timePoints = ai.getActiveTimeSegments(); + + let currentSegmentIndex = -1; + for (let i = 0; i < timePoints.length; i++) { + if (currentTimeMinutes >= timePoints[i]) { + currentSegmentIndex = i; + } else { + break; + } + } + + if (currentSegmentIndex === -1) { + remainingTimers.push(timer); + continue; + } + + const segmentTime = timePoints[currentSegmentIndex]; + const timeDiff = Math.abs(currentTimeMinutes - segmentTime); + + if (timeDiff <= 1) { + if (timer.lastTriggeredSegment === currentSegmentIndex) { + remainingTimers.push(timer); + continue; + } + + const hasRecentMessage = ai.hasMessageInCurrentSegment(currentSegmentIndex, timePoints); + + if (hasRecentMessage) { + timer.lastTriggeredSegment = currentSegmentIndex; + changed = true; + remainingTimers.push(timer); + continue; + } + + const { messageType, uid, gid, epId } = timer; + const msg = createMsg(messageType, uid, gid); + const ctx = createCtx(epId, msg); + + const s = `当前时间:${now.toLocaleString()}为你的活跃时间段`; + await ai.context.addSystemUserMessage("活跃时间段触发提示", s, []); + await ai.chat(ctx, msg, '活跃时间段触发'); + + timer.lastTriggeredSegment = currentSegmentIndex; + changed = true; + await new Promise(resolve => setTimeout(resolve, 2000)); + } + + remainingTimers.push(timer); + } + + if (changed) { + this.segmentTimerQueue = remainingTimers; + this.saveSegmentTimerQueue(); + } + } + static async executeTask() { - if (this.timerQueue.length === 0) { + if (this.timerQueue.length === 0 && this.segmentTimerQueue.length === 0) { this.destroy(); return; } @@ -121,5 +272,6 @@ export class TimerManager { static init() { this.getTimerQueue(); this.executeTask(); + this.getSegmentTimerQueue(); } } \ No newline at end of file diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 5ac1378..677ae0a 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -5,8 +5,19 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/config"; import { ToolInfo } from "../tool/tool"; +function formatTimestamp(timestamp: number): string { + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} + export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { - const { roleSettingTemplate, systemMessageTemplate, isPrefix, showNumber, showMsgId } = ConfigManager.message; + const { roleSettingTemplate, systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; const { isTool, usePromptEngineering } = ConfigManager.tool; const { localImagePaths, receiveImage, condition } = ConfigManager.image; const { isMemory, isShortMemory } = ConfigManager.memory; @@ -66,6 +77,7 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), "添加前缀": isPrefix, "展示消息ID": showMsgId, + "展示时间": showTime, "接收图片": receiveImage, "图片条件不为零": condition !== '0', "可发送图片不为空": sandableImagesPrompt, @@ -157,7 +169,7 @@ function buildContextMessages(systemMessage: Message, messages: Message[]): Mess } export function handleMessages(ctx: seal.MsgContext, ai: AI) { - const { isPrefix, showNumber, showMsgId, isMerge } = ConfigManager.message; + const { isPrefix, showNumber, showMsgId, isMerge, showTime } = ConfigManager.message; const systemMessage = buildSystemMessage(ctx, ai); const samplesMessages = buildSamplesMessages(ctx); @@ -208,7 +220,17 @@ export function handleMessages(ctx: seal.MsgContext, ai: AI) { `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` ) : ''; - const content = message.msgIdArray.map((msgId, index) => (showMsgId && msgId ? `<|msg_id:${msgId}|>` : '') + message.contentArray[index]).join('\f'); + const content = message.msgIdArray.map((msgId, index) => { + let result = ''; + if (showMsgId && msgId) { + result += `<|msg_id:${msgId}|>`; + } + if (showTime && message.time) { + result += `<|time:${formatTimestamp(message.time)}|>`; + } + result += message.contentArray[index]; + return result; + }).join('\f'); if (isMerge && message.role === last_role && message.role !== 'tool') { processedMessages[processedMessages.length - 1].content += '\f' + prefix + content; From ef9214b22a3526e6e6d6a790e852a85f36af2252 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 7 Oct 2025 01:52:06 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E5=B0=9A=E6=9C=AA=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=EF=BC=8C=E5=8F=AA=E6=98=AF=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E4=B8=80=E4=B8=8B=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AI/AI.ts | 202 +++++++-------------------------------------------- src/timer.ts | 2 +- 2 files changed, 26 insertions(+), 178 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 81df73d..4b7c103 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -16,8 +16,11 @@ export interface Privilege { timer: number, prob: number, standby: boolean, - activeTimeRange: { start: string, end: string } | null, - segments: number, + activeTimeInfo: { + start: number, + end: number, + segments: number + } } export class AI { @@ -43,7 +46,7 @@ export class AI { constructor(id: string) { this.id = id; - this.version = '0.0.0'; + this.version = '0.0.1'; this.context = new Context(); this.tool = new ToolManager(); this.memory = new Memory(); @@ -54,8 +57,11 @@ export class AI { timer: -1, prob: -1, standby: false, - activeTimeRange: null, - segments: 3 + activeTimeInfo: { + start: 0, + end: 0, + segments: 0 + } }; this.stream = { id: '', @@ -105,16 +111,6 @@ export class AI { } async chat(ctx: seal.MsgContext, msg: seal.Message, reason: string = ''): Promise { - const isKeywordTrigger = reason === "事件触发" || reason === "非指令"; - const isTimerTrigger = reason === "定时任务"; - - if (!isKeywordTrigger && !isTimerTrigger) { - if (!this.isInActiveTimeRange()) { - logger.info(`当前时间不在AI活跃时间段内,${reason}触发不进行回复`); - return; - } - } - logger.info('触发回复:', reason || '未知原因'); const { bucketLimit, fillInterval } = ConfigManager.received; @@ -324,177 +320,29 @@ export class AI { } isInActiveTimeRange(): boolean { - if (!this.privilege.activeTimeRange) { - return true; - } - const now = new Date(); - const currentHour = now.getHours(); - const currentMinute = now.getMinutes(); - const currentTimeMinutes = currentHour * 60 + currentMinute; - - const { start, end } = this.privilege.activeTimeRange; - const [startHour, startMinute] = start.split(':').map(Number); - const [endHour, endMinute] = end.split(':').map(Number); - - const startTimeMinutes = startHour * 60 + startMinute; - const endTimeMinutes = endHour * 60 + endMinute; - - if (startTimeMinutes <= endTimeMinutes) { - return currentTimeMinutes >= startTimeMinutes && currentTimeMinutes <= endTimeMinutes; - } else { - return currentTimeMinutes >= startTimeMinutes || currentTimeMinutes <= endTimeMinutes; - } - } + const current = now.getHours() * 60 + now.getMinutes(); + const { start, end } = this.privilege.activeTimeInfo; - getActiveTimeSegments(): number[] { - if (!this.privilege.activeTimeRange) { - return []; - } - - const { start, end } = this.privilege.activeTimeRange; - const [startHour, startMinute] = start.split(':').map(Number); - const [endHour, endMinute] = end.split(':').map(Number); - - const startTimeMinutes = startHour * 60 + startMinute; - const endTimeMinutes = endHour * 60 + endMinute; - const segments = this.privilege.segments; - - const seed = this.generateSeed(this.id, start, end, segments); - const timePoints: number[] = []; - - if (startTimeMinutes <= endTimeMinutes) { - const totalMinutes = endTimeMinutes - startTimeMinutes; - const segmentDuration = totalMinutes / segments; - - for (let i = 0; i < segments; i++) { - const segmentStart = startTimeMinutes + i * segmentDuration; - const randomOffset = this.seededRandom(seed + i) * segmentDuration * 0.6 - segmentDuration * 0.3; - const randomTime = segmentStart + segmentDuration / 2 + randomOffset; - timePoints.push(Math.floor(randomTime)); - } + if (start <= end) { + return current >= start && current <= end; } else { - const totalMinutes = (24 * 60 - startTimeMinutes) + endTimeMinutes; - const segmentDuration = totalMinutes / segments; - - for (let i = 0; i < segments; i++) { - let segmentStart = startTimeMinutes + i * segmentDuration; - - if (segmentStart >= 24 * 60) { - segmentStart -= 24 * 60; - } - - const randomOffset = this.seededRandom(seed + i) * segmentDuration * 0.6 - segmentDuration * 0.3; - let randomTime = segmentStart + segmentDuration / 2 + randomOffset; - - if (randomTime >= 24 * 60) { - randomTime -= 24 * 60; - } - - timePoints.push(Math.floor(randomTime)); - } + return current >= start || current <= end; } - - return timePoints; } - getCurrentSegmentIndex(currentTimeMinutes: number, timePoints: number[]): number { - if (!this.privilege.activeTimeRange) { - return -1; - } - - const { start, end } = this.privilege.activeTimeRange; - const [startHour, startMinute] = start.split(':').map(Number); - const [endHour, endMinute] = end.split(':').map(Number); - const startTimeMinutes = startHour * 60 + startMinute; - const endTimeMinutes = endHour * 60 + endMinute; - const isOvernight = startTimeMinutes > endTimeMinutes; - - for (let i = 0; i < timePoints.length; i++) { - const segStart = (i === 0) ? startTimeMinutes : timePoints[i - 1]; - const segEnd = timePoints[i]; - - if (!isOvernight) { - if (currentTimeMinutes >= segStart && currentTimeMinutes < segEnd) { - return i; - } - } else { - if (segStart <= segEnd) { - if (currentTimeMinutes >= segStart && currentTimeMinutes < segEnd) { - return i; - } - } else { - if (currentTimeMinutes >= segStart || currentTimeMinutes < segEnd) { - return i; - } - } - } - } - - return -1; - } - - hasMessageInCurrentSegment(currentSegmentIndex: number, timePoints: number[]): boolean { - if (!this.privilege.activeTimeRange) { - return false; - } - - const lastUserMessage = [...this.context.messages].reverse() - .find(m => m.role !== "assistant" && m.time); - - if (!lastUserMessage) { - return false; - } - - const lastMessageDate = new Date(lastUserMessage.time * 1000); - const lastMessageMinutes = lastMessageDate.getHours() * 60 + lastMessageDate.getMinutes(); - - const { start, end } = this.privilege.activeTimeRange; - const [startHour, startMinute] = start.split(':').map(Number); - const [endHour, endMinute] = end.split(':').map(Number); - const startTimeMinutes = startHour * 60 + startMinute; - const endTimeMinutes = endHour * 60 + endMinute; - const isOvernight = startTimeMinutes > endTimeMinutes; - - const currentSegmentTime = timePoints[currentSegmentIndex]; - const previousSegmentTime = currentSegmentIndex === 0 - ? this.getActiveTimeRangeStartMinutes() - : timePoints[currentSegmentIndex - 1]; - - if (isOvernight) { - if (previousSegmentTime <= currentSegmentTime) { - return lastMessageMinutes >= previousSegmentTime && lastMessageMinutes < currentSegmentTime; - } else { - return lastMessageMinutes >= previousSegmentTime || lastMessageMinutes < currentSegmentTime; - } - } else { - return lastMessageMinutes >= previousSegmentTime && lastMessageMinutes < currentSegmentTime; - } - } - - private getActiveTimeRangeStartMinutes(): number { - if (!this.privilege.activeTimeRange) { - return 0; - } - const [startHour, startMinute] = this.privilege.activeTimeRange.start.split(':').map(Number); - return startHour * 60 + startMinute; - } + getActiveTimePoints(): number[] { + const { start, end, segments } = this.privilege.activeTimeInfo; + const endReal = end > start ? end : end + 24 * 60; + const segLen = (endReal - start) / segments; - private generateSeed(id: string, start: string, end: string, segments: number): number { - const str = `${id}-${start}-${end}-${segments}`; - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; - } - return Math.abs(hash); - } + const timePoints: number[] = Array.from({ length: segments }, (_, i) => + Math.floor(start + i * segLen + Math.random() * segLen) % (24 * 60) + ); - private seededRandom(seed: number): number { - const x = Math.sin(seed) * 10000; - return x - Math.floor(x); + return timePoints; } + async stopCurrentChatStream(): Promise { const { id, reply, toolCallStatus } = this.stream; this.stream = { diff --git a/src/timer.ts b/src/timer.ts index 9e5f0f0..f662b6c 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -195,7 +195,7 @@ export class TimerManager { continue; } - const timePoints = ai.getActiveTimeSegments(); + const timePoints = ai.getActiveTimePoints(); let currentSegmentIndex = -1; for (let i = 0; i < timePoints.length; i++) { From 3d41679e21286c4acd3b170874b0cd8c4fb64444 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 8 Oct 2025 00:53:31 +0800 Subject: [PATCH 3/6] finish the work --- src/AI/AI.ts | 46 ++++++-- src/AI/context.ts | 20 ++-- src/index.ts | 156 +++++++++++++------------ src/timer.ts | 221 +++++++++++------------------------- src/tool/tool_roll_check.ts | 2 +- src/tool/tool_time.ts | 22 +--- src/update.ts | 3 +- src/utils/utils_message.ts | 31 ++--- src/utils/utils_string.ts | 11 ++ 9 files changed, 223 insertions(+), 289 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 4b7c103..f751e3e 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -19,7 +19,7 @@ export interface Privilege { activeTimeInfo: { start: number, end: number, - segments: number + segs: number } } @@ -60,7 +60,7 @@ export class AI { activeTimeInfo: { start: 0, end: 0, - segments: 0 + segs: 0 } }; this.stream = { @@ -319,20 +319,46 @@ export class AI { await this.stopCurrentChatStream(); } - isInActiveTimeRange(): boolean { + // 若不在活动时间范围内,返回-1 + getCurSegIndex(): number { const now = new Date(); - const current = now.getHours() * 60 + now.getMinutes(); - const { start, end } = this.privilege.activeTimeInfo; + const cur = now.getHours() * 60 + now.getMinutes(); + const { start, end, segs } = this.privilege.activeTimeInfo; + const endReal = end > start ? end : end + 24 * 60; + const curReal = cur > start ? cur : cur + 24 * 60; + + if (curReal > endReal) return -1; + + const segLen = (endReal - start) / segs; + const index = Math.floor((curReal - start) / segLen); + return Math.min(index, segs - 1); + } + + // 若没有下一个活跃时间点,返回-1 + getNextTimePoint(curSegIndex: number): number { + const { start, end, segs } = this.privilege.activeTimeInfo; - if (start <= end) { - return current >= start && current <= end; - } else { - return current >= start || current <= end; + if (start === 0 && end === 0) return -1; + + const endReal = end > start ? end : end + 24 * 60; + const segLen = (endReal - start) / segs; + const nextSegIndex = (curSegIndex + 1) % segs; + const todayMin = Math.floor(start + nextSegIndex * segLen + Math.random() * segLen) % (24 * 60); + + const nextTime = new Date(); + nextTime.setHours(Math.floor(todayMin / 60), todayMin % 60, Math.floor(Math.random() * 60), 0); + + // 如果时间已过,设置为明天 + if (nextTime.getTime() <= Date.now()) { + nextTime.setDate(nextTime.getDate() + 1); } + + return Math.floor(nextTime.getTime() / 1000); } + //no need getActiveTimePoints(): number[] { - const { start, end, segments } = this.privilege.activeTimeInfo; + const { start, end, segs: segments } = this.privilege.activeTimeInfo; const endReal = end > start ? end : end + 24 * 60; const segLen = (endReal - start) / segments; diff --git a/src/AI/context.ts b/src/AI/context.ts index 7e63830..e83baac 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -16,8 +16,8 @@ export interface Message { name: string; contentArray: string[]; msgIdArray: string[]; + timeArray: number[]; images: Image[]; - time?: number; } export class Context { @@ -71,8 +71,6 @@ export class Context { const { isShortMemory, shortMemorySummaryRound } = ConfigManager.memory; const messages = this.messages; - const messageTime = msg?.time || Date.now() / 1000; - //处理文本 s = s .replace(/\[CQ:(.*?),(?:qq|id)=(-?\d+)\]/g, (_, p1, p2) => { @@ -119,18 +117,17 @@ export class Context { if (length !== 0 && messages[length - 1].uid === uid && !//.test(s)) { messages[length - 1].contentArray.push(s); messages[length - 1].msgIdArray.push(msgId); + messages[length - 1].timeArray.push(Math.floor(Date.now() / 1000)); messages[length - 1].images.push(...images); - messages[length - 1].time = messageTime; } else { - const message = { + const message: Message = { role: role, - content: '', uid: uid, name: name, contentArray: [s], msgIdArray: [msgId], - images: images, - time: messageTime + timeArray: [Math.floor(Date.now() / 1000)], + images: images }; messages.push(message); @@ -154,26 +151,28 @@ export class Context { } async addToolCallsMessage(tool_calls: ToolCall[]) { - const message = { + const message: Message = { role: 'assistant', tool_calls: tool_calls, uid: '', name: '', contentArray: [], msgIdArray: [], + timeArray: [], images: [] }; this.messages.push(message); } async addToolMessage(tool_call_id: string, s: string) { - const message = { + const message: Message = { role: 'tool', tool_call_id: tool_call_id, uid: '', name: '', contentArray: [s], msgIdArray: [''], + timeArray: [Math.floor(Date.now() / 1000)], images: [] }; @@ -195,6 +194,7 @@ export class Context { name: `_${name}`, contentArray: [s], msgIdArray: [''], + timeArray: [Math.floor(Date.now() / 1000)], images: images }; this.messages.push(message); diff --git a/src/index.ts b/src/index.ts index 4aa8582..1603172 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,6 @@ import { transformTextToArray } from "./utils/utils_string"; import { checkUpdate } from "./utils/utils_update"; import { get_chart_url } from "./service"; import { TimerManager } from "./timer"; -import { registerEventHandlers } from "./events"; function main() { ConfigManager.registerConfig(); @@ -17,7 +16,6 @@ function main() { AIManager.getUsageMap(); ToolManager.registerTool(); TimerManager.init(); - registerEventHandlers(); const ext = ConfigManager.ext; @@ -141,14 +139,17 @@ function main() { return ret; } + const { start, end, segs } = pr.activeTimeInfo; + seal.replyToSender(ctx, msg, `${id} 权限限制: ${pr.limit} 上下文轮数: ${ai.context.messages.filter(m => m.role === 'user').length} 计数器模式(c): ${pr.counter > -1 ? `${pr.counter}条` : '关闭'} 计时器模式(t): ${pr.timer > -1 ? `${pr.timer}秒` : '关闭'} 概率模式(p): ${pr.prob > -1 ? `${pr.prob}%` : '关闭'} -待机模式: ${pr.standby ? '开启' : '关闭'} -活跃时间段: ${pr.activeTimeRange ? `${pr.activeTimeRange.start}至${pr.activeTimeRange.end}` : '未设置'}`); +活跃时间段: ${(start !== 0 || end !== 0) ? `${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}` : '未设置'} +活跃次数: ${segs > 0 ? segs : '未设置'} +待机模式: ${pr.standby ? '开启' : '关闭'}`); return ret; } case 'ctxn': { @@ -182,89 +183,93 @@ function main() { 单位/秒,默认60秒 【p】概率模式,每条消息按概率触发 单位/%,默认10% -【s】活跃时间段或时间段分割数量 -格式为"开始时间-结束时间"(如"09:00-18:00")或数字(如"5"表示分割5个时间段) +【a】活跃时间段和活跃次数 +格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5") 【.ai on --t --p=42】使用示例`); return ret; } let text = `AI已开启:`; - kwargs.forEach(kwarg => { + for (const kwarg of kwargs) { const name = kwarg.name; const exist = kwarg.valueExists; - const value = parseFloat(kwarg.value); + const valInt = parseInt(kwarg.value); + const valFloat = parseFloat(kwarg.value); + const valStr = kwarg.value.trim(); switch (name) { case 'c': case 'counter': { - pr.counter = exist && !isNaN(value) ? value : 10; + ai.context.counter = 0; + pr.counter = exist && !isNaN(valInt) ? valInt : 10; text += `\n计数器模式:${pr.counter}条`; break; } case 't': case 'timer': { - pr.timer = exist && !isNaN(value) ? value : 60; + clearTimeout(ai.context.timer); + ai.context.timer = null; + pr.timer = exist && !isNaN(valFloat) ? valFloat : 60; text += `\n计时器模式:${pr.timer}秒`; break; } case 'p': case 'prob': { - pr.prob = exist && !isNaN(value) ? value : 10; + pr.prob = exist && !isNaN(valFloat) ? valFloat : 10; text += `\n概率模式:${pr.prob}%`; break; } - case 's': - case 'schedule': - if (exist && kwarg.value) { - const timeRange = kwarg.value.trim(); - const timePattern = /^(\d{1,2}:\d{2})-(\d{1,2}:\d{2})$/; - const match = timeRange.match(timePattern); - - if (match) { - const [startTime, endTime] = match.slice(1); - const validateTime = (timeStr: string): boolean => { - const [hours, minutes] = timeStr.split(':').map(Number); - return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59; - }; - - if (validateTime(startTime) && validateTime(endTime)) { - const [startH, startM] = startTime.split(':').map(Number); - const [endH, endM] = endTime.split(':').map(Number); - pr.activeTimeRange = { - start: startH + ':' + startM, - end: endH + ':' + endM, - }; - TimerManager.addSegmentTimer(ctx, msg, ai); - text += `\n活跃时间段:${startTime}至${endTime}`; - } else { - text += `\n活跃时间段格式错误,应为HH:MM格式`; - } - } else if (!isNaN(parseInt(timeRange))) { - const segments = parseInt(timeRange); - if (segments > 0 && segments <= 10) { - pr.segments = segments; - text += `\n时间段分割数量:${segments}个`; - } else { - text += `\n时间段分割数量必须在1-10之间`; - } + case 'a': + case 'active': { + if (!exist) { + seal.replyToSender(ctx, msg, '请输入活跃时间段'); + return ret; + } + + const arr = valStr.split('-').map((item, index) => { + const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); + if (index < 2) { + return (parts[0] * 60 + parts[1] || 0) % (24 * 60); } else { - text += `\n活跃时间段格式错误,应为"开始时间-结束时间"或数字`; + return parts[0]; } - } else { - pr.activeTimeRange = null; - text += `\n已清除活跃时间段限制`; + }) + + const [start = 0, end = 0, segs = 1] = arr; + if (start === end) { + seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); + return ret; + } + + const endReal = end > start ? end : end + 24 * 60; + if (segs > endReal - start) { + seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); + return ret; + } + + TimerManager.removeTimer(id, '', 'activeTime', []); + pr.activeTimeInfo = { + start, + end, + segs, + } + + text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; + text += `\n活跃次数:${segs}`; + + const curSegIndex = ai.getCurSegIndex(); + const nextTimePoint = ai.getNextTimePoint(curSegIndex); + if (nextTimePoint !== -1) { + TimerManager.addTimer(ctx, msg, ai, nextTimePoint, '', 'activeTime'); } break; + } } - }); + }; pr.standby = true; - if (pr.activeTimeRange) { - TimerManager.addSegmentTimer(ctx, msg, ai); - } - seal.replyToSender(ctx, msg, text); AIManager.saveAI(id); return ret; @@ -276,12 +281,18 @@ function main() { return ret; } + ai.resetState(); + TimerManager.removeTimer(id, '', 'activeTime', []); + pr.counter = -1; pr.timer = -1; pr.prob = -1; pr.standby = true; - - ai.resetState(); + pr.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } seal.replyToSender(ctx, msg, 'AI已开启待机模式'); AIManager.saveAI(id); @@ -296,14 +307,18 @@ function main() { const kwargs = cmdArgs.kwargs; if (kwargs.length == 0) { + ai.resetState(); + TimerManager.removeTimer(id, '', 'activeTime', []); + pr.counter = -1; pr.timer = -1; pr.prob = -1; pr.standby = false; - - TimerManager.removeSegmentTimer(id); - - ai.resetState(); + pr.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } seal.replyToSender(ctx, msg, 'AI已关闭'); AIManager.saveAI(id); @@ -317,12 +332,15 @@ function main() { switch (name) { case 'c': case 'counter': { + ai.context.counter = 0; pr.counter = -1; text += `\n计数器模式`; break; } case 't': case 'timer': { + clearTimeout(ai.context.timer); + ai.context.timer = null; pr.timer = -1; text += `\n计时器模式`; break; @@ -333,19 +351,20 @@ function main() { text += `\n概率模式`; break; } - case 's': - case 'schedule': { - pr.activeTimeRange = null; - pr.segments = 3; - TimerManager.removeSegmentTimer(id); + case 'a': + case 'active': { + TimerManager.removeTimer(id, '', 'activeTime', []); + pr.activeTimeInfo = { + start: 0, + end: 0, + segs: 0, + } text += `\n活跃时间段`; break; } } }); - ai.resetState(); - seal.replyToSender(ctx, msg, text); AIManager.saveAI(id); return ret; @@ -1489,9 +1508,6 @@ ${Object.keys(tool.info.function.parameters.properties).map(key => { if (pattern && pattern.test(message)) { const fmtCondition = parseInt(seal.format(ctx, `{${triggerCondition}}`)); if (fmtCondition === 1) { - if (!ai.isInActiveTimeRange()) { - ai.context.addSystemUserMessage("睡眠中", "当前是你的睡眠时间,但触发了关键词", []); - } return ai.handleReceipt(ctx, msg, ai, message, CQTypes) .then(() => ai.chat(ctx, msg, '非指令')); } diff --git a/src/timer.ts b/src/timer.ts index f662b6c..0951f77 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -9,25 +9,14 @@ export interface TimerInfo { uid: string, gid: string, epId: string, - timestamp: number, + timestamp: number, // 定时器触发时间,单位秒 setTime: string, - content: string -}; - -export interface SegmentTimerInfo { - id: string, - messageType: 'private' | 'group', - uid: string, - gid: string, - epId: string, - timestamp: number, - setTime: string, - lastTriggeredSegment: number + content: string, + type: 'timer' | 'activeTime' }; export class TimerManager { static timerQueue: TimerInfo[] = []; - static segmentTimerQueue: SegmentTimerInfo[] = []; static isTaskRunning = false; static intervalId: number | null = null; @@ -46,31 +35,17 @@ export class TimerManager { ConfigManager.ext.storageSet(`timerQueue`, JSON.stringify(this.timerQueue)); } - static getSegmentTimerQueue() { - try { - JSON.parse(ConfigManager.ext.storageGet(`segmentTimerQueue`) || '[]') - .forEach((item: any) => { - this.segmentTimerQueue.push(item); - }); - } catch (e) { - logger.error('在获取segmentTimerQueue时出错', e); - } - } - - static saveSegmentTimerQueue() { - ConfigManager.ext.storageSet(`segmentTimerQueue`, JSON.stringify(this.segmentTimerQueue)); - } - - static addTimer(ctx: seal.MsgContext, msg: seal.Message, ai: AI, t: number, content: string) { + static addTimer(ctx: seal.MsgContext, msg: seal.Message, ai: AI, timestamp: number, content: string, reason: 'timer' | 'activeTime') { this.timerQueue.push({ id: ai.id, messageType: msg.messageType, uid: ctx.player.userId, gid: ctx.group.groupId, epId: ctx.endPoint.userId, - timestamp: Math.floor(Date.now() / 1000) + t * 60, + timestamp: timestamp, setTime: new Date().toLocaleString(), - content: content + content: content, + type: reason }) this.saveTimerQueue(); @@ -81,41 +56,37 @@ export class TimerManager { } } - static addSegmentTimer(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { - const existingIndex = this.segmentTimerQueue.findIndex(timer => timer.id === ai.id); - - const timerInfo = { - id: ai.id, - messageType: msg.messageType, - uid: ctx.player.userId, - gid: ctx.group.groupId, - epId: ctx.endPoint.userId, - timestamp: Math.floor(Date.now() / 1000), - setTime: new Date().toLocaleString(), - lastTriggeredSegment: -1 - }; - - if (existingIndex >= 0) { - timerInfo.lastTriggeredSegment = this.segmentTimerQueue[existingIndex].lastTriggeredSegment; - this.segmentTimerQueue[existingIndex] = timerInfo; + static removeTimer(id: string = '', content: string = '', reason: 'timer' | 'activeTime' = 'timer', index_list: number[] = []) { + if (index_list.length > 0) { + const timers = TimerManager.timerQueue.filter(t => + (id && t.id === id) && + (content && t.content === content) && + (reason && t.type === reason) + ); + + for (const index of index_list) { + if (index < 1 || index > timers.length) { + logger.warning(`序号${index}超出范围`); + continue; + } + + const i = TimerManager.timerQueue.indexOf(timers[index - 1]); + if (i === -1) { + logger.warning(`出错了:找不到序号${index}的定时器`); + continue; + } + + TimerManager.timerQueue.splice(i, 1); + } } else { - this.segmentTimerQueue.push(timerInfo); + this.timerQueue = this.timerQueue.filter(timer => + (id && timer.id !== id) && + (content && timer.content !== content) && + (reason && timer.type !== reason) + ); } - - this.saveSegmentTimerQueue(); - - if (!this.intervalId) { - logger.info('时间段检查任务启动'); - this.executeTask(); - } - } - static removeSegmentTimer(aiId: string) { - const index = this.segmentTimerQueue.findIndex(timer => timer.id === aiId); - if (index >= 0) { - this.segmentTimerQueue.splice(index, 1); - this.saveSegmentTimerQueue(); - } + this.saveTimerQueue(); } static async task() { @@ -136,23 +107,43 @@ export class TimerManager { continue; } - const { id, messageType, uid, gid, epId, setTime, content } = timer; + const { id, messageType, uid, gid, epId, setTime, content, type: reason } = timer; const msg = createMsg(messageType, uid, gid); const ctx = createCtx(epId, msg); const ai = AIManager.getAI(id); - const s = `你设置的定时器触发了,请按照以下内容发送回复: + switch (reason) { + case 'timer': { + const s = `你设置的定时器触发了,请按照以下内容发送回复: 定时器设定时间:${setTime} 当前触发时间:${new Date().toLocaleString()} 提示内容:${content}`; - await ai.context.addSystemUserMessage("定时器触发提示", s, []); - - if (!ai.isInActiveTimeRange()) { - await ai.context.addSystemUserMessage("睡眠中", "当前是你的睡眠时间,但定时任务触发了", []); + await ai.context.addSystemUserMessage("定时器触发提示", s, []); + await ai.chat(ctx, msg, '定时任务'); + break; + } + case 'activeTime': { + const curSegIndex = ai.getCurSegIndex(); + const nextTimePoint = ai.getNextTimePoint(curSegIndex); + if (nextTimePoint !== -1) { + this.addTimer(ctx, msg, ai, nextTimePoint, '', 'activeTime'); + } + + if (curSegIndex === -1) { + logger.error(`${id} 不在活跃时间内,触发了 activeTime 定时器,真奇怪`); + continue; + } + + const s = `现在是你的活跃时间:${new Date().toLocaleString()},请说点什么`; + + await ai.context.addSystemUserMessage("活跃时间触发提示", s, []); + await ai.chat(ctx, msg, '活跃时间'); + break; + } } - - await ai.chat(ctx, msg, '定时任务'); + + changed = true; await new Promise(resolve => setTimeout(resolve, 2000)); } @@ -162,97 +153,14 @@ export class TimerManager { this.saveTimerQueue(); } - await this.checkSegmentTimers(); - this.isTaskRunning = false; } catch (e) { logger.error(`定时任务处理出错,错误信息:${e.message}`); } } - static async checkSegmentTimers() { - const remainingTimers: SegmentTimerInfo[] = []; - let changed = false; - const now = new Date(); - const currentHour = now.getHours(); - const currentMinute = now.getMinutes(); - const currentTimeMinutes = currentHour * 60 + currentMinute; - - for (const timer of this.segmentTimerQueue) { - const ai = AIManager.getAI(timer.id); - if (!ai) { - changed = true; - continue; - } - - if (!ai.privilege.standby || !ai.privilege.activeTimeRange) { - changed = true; - continue; - } - - if (!ai.isInActiveTimeRange()) { - remainingTimers.push(timer); - continue; - } - - const timePoints = ai.getActiveTimePoints(); - - let currentSegmentIndex = -1; - for (let i = 0; i < timePoints.length; i++) { - if (currentTimeMinutes >= timePoints[i]) { - currentSegmentIndex = i; - } else { - break; - } - } - - if (currentSegmentIndex === -1) { - remainingTimers.push(timer); - continue; - } - - const segmentTime = timePoints[currentSegmentIndex]; - const timeDiff = Math.abs(currentTimeMinutes - segmentTime); - - if (timeDiff <= 1) { - if (timer.lastTriggeredSegment === currentSegmentIndex) { - remainingTimers.push(timer); - continue; - } - - const hasRecentMessage = ai.hasMessageInCurrentSegment(currentSegmentIndex, timePoints); - - if (hasRecentMessage) { - timer.lastTriggeredSegment = currentSegmentIndex; - changed = true; - remainingTimers.push(timer); - continue; - } - - const { messageType, uid, gid, epId } = timer; - const msg = createMsg(messageType, uid, gid); - const ctx = createCtx(epId, msg); - - const s = `当前时间:${now.toLocaleString()}为你的活跃时间段`; - await ai.context.addSystemUserMessage("活跃时间段触发提示", s, []); - await ai.chat(ctx, msg, '活跃时间段触发'); - - timer.lastTriggeredSegment = currentSegmentIndex; - changed = true; - await new Promise(resolve => setTimeout(resolve, 2000)); - } - - remainingTimers.push(timer); - } - - if (changed) { - this.segmentTimerQueue = remainingTimers; - this.saveSegmentTimerQueue(); - } - } - static async executeTask() { - if (this.timerQueue.length === 0 && this.segmentTimerQueue.length === 0) { + if (this.timerQueue.length === 0) { this.destroy(); return; } @@ -272,6 +180,5 @@ export class TimerManager { static init() { this.getTimerQueue(); this.executeTask(); - this.getSegmentTimerQueue(); } } \ No newline at end of file diff --git a/src/tool/tool_roll_check.ts b/src/tool/tool_roll_check.ts index 2924343..b318953 100644 --- a/src/tool/tool_roll_check.ts +++ b/src/tool/tool_roll_check.ts @@ -142,7 +142,7 @@ export function registerSanCheck() { msg = createMsg(msg.messageType, uid, ctx.group.groupId); ctx = createCtx(ctx.endPoint.userId, msg); - const value = seal.vars.intGet(ctx, 'san')[0]; console.log(value) + const value = seal.vars.intGet(ctx, 'san')[0]; if (value === 0) { seal.vars.intSet(ctx, 'san', 60); } diff --git a/src/tool/tool_time.ts b/src/tool/tool_time.ts index 73e0f98..00d84f4 100644 --- a/src/tool/tool_time.ts +++ b/src/tool/tool_time.ts @@ -1,5 +1,4 @@ import { TimerManager } from "../timer"; -import { ConfigManager } from "../config/config"; import { Tool, ToolInfo, ToolManager } from "./tool"; export function registerGetTime() { @@ -65,7 +64,7 @@ export function registerSetTimer() { return '时间应为数字'; } - TimerManager.addTimer(ctx, msg, ai, t, content); + TimerManager.addTimer(ctx, msg, ai, Math.floor(Date.now() / 1000) + t * 60, content, 'timer'); return `设置定时器成功,请等待`; } @@ -90,7 +89,7 @@ export function registerShowTimerList() { const tool = new Tool(info); tool.solve = async (_, __, ai, ___) => { - const timers = TimerManager.timerQueue.filter(t => t.id === ai.id); + const timers = TimerManager.timerQueue.filter(t => t.id === ai.id && t.type === 'timer'); if (timers.length === 0) { return '当前对话没有定时器'; @@ -132,7 +131,7 @@ export function registerCancelTimer() { const tool = new Tool(info); tool.solve = async (_, __, ai, args) => { const { index_list } = args; - const timers = TimerManager.timerQueue.filter(t => t.id === ai.id); + const timers = TimerManager.timerQueue.filter(t => t.id === ai.id && t.type === 'timer'); if (timers.length === 0) { return '当前对话没有定时器'; @@ -142,20 +141,7 @@ export function registerCancelTimer() { return '请输入要取消的定时器序号'; } - for (const index of index_list) { - if (index < 1 || index > timers.length) { - return `序号${index}超出范围`; - } - - const i = TimerManager.timerQueue.indexOf(timers[index - 1]); - if (i === -1) { - return `出错了:找不到序号${index}的定时器`; - } - - TimerManager.timerQueue.splice(i, 1); - } - - ConfigManager.ext.storageSet(`TimerMatimerQueue`, JSON.stringify(TimerManager.timerQueue)); + TimerManager.removeTimer(ai.id, '', 'timer', index_list); return '定时器取消成功'; } diff --git a/src/update.ts b/src/update.ts index 1cd6790..60b5294 100644 --- a/src/update.ts +++ b/src/update.ts @@ -2,7 +2,8 @@ export const updateInfo = { "4.10.2":`- 新增请求超时相关 - 修复addMemory时,keywords可以为null的问题 -- 新增表情包制作工具`, +- 新增表情包制作工具 +- 新增活跃时间`, "4.10.1": `- 可能修复了非指令无法响应的问题 - 修复了构建ctx时,isPrivate始终为0的问题 - 新增保存图片功能 diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 677ae0a..d3e9b57 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -4,17 +4,7 @@ import { Message } from "../AI/context"; import { logger } from "../logger"; import { ConfigManager } from "../config/config"; import { ToolInfo } from "../tool/tool"; - -function formatTimestamp(timestamp: number): string { - const date = new Date(timestamp * 1000); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; -} +import { fmtTime } from "./utils_string"; export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { const { roleSettingTemplate, systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; @@ -99,6 +89,7 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { name: '', contentArray: [content], msgIdArray: [''], + timeArray: [Math.floor(Date.now() / 1000)], images: [] }; @@ -119,6 +110,7 @@ function buildSamplesMessages(ctx: seal.MsgContext): Message[] { name: "用户", contentArray: [item], msgIdArray: [''], + timeArray: [Math.floor(Date.now() / 1000)], images: [] }; } else { @@ -128,6 +120,7 @@ function buildSamplesMessages(ctx: seal.MsgContext): Message[] { name: seal.formatTmpl(ctx, "核心:骰子名字"), contentArray: [item], msgIdArray: [''], + timeArray: [Math.floor(Date.now() / 1000)], images: [] }; } @@ -220,17 +213,11 @@ export function handleMessages(ctx: seal.MsgContext, ai: AI) { `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` ) : ''; - const content = message.msgIdArray.map((msgId, index) => { - let result = ''; - if (showMsgId && msgId) { - result += `<|msg_id:${msgId}|>`; - } - if (showTime && message.time) { - result += `<|time:${formatTimestamp(message.time)}|>`; - } - result += message.contentArray[index]; - return result; - }).join('\f'); + const content = message.msgIdArray.map((msgId, index) => + (showMsgId && msgId) ? `<|msg_id:${msgId}|>` : '' + + (showTime ? `<|time:${fmtTime(message.timeArray[index])}|>` : '') + + message.contentArray[index] + ).join('\f'); if (isMerge && message.role === last_role && message.role !== 'tool') { processedMessages[processedMessages.length - 1].content += '\f' + prefix + content; diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 1c109ed..91ce205 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -455,4 +455,15 @@ function advancedSplit(s: string, r: RegExp) { } return parts; +} + +export function fmtTime(timestamp: number) { + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hour = String(date.getHours()).padStart(2, '0'); + const minute = String(date.getMinutes()).padStart(2, '0'); + const second = String(date.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } \ No newline at end of file From cd8fd0fbefc421fd21d5e478dd40e086404f6a8a Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 8 Oct 2025 06:57:08 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=89=B9=E5=AE=9A?= =?UTF-8?q?=E6=83=85=E5=86=B5=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE=E6=9E=84?= =?UTF-8?q?=E5=BB=BAcontent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AI/AI.ts | 4 +-- src/AI/context.ts | 57 ++++++++++++++++++++-------------- src/AI/memory.ts | 17 ++++------ src/config/config_memory.ts | 3 ++ src/index.ts | 2 +- src/tool/tool_context.ts | 12 ++----- src/utils/utils_message.ts | 62 +++++++++++++++++++++---------------- src/utils/utils_string.ts | 2 +- 8 files changed, 84 insertions(+), 75 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index f751e3e..0a6861b 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -46,7 +46,7 @@ export class AI { constructor(id: string) { this.id = id; - this.version = '0.0.1'; + this.version = '0.0.0'; this.context = new Context(); this.tool = new ToolManager(); this.memory = new Memory(); @@ -389,7 +389,7 @@ export class AI { } export class AIManager { - static version = "1.0.0"; + static version = "1.0.1"; static cache: { [key: string]: AI } = {}; static usageMap: { [key: string]: { // 模型名 diff --git a/src/AI/context.ts b/src/AI/context.ts index e83baac..993583d 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -7,6 +7,12 @@ import { AI, AIManager } from "./AI"; import { logger } from "../logger"; import { transformMsgId } from "../utils/utils"; +export interface MessageInfo { + msgId: string; + time: number; // 秒 + content: string; +} + export interface Message { role: string; tool_calls?: ToolCall[]; @@ -14,10 +20,8 @@ export interface Message { uid: string; name: string; - contentArray: string[]; - msgIdArray: string[]; - timeArray: number[]; images: Image[]; + msgArray: MessageInfo[]; } export class Context { @@ -115,19 +119,23 @@ export class Context { const uid = role == 'user' ? ctx.player.userId : ctx.endPoint.userId; const length = messages.length; if (length !== 0 && messages[length - 1].uid === uid && !//.test(s)) { - messages[length - 1].contentArray.push(s); - messages[length - 1].msgIdArray.push(msgId); - messages[length - 1].timeArray.push(Math.floor(Date.now() / 1000)); messages[length - 1].images.push(...images); + messages[length - 1].msgArray.push({ + msgId: msgId, + time: Math.floor(Date.now() / 1000), + content: s + }); } else { const message: Message = { role: role, uid: uid, name: name, - contentArray: [s], - msgIdArray: [msgId], - timeArray: [Math.floor(Date.now() / 1000)], - images: images + images: images, + msgArray: [{ + msgId: msgId, + time: Math.floor(Date.now() / 1000), + content: s + }] }; messages.push(message); @@ -156,10 +164,8 @@ export class Context { tool_calls: tool_calls, uid: '', name: '', - contentArray: [], - msgIdArray: [], - timeArray: [], - images: [] + images: [], + msgArray: [] }; this.messages.push(message); } @@ -170,10 +176,12 @@ export class Context { tool_call_id: tool_call_id, uid: '', name: '', - contentArray: [s], - msgIdArray: [''], - timeArray: [Math.floor(Date.now() / 1000)], - images: [] + images: [], + msgArray: [{ + msgId: '', + time: Math.floor(Date.now() / 1000), + content: s + }] }; for (let i = this.messages.length - 1; i >= 0; i--) { @@ -187,15 +195,16 @@ export class Context { } async addSystemUserMessage(name: string, s: string, images: Image[]) { - const message = { + const message: Message = { role: 'user', - content: s, uid: '', name: `_${name}`, - contentArray: [s], - msgIdArray: [''], - timeArray: [Math.floor(Date.now() / 1000)], - images: images + images: images, + msgArray: [{ + msgId: '', + time: Math.floor(Date.now() / 1000), + content: s + }] }; this.messages.push(message); } diff --git a/src/AI/memory.ts b/src/AI/memory.ts index 2013ab4..caacea9 100644 --- a/src/AI/memory.ts +++ b/src/AI/memory.ts @@ -5,7 +5,7 @@ import { Context } from "./context"; import { generateId } from "../utils/utils"; import { logger } from "../logger"; import { fetchData } from "../service"; -import { parseBody } from "../utils/utils_message"; +import { buildContent, parseBody } from "../utils/utils_message"; import { ToolManager } from "../tool/tool"; export interface MemoryInfo { @@ -152,7 +152,7 @@ export class Memory { } const { url: chatUrl, apiKey: chatApiKey } = ConfigManager.request; - const { roleSettingTemplate, isPrefix, showNumber, showMsgId } = ConfigManager.message; + const { roleSettingTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = ConfigManager.memory; const messages = ai.context.messages; @@ -195,18 +195,13 @@ export class Memory { "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), "添加前缀": isPrefix, "展示消息ID": showMsgId, + "展示时间": showTime, "对话内容": isPrefix ? sumMessages.map(message => { if (message.role === 'assistant' && message?.tool_calls && message?.tool_calls.length > 0) { return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; } - const prefix = (isPrefix && message.name) ? ( - message.name.startsWith('_') ? - `<|${message.name}|>` : - `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` - ) : ''; - const content = message.msgIdArray.map((msgId, index) => (showMsgId && msgId ? `<|msg_id:${msgId}|>` : '') + message.contentArray[index]).join('\f'); - - return `[${message.role}]: ${prefix}${content}`; + + return `[${message.role}]: ${buildContent(message)}`; }).join('\n') : JSON.stringify(sumMessages) }) @@ -361,7 +356,7 @@ export class Memory { buildMemoryPrompt(ctx: seal.MsgContext, context: Context): string { const userMessages = context.messages.filter(msg => msg.role === 'user' && !msg.name.startsWith('_')); - const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1].contentArray.join('') : ''; + const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1].msgArray.map(mi => mi.content).join('') : ''; const ai = AIManager.getAI(ctx.endPoint.userId); let s = ai.memory.buildMemory(true, seal.formatTmpl(ctx, "核心:骰子名字"), ctx.endPoint.userId, '', '', lastMsg); diff --git a/src/config/config_memory.ts b/src/config/config_memory.ts index 944a359..88b60df 100644 --- a/src/config/config_memory.ts +++ b/src/config/config_memory.ts @@ -60,6 +60,9 @@ export class MemoryConfig { {{#if 展示消息ID}} - <|msg_id:xxx|>表示消息ID,仅用于调用函数时使用,不要在生成的回复中提及或使用 - <|quote:xxx|>表示引用消息,xxx为对应的消息ID +{{/if}} +{{#if 展示时间}} + - <|time:xxxx-xx-xx xx:xx:xx|>表示消息发送时间,不要在生成的回复中提及或使用 {{/if}} - \\f用于分割多条消息 diff --git a/src/index.ts b/src/index.ts index 1603172..a706f1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -129,7 +129,7 @@ function main() { const systemMessage = buildSystemMessage(ctx, ai); - seal.replyToSender(ctx, msg, systemMessage.contentArray[0]); + seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); return ret; } case 'status': { diff --git a/src/tool/tool_context.ts b/src/tool/tool_context.ts index 7bce3cc..f88bba8 100644 --- a/src/tool/tool_context.ts +++ b/src/tool/tool_context.ts @@ -1,5 +1,6 @@ import { AIManager } from "../AI/AI"; import { ConfigManager } from "../config/config"; +import { buildContent } from "../utils/utils_message"; import { createCtx, createMsg } from "../utils/utils_seal"; import { Tool, ToolInfo, ToolManager } from "./tool"; @@ -66,8 +67,6 @@ export function registerGetContext() { return `未知的上下文类型<${ctx_type}>`; } - const { isPrefix, showNumber, showMsgId } = ConfigManager.message; - const messages = ai.context.messages; const images = []; const s = messages.map(message => { @@ -77,14 +76,7 @@ export function registerGetContext() { return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; } - const prefix = (isPrefix && message.name) ? ( - message.name.startsWith('_') ? - `<|${message.name}|>` : - `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` - ) : ''; - const content = message.msgIdArray.map((msgId, index) => (showMsgId && msgId ? `<|msg_id:${msgId}|>` : '') + message.contentArray[index]).join('\f'); - - return `[${message.role}]: ${prefix}${content}`; + return `[${message.role}]: ${buildContent(message)}`; }).join('\n'); // 将images添加到最后一条消息,以便使用 diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index d3e9b57..7e13ccd 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -87,10 +87,12 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { role: "system", uid: '', name: '', - contentArray: [content], - msgIdArray: [''], - timeArray: [Math.floor(Date.now() / 1000)], - images: [] + images: [], + msgArray: [{ + msgId: '', + time: Math.floor(Date.now() / 1000), + content: content + }] }; return systemMessage; @@ -108,20 +110,24 @@ function buildSamplesMessages(ctx: seal.MsgContext): Message[] { role: "user", uid: '', name: "用户", - contentArray: [item], - msgIdArray: [''], - timeArray: [Math.floor(Date.now() / 1000)], - images: [] + images: [], + msgArray: [{ + msgId: '', + time: Math.floor(Date.now() / 1000), + content: item + }] }; } else { return { role: "assistant", uid: ctx.endPoint.userId, name: seal.formatTmpl(ctx, "核心:骰子名字"), - contentArray: [item], - msgIdArray: [''], - timeArray: [Math.floor(Date.now() / 1000)], - images: [] + images: [], + msgArray: [{ + msgId: '', + time: Math.floor(Date.now() / 1000), + content: item + }] }; } }) @@ -162,7 +168,7 @@ function buildContextMessages(systemMessage: Message, messages: Message[]): Mess } export function handleMessages(ctx: seal.MsgContext, ai: AI) { - const { isPrefix, showNumber, showMsgId, isMerge, showTime } = ConfigManager.message; + const { isMerge } = ConfigManager.message; const systemMessage = buildSystemMessage(ctx, ai); const samplesMessages = buildSamplesMessages(ctx); @@ -207,24 +213,13 @@ export function handleMessages(ctx: seal.MsgContext, ai: AI) { let last_role = ''; for (let i = 0; i < messages.length; i++) { const message = messages[i]; - const prefix = (isPrefix && message.name) ? ( - message.name.startsWith('_') ? - `<|${message.name}|>` : - `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` - ) : ''; - - const content = message.msgIdArray.map((msgId, index) => - (showMsgId && msgId) ? `<|msg_id:${msgId}|>` : '' + - (showTime ? `<|time:${fmtTime(message.timeArray[index])}|>` : '') + - message.contentArray[index] - ).join('\f'); if (isMerge && message.role === last_role && message.role !== 'tool') { - processedMessages[processedMessages.length - 1].content += '\f' + prefix + content; + processedMessages[processedMessages.length - 1].content += '\f' + buildContent(message); } else { processedMessages.push({ role: message.role, - content: prefix + content, + content: buildContent(message), tool_calls: message?.tool_calls, tool_call_id: message?.tool_call_id }); @@ -276,4 +271,19 @@ export function parseBody(template: string[], messages: any[], tools: ToolInfo[] } return bodyObject; +} + +export function buildContent(message: Message): string { + const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; + const prefix = (isPrefix && message.name) ? ( + message.name.startsWith('_') ? + `<|${message.name}|>` : + `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` + ) : ''; + const content = message.msgArray.map(mi => + ((showMsgId && mi.msgId) ? `<|msg_id:${mi.msgId}|>` : '') + + (showTime ? `<|time:${fmtTime(mi.time)}|>` : '') + + mi.content + ).join('\f'); + return prefix + content; } \ No newline at end of file diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 91ce205..ddea46e 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -147,7 +147,7 @@ export function checkRepeat(context: Context, s: string) { const message = messages[i]; // 寻找最后一条文本消息 if (message.role === 'assistant' && !message?.tool_calls) { - const content = message.contentArray[message.contentArray.length - 1] || ''; + const content = message.msgArray[message.msgArray.length - 1].content || ''; const similarity = calculateSimilarity(content.trim(), s.trim()); logger.info(`复读相似度:${similarity}`); From 2ae26e8c685ab06231039252b5425e9c6e314d40 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 8 Oct 2025 07:36:36 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AI/AI.ts | 21 ++++----------------- src/AI/memory.ts | 3 ++- src/index.ts | 27 +++++++++++++++++++++++++-- src/timer.ts | 7 ++++--- src/tool/tool_ban.ts | 3 ++- src/tool/tool_time.ts | 5 +++-- 6 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 0a6861b..7978e28 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -324,10 +324,10 @@ export class AI { const now = new Date(); const cur = now.getHours() * 60 + now.getMinutes(); const { start, end, segs } = this.privilege.activeTimeInfo; - const endReal = end > start ? end : end + 24 * 60; - const curReal = cur > start ? cur : cur + 24 * 60; + const endReal = end >= start ? end : end + 24 * 60; + const curReal = cur >= start ? cur : cur + 24 * 60; - if (curReal > endReal) return -1; + if (curReal >= endReal) return -1; const segLen = (endReal - start) / segs; const index = Math.floor((curReal - start) / segLen); @@ -340,7 +340,7 @@ export class AI { if (start === 0 && end === 0) return -1; - const endReal = end > start ? end : end + 24 * 60; + const endReal = end >= start ? end : end + 24 * 60; const segLen = (endReal - start) / segs; const nextSegIndex = (curSegIndex + 1) % segs; const todayMin = Math.floor(start + nextSegIndex * segLen + Math.random() * segLen) % (24 * 60); @@ -356,19 +356,6 @@ export class AI { return Math.floor(nextTime.getTime() / 1000); } - //no need - getActiveTimePoints(): number[] { - const { start, end, segs: segments } = this.privilege.activeTimeInfo; - const endReal = end > start ? end : end + 24 * 60; - const segLen = (endReal - start) / segments; - - const timePoints: number[] = Array.from({ length: segments }, (_, i) => - Math.floor(start + i * segLen + Math.random() * segLen) % (24 * 60) - ); - - return timePoints; - } - async stopCurrentChatStream(): Promise { const { id, reply, toolCallStatus } = this.stream; this.stream = { diff --git a/src/AI/memory.ts b/src/AI/memory.ts index caacea9..fe7cb9b 100644 --- a/src/AI/memory.ts +++ b/src/AI/memory.ts @@ -7,6 +7,7 @@ import { logger } from "../logger"; import { fetchData } from "../service"; import { buildContent, parseBody } from "../utils/utils_message"; import { ToolManager } from "../tool/tool"; +import { fmtTime } from "../utils/utils_string"; export interface MemoryInfo { id: string; @@ -75,7 +76,7 @@ export class Memory { groupId: ctx.group.groupId, groupName: ctx.group.groupName }, - time: new Date().toLocaleString(), + time: fmtTime(Math.floor(Date.now() / 1000)), createTime: Math.floor(Date.now() / 1000), lastMentionTime: Math.floor(Date.now() / 1000), keywords: kws || [], diff --git a/src/index.ts b/src/index.ts index a706f1e..cce9e12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { ConfigManager, CQTYPESALLOW } from "./config/config"; import { buildSystemMessage } from "./utils/utils_message"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; -import { transformTextToArray } from "./utils/utils_string"; +import { fmtTime, transformTextToArray } from "./utils/utils_string"; import { checkUpdate } from "./utils/utils_update"; import { get_chart_url } from "./service"; import { TimerManager } from "./timer"; @@ -27,6 +27,7 @@ function main() { 【.ai prompt】检查当前prompt(仅骰主可用) 【.ai status】查看当前AI状态 【.ai ctxn】查看上下文里的名字 +【.ai timer】查看当前聊天定时器 【.ai on】开启AI 【.ai sb】开启待机模式,此时AI将记忆聊天内容 【.ai off】关闭AI,此时仍能用关键词触发 @@ -164,6 +165,28 @@ function main() { seal.replyToSender(ctx, msg, s); return ret; } + case 'timer': { + const pr = ai.privilege; + if (ctx.privilegeLevel < pr.limit) { + seal.replyToSender(ctx, msg, seal.formatTmpl(ctx, "核心:提示_无权限")); + return ret; + } + + const timers = TimerManager.timerQueue.filter(t => t.id === ai.id); + + if (timers.length === 0) { + seal.replyToSender(ctx, msg, '当前对话没有定时器'); + return ret; + } + + const s = timers.map((t, i) => { + return `${i + 1}. 触发内容:${t.content} +${t.setTime} => ${fmtTime(t.timestamp)} +类型:${t.type}`; + }).join('\n'); + seal.replyToSender(ctx, msg, s); + return ret; + } case 'on': { const pr = ai.privilege; if (ctx.privilegeLevel < pr.limit) { @@ -242,7 +265,7 @@ function main() { return ret; } - const endReal = end > start ? end : end + 24 * 60; + const endReal = end >= start ? end : end + 24 * 60; if (segs > endReal - start) { seal.replyToSender(ctx, msg, '活跃次数不能大于活跃时间段分钟数'); return ret; diff --git a/src/timer.ts b/src/timer.ts index 0951f77..9a38c8e 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -2,6 +2,7 @@ import { ConfigManager } from "./config/config"; import { createCtx, createMsg } from "./utils/utils_seal"; import { AI, AIManager } from "./AI/AI"; import { logger } from "./logger"; +import { fmtTime } from "./utils/utils_string"; export interface TimerInfo { id: string, @@ -43,7 +44,7 @@ export class TimerManager { gid: ctx.group.groupId, epId: ctx.endPoint.userId, timestamp: timestamp, - setTime: new Date().toLocaleString(), + setTime: fmtTime(Math.floor(Date.now() / 1000)), content: content, type: reason }) @@ -116,7 +117,7 @@ export class TimerManager { case 'timer': { const s = `你设置的定时器触发了,请按照以下内容发送回复: 定时器设定时间:${setTime} -当前触发时间:${new Date().toLocaleString()} +当前触发时间:${fmtTime(Math.floor(Date.now() / 1000))} 提示内容:${content}`; await ai.context.addSystemUserMessage("定时器触发提示", s, []); @@ -135,7 +136,7 @@ export class TimerManager { continue; } - const s = `现在是你的活跃时间:${new Date().toLocaleString()},请说点什么`; + const s = `现在是你的活跃时间:${fmtTime(Math.floor(Date.now() / 1000))},请说点什么`; await ai.context.addSystemUserMessage("活跃时间触发提示", s, []); await ai.chat(ctx, msg, '活跃时间'); diff --git a/src/tool/tool_ban.ts b/src/tool/tool_ban.ts index 0ae8d7f..0393a31 100644 --- a/src/tool/tool_ban.ts +++ b/src/tool/tool_ban.ts @@ -1,6 +1,7 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/config"; import { Tool, ToolInfo, ToolManager } from "./tool"; +import { fmtTime } from "../utils/utils_string"; export function registerBan() { const info: ToolInfo = { @@ -160,7 +161,7 @@ export function registerGetBanList() { const data = await globalThis.http.getData(epId, `get_group_shut_list?group_id=${gid.replace(/^.+:/, '')}`); const s = `被禁言成员数量: ${data.length}\n` + data.slice(0, 50).map((item: any, index: number) => { - return `${index + 1}. ${item.nick}(${item.uin}) ${item.cardName && item.cardName !== item.nick ? `群名片: ${item.cardName}` : ''} 禁言结束时间: ${new Date(item.shutUpTime * 1000).toLocaleString()}`; + return `${index + 1}. ${item.nick}(${item.uin}) ${item.cardName && item.cardName !== item.nick ? `群名片: ${item.cardName}` : ''} 禁言结束时间: ${fmtTime(item.shutUpTime)}`; }).join('\n'); return s; diff --git a/src/tool/tool_time.ts b/src/tool/tool_time.ts index 00d84f4..f3c2e34 100644 --- a/src/tool/tool_time.ts +++ b/src/tool/tool_time.ts @@ -1,4 +1,5 @@ import { TimerManager } from "../timer"; +import { fmtTime } from "../utils/utils_string"; import { Tool, ToolInfo, ToolManager } from "./tool"; export function registerGetTime() { @@ -18,7 +19,7 @@ export function registerGetTime() { const tool = new Tool(info); tool.solve = async (_, __, ___, ____) => { - return new Date().toLocaleString(); + return fmtTime(Math.floor(Date.now() / 1000)); } ToolManager.toolMap[info.function.name] = tool; @@ -97,7 +98,7 @@ export function registerShowTimerList() { const s = timers.map((t, i) => { return `${i + 1}. 触发内容:${t.content} -${t.setTime} => ${new Date(t.timestamp * 1000).toLocaleString()}`; +${t.setTime} => ${fmtTime(t.timestamp)}`; }).join('\n'); return s; From 891d271326067863dcf4dcb8eee82f6954779d89 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 8 Oct 2025 07:48:18 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=E6=8C=87=E4=BB=A4=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index cce9e12..f6e3b44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -253,13 +253,14 @@ ${t.setTime} => ${fmtTime(t.timestamp)} const arr = valStr.split('-').map((item, index) => { const parts = item.split(/[::,,]+/).map(Number).map(i => isNaN(i) ? 0 : i); if (index < 2) { - return (parts[0] * 60 + parts[1] || 0) % (24 * 60); + return (parts[0] * 60 + (parts[1] || 0)) % (24 * 60); } else { return parts[0]; } }) const [start = 0, end = 0, segs = 1] = arr; + if (start === end) { seal.replyToSender(ctx, msg, '活跃时间段开始时间和结束时间不能相同'); return ret;