feat(telegram-bot): send stickers & process animated stickers

This commit is contained in:
Neko Ayaka
2025-04-08 02:33:36 +08:00
parent 6a692bdfab
commit 2e155cc6e0
11 changed files with 258 additions and 115 deletions
@@ -177,7 +177,7 @@ const activeTab = computed({
<DialogTitle text-2xl font-bold class="from-primary-500 to-primary-400 bg-gradient-to-r bg-clip-text text-transparent">
{{ selectedCard.name }}
</DialogTitle>
<div v-if="isActive" class="dark:text-primary-400 bg-primary-100 text-primary-600 dark:bg-primary-900/40 flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium">
<div v-if="isActive" class="bg-primary-100 text-primary-600 dark:bg-primary-900/40 dark:text-primary-400 flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium">
<div i-solar:check-circle-bold-duotone text-xs />
{{ t('settings.pages.card.active_badge') }}
</div>
@@ -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"
>
<div class="text-center">
<div i-solar:upload-minimalistic-bold class="text-primary-500 dark:text-primary-400 mb-2 text-5xl" />
<div i-solar:upload-minimalistic-bold class="dark:text-primary-400 text-primary-500 mb-2 text-5xl" />
<p font-medium text="primary-600 dark:primary-300">
{{ t('settings.pages.card.drop_here') }}
</p>
@@ -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')
}
+17 -10
View File
@@ -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: '<content>', chatId: '-1001231231234', reply_to_message_id: '151' },
example: { action: 'sendMessage', content: '<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: '<id of memory>' } }, description: 'Remove specific long-term memory entry from the memory component.' },
// { example: { action: 'searchGoogle', query: '<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
}
@@ -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
+23 -18
View File
@@ -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) {
@@ -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))
}
+24 -5
View File
@@ -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))
}
+45 -39
View File
@@ -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.'
+ '')
+ ''
}
+18 -2
View File
@@ -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
+26 -1
View File
@@ -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