Skip to content

Commit cde3ec2

Browse files
authored
Merge pull request #3 from Recoupable-com/sweetmantech/myc-3156-apichatgenerate-recoup-chat-response
/api/chat/generate - Recoup Chat response
2 parents 7bc19b1 + 103ec7a commit cde3ec2

7 files changed

Lines changed: 220 additions & 14 deletions

File tree

bot.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
require("dotenv").config();
2-
const TelegramBot = require("node-telegram-bot-api");
1+
import "dotenv/config";
2+
import TelegramBot from "node-telegram-bot-api";
3+
import { checkEnvVariables } from "./lib/checkEnvVariables.js";
4+
import { handleMessage } from "./lib/handleMessage.js";
35

4-
if (!process.env.TELEGRAM_BOT_TOKEN) {
5-
throw new Error("TELEGRAM_BOT_TOKEN environment variable is required");
6-
}
6+
checkEnvVariables();
77

88
const telegramBot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, {
99
polling: true,
1010
});
1111

12+
// Recoup Chat API configuration
13+
// API Docs: https://docs.recoupable.com/chat/generate
14+
const recoupConfig = {
15+
accountId: process.env.RECOUP_ACCOUNT_ID,
16+
artistId: process.env.RECOUP_ARTIST_ID,
17+
roomId: process.env.RECOUP_ROOM_ID,
18+
excludeTools: [],
19+
};
20+
1221
telegramBot.on("error", (error) => {
1322
console.error("Telegram client error:", error);
1423
});
1524

16-
telegramBot.on("message", (msg) => {
17-
const chatId = msg.chat.id;
18-
const messageText = msg.text;
19-
20-
telegramBot.sendMessage(
21-
chatId,
22-
`Welcome to the bot! Here's what you sent me: ${messageText}`
23-
);
24-
});
25+
telegramBot.on("message", (msg) =>
26+
handleMessage(msg, telegramBot, recoupConfig)
27+
);

lib/checkEnvVariables.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Validates that all required environment variables are present
3+
* @throws {Error} If any required environment variable is missing
4+
*/
5+
export function checkEnvVariables() {
6+
const requiredVars = [
7+
"TELEGRAM_BOT_TOKEN",
8+
"RECOUP_ACCOUNT_ID",
9+
"RECOUP_ARTIST_ID",
10+
];
11+
12+
const missingVars = requiredVars.filter((varName) => !process.env[varName]);
13+
14+
if (missingVars.length > 0) {
15+
throw new Error(
16+
`Missing required environment variables: ${missingVars.join(", ")}`
17+
);
18+
}
19+
}

lib/const.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Recoup Chat API configuration
2+
export const RECOUP_CHAT_API = "https://chat.recoupable.com/api/chat/generate";
3+
4+
// Telegram configuration
5+
export const TELEGRAM_MAX_LENGTH = 4096;

lib/handleMessage.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { generateChatResponse } from "./recoup/generateChatResponse.js";
2+
import { splitMessage } from "./telegram/splitMessage.js";
3+
4+
/**
5+
* Handles incoming Telegram messages and generates AI responses
6+
* @param {Object} msg - Telegram message object
7+
* @param {Object} telegramBot - Telegram bot instance
8+
* @param {Object} recoupConfig - Recoup API configuration
9+
*/
10+
export async function handleMessage(msg, telegramBot, recoupConfig) {
11+
const chatId = msg.chat.id;
12+
const messageText = msg.text;
13+
14+
if (!messageText) {
15+
return; // Ignore non-text messages
16+
}
17+
18+
try {
19+
// Create user message
20+
const userMessage = {
21+
id: `msg-${Date.now()}`,
22+
role: "user",
23+
parts: [
24+
{
25+
type: "text",
26+
text: messageText,
27+
},
28+
],
29+
};
30+
31+
// Send typing indicator
32+
await telegramBot.sendChatAction(chatId, "typing");
33+
34+
// Get AI response from Recoup Chat API
35+
const aiResponse = await generateChatResponse([userMessage], recoupConfig);
36+
37+
// Split response into chunks if it's too long
38+
const messageChunks = splitMessage(aiResponse);
39+
40+
// Send each chunk sequentially
41+
for (const chunk of messageChunks) {
42+
await telegramBot.sendMessage(chatId, chunk);
43+
// Small delay between chunks to avoid rate limiting
44+
if (messageChunks.length > 1) {
45+
await new Promise((resolve) => setTimeout(resolve, 100));
46+
}
47+
}
48+
} catch (error) {
49+
console.error("Error processing message:", error);
50+
await telegramBot.sendMessage(
51+
chatId,
52+
"Sorry, I encountered an error processing your message. Please try again."
53+
);
54+
}
55+
}

