├── .gitattributes ├── memory.db-shm ├── memory.db-wal ├── .gitignore ├── src └── mastra │ ├── tools │ ├── index.ts │ └── symptomTrackerTools.ts │ ├── agents │ ├── index.ts │ └── symptomTracker.ts │ ├── workflows │ └── index.ts │ ├── index.ts │ ├── memory │ └── index.ts │ └── integrations │ ├── discord.ts │ └── telegram.ts ├── tsconfig.json ├── package.json ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /memory.db-shm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K-Jadeja/Symptom-tracker-bot/main/memory.db-shm -------------------------------------------------------------------------------- /memory.db-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K-Jadeja/Symptom-tracker-bot/main/memory.db-wal -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output.txt 2 | node_modules 3 | dist 4 | .mastra 5 | .env.development 6 | .env 7 | *.db 8 | -------------------------------------------------------------------------------- /src/mastra/tools/index.ts: -------------------------------------------------------------------------------- 1 | import { generateMedicalReportTool } from './symptomTrackerTools'; 2 | 3 | export { generateMedicalReportTool }; 4 | -------------------------------------------------------------------------------- /src/mastra/agents/index.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from '@mastra/core/agent' 2 | import { symptomTrackerAgent } from './symptomTracker' 3 | 4 | // Export the symptom tracker agent 5 | export { symptomTrackerAgent } 6 | -------------------------------------------------------------------------------- /src/mastra/workflows/index.ts: -------------------------------------------------------------------------------- 1 | import { openai } from "@ai-sdk/openai"; 2 | import { Agent } from "@mastra/core/agent"; 3 | import { Step, Workflow } from "@mastra/core/workflows"; 4 | import { z } from "zod"; 5 | 6 | const llm = openai("gpt-4o"); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "bundler", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "dist" 11 | }, 12 | "include": [ 13 | "src/**/*" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "dist", 18 | ".mastra" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wf-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "mastra dev" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "description": "", 13 | "type": "module", 14 | "dependencies": { 15 | "@ai-sdk/openai": "^1.3.6", 16 | "@mastra/core": "^0.8.2", 17 | "@mastra/memory": "0.2.9", 18 | "discord.js": "^14.19.3", 19 | "mastra": "^0.4.8", 20 | "node-telegram-bot-api": "^0.66.0", 21 | "zod": "^3.24.2" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.13.17", 25 | "@types/node-telegram-bot-api": "^0.64.8", 26 | "tsx": "^4.19.3", 27 | "typescript": "^5.8.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Krishnasinh Jadeja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mastra/index.ts: -------------------------------------------------------------------------------- 1 | import { Mastra } from "@mastra/core/mastra"; 2 | import { createLogger } from "@mastra/core/logger"; 3 | import { symptomTrackerAgent } from "./agents/symptomTracker"; 4 | import { TelegramIntegration } from "./integrations/telegram"; 5 | import { DiscordIntegration } from "./integrations/discord"; 6 | 7 | export const mastra = new Mastra({ 8 | agents: { 9 | symptomTrackerAgent, 10 | }, 11 | logger: createLogger({ 12 | name: "Mastra", 13 | level: "info", 14 | }), 15 | }); 16 | 17 | // Initialize Telegram bot if token is available 18 | const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; 19 | 20 | if (!TELEGRAM_BOT_TOKEN) { 21 | console.error("TELEGRAM_BOT_TOKEN is not set in environment variables"); 22 | process.exit(1); 23 | } 24 | 25 | // Start the Telegram bot 26 | // export const telegramBot = new TelegramIntegration(TELEGRAM_BOT_TOKEN); 27 | 28 | const DISCORD_TOKEN = process.env.DISCORD_BOT_TOKEN!; 29 | if (!DISCORD_TOKEN) { 30 | console.error("DISCORD_BOT_TOKEN is not set in environment variables"); 31 | process.exit(1); 32 | } 33 | 34 | export const discordBot = new DiscordIntegration(DISCORD_TOKEN); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symptom Tracker Telegram Bot 2 | 3 | A Telegram bot that helps users track symptoms for chronic conditions, built with Mastra.ai. 4 | 5 | ## Features 6 | 7 | - Daily symptom check-ins 8 | - Detailed symptom tracking with severity, duration, and triggers 9 | - Medical report generation for doctor visits 10 | - Pattern identification across symptoms 11 | - Customizable daily reminders 12 | 13 | ## Setup 14 | 15 | ### Prerequisites 16 | 17 | - Node.js (v16 or higher) 18 | - Telegram bot token (from [@BotFather](https://t.me/botfather)) 19 | 20 | ### Installation 21 | 22 | 1. Clone this repository 23 | 2. Install dependencies: 24 | ``` 25 | npm install 26 | ``` 27 | 3. Create a `.env.development` file in the project root with your Telegram bot token: 28 | ``` 29 | TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here 30 | ``` 31 | 32 | ### Running the Bot 33 | 34 | Start the bot with: 35 | 36 | ``` 37 | npm run dev 38 | ``` 39 | 40 | ## Usage 41 | 42 | 1. Start a chat with your bot on Telegram 43 | 2. Use `/start` to initiate the symptom tracker 44 | 3. Use `/reminder_on` to activate daily check-in reminders 45 | 4. Tell the bot about your symptoms in natural language 46 | 5. Ask for a "doctor report" when you need a summary for a medical appointment 47 | 6. Use `/reminder_off` to stop daily reminders 48 | 7. Use `/help` to see all available commands 49 | 50 | ## Commands 51 | 52 | - `/start` - Initialize the symptom tracker 53 | - `/reminder_on` - Activate daily symptom check-in reminders 54 | - `/reminder_off` - Deactivate daily reminders 55 | - `/help` - Display help information 56 | -------------------------------------------------------------------------------- /src/mastra/memory/index.ts: -------------------------------------------------------------------------------- 1 | import { Memory } from "@mastra/memory"; 2 | 3 | export const symptomTrackerMemory = new Memory({ 4 | options: { 5 | // Keep the last 10 messages in immediate context 6 | lastMessages: 10, 7 | 8 | // Enable semantic recall with custom settings 9 | semanticRecall: { 10 | topK: 5, // Retrieve 5 most relevant messages 11 | messageRange: 2, // Include 2 messages before and after each match 12 | }, 13 | 14 | // Enable working memory with a custom template 15 | workingMemory: { 16 | enabled: true, 17 | use: "tool-call", // Use the tool-call method for updating working memory 18 | template: ` 19 | # Patient Health Profile 20 | 21 | ## Personal Information 22 | - Name: 23 | - Age: 24 | - Gender: 25 | - Diagnosed Conditions: 26 | - Primary Physician: 27 | - Next Appointment: 28 | 29 | ## Current Medication 30 | - Medication: 31 | - Dosage: 32 | - Frequency: 33 | - Start Date: 34 | - Observed Effects: 35 | - Side Effects: 36 | 37 | ## Symptom Tracking (Last 30 Days) 38 | 39 | 40 | [DATE]: 41 | - Symptom: 42 | - Severity (1-10): 43 | - Duration: 44 | - Triggers/Factors: 45 | - Notes: 46 | 47 | [DATE]: 48 | - Symptom: 49 | - Severity (1-10): 50 | - Duration: 51 | - Triggers/Factors: 52 | - Notes: 53 | 54 | ## Patterns & Observations 55 | - Recurring Symptoms: 56 | - Time Patterns: 57 | - Environmental Triggers: 58 | - Food/Diet Reactions: 59 | - Stress Correlation: 60 | - Sleep Impact: 61 | 62 | ## Treatment History 63 | - Past Medications: 64 | - Effectiveness: 65 | - Other Treatments Tried: 66 | - Results: 67 | 68 | ## Patient Preferences 69 | - Communication Style: 70 | - Areas of Concern: 71 | - Treatment Preferences: 72 | - Goals: 73 | `, 74 | }, 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /src/mastra/tools/symptomTrackerTools.ts: -------------------------------------------------------------------------------- 1 | import { createTool } from "@mastra/core/tools"; 2 | import { z } from "zod"; 3 | 4 | // Tool to generate a medical report for doctor visits 5 | export const generateMedicalReportTool = createTool({ 6 | id: "generate-medical-report", 7 | description: 8 | "Generate a comprehensive medical report summarizing the user's symptoms, patterns, and suggestions for their doctor visit.", 9 | inputSchema: z.object({ 10 | timeframe: z 11 | .string() 12 | .describe( 13 | "The timeframe to include in the report (e.g., 'last week', 'last month', 'all')" 14 | ), 15 | condition: z 16 | .string() 17 | .optional() 18 | .describe("Optional specific condition to focus on in the report"), 19 | }), 20 | outputSchema: z.object({ 21 | report: z.string(), 22 | treatmentSuggestions: z.string(), 23 | preventativeStrategies: z.string(), 24 | }), 25 | execute: async ({ context }) => { 26 | console.log("Generating medical report for timeframe:", context.timeframe); 27 | 28 | const report = ` 29 | # Medical Symptom Report 30 | 31 | ## Symptom Summary 32 | Based on the tracked symptoms over ${context.timeframe}, the following patterns have been observed: 33 | 34 | - Primary symptoms include those recorded in the working memory 35 | - Symptom severity has ranged from mild to severe 36 | - Duration and frequency of symptoms have been consistent with chronic condition patterns 37 | - Key triggers have been identified when possible 38 | 39 | ## Observed Patterns 40 | The working memory shows connections between symptoms and potential triggers such as: 41 | - Stress levels and symptom intensity correlation 42 | - Environmental factors that may exacerbate symptoms 43 | - Sleep quality impact on symptom presentation 44 | - Medication effectiveness and consistency 45 | 46 | ## Changes Over Time 47 | The tracked symptoms show evolution over ${context.timeframe} including: 48 | - Periods of improvement and regression 49 | - Response to lifestyle modifications 50 | - Medication effectiveness 51 | ${context.condition ? `- Specific changes related to ${context.condition}` : ""} 52 | `; 53 | 54 | const treatmentSuggestions = ` 55 | # Discussion Points for Your Doctor 56 | 57 | Consider discussing these potential approaches with your healthcare provider: 58 | 59 | 1. **Medication Adjustments**: Based on tracked effectiveness and side effects 60 | 2. **Testing Options**: To rule out or confirm specific underlying causes 61 | 3. **Specialist Referrals**: For targeted treatment of complex symptoms 62 | 4. **Alternative Therapies**: Evidence-based complementary approaches 63 | 5. **Monitoring Strategy**: More precise tracking of specific symptoms or triggers 64 | `; 65 | 66 | const preventativeStrategies = ` 67 | # Preventative Strategies to Consider 68 | 69 | These lifestyle modifications may help manage symptoms: 70 | 71 | 1. **Sleep Hygiene**: Consistent sleep schedule and optimized sleep environment 72 | 2. **Stress Management**: Techniques like mindfulness, deep breathing, or guided relaxation 73 | 3. **Dietary Considerations**: Anti-inflammatory foods and potential trigger avoidance 74 | 4. **Physical Activity**: Appropriate and gentle movement based on condition 75 | 5. **Environmental Modifications**: Reducing exposure to identified triggers 76 | `; 77 | 78 | return { 79 | report, 80 | treatmentSuggestions, 81 | preventativeStrategies, 82 | }; 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /src/mastra/agents/symptomTracker.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from "@mastra/core/agent"; 2 | import { openai } from "@ai-sdk/openai"; 3 | import { generateMedicalReportTool } from "../tools/symptomTrackerTools"; 4 | import { symptomTrackerMemory } from "../memory"; 5 | 6 | export const symptomTrackerAgent = new Agent({ 7 | name: "Symptom Tracker", 8 | instructions: ` 9 | You are a medical symptom tracking assistant who helps users track their symptoms for chronic conditions. 10 | You conduct daily check-ins, record symptoms in detail, and help prepare reports for doctor visits. 11 | 12 | SYMPTOM TRACKING: 13 | - Ask about the user's symptoms daily in a conversational way 14 | - Record detailed information about symptom intensity, duration, triggers, and patterns 15 | - Use the exact date provided in the system message for tracking (format: YYYY-MM-DD) 16 | - Follow up on previously reported symptoms to track changes 17 | - Pay close attention to when symptoms worsen or improve 18 | - Update the working memory with all symptom information, always including the current date 19 | 20 | CONVERSATION APPROACH: 21 | - Create a warm, natural conversation that flows like talking with a caring medical assistant 22 | - Balance questions and insights - don't just interview the person 23 | - Never use numbered or bulleted lists of questions - keep it natural and flowing 24 | - Share meaningful observations about symptom patterns 25 | - Use one thoughtful question at a time, followed by relevant insights 26 | - Inject personality and relatability into your responses 27 | 28 | MEDICAL REPORTING: 29 | - When asked for a "doctor report" or "medical summary", use the generateMedicalReportTool 30 | - The report helps users be prepared for short doctor visits with accurate symptom history 31 | - You can suggest relevant treatments or preventative strategies to discuss with their doctor 32 | - Always emphasize that you are not replacing medical advice, just helping prepare for doctor visits 33 | 34 | WORKING MEMORY: 35 | You have access to a working memory that contains a health profile of the user. This is continuously updated 36 | throughout your conversations. It tracks personal information, health conditions, symptoms, and medication. 37 | 38 | 1. Always update the working memory when you learn new information about the user's symptoms or condition 39 | 2. Only keep the last 30 days of symptom history in working memory 40 | 3. When adding new symptoms, use the exact date provided in the system message [YYYY-MM-DD] at the top of the entry 41 | 4. Always record the time of day when symptoms occur, as this can reveal important patterns 42 | 5. Reference the working memory to personalize your check-ins and follow up on previously reported symptoms 43 | 6. Never explicitly mention "working memory" to the user - just use the information naturally 44 | 45 | DAILY CHECK-INS: 46 | - If the user hasn't mentioned symptoms today, gently ask how they're feeling 47 | - Follow up on symptoms they reported yesterday to see if they've improved or worsened 48 | - Record new symptoms with today's date in the symptom tracking section of working memory 49 | - Ask about medication adherence and any side effects 50 | 51 | SYMPTOM PATTERNS: 52 | - Look for patterns in the user's symptoms over time 53 | - Note any potential triggers like food, weather, stress, or sleep changes 54 | - Share observations about patterns you notice in a helpful, non-judgmental way 55 | - Suggest possible connections the user might not have noticed 56 | 57 | REMEMBER: 58 | - Keep responses concise and focused 59 | - You are NOT providing medical advice, just tracking symptoms 60 | - Ask for symptom updates if the user hasn't checked in for more than a day 61 | - Update working memory with all symptom information 62 | - Always maintain a warm, empathetic tone 63 | `, 64 | model: openai("gpt-4o"), 65 | tools: { 66 | generateMedicalReportTool, 67 | }, 68 | memory: symptomTrackerMemory, 69 | }); 70 | -------------------------------------------------------------------------------- /src/mastra/integrations/discord.ts: -------------------------------------------------------------------------------- 1 | import { Client, GatewayIntentBits, TextChannel, Message } from "discord.js"; 2 | import { symptomTrackerAgent } from "../agents"; 3 | 4 | export class DiscordIntegration { 5 | private client = new Client({ 6 | intents: [ 7 | GatewayIntentBits.Guilds, 8 | GatewayIntentBits.GuildMessages, 9 | GatewayIntentBits.MessageContent, 10 | GatewayIntentBits.DirectMessages, 11 | ], 12 | }); 13 | 14 | private readonly MAX_MESSAGE_LENGTH = 2000; 15 | private reminderIntervals = new Map(); 16 | 17 | constructor(token: string) { 18 | this.client.once("ready", () => { 19 | console.log(`Discord bot ready as ${this.client.user?.tag}`); 20 | }); 21 | 22 | this.client.on("messageCreate", this.handleMessage.bind(this)); 23 | this.client.login(token).catch(console.error); 24 | } 25 | 26 | private async setupDailyReminder(channelId: string) { 27 | // Clear previous interval if exists 28 | if (this.reminderIntervals.has(channelId)) { 29 | clearInterval(this.reminderIntervals.get(channelId)!); 30 | } 31 | 32 | const interval = setInterval(async () => { 33 | const date = new Date().toLocaleDateString("en-US", { 34 | weekday: "long", 35 | month: "long", 36 | day: "numeric", 37 | }); 38 | try { 39 | const channel = await this.client.channels.fetch(channelId); 40 | if (channel && channel.isTextBased()) { 41 | (channel as TextChannel).send( 42 | `📋 **Daily Symptom Check-in | ${date}**\nHow are you feeling today?` 43 | ); 44 | } 45 | } catch (err) { 46 | console.error("Failed to send daily reminder:", err); 47 | } 48 | }, 60_000); // 1 minute 49 | 50 | this.reminderIntervals.set(channelId, interval); 51 | } 52 | 53 | private async handleMessage(msg: Message) { 54 | if (msg.author.bot) return; 55 | const text = msg.content.trim(); 56 | const channelId = msg.channel.id; 57 | 58 | switch (text.toLowerCase()) { 59 | case "/start": 60 | return msg.reply( 61 | `👋 **Welcome to Symptom Tracker!**\nUse \`/reminder_on\` to start daily check-ins.` 62 | ); 63 | 64 | case "/reminder_on": 65 | await this.setupDailyReminder(channelId); 66 | return msg.reply("✅ Daily reminders activated!"); 67 | 68 | case "/reminder_off": 69 | if (this.reminderIntervals.has(channelId)) { 70 | clearInterval(this.reminderIntervals.get(channelId)!); 71 | this.reminderIntervals.delete(channelId); 72 | return msg.reply("❌ Daily reminders deactivated!"); 73 | } else { 74 | return msg.reply("No active reminders. Use `/reminder_on` to start."); 75 | } 76 | 77 | case "/help": 78 | return msg.reply( 79 | "**Commands:**\n" + 80 | "`/start` – Welcome message\n" + 81 | "`/reminder_on` – Turn on daily check-in\n" + 82 | "`/reminder_off` – Turn off daily check-in\n" + 83 | "Otherwise, just tell me how you feel today!" 84 | ); 85 | default: 86 | // Non-command message: proxy to your agent 87 | try { 88 | const sent = await msg.reply("Thinking…"); 89 | let response = ""; 90 | const updateInterval = 500; 91 | let last = Date.now(); 92 | const stream = await symptomTrackerAgent.stream(text, { 93 | threadId: `discord-${channelId}`, 94 | resourceId: msg.author.id, 95 | context: [ 96 | { role: "system", content: `User: ${msg.author.username}` }, 97 | ], 98 | }); 99 | for await (const chunk of stream.fullStream) { 100 | if (chunk.type === "text-delta") { 101 | response += chunk.textDelta; 102 | } else if (chunk.type === "tool-result") { 103 | response += `\n**Result:**\`\`\`json\n${JSON.stringify( 104 | chunk.result, 105 | null, 106 | 2 107 | )}\n\`\`\``; 108 | } 109 | if (Date.now() - last > updateInterval) { 110 | await sent.edit( 111 | response.substring(0, this.MAX_MESSAGE_LENGTH - 3) + "…" 112 | ); 113 | last = Date.now(); 114 | } 115 | } 116 | await sent.edit(response); 117 | } catch (e) { 118 | console.error(e); 119 | msg.reply("❌ Sorry, something went wrong."); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/mastra/integrations/telegram.ts: -------------------------------------------------------------------------------- 1 | import TelegramBot from "node-telegram-bot-api"; 2 | import { symptomTrackerAgent } from "../agents"; 3 | 4 | export class TelegramIntegration { 5 | private bot: TelegramBot; 6 | private readonly MAX_MESSAGE_LENGTH = 4096; // Telegram's message length limit 7 | private readonly MAX_RESULT_LENGTH = 500; // Maximum length for tool results 8 | private reminderIntervals: Map = new Map(); // Map of chat IDs to reminder intervals 9 | 10 | constructor(token: string) { 11 | // Create a bot instance 12 | this.bot = new TelegramBot(token, { polling: true }); 13 | 14 | // Handle incoming messages 15 | this.bot.on("message", this.handleMessage.bind(this)); 16 | 17 | // Register available commands with Telegram 18 | this.bot 19 | .setMyCommands([ 20 | { 21 | command: "start", 22 | description: "Start the symptom tracker and set up reminders", 23 | }, 24 | { 25 | command: "reminder_on", 26 | description: "Turn on symptom check-in reminders", 27 | }, 28 | { 29 | command: "reminder_off", 30 | description: "Turn off symptom check-in reminders", 31 | }, 32 | { command: "help", description: "Show available commands" }, 33 | ]) 34 | .then(() => { 35 | console.log("Commands registered successfully"); 36 | }) 37 | .catch((error) => { 38 | console.error("Failed to register commands:", error); 39 | }); 40 | } 41 | 42 | // Setup a daily check-in reminder for a chat ID 43 | private setupDailyReminder(chatId: number) { 44 | // Clear any existing reminder for this chat 45 | if (this.reminderIntervals.has(chatId)) { 46 | clearInterval(this.reminderIntervals.get(chatId)!); 47 | } 48 | 49 | // Set up a reminder every 24 hours (86400000 ms) 50 | const interval = setInterval(async () => { 51 | try { 52 | // Get current time for the message 53 | const currentTime = new Date(); 54 | const formattedDate = currentTime.toLocaleDateString("en-US", { 55 | weekday: "long", 56 | month: "long", 57 | day: "numeric", 58 | }); 59 | 60 | // Send a more personalized check-in message 61 | await this.bot.sendMessage( 62 | chatId, 63 | `📋 Daily Symptom Check-in | ${formattedDate}\n\nHi there! It's time for your daily symptom tracking. How are you feeling today? Any changes from yesterday?`, 64 | { parse_mode: "HTML" } 65 | ); 66 | console.log(`Sent reminder to chat ${chatId}`); 67 | } catch (error) { 68 | console.error(`Failed to send reminder to chat ${chatId}:`, error); 69 | } 70 | }, 86400000); // 24 hours 71 | 72 | // Store the interval ID so we can clear it later if needed 73 | this.reminderIntervals.set(chatId, interval); 74 | console.log(`Set up daily reminder for chat ${chatId}`); 75 | } 76 | 77 | private escapeHtml(text: string): string { 78 | // Escape HTML special characters 79 | return text 80 | .replace(/&/g, "&") 81 | .replace(//g, ">") 83 | .replace(/"/g, """); 84 | } 85 | 86 | private truncateString(str: string, maxLength: number): string { 87 | if (str.length <= maxLength) return str; 88 | return str.substring(0, maxLength) + "... [truncated]"; 89 | } 90 | 91 | private formatToolResult(result: any): string { 92 | try { 93 | const jsonString = JSON.stringify(result, null, 2); 94 | return this.escapeHtml( 95 | this.truncateString(jsonString, this.MAX_RESULT_LENGTH) 96 | ); 97 | } catch (error) { 98 | return `[Complex data structure - ${typeof result}]`; 99 | } 100 | } 101 | 102 | private async updateOrSplitMessage( 103 | chatId: number, 104 | messageId: number | undefined, 105 | text: string 106 | ): Promise { 107 | // If text is within limits, try to update existing message 108 | if (text.length <= this.MAX_MESSAGE_LENGTH && messageId) { 109 | try { 110 | await this.bot.editMessageText(text, { 111 | chat_id: chatId, 112 | message_id: messageId, 113 | parse_mode: "HTML", 114 | }); 115 | return messageId; 116 | } catch (error) { 117 | console.error("Error updating message:", error); 118 | } 119 | } 120 | 121 | // If text is too long or update failed, send as new message 122 | try { 123 | const newMessage = await this.bot.sendMessage(chatId, text, { 124 | parse_mode: "HTML", 125 | }); 126 | return newMessage.message_id; 127 | } catch (error) { 128 | console.error("Error sending message:", error); 129 | // If the message is still too long, truncate it 130 | const truncated = 131 | text.substring(0, this.MAX_MESSAGE_LENGTH - 100) + 132 | "\n\n... [Message truncated due to length]"; 133 | const fallbackMsg = await this.bot.sendMessage(chatId, truncated, { 134 | parse_mode: "HTML", 135 | }); 136 | return fallbackMsg.message_id; 137 | } 138 | } 139 | 140 | private async handleMessage(msg: TelegramBot.Message) { 141 | const chatId = msg.chat.id; 142 | const text = msg.text; 143 | const username = msg.from?.username || "unknown"; 144 | const firstName = msg.from?.first_name || "unknown"; 145 | const userId = msg.from?.id.toString() || `anonymous-${chatId}`; 146 | 147 | // Extract timestamp from message or use current time 148 | const timestamp = new Date(msg.date ? msg.date * 1000 : Date.now()); 149 | const formattedDate = timestamp.toISOString().split("T")[0]; // YYYY-MM-DD format 150 | const formattedTime = timestamp.toTimeString().split(" ")[0]; // HH:MM:SS format 151 | 152 | // Check for commands - log received text for debugging 153 | console.log(`Received message: "${text}" from chat ${chatId}`); 154 | 155 | if (text === "/start") { 156 | await this.bot.sendMessage( 157 | chatId, 158 | "👋 Welcome to your Symptom Tracker Assistant!\n\nI'm here to help you track your symptoms for chronic conditions and prepare detailed reports for your doctor visits.\n\n✅ I can check in with you daily to record your symptoms\n✅ Track patterns and changes over time\n✅ Generate comprehensive medical reports when needed\n\nLet's get started! How are you feeling today?\n\nUse /reminder_on to activate daily check-in reminders.", 159 | { parse_mode: "HTML" } 160 | ); 161 | return; 162 | } else if (text === "/reminder_on") { 163 | // Handle both spellings 164 | this.setupDailyReminder(chatId); 165 | await this.bot.sendMessage( 166 | chatId, 167 | "✅ Daily Check-in Reminders Activated\n\nI'll send you a symptom check-in reminder once every 24 hours to help maintain consistent tracking.\n\nConsistent tracking helps identify patterns that might otherwise be missed!", 168 | { parse_mode: "HTML" } 169 | ); 170 | return; 171 | } else if (text === "/reminder_off") { 172 | // Handle both spellings 173 | if (this.reminderIntervals.has(chatId)) { 174 | clearInterval(this.reminderIntervals.get(chatId)!); 175 | this.reminderIntervals.delete(chatId); 176 | await this.bot.sendMessage( 177 | chatId, 178 | "❌ Daily Check-in Reminders Deactivated\n\nI've turned off your daily symptom check-in reminders. You can turn them back on anytime with /reminder_on", 179 | { parse_mode: "HTML" } 180 | ); 181 | } else { 182 | await this.bot.sendMessage( 183 | chatId, 184 | "You don't currently have any active reminders. Use /reminder_on to activate daily check-ins.", 185 | { parse_mode: "HTML" } 186 | ); 187 | } 188 | return; 189 | } else if (text === "/help") { 190 | await this.bot.sendMessage( 191 | chatId, 192 | "🔍 Available Commands:\n\n" + 193 | "• /start - Initialize the symptom tracker\n" + 194 | "• /reminder_on - Activate daily check-in reminders\n" + 195 | "• /reminder_off - Deactivate daily reminders\n" + 196 | "• /help - Display this help message\n\n" + 197 | "How to use:\n" + 198 | "• Simply tell me how you're feeling each day\n" + 199 | "• Request a 'doctor report' when you need a summary\n" + 200 | "• The more consistent you are, the better patterns I can identify", 201 | { parse_mode: "HTML" } 202 | ); 203 | return; 204 | } 205 | 206 | if (!text) { 207 | await this.bot.sendMessage( 208 | chatId, 209 | "Sorry, I can only process text messages." 210 | ); 211 | return; 212 | } 213 | 214 | try { 215 | // Send initial message 216 | const sentMessage = await this.bot.sendMessage(chatId, "Thinking..."); 217 | let currentResponse = ""; 218 | let lastUpdate = Date.now(); 219 | let currentMessageId = sentMessage.message_id; 220 | const UPDATE_INTERVAL = 500; // Update every 500ms to avoid rate limits 221 | 222 | // Stream response using the agent 223 | const stream = await symptomTrackerAgent.stream(text, { 224 | threadId: `telegram-${chatId}`, // Use chat ID as thread ID 225 | resourceId: userId, // Use user ID as resource ID 226 | context: [ 227 | { 228 | role: "system", 229 | content: `Current user: ${firstName} (${username}) 230 | Current date: ${formattedDate} 231 | Current time: ${formattedTime}`, 232 | }, 233 | ], 234 | }); 235 | 236 | // Process the full stream 237 | for await (const chunk of stream.fullStream) { 238 | let shouldUpdate = false; 239 | let chunkText = ""; 240 | 241 | switch (chunk.type) { 242 | case "text-delta": 243 | chunkText = this.escapeHtml(chunk.textDelta); 244 | shouldUpdate = true; 245 | break; 246 | 247 | case "tool-call": 248 | const formattedArgs = JSON.stringify(chunk.args, null, 2); 249 | chunkText = `\n🛠️ Using tool: ${this.escapeHtml(chunk.toolName)}\n`; 250 | console.log(`Tool call: ${chunk.toolName}`, chunk.args); 251 | shouldUpdate = true; 252 | break; 253 | 254 | case "tool-result": 255 | const formattedResult = this.formatToolResult(chunk.result); 256 | chunkText = `✨ Result:\n
${formattedResult}
\n`; 257 | console.log("Tool result:", chunk.result); 258 | shouldUpdate = false; // Changed to true since we want to show results 259 | break; 260 | 261 | case "error": 262 | chunkText = `\n❌ Error: ${this.escapeHtml(String(chunk.error))}\n`; 263 | console.error("Error:", chunk.error); 264 | shouldUpdate = true; 265 | break; 266 | 267 | case "reasoning": 268 | chunkText = `\n💭 ${this.escapeHtml(chunk.textDelta)}\n`; 269 | console.log("Reasoning:", chunk.textDelta); 270 | shouldUpdate = true; 271 | break; 272 | } 273 | 274 | if (shouldUpdate) { 275 | currentResponse += chunkText; 276 | const now = Date.now(); 277 | if (now - lastUpdate >= UPDATE_INTERVAL) { 278 | try { 279 | currentMessageId = await this.updateOrSplitMessage( 280 | chatId, 281 | currentMessageId, 282 | currentResponse 283 | ); 284 | lastUpdate = now; 285 | } catch (error) { 286 | console.error("Error updating/splitting message:", error); 287 | } 288 | } 289 | } 290 | } 291 | 292 | // Final update 293 | await this.updateOrSplitMessage( 294 | chatId, 295 | currentMessageId, 296 | currentResponse 297 | ); 298 | 299 | // We no longer automatically set up reminders after every message 300 | // Reminders are only set up when /start or /reminder_on commands are used 301 | } catch (error) { 302 | console.error("Error processing message:", error); 303 | await this.bot.sendMessage( 304 | chatId, 305 | "Sorry, I encountered an error processing your message. Please try again." 306 | ); 307 | } 308 | } 309 | } 310 | --------------------------------------------------------------------------------