halbot 1994.1.7 → 1995.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/halbot.mjs +1 -1
- package/index.mjs +55 -64
- package/lib/hal.mjs +210 -938
- package/package.json +6 -6
- package/pipeline/010_broca.mjs +261 -0
- package/pipeline/020_cmd.mjs +77 -0
- package/pipeline/030_echo.mjs +70 -0
- package/pipeline/040_help.mjs +49 -0
- package/pipeline/050_auth.mjs +30 -0
- package/pipeline/060_config.mjs +84 -0
- package/pipeline/070_collect.mjs +115 -0
- package/pipeline/080_history.mjs +111 -0
- package/pipeline/090_ai.mjs +77 -0
- package/pipeline/100_chat.mjs +65 -0
- package/skills/-8845_thread.mjs +0 -185
- package/skills/10_ai.mjs +0 -97
- package/skills/20_instant.mjs +0 -25
- package/skills/30_wording.mjs +0 -71
- package/skills/40_dream.mjs +0 -61
- package/skills/50_prompt.mjs +0 -60
- package/skills/60_prepare.mjs +0 -61
- package/skills/70_chat.mjs +0 -101
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { bot, hal, media, storage, utilitas, vision, web } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const collectableFiles = ['document', 'sticker', 'video_note', 'video'];
|
|
4
|
+
const sendInit = async (ctx, txt) => ctx._.done.length || await ctx.ok(txt);
|
|
5
|
+
|
|
6
|
+
const [API_ROOT, BUFFER_ENCODE, EMOJI_SPEECH, EMOJI_LOOK, ATTACHMENT, PROMPT]
|
|
7
|
+
= [
|
|
8
|
+
'https://api.telegram.org/', { encode: storage.BUFFER }, '👂', '👀',
|
|
9
|
+
'ATTACHMENT', 'PROMPT',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const collectableObjects = [
|
|
13
|
+
'venue', 'location', 'contact', 'poll', 'left_chat_member',
|
|
14
|
+
'new_chat_member', 'checklist'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const metaPrompt = "The following are meta information changes or attachment details for the current chat. Please respond appropriately. For example, if it's a poll, make a selection based on your understanding. If there are changes in group members, greet or bid farewell to the respective individuals. If it's a geographical location description, provide a suitable answer based on the context. You may also receive other types of information, for which a reasonable, human-like response is expected.";
|
|
18
|
+
|
|
19
|
+
const getFileUrl = async (ctx, file_id) => {
|
|
20
|
+
assert(file_id, 'File ID is required.', 400);
|
|
21
|
+
const file = await ctx.telegram.getFile(file_id);
|
|
22
|
+
assert(file.file_path, 'Error getting file info.', 500);
|
|
23
|
+
return `${API_ROOT}file/bot${ctx.telegram.token}/${file.file_path}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const officeParser = async file => await utilitas.ignoreErrFunc(async () =>
|
|
27
|
+
await vision.parseOfficeFile(file, { input: storage.BUFFER }), { log: true }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const ctxExt = ctx => {
|
|
31
|
+
ctx.getFileUrl = async (file_id) => await getFileUrl(ctx, file_id);
|
|
32
|
+
ctx.getFile = async (file_id, options) => (await web.get(
|
|
33
|
+
await ctx.getFileUrl(file_id), { ...BUFFER_ENCODE, ...options }
|
|
34
|
+
)).content;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let mime;
|
|
38
|
+
|
|
39
|
+
const collectFile = async (ctx, f) => {
|
|
40
|
+
f = { ...f };
|
|
41
|
+
f.url || (f.url = await ctx.getFileUrl(f.file_id));
|
|
42
|
+
f.file_name || (f.file_name = utilitas.basename(f.url));
|
|
43
|
+
f.data || (f.data = await ctx.getFile(f.file_id));
|
|
44
|
+
f.mime_type || (f.mime_type = (await storage.getMime(f.data, f.file_name))?.mime);
|
|
45
|
+
let text = '';
|
|
46
|
+
f.emoji && ctx.collect(f.emoji, PROMPT);
|
|
47
|
+
if (hal._.supportedMimeTypes.has(f.mime_type)) {
|
|
48
|
+
ctx.collect({ mime_type: f.mime_type, data: f.data }, ATTACHMENT);
|
|
49
|
+
} else if (await storage.isTextFile(f.data)) {
|
|
50
|
+
text = f.data.toString();
|
|
51
|
+
} else if (/^.*\.(docx|xlsx|pptx)$/.test(f.file_name)) {
|
|
52
|
+
text = await officeParser(f.data);
|
|
53
|
+
}
|
|
54
|
+
if (text) {
|
|
55
|
+
ctx.collect(bot.lines([
|
|
56
|
+
'---', `file_name: ${f.file_name}`,
|
|
57
|
+
`mime_type: ${f.mime_type}`, `type: DOCUMENT`,
|
|
58
|
+
'---', text
|
|
59
|
+
]), PROMPT);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const extract = async (ctx, m) => {
|
|
64
|
+
collectableObjects.map(k => m[k] && ctx.collect(
|
|
65
|
+
bot.lines([
|
|
66
|
+
'---', metaPrompt, `type: ${k}`,
|
|
67
|
+
'---', JSON.stringify(m[k])
|
|
68
|
+
]), PROMPT
|
|
69
|
+
));
|
|
70
|
+
await Promise.all(collectableFiles.map(async k => {
|
|
71
|
+
if (!m[k]) { return; }
|
|
72
|
+
await sendInit(ctx, EMOJI_LOOK);
|
|
73
|
+
await collectFile(ctx, m[k]);
|
|
74
|
+
}));
|
|
75
|
+
if (m.photo?.[m.photo?.length - 1]) {
|
|
76
|
+
await sendInit(ctx, EMOJI_LOOK);
|
|
77
|
+
const f = m.photo[m.photo.length - 1];
|
|
78
|
+
await collectFile(ctx, { ...f, file_name: `${f.file_unique_id}.jpg` });
|
|
79
|
+
}
|
|
80
|
+
let a;
|
|
81
|
+
if ((a = m.voice || m.audio)) {
|
|
82
|
+
await sendInit(ctx, EMOJI_SPEECH);
|
|
83
|
+
await collectFile(ctx, {
|
|
84
|
+
...a, mime_type: storage.MIME_WAV,
|
|
85
|
+
data: await media.convertAudioTo16kNanoPcmWave(
|
|
86
|
+
await ctx.getFile(a.file_id)
|
|
87
|
+
),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const action = async (ctx, next) => {
|
|
93
|
+
// init
|
|
94
|
+
ctxExt(ctx);
|
|
95
|
+
mime || (mime = await utilitas.need('mime'));
|
|
96
|
+
// collect objects
|
|
97
|
+
await Promise.all([
|
|
98
|
+
ctx._.message, ctx._.message?.reply_to_message
|
|
99
|
+
].filter(x => x).map(async m => await extract(ctx, m)));
|
|
100
|
+
// collect reply_to_message
|
|
101
|
+
ctx._.message.reply_to_message?.text && ctx.collect(
|
|
102
|
+
ctx._.message.reply_to_message.text, PROMPT
|
|
103
|
+
);
|
|
104
|
+
ctx._.message.reply_to_message?.caption && ctx.collect(
|
|
105
|
+
ctx._.message.reply_to_message.caption, PROMPT
|
|
106
|
+
);
|
|
107
|
+
// print(JSON.stringify(ctx.update, null, 2));
|
|
108
|
+
// print(ctx._.text);
|
|
109
|
+
// print(ctx._.collected);
|
|
110
|
+
await next();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const { _NEED, name, run, priority, func } = {
|
|
114
|
+
_NEED: ['mime'], name: 'Collect', run: true, priority: 70, func: action,
|
|
115
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { bot, dbio, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const compact = (str, op) => utilitas.ensureString(str, { ...op || {}, compact: true });
|
|
4
|
+
const compactLimit = (str, op) => compact(str, { ...op || {}, limit: 140 });
|
|
5
|
+
|
|
6
|
+
const memorize = async (ctx) => {
|
|
7
|
+
// https://limits.tginfo.me/en
|
|
8
|
+
if (!ctx._.chatId || ctx._.cmd?.cmd) { return; }
|
|
9
|
+
const received = ctx.update;
|
|
10
|
+
const received_text = ctx._.request || ctx._.text || '';
|
|
11
|
+
const id = received.update_id;
|
|
12
|
+
let response = {};
|
|
13
|
+
ctx._.done.map(m => m?.text && (response[m.message_id] = m));
|
|
14
|
+
response = Object.values(response).sort((a, b) => a.message_id - b.message_id);
|
|
15
|
+
const response_text = ctx?._.response || response.map(x => x.text).join('\n');
|
|
16
|
+
const collected = ctx._.collected.filter(x => String.isString(x.content));
|
|
17
|
+
const distilled = compact(bot.lines([
|
|
18
|
+
received_text, response_text, ...collected.map(x => x.content)
|
|
19
|
+
]));
|
|
20
|
+
if (!ctx._.messageId || !distilled) { return; }
|
|
21
|
+
const event = {
|
|
22
|
+
id, bot_id: ctx.botInfo.id, chat_id: ctx._.chatId,
|
|
23
|
+
chat_type: ctx._.chatType, message_id: ctx._.messageId,
|
|
24
|
+
received: JSON.stringify(received), received_text,
|
|
25
|
+
response: JSON.stringify(response), response_text,
|
|
26
|
+
collected: JSON.stringify(collected), distilled,
|
|
27
|
+
};
|
|
28
|
+
await utilitas.ignoreErrFunc(async () => {
|
|
29
|
+
event.distilled_vector = hal._.embed
|
|
30
|
+
? await hal._.embed(event.distilled) : [];
|
|
31
|
+
switch (hal._.storage?.provider) {
|
|
32
|
+
case dbio.MYSQL:
|
|
33
|
+
event.distilled_vector = JSON.stringify(event.distilled_vector);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
await hal._.storage?.client?.upsert?.(hal.table, event, { skipEcho: true });
|
|
37
|
+
}, hal.logOptions);
|
|
38
|
+
// TODO: 調整,如果命令執行過,應該更新菜單 !?
|
|
39
|
+
await utilitas.ignoreErrFunc(async () => await hal._.bot.telegram.setMyCommands([
|
|
40
|
+
...hal._.cmds, ...Object.keys(ctx._.message.prompts || {}).map(
|
|
41
|
+
command => hal.newCommand(command, ctx._.message.prompts[command])
|
|
42
|
+
)
|
|
43
|
+
].sort((x, y) =>
|
|
44
|
+
(ctx._.message?.cmds?.[y.command.toLowerCase()]?.touchedAt || 0)
|
|
45
|
+
- (ctx._.message?.cmds?.[x.command.toLowerCase()]?.touchedAt || 0)
|
|
46
|
+
).slice(0, hal.COMMAND_LIMIT), {
|
|
47
|
+
scope: { type: 'chat', chat_id: ctx._.chatId },
|
|
48
|
+
}), hal.logOptions);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const ctxExt = ctx => {
|
|
52
|
+
ctx.memorize = async () => await memorize(ctx);
|
|
53
|
+
ctx.recall = async (keyword, offset = 0, limit = hal.SEARCH_LIMIT, options = {}) =>
|
|
54
|
+
await recall(ctx._.chatId, keyword, offset, limit, options);
|
|
55
|
+
// ctx.getContext = async (offset = 0, limit = hal.SEARCH_LIMIT, options = {}) =>
|
|
56
|
+
// await getContext(ctx._.chatId, offset, limit, options);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const action = async (ctx, next) => {
|
|
60
|
+
ctxExt(ctx);
|
|
61
|
+
switch (ctx._.cmd?.cmd) {
|
|
62
|
+
case 'search':
|
|
63
|
+
(ctx._.type === 'callback_query')
|
|
64
|
+
&& await ctx.deleteMessage(ctx._.message.message_id);
|
|
65
|
+
const regex = '[-—]+skip=[0-9]*';
|
|
66
|
+
const keywords = ctx._.cmd.args.replace(new RegExp(regex, 'i'), '').trim();
|
|
67
|
+
let catchArgs = ctx._.cmd.args.replace(new RegExp(`^.*(${regex}).*$`, 'i'), '$1');
|
|
68
|
+
catchArgs === ctx._.cmd.args && (catchArgs = '');
|
|
69
|
+
const offset = ~~catchArgs.replace(/^.*=([0-9]*).*$/i, '$1');
|
|
70
|
+
if (!keywords) { return await ctx.er('Topic is required.'); }
|
|
71
|
+
const result = await ctx.recall(keywords, offset);
|
|
72
|
+
for (const i in result) {
|
|
73
|
+
const content = bot.lines([
|
|
74
|
+
'```↩️', compactLimit(result[i].response_text), '```',
|
|
75
|
+
[`${utilitas.getTimeIcon(result[i].created_at)} ${result[i].created_at.toLocaleString()}`,
|
|
76
|
+
`🏆 ${(Math.round(result[i].score * 100) / 100).toFixed(2)}`].join(' '),
|
|
77
|
+
]);
|
|
78
|
+
await ctx.resp(content, true, {
|
|
79
|
+
reply_parameters: {
|
|
80
|
+
message_id: result[i].message_id,
|
|
81
|
+
}, disable_notification: ~~i > 0,
|
|
82
|
+
});
|
|
83
|
+
await ctx.timeout();
|
|
84
|
+
}
|
|
85
|
+
// TODO: NEED MORE DEBUG
|
|
86
|
+
result.length === hal.SEARCH_LIMIT && await ctx.resp(
|
|
87
|
+
'___', true, ctx.getExtra({
|
|
88
|
+
buttons: [{
|
|
89
|
+
label: '🔍 More',
|
|
90
|
+
text: `/search@${ctx.botInfo.username} ${keywords} `
|
|
91
|
+
+ `--skip=${offset + result.length}`,
|
|
92
|
+
}]
|
|
93
|
+
}));
|
|
94
|
+
result.length || await ctx.err('No more records.');
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
await next();
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const { name, run, priority, func, help, cmdx } = {
|
|
103
|
+
name: 'History', run: true, priority: 80, func: action,
|
|
104
|
+
help: bot.lines([
|
|
105
|
+
'¶ Search history.',
|
|
106
|
+
'Example 1: /search Answer to the Ultimate Question',
|
|
107
|
+
'Example 2: /search Answer to the Ultimate Question --skip=10',
|
|
108
|
+
]), cmdx: {
|
|
109
|
+
search: 'Usage: /search `ANYTHING` --skip=`OFFSET`',
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { alan, bot, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const ais = await alan.getAi(null, { all: true });
|
|
4
|
+
const TOP = 'top';
|
|
5
|
+
|
|
6
|
+
const listAIs = async ctx => {
|
|
7
|
+
const lastMessageId = ctx?.update?.callback_query?.message?.message_id;
|
|
8
|
+
const message = `Features:\n`
|
|
9
|
+
+ hal.uList(Object.entries(alan.FEATURE_ICONS).map(
|
|
10
|
+
x => `${x[1]} ${x[0]}`
|
|
11
|
+
)) + `\n\nAI${ais.length > 0 ? 's' : ''}:\n`;
|
|
12
|
+
const buttons = ais.map((x, i) => ({
|
|
13
|
+
label: `${ctx._.session.config?.ai === x.id
|
|
14
|
+
|| (!ctx._.session.config?.ai && i === 0) ? `${hal.CHECK} `
|
|
15
|
+
: ''}${x.name}: ${x.features}`,
|
|
16
|
+
text: `/set --ai=${x.id}`,
|
|
17
|
+
}));
|
|
18
|
+
return await ctx.ok(message, { lastMessageId, buttons });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const action = async (ctx, next) => {
|
|
22
|
+
ctx._.ais = ais;
|
|
23
|
+
switch (ctx._.cmd?.cmd) {
|
|
24
|
+
case 'ai':
|
|
25
|
+
return await listAIs(ctx);
|
|
26
|
+
case TOP:
|
|
27
|
+
ctx.hello(ctx._.cmd.args);
|
|
28
|
+
ctx._.aiId = TOP;
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
ctx._.aiId = ctx._.session.config?.ai;
|
|
32
|
+
}
|
|
33
|
+
await next();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const validateAi = async (val, ctx) => {
|
|
37
|
+
for (let name of [...ais.map(x => x.id), '', TOP]) {
|
|
38
|
+
if (utilitas.insensitiveCompare(val, name)) {
|
|
39
|
+
ctx && (ctx.sendConfig = async (_c, _o) => await listAIs(ctx));
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
utilitas.throwError('No AI engine matched.');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const { name, run, priority, func, help, args, cmdx } = {
|
|
47
|
+
name: 'AI',
|
|
48
|
+
run: true,
|
|
49
|
+
priority: 90,
|
|
50
|
+
func: action,
|
|
51
|
+
help: bot.lines([
|
|
52
|
+
'¶ Set initial prompt to the AI model.',
|
|
53
|
+
"Tip 1: Set `hello=''` to reset to default initial prompt.",
|
|
54
|
+
'¶ Select between AI models.',
|
|
55
|
+
"Tip 2: Set `ai=''` to use default AI model.",
|
|
56
|
+
'Tip 3: Set `ai=[AI_ID]` to use specific AI model.',
|
|
57
|
+
'Tip 4: Set `ai=top` to configure to use the top 3 AI models simultaneously.',
|
|
58
|
+
'¶ Use an AI model `temporary` without touching your settings.',
|
|
59
|
+
'Tip 5: `/[AI_ID]` Tell me a joke.',
|
|
60
|
+
'Tip 6: `/all` Use the top 3 AI models simultaneously, for current prompt.',
|
|
61
|
+
]),
|
|
62
|
+
args: {
|
|
63
|
+
hello: {
|
|
64
|
+
type: 'string', short: 's', default: 'Hello!',
|
|
65
|
+
desc: "Change initial prompt: /set --hello 'Bonjour!'",
|
|
66
|
+
},
|
|
67
|
+
ai: {
|
|
68
|
+
type: 'string', short: 'a', default: '',
|
|
69
|
+
desc: "`(AI_ID, ..., @)` Select AI model.",
|
|
70
|
+
validate: validateAi,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
cmdx: {
|
|
74
|
+
ai: 'List all available AIs.',
|
|
75
|
+
all: 'Use the top 3 AI models simultaneously: /all Say hello to all AIs!',
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { alan } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const _name = 'Chat';
|
|
4
|
+
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
5
|
+
|
|
6
|
+
const generate = async (ctx) => {
|
|
7
|
+
try {
|
|
8
|
+
let [resp, extra, lock, sResp, lastMsg, lastSent] =
|
|
9
|
+
[null, { buttons: [] }, 1000 * 5, null, null, 0];
|
|
10
|
+
const ok = async options => {
|
|
11
|
+
const curTime = Date.now();
|
|
12
|
+
if (options?.processing && (
|
|
13
|
+
curTime - lastSent < ctx._.limit || lastMsg === resp.text
|
|
14
|
+
)) { return; }
|
|
15
|
+
[lastSent, lastMsg] = [curTime + lock, resp.text];
|
|
16
|
+
if (!options?.processing) {
|
|
17
|
+
(resp.annotations || []).map((x, i) => extra.buttons.push({
|
|
18
|
+
label: `${i + 1}. ${x.title}`, url: x.url,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
sResp = await ctx.ok(resp.text, {
|
|
22
|
+
...ctx._.keyboards ? { keyboards: ctx._.keyboards } : {},
|
|
23
|
+
md: true, ...extra, ...options || {},
|
|
24
|
+
});
|
|
25
|
+
lastSent = curTime;
|
|
26
|
+
return sResp;
|
|
27
|
+
};
|
|
28
|
+
resp = await alan.talk(ctx._.text, {
|
|
29
|
+
...ctx._, sessionId: ctx._.chatId, // THIS LINE IS IMPORTANT
|
|
30
|
+
stream: async rsp => { resp = rsp; ok({ processing: true }); }, // Never await, it will block the stream.
|
|
31
|
+
});
|
|
32
|
+
for (let image of resp?.images || []) {
|
|
33
|
+
await ctx.timeout();
|
|
34
|
+
await ctx.image(image.data, { caption: image.caption });
|
|
35
|
+
}
|
|
36
|
+
for (let video of resp?.videos || []) {
|
|
37
|
+
await ctx.timeout();
|
|
38
|
+
await ctx.video(video.data, { caption: video.caption });
|
|
39
|
+
}
|
|
40
|
+
for (let audio of resp?.audios || []) {
|
|
41
|
+
await ctx.timeout();
|
|
42
|
+
await ctx.audio(audio.data, { caption: audio.caption });
|
|
43
|
+
}
|
|
44
|
+
// print(resp);
|
|
45
|
+
await resp.text.trim() ? ok({ processing: false })
|
|
46
|
+
: ctx.deleteMessage(ctx._.done[0].message_id);
|
|
47
|
+
ctx._.request = resp.request;
|
|
48
|
+
ctx._.response = resp.response;
|
|
49
|
+
ctx.memorize && await ctx.memorize();
|
|
50
|
+
} catch (err) { log(err); }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const action = async (ctx, next) => {
|
|
54
|
+
ctx.finish();
|
|
55
|
+
await next();
|
|
56
|
+
if (!ctx._.text && !ctx._.collected.length) { return; }
|
|
57
|
+
generate(ctx);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const { name, run, priority, func } = {
|
|
61
|
+
name: _name,
|
|
62
|
+
run: true,
|
|
63
|
+
priority: 100,
|
|
64
|
+
func: action,
|
|
65
|
+
};
|
package/skills/-8845_thread.mjs
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { alan, hal, uoid, utilitas } from '../index.mjs';
|
|
2
|
-
|
|
3
|
-
const [EMIJI_FINISH, END, NEW, THREAD, CLR] = ['☑️', '❎', '✨', '🧵', '🆑'];
|
|
4
|
-
|
|
5
|
-
const [CREATED, SWITCHED] = [
|
|
6
|
-
`${NEW} Thread created: `, `${EMIJI_FINISH} Thread switched: `
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
// https://stackoverflow.com/questions/69924954/an-error-is-issued-when-opening-the-telebot-keyboard
|
|
10
|
-
const keyboards = [[
|
|
11
|
-
{ text: `/ai ${hal.EMOJI_BOT}` },
|
|
12
|
-
{ text: `/new ${NEW}` },
|
|
13
|
-
{ text: `/end ${END}` },
|
|
14
|
-
{ text: `/list ${THREAD}` },
|
|
15
|
-
], [
|
|
16
|
-
{ text: '/polish ❇️' },
|
|
17
|
-
{ text: '/to 🇨🇳' },
|
|
18
|
-
{ text: '/to 🇺🇸' },
|
|
19
|
-
], [
|
|
20
|
-
{ text: '/help 🛟' },
|
|
21
|
-
{ text: '/set --tts=🔇' },
|
|
22
|
-
{ text: '/set --tts=🔊' },
|
|
23
|
-
]];
|
|
24
|
-
|
|
25
|
-
const action = async (ctx, next) => {
|
|
26
|
-
// reset session storage
|
|
27
|
-
const resetSession = () => ctx.session.sessionId = uoid.create({
|
|
28
|
-
type: `ALAN_SESSION_${ctx.chatId}`, security: true,
|
|
29
|
-
});
|
|
30
|
-
const resetSessions = () => ctx.session.sessions = [];
|
|
31
|
-
const resetContext = context => ctx.session.context = context || {};
|
|
32
|
-
const now = Date.now();
|
|
33
|
-
const preSessionId = ctx.session.sessionId || resetSession();
|
|
34
|
-
ctx.session.sessions || resetSessions();
|
|
35
|
-
ctx.session.context || resetContext();
|
|
36
|
-
ctx.carry || (ctx.carry = {});
|
|
37
|
-
ctx.carry.threadInfo || (ctx.carry.threadInfo = []);
|
|
38
|
-
// load functions
|
|
39
|
-
ctx.clear = async context => {
|
|
40
|
-
await alan.resetSession(
|
|
41
|
-
ctx.session.sessionId,
|
|
42
|
-
{ systemPrompt: context?.prompt } // @todo: switch to real system prompt
|
|
43
|
-
);
|
|
44
|
-
resetContext(context);
|
|
45
|
-
const id = findSession(ctx.session.sessionId);
|
|
46
|
-
ctx.cmd && (ctx.cmd.ignored = true);
|
|
47
|
-
ctx.session.sessions?.[id] && (
|
|
48
|
-
ctx.session.sessions[id].context = ctx.session.context
|
|
49
|
-
);
|
|
50
|
-
ctx.hello();
|
|
51
|
-
};
|
|
52
|
-
const switchSession = async () => {
|
|
53
|
-
let resp;
|
|
54
|
-
for (const session of ctx.session.sessions) {
|
|
55
|
-
if (session.id === ctx.session.sessionId) {
|
|
56
|
-
ctx.session.sessionId = session.id;
|
|
57
|
-
ctx.session.context = session.context;
|
|
58
|
-
session.touchedAt = now;
|
|
59
|
-
resp = session;
|
|
60
|
-
preSessionId !== ctx.session.sessionId
|
|
61
|
-
&& ctx.carry.threadInfo.push(SWITCHED
|
|
62
|
-
+ getLabel(findSession(ctx.session.sessionId)));
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (!resp) {
|
|
67
|
-
ctx.session.sessions.push(resp = {
|
|
68
|
-
id: resetSession(),
|
|
69
|
-
createdAt: now, touchedAt: now, context: {},
|
|
70
|
-
});
|
|
71
|
-
ctx.carry.threadInfo.push(CREATED
|
|
72
|
-
+ `\`${getLabel(findSession(ctx.session.sessionId))}\``);
|
|
73
|
-
await ctx.clear();
|
|
74
|
-
}
|
|
75
|
-
ctx.carry.sessionId = ctx.session.sessionId;
|
|
76
|
-
ctx.carry.threadInfo.length && (ctx.carry.keyboards = keyboards);
|
|
77
|
-
return resp;
|
|
78
|
-
};
|
|
79
|
-
const defauleTitle = i => (ctx.session.sessions[i]?.context?.cmd
|
|
80
|
-
? `\`${ctx.session.sessions[i].context.cmd}\` ` : 'Untitled thread '
|
|
81
|
-
) + `(${new Date(ctx.session.sessions[i].createdAt).toLocaleString()})`;
|
|
82
|
-
const getLabel = i => ctx.session.sessions[i].label || defauleTitle(i);
|
|
83
|
-
const findSession = id => {
|
|
84
|
-
for (let i = 0; i < ctx.session.sessions.length; i++) {
|
|
85
|
-
if (ctx.session.sessions[i].id === utilitas.trim(id)) {
|
|
86
|
-
return i;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
const ok = async (message, options) => await ctx.ok(message, {
|
|
91
|
-
...options || {},
|
|
92
|
-
...options?.buttons ? {} : (options?.keyboards || { keyboards }),
|
|
93
|
-
});
|
|
94
|
-
const listThreads = async (names, lastMsgId) => {
|
|
95
|
-
lastMsgId = lastMsgId || ctx.update?.callback_query?.message?.message_id;
|
|
96
|
-
const message = `${THREAD} Thread${ctx.session.sessions.length > 0 ? 's' : ''}:`;
|
|
97
|
-
const buttons = ctx.session.sessions.map((x, i) => {
|
|
98
|
-
names?.[x.id]
|
|
99
|
-
&& (ctx.session.sessions[i].label = names[x.id])
|
|
100
|
-
&& (ctx.session.sessions[i].labelUpdatedAt = now);
|
|
101
|
-
return {
|
|
102
|
-
label: `${ctx.session.sessions[i].id === ctx.session.sessionId
|
|
103
|
-
? `${EMIJI_FINISH} ` : ''}${getLabel(i)}`,
|
|
104
|
-
text: `/switch ${x.id}`,
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
return await ok(message, { lastMessageId: lastMsgId, buttons });
|
|
108
|
-
};
|
|
109
|
-
const switched = async (preTitle, newThread) => await ok(
|
|
110
|
-
`${preTitle ? `${END} Thread ended: \`${preTitle}\`\n\n` : ''}`
|
|
111
|
-
+ (newThread ? CREATED : SWITCHED)
|
|
112
|
-
+ `\`${getLabel(findSession(ctx.session.sessionId))}\``,
|
|
113
|
-
{ pageBreak: true }
|
|
114
|
-
);
|
|
115
|
-
// handle commands
|
|
116
|
-
switch (ctx.cmd?.cmd) {
|
|
117
|
-
case 'clearkb':
|
|
118
|
-
return await ok(EMIJI_FINISH, { keyboards: [] });
|
|
119
|
-
case 'clear':
|
|
120
|
-
ctx.carry.threadInfo.push(`${CLR} Thread cleared: \``
|
|
121
|
-
+ `${getLabel(findSession(ctx.session.sessionId))}\``);
|
|
122
|
-
await ctx.clear();
|
|
123
|
-
break;
|
|
124
|
-
case 'endall':
|
|
125
|
-
ctx.carry.threadInfo.push(`🔄 All threads have been cleared.`);
|
|
126
|
-
resetSessions();
|
|
127
|
-
break;
|
|
128
|
-
case 'new':
|
|
129
|
-
resetSession();
|
|
130
|
-
break;
|
|
131
|
-
case 'list':
|
|
132
|
-
const resp = await listThreads();
|
|
133
|
-
utilitas.ignoreErrFunc(async () => {
|
|
134
|
-
const sNames = await alan.analyzeSessions(
|
|
135
|
-
ctx.session.sessions.filter(
|
|
136
|
-
x => (x.labelUpdatedAt || 0) < x.touchedAt
|
|
137
|
-
).map(x => x.id), { ignoreRequest: hal.HELLO }
|
|
138
|
-
);
|
|
139
|
-
return await listThreads(sNames, resp[0]?.message_id);
|
|
140
|
-
}, { log: true });
|
|
141
|
-
return resp;
|
|
142
|
-
case 'end':
|
|
143
|
-
const id = findSession(
|
|
144
|
-
ctx.cmd.args.startsWith('ALAN_SESSION_')
|
|
145
|
-
&& utilitas.trim(ctx.cmd.args) || ctx.session.sessionId
|
|
146
|
-
);
|
|
147
|
-
let preTitle = '';
|
|
148
|
-
ctx.session.sessions?.[id] && (preTitle = getLabel(id))
|
|
149
|
-
&& (ctx.session.sessions.splice(id, 1));
|
|
150
|
-
const newThread = ctx.session.sessions.length === 0
|
|
151
|
-
const sorted = ctx.session.sessions.slice().sort(
|
|
152
|
-
(x, y) => y.touchedAt - x.touchedAt
|
|
153
|
-
);
|
|
154
|
-
ctx.session.sessionId = sorted?.[0]?.id;
|
|
155
|
-
await switchSession();
|
|
156
|
-
return await switched(preTitle, newThread);
|
|
157
|
-
case 'switch':
|
|
158
|
-
ctx.session.sessionId = utilitas.trim(ctx.cmd.args);
|
|
159
|
-
await switchSession();
|
|
160
|
-
await listThreads();
|
|
161
|
-
return await switched();
|
|
162
|
-
case 'factory':
|
|
163
|
-
case 'reset':
|
|
164
|
-
await alan.resetSession(ctx.session.sessionId);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
await switchSession();
|
|
168
|
-
await next();
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export const { name, run, priority, func, help, cmdx } = {
|
|
172
|
-
name: 'Thread',
|
|
173
|
-
run: true,
|
|
174
|
-
priority: -8845,
|
|
175
|
-
func: action,
|
|
176
|
-
help: '¶ Thread management.',
|
|
177
|
-
cmdx: {
|
|
178
|
-
clear: 'Clear current thread.',
|
|
179
|
-
end: 'End current thread.',
|
|
180
|
-
endall: 'End all threads.',
|
|
181
|
-
list: 'List all threads.',
|
|
182
|
-
new: 'Create a new thread.',
|
|
183
|
-
switch: 'Switch to a thread. Usage: /switch `THREAD_ID`.',
|
|
184
|
-
},
|
|
185
|
-
};
|
package/skills/10_ai.mjs
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { alan, bot, hal, utilitas } from '../index.mjs';
|
|
2
|
-
|
|
3
|
-
const ais = await alan.getAi(null, { all: true });
|
|
4
|
-
const EMIJI_FINISH = '☑️';
|
|
5
|
-
|
|
6
|
-
const listAIs = async ctx => {
|
|
7
|
-
const lastMessageId = ctx?.update?.callback_query?.message?.message_id;
|
|
8
|
-
const message = `Features:\n`
|
|
9
|
-
+ hal.uList(Object.entries(alan.FEATURE_ICONS).map(
|
|
10
|
-
x => `${x[1]} ${x[0]}`
|
|
11
|
-
)) + `\n\nAI${ais.length > 0 ? 's' : ''}:\n`;
|
|
12
|
-
const buttons = ais.map(x => ({
|
|
13
|
-
label: `${ctx.session.config?.ai === x.id
|
|
14
|
-
? `${EMIJI_FINISH} ` : ''}${x.name}: ${x.features}`,
|
|
15
|
-
text: `/set --ai=${x.id}`,
|
|
16
|
-
}));
|
|
17
|
-
return await ctx.ok(message, { lastMessageId, buttons });
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const action = async (ctx, next) => {
|
|
21
|
-
switch (ctx.cmd?.cmd) {
|
|
22
|
-
case 'ai': return await listAIs(ctx);
|
|
23
|
-
}
|
|
24
|
-
if (ctx.session.config?.ai === '@') {
|
|
25
|
-
ctx.selectedAi = ais.map(x => x.id);
|
|
26
|
-
} else if (ctx.collected?.length) {
|
|
27
|
-
const supported = {};
|
|
28
|
-
for (const x of ais) {
|
|
29
|
-
const supportedMimeTypes = [
|
|
30
|
-
...x.model.supportedMimeTypes,
|
|
31
|
-
...x.model.supportedDocTypes,
|
|
32
|
-
...x.model.supportedAudioTypes,
|
|
33
|
-
];
|
|
34
|
-
for (const i of ctx.collected) {
|
|
35
|
-
supported[x.id] || (supported[x.id] = 0);
|
|
36
|
-
// Priority for supported mime types
|
|
37
|
-
supportedMimeTypes.includes(i?.content?.mime_type)
|
|
38
|
-
&& supported[x.id]++;
|
|
39
|
-
// Priority for user selected AI
|
|
40
|
-
x.id === ctx.session.config?.ai && supported[x.id]++;
|
|
41
|
-
// Priority for audio models
|
|
42
|
-
ctx.checkSpeech() && (
|
|
43
|
-
x.model.supportedAudioTypes || []
|
|
44
|
-
).includes(i?.content?.mime_type)
|
|
45
|
-
&& (ctx.carry.audioMode = true)
|
|
46
|
-
&& x.model.audio && supported[x.id]++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
ctx.selectedAi = [Object.keys(supported).sort(
|
|
50
|
-
(x, y) => supported[y] - supported[x]
|
|
51
|
-
)?.[0] || ais[0].id];
|
|
52
|
-
} else if (ais.map(x => x.id).includes(ctx.session.config?.ai)) {
|
|
53
|
-
ctx.selectedAi = [ctx.session.config.ai];
|
|
54
|
-
} else {
|
|
55
|
-
ctx.selectedAi = [ais[0].id];
|
|
56
|
-
}
|
|
57
|
-
await next();
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const validateAi = async (val, ctx) => {
|
|
61
|
-
for (let name of [...ais.map(x => x.id), '', '@']) {
|
|
62
|
-
if (utilitas.insensitiveCompare(val, name)) {
|
|
63
|
-
ctx && (ctx.sendConfig = async (_c, _o, ctx) => await listAIs(ctx));
|
|
64
|
-
return name;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
utilitas.throwError('No AI engine matched.');
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const { name, run, priority, func, help, args, cmdx } = {
|
|
71
|
-
name: 'AI',
|
|
72
|
-
run: true,
|
|
73
|
-
priority: 10,
|
|
74
|
-
func: action,
|
|
75
|
-
help: bot.lines([
|
|
76
|
-
'¶ Set initial prompt to the AI engine.',
|
|
77
|
-
"Tip 1: Set `hello=''` to reset to default initial prompt.",
|
|
78
|
-
'¶ Select between AI engines.',
|
|
79
|
-
"Tip 2: Set `ai=''` to use default AI engine.",
|
|
80
|
-
'Tip 3: Set `ai=[AI_ID]` to use specific AI engine.',
|
|
81
|
-
'Tip 4: Set `ai=@` to use all AI engines simultaneously.',
|
|
82
|
-
]),
|
|
83
|
-
args: {
|
|
84
|
-
hello: {
|
|
85
|
-
type: 'string', short: 's', default: 'Hello!',
|
|
86
|
-
desc: "Change initial prompt: /set --hello 'Bonjour!'",
|
|
87
|
-
},
|
|
88
|
-
ai: {
|
|
89
|
-
type: 'string', short: 'a', default: '',
|
|
90
|
-
desc: "`(AI_ID, ..., @)` Select AI engine.",
|
|
91
|
-
validate: validateAi,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
cmdx: {
|
|
95
|
-
ai: 'List all available AIs.',
|
|
96
|
-
}
|
|
97
|
-
};
|
package/skills/20_instant.mjs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { alan, bot } from '../index.mjs';
|
|
2
|
-
|
|
3
|
-
const action = async (ctx, next) => {
|
|
4
|
-
const ais = await alan.getAi(null, { all: true });
|
|
5
|
-
const allAi = ais.map(x => x.id);
|
|
6
|
-
switch (ctx.cmd.cmd) {
|
|
7
|
-
case 'all':
|
|
8
|
-
ctx.selectedAi = allAi;
|
|
9
|
-
ctx.hello(ctx.cmd.args);
|
|
10
|
-
}
|
|
11
|
-
await next();
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const { name, run, priority, func, help, cmds } = {
|
|
15
|
-
name: 'Instant',
|
|
16
|
-
run: true,
|
|
17
|
-
priority: 20,
|
|
18
|
-
func: action,
|
|
19
|
-
help: bot.lines([
|
|
20
|
-
'¶ Use an AI engine `temporary` without touching your settings.',
|
|
21
|
-
]),
|
|
22
|
-
cmds: {
|
|
23
|
-
all: 'Use all AI engines simultaneously: /all Say hello to all AIs!',
|
|
24
|
-
},
|
|
25
|
-
};
|