lib/recoup/generateChatResponse.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { RECOUP_CHAT_API } from "../const.js";
2+
3+
/**
4+
* Generates a chat response using the Recoup Chat API
5+
* @param {Array} messages - Array of UIMessage objects for conversation context
6+
* @param {Object} config - Configuration object
7+
* @param {string} config.accountId - Account ID for Recoup API
8+
* @param {string} config.artistId - Artist ID for Recoup API
9+
* @param {string} config.roomId - Room ID for the chat session
10+
* @param {Array} [config.excludeTools=[]] - Array of tool names to exclude
11+
* @returns {Promise<string>} The generated response text
12+
* @throws {Error} If the API request fails
13+
*/
14+
export async function generateChatResponse(messages, config) {
15+
const { accountId, artistId, roomId, excludeTools = [] } = config;
16+
17+
try {
18+
const response = await fetch(RECOUP_CHAT_API, {
19+
method: "POST",
20+
headers: {
21+
"Content-Type": "application/json",
22+
},
23+
body: JSON.stringify({
24+
messages,
25+
roomId,
26+
accountId,
27+
artistId,
28+
excludeTools,
29+
}),
30+
});
31+
32+
if (!response.ok) {
33+
throw new Error(`API error: ${response.status} ${response.statusText}`);
34+
}
35+
36+
const data = await response.json();
37+
38+
// Extract text from the response
39+
if (data.text && Array.isArray(data.text)) {
40+
const textParts = data.text
41+
.filter((part) => part.type === "text")
42+
.map((part) => part.text)
43+
.join("\n");
44+
return textParts || "I'm sorry, I couldn't generate a response.";
45+
}
46+
47+
return "I'm sorry, I couldn't generate a response.";
48+
} catch (error) {
49+
console.error("Error calling Recoup Chat API:", error);
50+
throw error;
51+
}
52+
}

lib/telegram/splitMessage.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { TELEGRAM_MAX_LENGTH } from "../const.js";
2+
3+
/**
4+
* Splits long messages into chunks that fit within Telegram's character limit
5+
* @param {string} text - The text to split
6+
* @param {number} maxLength - Maximum length per chunk (default: 4096)
7+
* @returns {string[]} Array of message chunks
8+
*/
9+
export function splitMessage(text, maxLength = TELEGRAM_MAX_LENGTH) {
10+
if (text.length <= maxLength) {
11+
return [text];
12+
}
13+
14+
const chunks = [];
15+
let currentChunk = "";
16+
17+
// Split by paragraphs first (double newline)
18+
const paragraphs = text.split("\n\n");
19+
20+
for (const paragraph of paragraphs) {
21+
// If adding this paragraph would exceed the limit
22+
if (currentChunk.length + paragraph.length + 2 > maxLength) {
23+
// If current chunk has content, push it
24+
if (currentChunk) {
25+
chunks.push(currentChunk.trim());
26+
currentChunk = "";
27+
}
28+
29+
// If the paragraph itself is too long, split by sentences
30+
if (paragraph.length > maxLength) {
31+
const sentences = paragraph.match(/[^.!?]+[.!?]+/g) || [paragraph];
32+
for (const sentence of sentences) {
33+
if (currentChunk.length + sentence.length > maxLength) {
34+
if (currentChunk) {
35+
chunks.push(currentChunk.trim());
36+
currentChunk = "";
37+
}
38+
// If even a single sentence is too long, split by words
39+
if (sentence.length > maxLength) {
40+
const words = sentence.split(" ");
41+
for (const word of words) {
42+
if (currentChunk.length + word.length + 1 > maxLength) {
43+
chunks.push(currentChunk.trim());
44+
currentChunk = word + " ";
45+
} else {
46+
currentChunk += word + " ";
47+
}
48+
}
49+
} else {
50+
currentChunk = sentence;
51+
}
52+
} else {
53+
currentChunk += sentence;
54+
}
55+
}
56+
} else {
57+
currentChunk = paragraph + "\n\n";
58+
}
59+
} else {
60+
currentChunk += paragraph + "\n\n";
61+
}
62+
}
63+
64+
// Push any remaining content
65+
if (currentChunk.trim()) {
66+
chunks.push(currentChunk.trim());
67+
}
68+
69+
return chunks;
70+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
{
22
"name": "recoup-telegram-bot",
33
"version": "1.0.0",
4+
"type": "module",
45
"main": "bot.js",
56
"scripts": {
7+
"start": "node bot.js",
68
"test": "echo \"Error: no test specified\" && exit 1"
79
},
810
"author": "sweetmantech",

0 commit comments

Comments
 (0)