halbot 1995.1.26 → 1995.1.28
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/hal.mjs +14 -130
- package/package.json +4 -4
- package/pipeline/010_broca.mjs +39 -26
- package/pipeline/020_cmd.mjs +26 -10
- package/pipeline/030_echo.mjs +1 -3
- package/pipeline/060_config.mjs +15 -14
- package/pipeline/080_history.mjs +99 -4
- package/pipeline/090_ai.mjs +3 -3
- package/pipeline/100_chat.mjs +18 -4
package/lib/hal.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { alan, bot, callosum, dbio, storage, utilitas
|
|
1
|
+
import { alan, bot, callosum, dbio, storage, utilitas } from 'utilitas';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { parseArgs as _parseArgs } from 'node:util';
|
|
4
4
|
import { readdirSync } from 'fs';
|
|
@@ -8,11 +8,11 @@ import { ignoreErrFunc } from 'utilitas/lib/utilitas.mjs';
|
|
|
8
8
|
const [ // https://limits.tginfo.me/en
|
|
9
9
|
HELLO, GROUP, PRIVATE, CHANNEL, MENTION, jsonOptions, COMMAND_LIMIT, ON,
|
|
10
10
|
OFF, BOT_COMMAND, CHECK, logOptions, COMMAND_LENGTH,
|
|
11
|
-
COMMAND_DESCRIPTION_LENGTH,
|
|
11
|
+
COMMAND_DESCRIPTION_LENGTH,
|
|
12
12
|
] = [
|
|
13
13
|
'Hello!', 'group', 'private', 'channel', 'mention',
|
|
14
14
|
{ code: true, extraCodeBlock: 1 }, 100, 'on', 'off', 'bot_command',
|
|
15
|
-
'☑️', { log: true }, 32, 256,
|
|
15
|
+
'☑️', { log: true }, 32, 256,
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
const table = 'utilitas_hal_events';
|
|
@@ -21,6 +21,10 @@ const [end] = [bot.end];
|
|
|
21
21
|
const uList = arr => bot.lines(arr.map(x => `- ${x}`));
|
|
22
22
|
const oList = arr => bot.lines(arr.map((v, k) => `${k + 1}. ${v}`));
|
|
23
23
|
const [BINARY_STRINGS] = [[OFF, ON]];
|
|
24
|
+
const assembleKey = (c, t, k) => `HAL/SESSIONS/${c}/${t}${k ? `/${k}` : ''}`;
|
|
25
|
+
const assembleSettingsKey = (c, k) => assembleKey(c, 'SETTINGS', k);
|
|
26
|
+
const assembleCommandsKey = (c, k) => assembleKey(c, 'COMMANDS', k);
|
|
27
|
+
const assembleCallbacksKey = (c, k) => assembleKey(c, 'CALLBACKS', k);
|
|
24
28
|
|
|
25
29
|
const initSql = {
|
|
26
30
|
[dbio.MYSQL]: [[
|
|
@@ -143,124 +147,6 @@ const parseArgs = async (args, ctx) => {
|
|
|
143
147
|
return result;
|
|
144
148
|
};
|
|
145
149
|
|
|
146
|
-
const packMessage = (messages) => messages.map(x => ({
|
|
147
|
-
message_id: x.message_id, score: x.score, created_at: x.created_at,
|
|
148
|
-
request: x.received_text, response: x.response_text,
|
|
149
|
-
}));
|
|
150
|
-
|
|
151
|
-
const recall = async (sessionId, keyword, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
152
|
-
assert(sessionId, 'Session ID is required.');
|
|
153
|
-
let [result, _limit, exclude] = [
|
|
154
|
-
[], _.rerank ? SUB_LIMIT : limit,
|
|
155
|
-
(options?.exclude || []).map(x => `${~~x}`),
|
|
156
|
-
];
|
|
157
|
-
if (!keyword) { return result; }
|
|
158
|
-
switch (_.storage?.provider) {
|
|
159
|
-
case dbio.MYSQL:
|
|
160
|
-
result = await _.storage?.client?.query?.(
|
|
161
|
-
'SELECT *, MATCH(`distilled`) '
|
|
162
|
-
+ 'AGAINST(? IN NATURAL LANGUAGE MODE) AS `relevance` '
|
|
163
|
-
+ "FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? "
|
|
164
|
-
+ "AND `received_text` != '' "
|
|
165
|
-
+ "AND `received_text` NOT LIKE '/%' "
|
|
166
|
-
+ "AND `response_text` != '' HAVING relevance > 0 "
|
|
167
|
-
+ (exclude.length ? `AND \`message_id\` NOT IN (${exclude.join(',')}) ` : '')
|
|
168
|
-
+ 'ORDER BY `relevance` DESC '
|
|
169
|
-
+ `LIMIT ${_limit} OFFSET ?`,
|
|
170
|
-
[keyword, table, _.bot.botInfo.id, sessionId, offset]
|
|
171
|
-
);
|
|
172
|
-
break;
|
|
173
|
-
case dbio.POSTGRESQL:
|
|
174
|
-
// globalThis.debug = 2;
|
|
175
|
-
const vector = await dbio.encodeVector(await _.embed(keyword));
|
|
176
|
-
result = await _.storage?.client?.query?.(
|
|
177
|
-
`SELECT *, (1 - (distilled_vector <=> $1)) as relevance `
|
|
178
|
-
+ `FROM ${table} WHERE bot_id = $2 AND chat_id = $3 `
|
|
179
|
-
+ `AND received_text != '' `
|
|
180
|
-
+ `AND received_text NOT LIKE '/%' `
|
|
181
|
-
+ `AND response_text != '' `
|
|
182
|
-
+ (exclude.length ? `AND message_id NOT IN (${exclude.join(',')}) ` : '')
|
|
183
|
-
+ `ORDER BY (distilled_vector <=> $1) ASC `
|
|
184
|
-
+ `LIMIT ${_limit} OFFSET $4`,
|
|
185
|
-
[vector, _.bot.botInfo.id, sessionId, offset]
|
|
186
|
-
);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
return await rerank(keyword, result, offset, limit, options);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const getContext = async (sessionId, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
193
|
-
assert(sessionId, 'Session ID is required.');
|
|
194
|
-
let result = [];
|
|
195
|
-
switch (_.storage?.provider) {
|
|
196
|
-
case dbio.MYSQL:
|
|
197
|
-
result = await _.storage?.client?.query?.(
|
|
198
|
-
'SELECT * FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? '
|
|
199
|
-
+ "AND `received_text` != '' "
|
|
200
|
-
+ "AND `received_text` NOT LIKE '/%' "
|
|
201
|
-
+ "AND `response_text` != '' "
|
|
202
|
-
+ `ORDER BY \`created_at\` DESC LIMIT ${limit} OFFSET ?`,
|
|
203
|
-
[table, _.bot.botInfo.id, sessionId, offset]
|
|
204
|
-
);
|
|
205
|
-
break;
|
|
206
|
-
case dbio.POSTGRESQL:
|
|
207
|
-
result = await _.storage?.client?.query?.(
|
|
208
|
-
`SELECT * FROM ${table} WHERE bot_id = $1 AND chat_id = $2 `
|
|
209
|
-
+ `AND received_text != '' `
|
|
210
|
-
+ `AND received_text NOT LIKE '/%' `
|
|
211
|
-
+ `AND response_text != '' `
|
|
212
|
-
+ `ORDER BY created_at DESC LIMIT ${limit} OFFSET $3`,
|
|
213
|
-
[_.bot.botInfo.id, sessionId, offset]
|
|
214
|
-
);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
return packMessage(result);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const rerank = async (keyword, result, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
221
|
-
if (result.length && _.rerank) {
|
|
222
|
-
const keys = {};
|
|
223
|
-
const _result = [];
|
|
224
|
-
for (const x of result) {
|
|
225
|
-
if (!keys[x.distilled]) {
|
|
226
|
-
keys[x.distilled] = true;
|
|
227
|
-
_result.push(x);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const resp = await _.rerank(keyword, _result.map(x => x.distilled));
|
|
231
|
-
resp.map(x => result[x.index].score = x.score);
|
|
232
|
-
result.sort((a, b) => b.score - a.score);
|
|
233
|
-
result = result.filter(
|
|
234
|
-
x => x.score > RELEVANCE
|
|
235
|
-
).slice(offset, offset + limit);
|
|
236
|
-
}
|
|
237
|
-
return packMessage(result);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const extendSessionStorage = storage => storage && storage.client && {
|
|
241
|
-
get: async (id, options) => {
|
|
242
|
-
const CONTEXT_LIMIT_WITH_RAG = 5;
|
|
243
|
-
let resp = await storage.get(id);
|
|
244
|
-
if (resp) {
|
|
245
|
-
const ctxResp = await getContext(id, undefined, undefined);
|
|
246
|
-
const ragResp = await recall(
|
|
247
|
-
id, options?.prompt, undefined, undefined, {
|
|
248
|
-
exclude: ctxResp.map(x => x.message_id),
|
|
249
|
-
});
|
|
250
|
-
ctxResp.sort((a, b) => ~~a.message_id - ~~b.message_id);
|
|
251
|
-
ragResp.sort((a, b) => a.score - b.score);
|
|
252
|
-
resp.messages = [...ragResp, ...ragResp?.length ? ctxResp.slice(
|
|
253
|
-
ctxResp.length - CONTEXT_LIMIT_WITH_RAG
|
|
254
|
-
) : ctxResp];
|
|
255
|
-
} else {
|
|
256
|
-
resp = { messages: [] };
|
|
257
|
-
}
|
|
258
|
-
// print(resp);
|
|
259
|
-
return resp;
|
|
260
|
-
},
|
|
261
|
-
set: async (id, session, options) => await storage.set(id, session, options),
|
|
262
|
-
};
|
|
263
|
-
|
|
264
150
|
const subconscious = {
|
|
265
151
|
name: 'Subconscious', run: true, priority: 0,
|
|
266
152
|
func: async (_, next) => { ignoreErrFunc(next, logOptions) }, // non-blocking
|
|
@@ -268,9 +154,7 @@ const subconscious = {
|
|
|
268
154
|
|
|
269
155
|
const init = async (options) => {
|
|
270
156
|
if (options) {
|
|
271
|
-
const { ais } = await alan.initChat({
|
|
272
|
-
sessions: extendSessionStorage(options?.storage),
|
|
273
|
-
});
|
|
157
|
+
const { ais } = await alan.initChat({ sessions: null });
|
|
274
158
|
if (callosum.isPrimary) {
|
|
275
159
|
const cmds = options?.cmds || [];
|
|
276
160
|
// config multimodal engines
|
|
@@ -353,18 +237,18 @@ export {
|
|
|
353
237
|
OFF,
|
|
354
238
|
ON,
|
|
355
239
|
PRIVATE,
|
|
356
|
-
SEARCH_LIMIT,
|
|
357
|
-
parseArgs,
|
|
358
240
|
_,
|
|
241
|
+
assembleKey,
|
|
242
|
+
assembleCommandsKey,
|
|
243
|
+
assembleSettingsKey,
|
|
244
|
+
assembleCallbacksKey,
|
|
359
245
|
end,
|
|
360
|
-
getContext,
|
|
361
246
|
init,
|
|
362
247
|
json,
|
|
363
248
|
logOptions,
|
|
364
249
|
newCommand,
|
|
365
250
|
oList,
|
|
366
|
-
|
|
367
|
-
rerank,
|
|
251
|
+
parseArgs,
|
|
368
252
|
table,
|
|
369
|
-
uList
|
|
253
|
+
uList,
|
|
370
254
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halbot",
|
|
3
3
|
"description": "Just another AI powered Telegram bot, which is simple design, easy to use, extendable and fun.",
|
|
4
|
-
"version": "1995.1.
|
|
4
|
+
"version": "1995.1.28",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/Leask/halbot",
|
|
7
7
|
"type": "module",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"fluent-ffmpeg": "^2.1.3",
|
|
38
38
|
"google-gax": "^5.0.6",
|
|
39
39
|
"ioredis": "^5.8.2",
|
|
40
|
-
"jsdom": "^27.
|
|
40
|
+
"jsdom": "^27.4.0",
|
|
41
41
|
"lorem-ipsum": "^2.0.8",
|
|
42
42
|
"mime": "^4.1.0",
|
|
43
43
|
"mysql2": "^3.16.0",
|
|
44
|
-
"office-text-extractor": "^
|
|
44
|
+
"office-text-extractor": "^4.0.0",
|
|
45
45
|
"openai": "^6.15.0",
|
|
46
46
|
"pg": "^8.16.3",
|
|
47
47
|
"pgvector": "^0.2.1",
|
|
48
48
|
"telegraf": "^4.16.3",
|
|
49
49
|
"tellegram": "^1.1.3",
|
|
50
50
|
"tesseract.js": "^7.0.0",
|
|
51
|
-
"utilitas": "^2001.1.
|
|
51
|
+
"utilitas": "^2001.1.108",
|
|
52
52
|
"youtube-transcript": "^1.2.1"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/pipeline/010_broca.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bot, hal, uoid, utilitas } from '../index.mjs';
|
|
2
2
|
import { convert, paginate } from 'tellegram';
|
|
3
3
|
|
|
4
4
|
const _name = 'Broca';
|
|
5
5
|
const [PRIVATE_LIMIT, GROUP_LIMIT] = [60 / 60, 60 / 20].map(x => x * 1000);
|
|
6
6
|
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
7
7
|
const getKey = s => s?.toLowerCase?.()?.startsWith?.('http') ? 'url' : 'source';
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const parse_mode = bot.PARSE_MODE_MD_V2;
|
|
9
|
+
// const isMarkdownError = e => e?.description?.includes?.("can't parse entities");
|
|
10
10
|
|
|
11
11
|
const KNOWN_UPDATE_TYPES = [
|
|
12
12
|
'callback_query', 'channel_post', 'edited_message', 'message',
|
|
@@ -26,7 +26,7 @@ const getExtra = (ctx, options) => {
|
|
|
26
26
|
return { text: button.label, url: button.url };
|
|
27
27
|
} else if (button.text) {
|
|
28
28
|
const id = uoid.fakeUuid(button.text);
|
|
29
|
-
ctx._.
|
|
29
|
+
ctx._.callbacks_updated[id] = button;
|
|
30
30
|
return {
|
|
31
31
|
text: button.label,
|
|
32
32
|
callback_data: JSON.stringify({ callback: id }),
|
|
@@ -96,29 +96,47 @@ const edit = async (ctx, lastMsgId, text, extra) => {
|
|
|
96
96
|
return resp;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
const loadSettings = async chatId => await hal._.storage.get(
|
|
100
|
+
hal.assembleSettingsKey(chatId), { asPrefix: true }
|
|
101
|
+
) || {};
|
|
102
|
+
|
|
103
|
+
const saveSettings = async (chatId, settings = {}) => await Promise.all(
|
|
104
|
+
Object.entries(settings).map(([key, value]) =>
|
|
105
|
+
hal._.storage.set(hal.assembleSettingsKey(chatId, key), value)
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const saveCallbacks = async (chatId, callbacks = {}) => await Promise.all(
|
|
110
|
+
Object.entries(callbacks).map(([key, value]) =>
|
|
111
|
+
hal._.storage.set(hal.assembleCallbacksKey(chatId, key), value)
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const loadSession = async ctx => {
|
|
116
|
+
ctx._.settings = await loadSettings(ctx._.chatId);
|
|
117
|
+
ctx._.settings_updated = {};
|
|
118
|
+
ctx._.callbacks_updated = {};
|
|
119
|
+
// print('Loaded settings: ', ctx._.settings);
|
|
104
120
|
};
|
|
105
121
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
const saveSession = async ctx => {
|
|
123
|
+
// print('Saving settings: ', ctx._.settings_updated);
|
|
124
|
+
// print('Saving callbacks: ', ctx._.callbacks_updated);
|
|
125
|
+
await Promise.all([
|
|
126
|
+
saveSettings(ctx._.chatId, ctx._.settings_updated),
|
|
127
|
+
saveCallbacks(ctx._.chatId, ctx._.callbacks_updated),
|
|
128
|
+
]);
|
|
111
129
|
};
|
|
112
130
|
|
|
113
131
|
const ctxExt = ctx => {
|
|
114
|
-
ctx.timeout = async () => await utilitas.timeout(ctx._.limit);
|
|
132
|
+
ctx.timeout = async (t) => await utilitas.timeout(t ?? ctx._.limit);
|
|
115
133
|
ctx.collect = (content, type, options) => type ? ctx._.collected.push(
|
|
116
134
|
{ type, content }
|
|
117
135
|
) : (ctx._.text = [
|
|
118
136
|
(options?.refresh ? '' : ctx._.text) || '', content || ''
|
|
119
137
|
].filter(x => x.length).join('\n\n'));
|
|
120
138
|
ctx.hello = str => {
|
|
121
|
-
str = str || ctx._.
|
|
139
|
+
str = str || ctx._.settings?.hello || hal._.hello;
|
|
122
140
|
ctx.collect(str, null, { refresh: true });
|
|
123
141
|
return str;
|
|
124
142
|
};
|
|
@@ -160,7 +178,7 @@ const ctxExt = ctx => {
|
|
|
160
178
|
};
|
|
161
179
|
ctx.shouldReply = async (text, options) => {
|
|
162
180
|
const should = utilitas.insensitiveHas(hal._?.chatType, ctx._.chatType)
|
|
163
|
-
|| ctx._.
|
|
181
|
+
|| ctx._.settings?.chatty;
|
|
164
182
|
text = utilitas.isSet(text, true) ? (text || '') : '';
|
|
165
183
|
if (!should || !text) { return should; }
|
|
166
184
|
return await (options?.error
|
|
@@ -174,10 +192,6 @@ const ctxExt = ctx => {
|
|
|
174
192
|
ctx.image = async (s, o) => await replyWith(ctx, 'replyWithPhoto', s, o);
|
|
175
193
|
ctx.video = async (s, o) => await replyWith(ctx, 'replyWithVideo', s, o);
|
|
176
194
|
ctx.media = async (s, o) => await replyWith(ctx, 'replyWithMediaGroup', s, o);
|
|
177
|
-
ctx.sessionSet = async () => {
|
|
178
|
-
ctx._.saved || await sessionSet(ctx._.chatId, ctx._.session);
|
|
179
|
-
ctx._.saved = true;
|
|
180
|
-
};
|
|
181
195
|
};
|
|
182
196
|
|
|
183
197
|
const action = async (ctx, next) => {
|
|
@@ -220,8 +234,8 @@ const action = async (ctx, next) => {
|
|
|
220
234
|
ctx._.message.text && ctx.collect(ctx._.message.text);
|
|
221
235
|
ctx._.message.caption && ctx.collect(ctx._.message.caption);
|
|
222
236
|
// get session
|
|
223
|
-
|
|
224
|
-
ctx._.limit = ctx.chatType === hal.PRIVATE ? PRIVATE_LIMIT : GROUP_LIMIT;
|
|
237
|
+
await loadSession(ctx);
|
|
238
|
+
ctx._.limit = ctx._.chatType === hal.PRIVATE ? PRIVATE_LIMIT : GROUP_LIMIT;
|
|
225
239
|
ctx._.entities = [
|
|
226
240
|
...(ctx._.message.entities || []).map(e => ({ ...e, text: ctx._.message.text })),
|
|
227
241
|
...(ctx._.message.caption_entities || []).map(e => ({ ...e, text: ctx._.message.caption })),
|
|
@@ -255,7 +269,7 @@ const action = async (ctx, next) => {
|
|
|
255
269
|
|| (ctx._.message.new_chat_member || ctx._.message.left_chat_member))
|
|
256
270
|
&& await next();
|
|
257
271
|
// persistence
|
|
258
|
-
await ctx
|
|
272
|
+
await saveSession(ctx);
|
|
259
273
|
// fallback response and log
|
|
260
274
|
if (ctx._.done.length) { return; }
|
|
261
275
|
const errStr = ctx._.cmd?.cmd
|
|
@@ -265,6 +279,5 @@ const action = async (ctx, next) => {
|
|
|
265
279
|
};
|
|
266
280
|
|
|
267
281
|
export const { _NEED, name, run, priority, func } = {
|
|
268
|
-
_NEED: ['
|
|
269
|
-
run: true, priority: 10, func: action,
|
|
282
|
+
_NEED: ['tellegram'], name: _name, run: true, priority: 10, func: action,
|
|
270
283
|
};
|
package/pipeline/020_cmd.mjs
CHANGED
|
@@ -19,13 +19,28 @@ const ctxExt = ctx => {
|
|
|
19
19
|
ctx.getKeyboard = () => getKeyboard(ctx);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const loadCommands = async chatId => await hal._.storage.get(
|
|
23
|
+
hal.assembleCommandsKey(chatId), {
|
|
24
|
+
asPrefix: true, order: { 'updated_at': '-' }, limit: hal.COMMAND_LIMIT
|
|
25
|
+
}) || {};
|
|
26
|
+
|
|
27
|
+
const saveCommands = async (chatId, commands = {}) => await Promise.all(
|
|
28
|
+
Object.entries(commands).map(([key, value]) =>
|
|
29
|
+
hal._.storage.set(hal.assembleCommandsKey(chatId, key), value)
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const queryCallback = async (chatId, id) => await hal._.storage.get(
|
|
34
|
+
hal.assembleCallbacksKey(chatId, id)
|
|
35
|
+
);
|
|
36
|
+
|
|
22
37
|
const action = async (ctx, next) => {
|
|
23
38
|
// extend ctx
|
|
24
39
|
ctxExt(ctx);
|
|
25
40
|
// handle callback query
|
|
26
41
|
if (ctx._.type === 'callback_query') {
|
|
27
42
|
const data = utilitas.parseJson(ctx.update.callback_query.data);
|
|
28
|
-
const cb = ctx._.
|
|
43
|
+
const cb = await queryCallback(ctx._.chatId, data?.callback);
|
|
29
44
|
if (cb?.text) {
|
|
30
45
|
log(`Callback: ${cb.text}`); // Avoid ctx._.text interference:
|
|
31
46
|
ctx.collect(cb.text, null, { refresh: true });
|
|
@@ -57,9 +72,7 @@ const action = async (ctx, next) => {
|
|
|
57
72
|
// update last touched command
|
|
58
73
|
if (ctx._.cmd) {
|
|
59
74
|
log(`Command: ${JSON.stringify(ctx._.cmd)}`);
|
|
60
|
-
ctx._.
|
|
61
|
-
ctx._.session.cmds[ctx._.cmd.cmd]
|
|
62
|
-
= { args: ctx._.cmd.args, touchedAt: Date.now() };
|
|
75
|
+
await saveCommands(ctx._.chatId, { [ctx._.cmd.cmd]: ctx._.cmd.args });
|
|
63
76
|
}
|
|
64
77
|
// handle commands
|
|
65
78
|
switch (ctx._.cmd?.cmd) {
|
|
@@ -67,14 +80,17 @@ const action = async (ctx, next) => {
|
|
|
67
80
|
return await ctx.complete({ keyboards: [] });
|
|
68
81
|
}
|
|
69
82
|
// update commands
|
|
70
|
-
await utilitas.ignoreErrFunc(async () =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
await utilitas.ignoreErrFunc(async () => {
|
|
84
|
+
const cmds = Object.keys(await loadCommands(ctx._.chatId));
|
|
85
|
+
await hal._.bot.telegram.setMyCommands(hal._.cmds.sort((x, y) => {
|
|
86
|
+
const [_x, _y] = [x, y].map(
|
|
87
|
+
i => cmds.indexOf(i.command.toLowerCase())
|
|
88
|
+
).map(x => x === -1 ? cmds.length : x);
|
|
89
|
+
return _x - _y;
|
|
90
|
+
}).slice(0, hal.COMMAND_LIMIT), {
|
|
75
91
|
scope: { type: 'chat', chat_id: ctx._.chatId },
|
|
76
92
|
}), hal.logOptions
|
|
77
|
-
);
|
|
93
|
+
});
|
|
78
94
|
// next middleware
|
|
79
95
|
await next();
|
|
80
96
|
};
|
package/pipeline/030_echo.mjs
CHANGED
|
@@ -6,9 +6,7 @@ const action = async (ctx, next) => {
|
|
|
6
6
|
let resp;
|
|
7
7
|
switch (ctx._.cmd.cmd) {
|
|
8
8
|
case 'echo':
|
|
9
|
-
|
|
10
|
-
delete carry.session.messages;
|
|
11
|
-
resp = hal.json({ update: ctx.update, _: carry });
|
|
9
|
+
resp = hal.json({ update: ctx.update, _: ctx._ });
|
|
12
10
|
break;
|
|
13
11
|
case 'uptime':
|
|
14
12
|
resp = utilitas.uptime();
|
package/pipeline/060_config.mjs
CHANGED
|
@@ -4,6 +4,10 @@ const sendConfig = async (ctx, obj, options) => await ctx.ok(
|
|
|
4
4
|
utilitas.prettyJson(obj, { code: true, md: true }), options
|
|
5
5
|
);
|
|
6
6
|
|
|
7
|
+
const deleteSettings = async chatId => await hal._.storage.del(
|
|
8
|
+
hal.assembleSettingsKey(chatId), { asPrefix: true }
|
|
9
|
+
);
|
|
10
|
+
|
|
7
11
|
const ctxExt = ctx => {
|
|
8
12
|
ctx.sendConfig = async (obj, options) => await sendConfig(ctx, obj, options);
|
|
9
13
|
};
|
|
@@ -17,40 +21,37 @@ const action = async (ctx, next) => {
|
|
|
17
21
|
return await ctx.ok('Please specify a language.');
|
|
18
22
|
}
|
|
19
23
|
const _config = {
|
|
20
|
-
...ctx._.
|
|
21
|
-
...ctx._.
|
|
22
|
-
...ctx._.
|
|
24
|
+
...ctx._.settings = {
|
|
25
|
+
...ctx._.settings,
|
|
26
|
+
...ctx._.settings_updated = {
|
|
23
27
|
lang: ctx._.cmd.args,
|
|
24
28
|
hello: `Please reply in ${ctx._.cmd.args}. Hello!`,
|
|
25
29
|
},
|
|
26
30
|
}
|
|
27
31
|
};
|
|
28
|
-
Object.keys(ctx._.
|
|
32
|
+
Object.keys(ctx._.settings_updated).map(x => _config[x] += ` ${hal.CHECK}`);
|
|
29
33
|
ctx.hello();
|
|
30
|
-
await ctx.sessionSet(); // save config before break
|
|
31
34
|
break;
|
|
32
35
|
case 'toggle':
|
|
33
36
|
parsed = {};
|
|
34
37
|
Object.keys(await hal.parseArgs(ctx._.cmd.args)).map(x =>
|
|
35
|
-
parsed[x] = !ctx._.
|
|
38
|
+
parsed[x] = !ctx._.settings?.[x]);
|
|
36
39
|
case 'set':
|
|
37
40
|
try {
|
|
38
41
|
const _config = {
|
|
39
|
-
...ctx._.
|
|
40
|
-
...ctx._.
|
|
41
|
-
...ctx._.
|
|
42
|
+
...ctx._.settings = {
|
|
43
|
+
...ctx._.settings,
|
|
44
|
+
...ctx._.settings_updated = parsed || await hal.parseArgs(ctx._.cmd.args, ctx),
|
|
42
45
|
}
|
|
43
46
|
};
|
|
44
|
-
assert(utilitas.countKeys(ctx._.
|
|
45
|
-
Object.keys(ctx._.
|
|
46
|
-
await ctx.sessionSet(); // save config before return
|
|
47
|
+
assert(utilitas.countKeys(ctx._.settings_updated), 'No option matched.');
|
|
48
|
+
Object.keys(ctx._.settings_updated).map(x => _config[x] += ` ${hal.CHECK}`);
|
|
47
49
|
return await ctx.sendConfig(_config);
|
|
48
50
|
} catch (err) {
|
|
49
51
|
return await ctx.err(err.message || err);
|
|
50
52
|
}
|
|
51
53
|
case 'reset':
|
|
52
|
-
|
|
53
|
-
await ctx.sessionSet(); // save config before return
|
|
54
|
+
await deleteSettings(ctx._.chatId);
|
|
54
55
|
return await ctx.complete({ keyboards: ctx.getKeyboard() });
|
|
55
56
|
}
|
|
56
57
|
await next();
|
package/pipeline/080_history.mjs
CHANGED
|
@@ -1,8 +1,103 @@
|
|
|
1
1
|
import { bot, dbio, hal, utilitas } from '../index.mjs';
|
|
2
2
|
|
|
3
|
+
const [RELEVANCE, SEARCH_LIMIT, SUB_LIMIT] = [0.2, 10, 200]; // Google Rerank limit
|
|
3
4
|
const compact = (str, op) => utilitas.ensureString(str, { ...op || {}, compact: true });
|
|
4
5
|
const compactLimit = (str, op) => compact(str, { ...op || {}, limit: 140 });
|
|
5
6
|
|
|
7
|
+
const packMessage = (messages) => messages.map(x => ({
|
|
8
|
+
message_id: x.message_id, score: x.score, created_at: x.created_at,
|
|
9
|
+
request: x.received_text, response: x.response_text,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const recall = async (sessionId, keyword, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
13
|
+
assert(sessionId, 'Session ID is required.');
|
|
14
|
+
let [result, _limit, exclude] = [
|
|
15
|
+
[], hal._.rerank ? SUB_LIMIT : limit,
|
|
16
|
+
(options?.exclude || []).map(x => `${~~x}`),
|
|
17
|
+
];
|
|
18
|
+
if (!keyword) { return result; }
|
|
19
|
+
switch (hal._.storage?.provider) {
|
|
20
|
+
case dbio.MYSQL:
|
|
21
|
+
result = await hal._.storage?.client?.query?.(
|
|
22
|
+
'SELECT *, MATCH(`distilled`) '
|
|
23
|
+
+ 'AGAINST(? IN NATURAL LANGUAGE MODE) AS `relevance` '
|
|
24
|
+
+ "FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? "
|
|
25
|
+
+ "AND `received_text` != '' "
|
|
26
|
+
+ "AND `received_text` NOT LIKE '/%' "
|
|
27
|
+
+ "AND `response_text` != '' HAVING relevance > 0 "
|
|
28
|
+
+ (exclude.length ? `AND \`message_id\` NOT IN (${exclude.join(',')}) ` : '')
|
|
29
|
+
+ 'ORDER BY `relevance` DESC '
|
|
30
|
+
+ `LIMIT ${_limit} OFFSET ?`,
|
|
31
|
+
[keyword, hal.table, hal._.bot.botInfo.id, sessionId, offset]
|
|
32
|
+
);
|
|
33
|
+
break;
|
|
34
|
+
case dbio.POSTGRESQL:
|
|
35
|
+
// globalThis.debug = 2;
|
|
36
|
+
const vector = await dbio.encodeVector(await hal._.embed(keyword));
|
|
37
|
+
result = await hal._.storage?.client?.query?.(
|
|
38
|
+
`SELECT *, (1 - (distilled_vector <=> $1)) as relevance `
|
|
39
|
+
+ `FROM ${hal.table} WHERE bot_id = $2 AND chat_id = $3 `
|
|
40
|
+
+ `AND received_text != '' `
|
|
41
|
+
+ `AND received_text NOT LIKE '/%' `
|
|
42
|
+
+ `AND response_text != '' `
|
|
43
|
+
+ (exclude.length ? `AND message_id NOT IN (${exclude.join(',')}) ` : '')
|
|
44
|
+
+ `ORDER BY (distilled_vector <=> $1) ASC `
|
|
45
|
+
+ `LIMIT ${_limit} OFFSET $4`,
|
|
46
|
+
[vector, hal._.bot.botInfo.id, sessionId, offset]
|
|
47
|
+
);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return await rerank(keyword, result, offset, limit, options);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getContext = async (sessionId, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
54
|
+
assert(sessionId, 'Session ID is required.');
|
|
55
|
+
let result = [];
|
|
56
|
+
switch (hal._.storage?.provider) {
|
|
57
|
+
case dbio.MYSQL:
|
|
58
|
+
result = await hal._.storage?.client?.query?.(
|
|
59
|
+
'SELECT * FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? '
|
|
60
|
+
+ "AND `received_text` != '' "
|
|
61
|
+
+ "AND `received_text` NOT LIKE '/%' "
|
|
62
|
+
+ "AND `response_text` != '' "
|
|
63
|
+
+ `ORDER BY \`created_at\` DESC LIMIT ${limit} OFFSET ?`,
|
|
64
|
+
[hal.table, hal._.bot.botInfo.id, sessionId, offset]
|
|
65
|
+
);
|
|
66
|
+
break;
|
|
67
|
+
case dbio.POSTGRESQL:
|
|
68
|
+
result = await hal._.storage?.client?.query?.(
|
|
69
|
+
`SELECT * FROM ${hal.table} WHERE bot_id = $1 AND chat_id = $2 `
|
|
70
|
+
+ `AND received_text != '' `
|
|
71
|
+
+ `AND received_text NOT LIKE '/%' `
|
|
72
|
+
+ `AND response_text != '' `
|
|
73
|
+
+ `ORDER BY created_at DESC LIMIT ${limit} OFFSET $3`,
|
|
74
|
+
[hal._.bot.botInfo.id, sessionId, offset]
|
|
75
|
+
);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
return packMessage(result);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const rerank = async (keyword, result, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
82
|
+
if (result.length && hal._.rerank) {
|
|
83
|
+
const keys = {};
|
|
84
|
+
const _result = [];
|
|
85
|
+
for (const x of result) {
|
|
86
|
+
if (!keys[x.distilled]) {
|
|
87
|
+
keys[x.distilled] = true;
|
|
88
|
+
_result.push(x);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const resp = await hal._.rerank(keyword, _result.map(x => x.distilled));
|
|
92
|
+
resp.map(x => result[x.index].score = x.score);
|
|
93
|
+
result.sort((a, b) => b.score - a.score);
|
|
94
|
+
result = result.filter(
|
|
95
|
+
x => x.score > RELEVANCE
|
|
96
|
+
).slice(offset, offset + limit);
|
|
97
|
+
}
|
|
98
|
+
return packMessage(result);
|
|
99
|
+
};
|
|
100
|
+
|
|
6
101
|
const memorize = async (ctx) => {
|
|
7
102
|
// https://limits.tginfo.me/en
|
|
8
103
|
if (!ctx._.chatId || ctx._.cmd?.cmd) { return; }
|
|
@@ -39,10 +134,10 @@ const memorize = async (ctx) => {
|
|
|
39
134
|
|
|
40
135
|
const ctxExt = ctx => {
|
|
41
136
|
ctx.memorize = async () => await memorize(ctx);
|
|
42
|
-
ctx.recall = async (keyword, offset = 0, limit =
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
137
|
+
ctx.recall = async (keyword, offset = 0, limit = SEARCH_LIMIT, options = {}) =>
|
|
138
|
+
await recall(ctx._.chatId, keyword, offset, limit, options);
|
|
139
|
+
ctx.getContext = async (offset = 0, limit = hal.SEARCH_LIMIT, options = {}) =>
|
|
140
|
+
await getContext(ctx._.chatId, offset, limit, options);
|
|
46
141
|
};
|
|
47
142
|
|
|
48
143
|
const action = async (ctx, next) => {
|
package/pipeline/090_ai.mjs
CHANGED
|
@@ -12,8 +12,8 @@ const listAIs = async ctx => {
|
|
|
12
12
|
x => `${x[1]} \`${x[0]}\``
|
|
13
13
|
)) + `\n\n*AI${ais.length > 0 ? 's' : ''}:*\n`;
|
|
14
14
|
const buttons = ais.map((x, i) => ({
|
|
15
|
-
label: `${ctx._.
|
|
16
|
-
|| (!ctx._.
|
|
15
|
+
label: `${ctx._.settings?.ai === x.id
|
|
16
|
+
|| (!ctx._.settings?.ai && i === 0) ? `${hal.CHECK} `
|
|
17
17
|
: ''}${x.label}`,
|
|
18
18
|
text: `/set --ai=${x.id}`,
|
|
19
19
|
}));
|
|
@@ -30,7 +30,7 @@ const action = async (ctx, next) => {
|
|
|
30
30
|
ctx._.aiId = TOP;
|
|
31
31
|
break;
|
|
32
32
|
default:
|
|
33
|
-
ctx._.aiId = ctx._.
|
|
33
|
+
ctx._.aiId = ctx._.settings?.ai;
|
|
34
34
|
}
|
|
35
35
|
await next();
|
|
36
36
|
};
|
package/pipeline/100_chat.mjs
CHANGED
|
@@ -3,8 +3,22 @@ import { alan } from '../index.mjs';
|
|
|
3
3
|
const _name = 'Chat';
|
|
4
4
|
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
5
5
|
|
|
6
|
+
const assembleContext = async (ctx) => {
|
|
7
|
+
const CONTEXT_LIMIT_WITH_RAG = 5;
|
|
8
|
+
const ctxResp = await ctx.getContext();
|
|
9
|
+
const ragResp = await ctx.recall(ctx._.prompt, undefined, undefined, {
|
|
10
|
+
exclude: ctxResp.map(x => x.message_id),
|
|
11
|
+
});
|
|
12
|
+
ctxResp.sort((a, b) => ~~a.message_id - ~~b.message_id);
|
|
13
|
+
ragResp.sort((a, b) => a.score - b.score);
|
|
14
|
+
ctx._.messages = [...ragResp, ...ragResp?.length ? ctxResp.slice(
|
|
15
|
+
ctxResp.length - CONTEXT_LIMIT_WITH_RAG
|
|
16
|
+
) : ctxResp];
|
|
17
|
+
};
|
|
18
|
+
|
|
6
19
|
const action = async (ctx, next) => {
|
|
7
20
|
if (!ctx._.text && !ctx._.collected.length) { return await next(); }
|
|
21
|
+
await assembleContext(ctx);
|
|
8
22
|
let [resp, extra, lastLen, status] = [null, { buttons: [] }, 0, 0];
|
|
9
23
|
const ok = async options => {
|
|
10
24
|
const newLen = ~~resp?.text?.length;
|
|
@@ -24,7 +38,7 @@ const action = async (ctx, next) => {
|
|
|
24
38
|
while (!status) {
|
|
25
39
|
// console.log('Processing...');
|
|
26
40
|
await ok({ processing: true });
|
|
27
|
-
await ctx.timeout(
|
|
41
|
+
await ctx.timeout(0);
|
|
28
42
|
}
|
|
29
43
|
status++;
|
|
30
44
|
})();
|
|
@@ -35,16 +49,16 @@ const action = async (ctx, next) => {
|
|
|
35
49
|
status++;
|
|
36
50
|
// console.log('Done');
|
|
37
51
|
for (let image of resp?.images || []) {
|
|
38
|
-
await ctx.timeout();
|
|
39
52
|
await ctx.image(image.data, { caption: image.caption });
|
|
53
|
+
await ctx.timeout();
|
|
40
54
|
}
|
|
41
55
|
for (let video of resp?.videos || []) {
|
|
42
|
-
await ctx.timeout();
|
|
43
56
|
await ctx.video(video.data, { caption: video.caption });
|
|
57
|
+
await ctx.timeout();
|
|
44
58
|
}
|
|
45
59
|
for (let audio of resp?.audios || []) {
|
|
46
|
-
await ctx.timeout();
|
|
47
60
|
await ctx.audio(audio.data, { caption: audio.caption });
|
|
61
|
+
await ctx.timeout();
|
|
48
62
|
}
|
|
49
63
|
// print(resp);
|
|
50
64
|
while (status < 2) {
|