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.
@@ -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
+ };
@@ -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
- };
@@ -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
- };