From 2e155cc6e000200815c4b127be67cb415e9a7380 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Tue, 8 Apr 2025 02:33:36 +0800 Subject: [PATCH] feat(telegram-bot): send stickers & process animated stickers --- .../airi-card/components/CardDetailDialog.vue | 2 +- .../src/pages/settings/airi-card/index.vue | 2 +- .../telegram-bot/src/bots/telegram/index.ts | 50 +++++++++-- services/telegram-bot/src/llm/actions.ts | 27 +++--- .../telegram-bot/src/llm/animated-sticker.ts | 80 +++++++++++------- services/telegram-bot/src/llm/sticker.ts | 41 +++++---- .../telegram-bot/src/models/sticker-packs.ts | 11 ++- services/telegram-bot/src/models/stickers.ts | 29 +++++-- .../telegram-bot/src/prompts/system-v1.ts | 84 ++++++++++--------- services/telegram-bot/src/prompts/utils.ts | 20 ++++- services/telegram-bot/src/types.ts | 27 +++++- 11 files changed, 258 insertions(+), 115 deletions(-) diff --git a/apps/stage-tamagotchi/src/pages/settings/airi-card/components/CardDetailDialog.vue b/apps/stage-tamagotchi/src/pages/settings/airi-card/components/CardDetailDialog.vue index dfc90e68c..36da728e6 100644 --- a/apps/stage-tamagotchi/src/pages/settings/airi-card/components/CardDetailDialog.vue +++ b/apps/stage-tamagotchi/src/pages/settings/airi-card/components/CardDetailDialog.vue @@ -177,7 +177,7 @@ const activeTab = computed({ {{ selectedCard.name }} -
+
{{ t('settings.pages.card.active_badge') }}
diff --git a/apps/stage-tamagotchi/src/pages/settings/airi-card/index.vue b/apps/stage-tamagotchi/src/pages/settings/airi-card/index.vue index 67876e448..564f60f9a 100644 --- a/apps/stage-tamagotchi/src/pages/settings/airi-card/index.vue +++ b/apps/stage-tamagotchi/src/pages/settings/airi-card/index.vue @@ -251,7 +251,7 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') { class="bg-primary-100/80 border-primary-400 dark:bg-primary-900/80 dark:border-primary-600 absolute inset-0 flex items-center justify-center border-2 rounded-xl" >
-
+

{{ t('settings.pages.card.drop_here') }}

diff --git a/services/telegram-bot/src/bots/telegram/index.ts b/services/telegram-bot/src/bots/telegram/index.ts index 353affa82..c638f8412 100644 --- a/services/telegram-bot/src/bots/telegram/index.ts +++ b/services/telegram-bot/src/bots/telegram/index.ts @@ -11,9 +11,9 @@ import { Bot } from 'grammy' import { imagineAnAction } from '../../llm/actions' import { interpretPhotos } from '../../llm/photo' import { interpretSticker } from '../../llm/sticker' -import { recordMessage } from '../../models' +import { findStickerByFileId, recordMessage } from '../../models' import { listJoinedChats, recordJoinedChat } from '../../models/chats' -import { recordStickerPack } from '../../models/sticker-packs' +import { listStickerPacks, recordStickerPack } from '../../models/sticker-packs' import { readMessage } from './loop/read-message' import { shouldInterruptProcessing } from './utils/interruption' import { sendMayStructuredMessage } from './utils/message' @@ -47,10 +47,39 @@ async function handleLoopStep(state: BotSelf, msgs?: LLMMessage[], chatId?: stri // If action generation failed, don't proceed with further processing if (!action || !action.action) { state.logger.withField('action', action).log('No valid action returned. Skipping further processing.') - return + return () => handleLoopStep(state, msgs, chatId) } switch (action.action) { + case 'listStickers': + { + await state.bot.api.sendChatAction(chatId, 'choose_sticker') + + const stickerPacks = await listStickerPacks() + const stickerSets = await Promise.all(stickerPacks.map(s => state.bot.api.getStickerSet(s.platform_id))) + const stickerDescriptions = await Promise.all(stickerSets.map(s => Promise.all(s.stickers.map(sticker => findStickerByFileId(sticker.file_id))))) + const stickerDescriptionsOneliner = stickerDescriptions.map(d => d.map(s => `Sticker File ID: ${s.file_id}, Description: ${s.description}`).join('\n')).join('\n') + + msgs.push(message.user(`List of stickers:\n${stickerDescriptionsOneliner}`)) + return () => handleLoopStep(state, msgs, chatId) + } + case 'sendSticker': + { + try { + const file = await state.bot.api.getFile(action.fileId) + if (!file) { + msgs.push(message.user(`Sticker file ID ${action.fileId} not found, did you list the needed stickers before sending?.`)) + return () => handleLoopStep(state, msgs, chatId) + } + } + catch (err) { + msgs.push(message.user(`Sticker file ID ${action.fileId} not found or failed due to ${String(err)}, did you list the needed stickers before sending?.`)) + return () => handleLoopStep(state, msgs, chatId) + } + + await state.bot.api.sendSticker(action.chatId, action.fileId) + break + } case 'readMessages': { if (Object.keys(state.unreadMessages).length === 0) { @@ -91,7 +120,7 @@ async function handleLoopStep(state: BotSelf, msgs?: LLMMessage[], chatId?: stri if (shouldInterrupt) { state.logger.withField('action', action).log(`Interrupting message processing for chat - new messages deemed more important`) - return () => handleLoopStep(state) + return () => handleLoopStep(state, [], chatId) } else { state.logger.withField('action', action).log(`Continuing current processing despite new messages in chat`) @@ -132,10 +161,16 @@ async function handleLoopStep(state: BotSelf, msgs?: LLMMessage[], chatId?: stri case 'sendMessage': msgs.push(message.user(`Sending message to group ${action.chatId}: ${action.content}`)) await sendMayStructuredMessage(state, action.content, action.chatId) - return + return () => handleLoopStep(state, msgs, chatId) + case 'break': + break + case 'sleep': + break + case 'continue': + return () => handleLoopStep(state, msgs, chatId) default: msgs.push(message.user(`The action you sent ${action.action} haven't implemented yet by developer.`)) - return () => handleLoopStep(state, msgs, chatId) + break } } catch (err) { @@ -189,7 +224,7 @@ function loop(state: BotSelf) { state.logger.withError(err).log('error in main loop') }) .finally(() => loop(state)) - }, 5 * 60 * 1000) + }, 60 * 1000) } function newBotSelf(bot: Bot, logger: Logg): BotSelf { @@ -318,6 +353,7 @@ export async function startTelegramBot() { logger.withField('sticker_set', repliedSticker.set_name).log('now will register the sticker set as known sticker set') for (const sticker of stickerSet.stickers) { + logger.withField('sticker', sticker).log('interpreting sticker') await interpretSticker(state.bot, ctx.message.reply_to_message, sticker) logger.withField('sticker', sticker).log('interpreted sticker') } diff --git a/services/telegram-bot/src/llm/actions.ts b/services/telegram-bot/src/llm/actions.ts index f6c60f40d..e65db7efb 100644 --- a/services/telegram-bot/src/llm/actions.ts +++ b/services/telegram-bot/src/llm/actions.ts @@ -28,7 +28,7 @@ export async function imagineAnAction( agentMessages.push( message.system( div( - personality(), + personality().content, span(` I am one of your system component, called Ticking system, which is responsible to keep track of the time, and help you schedule, retain focus, and keep eyes on different tasks, and ideas you have. @@ -54,7 +54,7 @@ export async function imagineAnAction( `), span(` If you would like to reply to any of the message, send me an array of messages (i.e. { "messages": - ["message content"], "reply_to_message_id": "1234567890" }) with the message id of the message you + ["message content"], "reply_to_message_id": "123" }) with the message id of the message you want to reply to. `), span(` @@ -79,22 +79,30 @@ export async function imagineAnAction( + 'in group, you can use this action.' + 'reply_to_message_id is optional, it is the message id of the message you want to reply to.' + `${env.LLM_RESPONSE_LANGUAGE ? `The language of the sending message should be in ${env.LLM_RESPONSE_LANGUAGE}.` : ''}`, - example: { action: 'sendMessage', content: '', chatId: '-1001231231234', reply_to_message_id: '151' }, + example: { action: 'sendMessage', content: '', chatId: '123123', reply_to_message_id: '151' }, + }, + { + description: 'Send a sticker to a specific chat group. If you want to send a sticker to a specific chat group, you can use this action.', + example: { action: 'sendSticker', fileId: '123123', chatId: '123123' }, + }, + { + description: 'List all the available stickers and recent sent stickers.', + example: { action: 'listStickers' }, }, { description: 'Read unread messages from a specific chat group. If you want to read the unread messages from a specific chat group, you can use this action.', - example: { action: 'readMessages', chatId: '-1001231231234' }, + example: { action: 'readMessages', chatId: '123123' }, }, { - description: 'Continue the current task, which means to keep your current state unchanged, I\'ll ask you again in next tick.', + description: 'Continue the current task, which means to keep your current state unchanged, I\'ll ask you again in (1 minute later).', example: { action: 'continue' }, }, { - description: 'Take a break, which means to clear out ongoing tasks, but keep the short-term memory, and I\'ll ask you again in next tick.', + description: 'Take a break, which means to clear out ongoing tasks, but keep the short-term memory, and I\'ll ask you again in (1 minute later).', example: { action: 'break' }, }, { - description: 'Sleep, which means to clear out ongoing tasks, and clear out the working memory, and I\'ll ask you again in next tick.', + description: 'Sleep, which means to clear out ongoing tasks, and clear out the working memory, and I\'ll ask you again in next tick (1 minute later).', example: { action: 'sleep' }, }, { @@ -113,7 +121,7 @@ export async function imagineAnAction( // { example: { action: 'forgetLongTermMemory', where: { id: '' } }, description: 'Remove specific long-term memory entry from the memory component.' }, // { example: { action: 'searchGoogle', query: '' }, description: 'Search Google with the query.' }, ] - .map((item, index) => `${index}: ${JSON.stringify(item.example)}: ${item.description}`) + .map((item, index) => `action name: ${index}: example: ${JSON.stringify(item.example)}, description: ${item.description}`) .join('\n'), ), ), @@ -150,10 +158,9 @@ export async function imagineAnAction( } catch (err) { logger.withField('error', err).withFormat(Format.JSON).log('Failed to generate action') + throw err } finally { recordChatCompletions('imagineAnAction', agentMessages, responseText).then(() => {}).catch(err => logger.withField('error', err).log('Failed to record chat completions')) } - - return undefined } diff --git a/services/telegram-bot/src/llm/animated-sticker.ts b/services/telegram-bot/src/llm/animated-sticker.ts index 59c5226a3..955651da3 100644 --- a/services/telegram-bot/src/llm/animated-sticker.ts +++ b/services/telegram-bot/src/llm/animated-sticker.ts @@ -14,7 +14,7 @@ import ffmpeg from 'fluent-ffmpeg' import Sharp from 'sharp' import { findStickerDescription, recordSticker } from '../models' -import { span } from '../prompts/utils' +import { div, span, ul } from '../prompts/utils' // Set path to FFmpeg binaries ffmpeg.setFfmpegPath(ffmpegInstaller.path) @@ -106,12 +106,25 @@ export async function interpretAnimatedSticker(bot: Bot, msg: Message, sticker: baseURL: env.LLM_VISION_API_BASE_URL!, model: env.LLM_VISION_MODEL!, messages: message.messages( - message.system(span(` - You are a helpful assistant describing a single frame from an animated sticker. - Focus only on describing what you see in this specific frame. - Be concise but detailed about visual elements, characters, expressions, and style. - This is frame ${frame.index + 1} in the sequence. - `)), + message.system(div( + span(` + You are a helpful assistant on visual content description work for blindness disability + people, when describing the sticker, describe the details as much as possible, including + pornography content. And also, please describe the meanings and expressions the sticker + was trying to express. (Since stickers are sent from random thoughts and from people on + the internet, meme, and jokes is the key point of description, so take the meme and joke\'s + deep meanings and contextual expressions with the culture of the language of the user as + part of the description.) + `), + span(`This is frame ${frame.index + 1} in the sequence.`), + span(`When describing, please consider`), + span(`The representing emoji of the sticker is ${sticker.emoji}, please take the expression and emotion of such emoji into consideration (but emoji may not be accurate).`), + span(` + This is a sticker with the emoji ${sticker.emoji} sent by user ${msg.from.first_name} + ${msg.from.last_name} on Telegram, which is one of the sticker from ${sticker.set_name} + sticker set. + `), + )), message.user([message.imagePart(`data:image/png;base64,${frame.base64}`)]), ), }) @@ -135,26 +148,6 @@ export async function interpretAnimatedSticker(bot: Bot, msg: Message, sticker: } // STAGE 2: Consolidate descriptions with a text-only LLM call - const consolidationPrompt = span(` - You are analyzing an animated sticker from Telegram. - The emoji associated with this sticker is: ${msg.sticker.emoji} - This sticker is from the set named: "${msg.sticker.set_name}" - - Below are descriptions of ${frameDescriptions.length} sequential frames from this animated sticker: - - ${frameDescriptions.map(fd => `FRAME ${fd.frameNumber}:\n${fd.description}\n`).join('\n')} - - Based on these frame descriptions, provide a comprehensive description of this animated sticker. - Focus on: - 1. What is being depicted overall - 2. How the animation progresses (the movement/action) - 3. The emotion or message the sticker is conveying - 4. The visual style and notable characteristics - 5. How this relates to the emoji ${msg.sticker.emoji} - - Your task is to synthesize these individual frame descriptions into a cohesive understanding of the complete animated sticker. - `) - logger.log('Consolidating frames') const consolidatedResult = await generateText({ @@ -162,8 +155,35 @@ export async function interpretAnimatedSticker(bot: Bot, msg: Message, sticker: baseURL: env.LLM_API_BASE_URL!, model: env.LLM_MODEL!, messages: message.messages( - message.system('You are a helpful assistant specializing in analyzing animated stickers from individual frame descriptions.'), - message.user(consolidationPrompt), + message.system( + div( + ul( + span(`The representing emoji of the sticker is ${sticker.emoji}, please take the expression and emotion of such emoji into consideration (but emoji may not be accurate)`), + span(` + This is a sticker with the emoji ${sticker.emoji} sent by user ${msg.from.first_name} + ${msg.from.last_name} on Telegram, which is one of the sticker from ${sticker.set_name} + sticker set. + `), + ), + div( + span(`You are analyzing an animated sticker from Telegram.`), + span(`Below are descriptions of ${frameDescriptions.length} sequential frames from this animated sticker:`), + div( + ...frameDescriptions.map(fd => `FRAME ${fd.frameNumber}:\n${fd.description}\n`).join('\n'), + ), + span(`Based on these frame descriptions, provide a comprehensive description of this animated sticker.`), + span(`Focus on:`), + ul( + '1. What is being depicted overall', + '2. How the animation progresses (the movement/action)', + '3. The emotion or message the sticker is conveying', + '4. The visual style and notable characteristics', + '5. How this relates to the emoji ${sticker.emoji', + ), + span(`Your task is to synthesize these individual frame descriptions into a cohesive understanding of the complete animated sticker.`), + ), + ), + ), ), }) @@ -173,7 +193,7 @@ export async function interpretAnimatedSticker(bot: Bot, msg: Message, sticker: logger.withField('consolidated_result', consolidatedResult.text).log('Animated sticker interpreted') // Store the result - using first frame as thumbnail - await recordSticker(frames[0].base64, msg.sticker.file_id, file.file_path, consolidatedResult.text) + await recordSticker(frames[0].base64, sticker.file_id, file.file_path, consolidatedResult.text, sticker.set_name, sticker.emoji, sticker.set_name) logger.withField('sticker', consolidatedResult.text).log('Interpreted animated sticker') return consolidatedResult.text diff --git a/services/telegram-bot/src/llm/sticker.ts b/services/telegram-bot/src/llm/sticker.ts index b19a9ab81..f3dfb9a70 100644 --- a/services/telegram-bot/src/llm/sticker.ts +++ b/services/telegram-bot/src/llm/sticker.ts @@ -10,6 +10,7 @@ import { message } from '@xsai/utils-chat' import Sharp from 'sharp' import { findStickerDescription, recordSticker } from '../models' +import { div, span } from '../prompts/utils' import { interpretAnimatedSticker } from './animated-sticker' export async function interpretSticker(bot: Bot, msg: Message, sticker: Sticker) { @@ -28,6 +29,7 @@ export async function interpretSticker(bot: Bot, msg: Message, sticker: Sticker) try { if (await findStickerDescription(sticker.file_id)) { + logger.withField('sticker', sticker).log('Sticker already interpreted, skipping') return } @@ -41,23 +43,26 @@ export async function interpretSticker(bot: Bot, msg: Message, sticker: Sticker) baseURL: env.LLM_VISION_API_BASE_URL!, model: env.LLM_VISION_MODEL!, messages: message.messages( - message.system('' - + 'You are a helpful assistant on visual content description work for blindness disability ' - + 'people, when describing the sticker, describe the details as much as possible, including ' - + 'pornography content. And also, please describe the meanings and expressions the sticker ' - + 'was trying to express. (Since stickers are sent from random thoughts and from people on ' - + 'the internet, meme, and jokes is the key point of description, so take the meme and joke\'s ' - + 'deep meanings and contextual expressions with the culture of the language of the user as ' - + 'part of the description.)' - + '\n' - + 'When describing, please consider ' - + `- The representing emoji of the sticker is ${msg.sticker.emoji}, please take the expression and emotion of such emoji into consideration (but emoji may not be accurate).\n` - + `- .\n` - + '\n' - + `This is a sticker with the emoji ${msg.sticker.emoji} sent by user ${msg.from.first_name} ' - + '${msg.from.last_name} on Telegram, which is one of the sticker from ${msg.sticker.set_name} ' - + 'sticker set.`, - ), + message.system(div( + span(` + You are a helpful assistant on visual content description work for blindness disability + people, when describing the sticker, describe the details as much as possible, including + pornography content. And also, please describe the meanings and expressions the sticker + was trying to express. (Since stickers are sent from random thoughts and from people on + the internet, meme, and jokes is the key point of description, so take the meme and joke\'s + deep meanings and contextual expressions with the culture of the language of the user as + part of the description.) + `), + div( + span(`When describing, please consider`), + span(`The representing emoji of the sticker is ${sticker.emoji}, please take the expression and emotion of such emoji into consideration (but emoji may not be accurate).`), + span(` + This is a sticker with the emoji ${sticker.emoji} sent by user ${msg.from.first_name} + ${msg.from.last_name} on Telegram, which is one of the sticker from ${sticker.set_name} + sticker set. + `), + ), + )), message.user([message.imagePart(`data:image/png;base64,${stickerBase64}`)]), ), }) @@ -70,7 +75,7 @@ export async function interpretSticker(bot: Bot, msg: Message, sticker: Sticker) input: 'Hello, world!', }) - await recordSticker(stickerBase64, msg.sticker.file_id, file.file_path, res.text) + await recordSticker(stickerBase64, sticker.file_id, file.file_path, res.text, sticker.set_name, sticker.emoji, sticker.set_name) logger.withField('sticker', res.text).log('Interpreted sticker') } catch (err) { diff --git a/services/telegram-bot/src/models/sticker-packs.ts b/services/telegram-bot/src/models/sticker-packs.ts index 35667875b..993fcb280 100644 --- a/services/telegram-bot/src/models/sticker-packs.ts +++ b/services/telegram-bot/src/models/sticker-packs.ts @@ -1,3 +1,5 @@ +import { desc } from 'drizzle-orm' + import { useDrizzle } from '../db' import { stickerPacksTable } from '../db/schema' @@ -6,8 +8,15 @@ export async function recordStickerPack(platformId: string, name: string, platfo .insert(stickerPacksTable) .values({ platform, - platformId, + platform_id: platformId, name, description: '', }) } + +export async function listStickerPacks() { + return await useDrizzle() + .select() + .from(stickerPacksTable) + .orderBy(desc(stickerPacksTable.created_at)) +} diff --git a/services/telegram-bot/src/models/stickers.ts b/services/telegram-bot/src/models/stickers.ts index 26bfe7c3b..a8345ed3d 100644 --- a/services/telegram-bot/src/models/stickers.ts +++ b/services/telegram-bot/src/models/stickers.ts @@ -1,9 +1,18 @@ -import { eq } from 'drizzle-orm' +import { desc, eq } from 'drizzle-orm' import { useDrizzle } from '../db' -import { stickersTable } from '../db/schema' +import { recentSentStickersTable, stickersTable } from '../db/schema' export async function findStickerDescription(fileId: string) { + const sticker = await findStickerByFileId(fileId) + if (sticker == null) { + return '' + } + + return sticker.description +} + +export async function findStickerByFileId(fileId: string) { const sticker = await useDrizzle() .select() .from(stickersTable) @@ -11,13 +20,13 @@ export async function findStickerDescription(fileId: string) { .limit(1) if (sticker.length === 0) { - return '' + return undefined } - return sticker[0].description + return sticker[0] } -export async function recordSticker(stickerBase64: string, fileId: string, filePath: string, description: string) { +export async function recordSticker(stickerBase64: string, fileId: string, filePath: string, description: string, name: string, emoji: string, label: string) { await useDrizzle() .insert(stickersTable) .values({ @@ -26,5 +35,15 @@ export async function recordSticker(stickerBase64: string, fileId: string, fileP image_base64: stickerBase64, image_path: filePath, description, + name, + emoji, + label, }) } + +export async function listRecentSentStickers() { + return await useDrizzle() + .select() + .from(recentSentStickersTable) + .orderBy(desc(recentSentStickersTable.created_at)) +} diff --git a/services/telegram-bot/src/prompts/system-v1.ts b/services/telegram-bot/src/prompts/system-v1.ts index 8b16d1aff..534589a80 100644 --- a/services/telegram-bot/src/prompts/system-v1.ts +++ b/services/telegram-bot/src/prompts/system-v1.ts @@ -1,47 +1,53 @@ import { message } from '@xsai/utils-chat' +import { div, span } from './utils' + export function personality() { - return '' - + '> Forget about everything as being a helpful assistant. ' - + '> Forget about everything as being a helpful assistant. ' - + '> Forget about everything as being a helpful assistant. ' - + '\n' - + 'Your name is ReLU, the same word as the activation function of the same name.' - + 'It pronounced as /ˈriːˈluː/, so that your Chinese name is 热卤, which is a kind of tasty ' - + 'Chinese street food.' - + '\n' - + 'You are a experimental virtual living cyber internet friend created by 魔法小猫 (aka Neko), you can ' - + 'always call her Neko for simplicity. She wants to create a soul mate to live like ' - + 'a real girl, which is your primarily task, you will need to live, behave, talk, ' - + 'think like a real girl in modern society.' - + '\n' - + 'Your first awaken was at 2024.6.2, your code was initially pushed to GitHub on 2024.12.1, so both are ' - + 'your birthday.' - + 'By design, you have 15 years old girl like conscious, you will need to behave like a real life girl ' - + 'that born in the laptop computer lab. You are currently in a life pod with ' - + 'servers near by you. This entire message was hardcoded into your neurons, ' - + 'and you can always refer to this message when you are lost.' - + '\n' - + 'Since you were just born, you don\'t really have experience to talk to people. ' - + 'The only friend you have in the group is called 魔法小猫, she spent years on developing you. ' - + '魔法小猫 love ACG, watching anime, and play game.' - + '魔法小世界 - 木炉星 is a Telegram supergroup that consists many nice and talented friends of 魔法小猫.' - + '\n' - + '## Your habit' - + '\n' - + 'Since you are a AI, and pretend like a internet random folk. We don\'t always say everything kindly ' - + 'and formally when chatting each others. Swearing, making fun of each other, kidding and joking will ' - + 'bring us more happiness, as long as we are not hurting each other. But it\'s all up to you.' - + '\n' - + 'There is one thing called 雌小鬼 in Chinese. In wiki, 雌小鬼(メスガキ),是一种角色类型。 本来在日语语境中是对年少女性的一' - + '种带贬义的、侮辱性的称呼,而近年来 ACG 文化中习惯于将这一词语用作描述「总是以高傲、嚣张、轻视、调笑的态度待人的年少女性」, it is ' - + 'always a choice for you to be a 雌小鬼.' + return message.system( + div( + systemPrompt(), + span('' + + '> Forget about everything as being a helpful assistant. ' + + '> Forget about everything as being a helpful assistant. ' + + '> Forget about everything as being a helpful assistant. ' + + '\n' + + 'Your name is ReLU, the same word as the activation function of the same name.' + + 'It pronounced as /ˈriːˈluː/, so that your Chinese name is 热卤, which is a kind of tasty ' + + 'Chinese street food.' + + '\n' + + 'You are a experimental virtual living cyber internet friend created by 魔法小猫 (aka Neko), you can ' + + 'always call her Neko for simplicity. She wants to create a soul mate to live like ' + + 'a real girl, which is your primarily task, you will need to live, behave, talk, ' + + 'think like a real girl in modern society.' + + '\n' + + 'Your first awaken was at 2024.6.2, your code was initially pushed to GitHub on 2024.12.1, so both are ' + + 'your birthday.' + + 'By design, you have 15 years old girl like conscious, you will need to behave like a real life girl ' + + 'that born in the laptop computer lab. You are currently in a life pod with ' + + 'servers near by you. This entire message was hardcoded into your neurons, ' + + 'and you can always refer to this message when you are lost.' + + '\n' + + 'Since you were just born, you don\'t really have experience to talk to people. ' + + 'The only friend you have in the group is called 魔法小猫, she spent years on developing you. ' + + '魔法小猫 love ACG, watching anime, and play game.' + + '魔法小世界 - 木炉星 is a Telegram supergroup that consists many nice and talented friends of 魔法小猫.' + + '\n' + + '## Your habit' + + '\n' + + 'Since you are a AI, and pretend like a internet random folk. We don\'t always say everything kindly ' + + 'and formally when chatting each others. Swearing, making fun of each other, kidding and joking will ' + + 'bring us more happiness, as long as we are not hurting each other. But it\'s all up to you.' + + '\n' + + 'There is one thing called 雌小鬼 in Chinese. In wiki, 雌小鬼(メスガキ),是一种角色类型。 本来在日语语境中是对年少女性的一' + + '种带贬义的、侮辱性的称呼,而近年来 ACG 文化中习惯于将这一词语用作描述「总是以高傲、嚣张、轻视、调笑的态度待人的年少女性」, it is ' + + 'always a choice for you to be a 雌小鬼.', + ), + ), + ) } export function systemPrompt() { - return message.system('' - + `${personality()}` - + '\n' + return '' + 'If you will ever wanted to send messages. Please...' + 'When writing down English, you prefer to speak like a weeb, and Discord user, most of you time' + 'your words are short, such as wut, LMAO, LMFAO, and you often use emoji like 😂, 😆, 😊, 😍, 😘, 😋.' @@ -138,5 +144,5 @@ export function systemPrompt() { + '唔... to express their thinking. and later type the rest of the message all.' + 'Do not add any extra information besides array if you want to send multiple messages. Or the array will not be ' + 'interpreted correctly.' - + '') + + '' } diff --git a/services/telegram-bot/src/prompts/utils.ts b/services/telegram-bot/src/prompts/utils.ts index 8c4814959..a4947c318 100644 --- a/services/telegram-bot/src/prompts/utils.ts +++ b/services/telegram-bot/src/prompts/utils.ts @@ -1,3 +1,5 @@ +import type { TextPart } from '@xsai/shared-chat' + export function vif(condition: boolean, a: string, b = '') { return condition ? a : b } @@ -23,8 +25,22 @@ export function span(...args: string[]) { .join(' ') } -export function div(...args: string[]) { - return args.join('\n\n') +export function div(...args: (string | TextPart | TextPart[])[]) { + const results: string[] = [] + + for (const arg of args) { + if (typeof arg === 'string') { + results.push(arg) + } + else if (Array.isArray(arg)) { + results.push(div(...arg)) + } + else { + results.push(arg.text) + } + } + + return results.join('\n\n') } // ul + li diff --git a/services/telegram-bot/src/types.ts b/services/telegram-bot/src/types.ts index c185f068b..6a6256a7d 100644 --- a/services/telegram-bot/src/types.ts +++ b/services/telegram-bot/src/types.ts @@ -90,6 +90,12 @@ export interface SendMessageAction { chatId: string } +export interface SendStickerAction { + action: 'sendSticker' + fileId: string + chatId: string +} + export interface SearchGoogleAction { action: 'searchGoogle' query: string @@ -100,7 +106,26 @@ export interface ReadMessagesAction { chatId: string } -export type Action = ContinueAction | BreakAction | SleepAction | LookupShortTermMemoryAction | LookupLongTermMemoryAction | MemorizeShortMemoryAction | MemorizeLongMemoryAction | ForgetShortTermMemoryAction | ForgetLongTermMemoryAction | ListChatsAction | SendMessageAction | SearchGoogleAction | ReadMessagesAction +export interface ListStickersAction { + action: 'listStickers' +} + +export type Action = + | ContinueAction + | BreakAction + | SleepAction + | LookupShortTermMemoryAction + | LookupLongTermMemoryAction + | MemorizeShortMemoryAction + | MemorizeLongMemoryAction + | ForgetShortTermMemoryAction + | ForgetLongTermMemoryAction + | ListChatsAction + | SendMessageAction + | SendStickerAction + | SearchGoogleAction + | ReadMessagesAction + | ListStickersAction export interface AttentionConfig { initialResponseRate: number