Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled

Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
FROM node:20-alpine
# Install build dependencies
RUN apk add --no-cache python3 make g++
# Create app directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy app source
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Create logs directory with proper permissions
RUN mkdir -p logs && chown -R nodejs:nodejs logs
USER nodejs
# Expose port
EXPOSE 3002
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD node healthcheck.js || exit 1
# Start the service
CMD ["node", "src/app.js"]

View File

@@ -0,0 +1,29 @@
{
"name": "claude-agent-service",
"version": "1.0.0",
"description": "AI-powered decision making service using Claude",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.20.0",
"@hapi/hapi": "^21.3.2",
"@hapi/joi": "^17.1.1",
"axios": "^1.6.5",
"dotenv": "^16.4.1",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2",
"prom-client": "^15.1.0",
"uuid": "^9.0.1",
"winston": "^3.11.0",
"zod": "^3.22.4"
},
"devDependencies": {
"nodemon": "^3.0.3",
"jest": "^29.7.0"
}
}

View File

@@ -0,0 +1 @@
import './index.js';

View File

@@ -0,0 +1,101 @@
import Redis from 'ioredis';
import { logger } from '../utils/logger.js';
export class RedisClient {
constructor() {
this.client = null;
}
static getInstance() {
if (!RedisClient.instance) {
RedisClient.instance = new RedisClient();
}
return RedisClient.instance;
}
async connect() {
const config = {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || undefined,
db: parseInt(process.env.REDIS_DB) || 0,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
}
};
try {
this.client = new Redis(config);
this.client.on('connect', () => {
logger.info('Redis connection established');
});
this.client.on('error', (err) => {
logger.error('Redis error:', err);
});
// Wait for connection
await this.client.ping();
return this.client;
} catch (error) {
logger.error('Failed to connect to Redis:', error);
throw error;
}
}
async checkHealth() {
try {
const result = await this.client.ping();
return result === 'PONG';
} catch (error) {
logger.error('Redis health check failed:', error);
return false;
}
}
async disconnect() {
if (this.client) {
await this.client.quit();
logger.info('Redis connection closed');
}
}
// Utility methods
async setWithExpiry(key, value, ttl) {
return await this.client.setex(key, ttl, JSON.stringify(value));
}
async get(key) {
const value = await this.client.get(key);
return value ? JSON.parse(value) : null;
}
async del(key) {
return await this.client.del(key);
}
async hset(key, field, value) {
return await this.client.hset(key, field, JSON.stringify(value));
}
async hget(key, field) {
const value = await this.client.hget(key, field);
return value ? JSON.parse(value) : null;
}
async hgetall(key) {
const data = await this.client.hgetall(key);
const result = {};
for (const [field, value] of Object.entries(data)) {
result[field] = JSON.parse(value);
}
return result;
}
async hincrby(key, field, increment) {
return await this.client.hincrby(key, field, increment);
}
}

View File

@@ -0,0 +1,103 @@
import Hapi from '@hapi/hapi';
import dotenv from 'dotenv';
import { logger } from './utils/logger.js';
import { ClaudeService } from './services/ClaudeService.js';
import { PromptManager } from './services/PromptManager.js';
import { ContextManager } from './services/ContextManager.js';
import { FunctionRegistry } from './services/FunctionRegistry.js';
import { RedisClient } from './config/redis.js';
import routes from './routes/index.js';
// Load environment variables
dotenv.config();
const init = async () => {
// Initialize Redis
await RedisClient.getInstance().connect();
// Initialize services
const claudeService = ClaudeService.getInstance();
const promptManager = PromptManager.getInstance();
const contextManager = ContextManager.getInstance();
const functionRegistry = FunctionRegistry.getInstance();
// Initialize function registry
await functionRegistry.initialize();
// Create Hapi server
const server = Hapi.server({
port: process.env.PORT || 3002,
host: '0.0.0.0',
routes: {
cors: {
origin: ['*'],
headers: ['Accept', 'Content-Type', 'Authorization'],
credentials: true
}
}
});
// Register routes
server.route(routes);
// Health check endpoint
server.route({
method: 'GET',
path: '/health',
options: {
auth: false,
handler: async (request, h) => {
const redisHealth = await RedisClient.getInstance().checkHealth();
const claudeHealth = await claudeService.checkHealth();
const isHealthy = redisHealth && claudeHealth;
return h.response({
status: isHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
services: {
redis: redisHealth ? 'up' : 'down',
claude: claudeHealth ? 'up' : 'down'
}
}).code(isHealthy ? 200 : 503);
}
}
});
// Metrics endpoint
server.route({
method: 'GET',
path: '/metrics',
options: {
auth: false,
handler: async (request, h) => {
const metrics = await claudeService.getMetrics();
return h.response(metrics).type('text/plain');
}
}
});
// Start server
await server.start();
logger.info(`Claude Agent service started on ${server.info.uri}`);
// Graceful shutdown
process.on('SIGINT', async () => {
logger.info('Shutting down gracefully...');
await server.stop();
await RedisClient.getInstance().disconnect();
process.exit(0);
});
};
// Handle uncaught errors
process.on('unhandledRejection', (err) => {
logger.error('Unhandled rejection:', err);
process.exit(1);
});
// Start the service
init().catch((err) => {
logger.error('Failed to start service:', err);
process.exit(1);
});

View File

@@ -0,0 +1,60 @@
import Joi from '@hapi/joi';
import { logger } from '../utils/logger.js';
/**
* Validation middleware for Hapi routes
*/
export function validateRequest(schema) {
return {
validate: {
payload: schema.payload,
params: schema.params,
query: schema.query,
headers: schema.headers,
failAction: (request, h, err) => {
logger.warn('Validation error', {
error: err.message,
path: request.path,
method: request.method
});
const error = {
success: false,
error: 'Validation failed',
details: err.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}))
};
return h.response(error).code(400).takeover();
}
}
};
}
/**
* Common validation schemas
*/
export const schemas = {
id: Joi.string().uuid(),
sessionId: Joi.string().required(),
pagination: {
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(20)
},
dateRange: {
startDate: Joi.date().iso(),
endDate: Joi.date().iso().greater(Joi.ref('startDate'))
}
};
/**
* Validator builders
*/
export const validators = {
body: (schema) => ({ payload: schema }),
params: (schema) => ({ params: schema }),
query: (schema) => ({ query: schema }),
headers: (schema) => ({ headers: schema })
};

View File

@@ -0,0 +1,375 @@
import Joi from '@hapi/joi';
import { ClaudeService } from '../services/ClaudeService.js';
import { ContextManager } from '../services/ContextManager.js';
import { logger } from '../utils/logger.js';
import { intelligenceRoutes } from './intelligenceHapi.js';
const claudeService = ClaudeService.getInstance();
const contextManager = ContextManager.getInstance();
export default [
// Generate campaign strategy
{
method: 'POST',
path: '/api/v1/campaign/strategy',
options: {
validate: {
payload: Joi.object({
sessionId: Joi.string().required(),
campaignData: Joi.object({
goals: Joi.array().items(Joi.string()).required(),
targetAudience: Joi.object().required(),
budget: Joi.number().positive().required(),
duration: Joi.string().required()
}).required()
})
},
handler: async (request, h) => {
try {
const { sessionId, campaignData } = request.payload;
// Add to context
await contextManager.addMessage(
sessionId,
'user',
`Generate campaign strategy with parameters: ${JSON.stringify(campaignData)}`
);
// Generate strategy
const strategy = await claudeService.generateCampaignStrategy(campaignData);
// Add response to context
await contextManager.addMessage(
sessionId,
'assistant',
JSON.stringify(strategy),
{ type: 'campaign_strategy' }
);
return h.response({
success: true,
data: strategy
});
} catch (error) {
logger.error('Failed to generate campaign strategy:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Analyze message
{
method: 'POST',
path: '/api/v1/message/analyze',
options: {
validate: {
payload: Joi.object({
sessionId: Joi.string().required(),
messageData: Joi.object({
content: Joi.string().required(),
context: Joi.object().default({}),
intent: Joi.string().optional()
}).required()
})
},
handler: async (request, h) => {
try {
const { sessionId, messageData } = request.payload;
// Add to context
await contextManager.addMessage(
sessionId,
'user',
`Analyze message: ${messageData.content}`
);
// Analyze message
const analysis = await claudeService.analyzeMessage(messageData);
// Add response to context
await contextManager.addMessage(
sessionId,
'assistant',
JSON.stringify(analysis),
{ type: 'message_analysis' }
);
return h.response({
success: true,
data: analysis
});
} catch (error) {
logger.error('Failed to analyze message:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Optimize content
{
method: 'POST',
path: '/api/v1/content/optimize',
options: {
validate: {
payload: Joi.object({
sessionId: Joi.string().required(),
content: Joi.string().required(),
criteria: Joi.object({
platform: Joi.string().valid('telegram', 'whatsapp', 'facebook').default('telegram'),
objective: Joi.string().valid('engagement', 'conversion', 'awareness').default('engagement'),
tone: Joi.string().valid('formal', 'casual', 'friendly', 'professional').optional(),
length: Joi.string().valid('short', 'medium', 'long').optional()
}).default({})
})
},
handler: async (request, h) => {
try {
const { sessionId, content, criteria } = request.payload;
// Add to context
await contextManager.addMessage(
sessionId,
'user',
`Optimize content: ${content.substring(0, 100)}...`
);
// Optimize content
const optimized = await claudeService.optimizeContent(content, criteria);
// Add response to context
await contextManager.addMessage(
sessionId,
'assistant',
optimized,
{ type: 'content_optimization' }
);
return h.response({
success: true,
data: {
original: content,
optimized: optimized,
criteria: criteria
}
});
} catch (error) {
logger.error('Failed to optimize content:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Chat with Claude (general purpose)
{
method: 'POST',
path: '/api/v1/chat',
options: {
validate: {
payload: Joi.object({
sessionId: Joi.string().required(),
message: Joi.string().required(),
functions: Joi.array().items(Joi.string()).optional(),
temperature: Joi.number().min(0).max(1).optional(),
maxTokens: Joi.number().positive().optional()
})
},
handler: async (request, h) => {
try {
const { sessionId, message, functions, temperature, maxTokens } = request.payload;
// Get conversation history
const history = await contextManager.getConversationHistory(sessionId, 10);
// Add user message to context
await contextManager.addMessage(sessionId, 'user', message);
// Prepare messages for Claude
const messages = [
...history.map(msg => ({
role: msg.role,
content: msg.content
})),
{
role: 'user',
content: message
}
];
// Call Claude
const response = await claudeService.callClaude({
messages,
functions: functions || [],
temperature,
maxTokens
});
// Add response to context
await contextManager.addMessage(
sessionId,
'assistant',
response.content,
{
functions_called: response.function_calls,
usage: response.usage
}
);
return h.response({
success: true,
data: response
});
} catch (error) {
logger.error('Chat failed:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Get context/conversation history
{
method: 'GET',
path: '/api/v1/context/{sessionId}',
options: {
validate: {
params: Joi.object({
sessionId: Joi.string().required()
}),
query: Joi.object({
limit: Joi.number().positive().default(20)
})
},
handler: async (request, h) => {
try {
const { sessionId } = request.params;
const { limit } = request.query;
const context = await contextManager.getContext(sessionId);
const history = await contextManager.getConversationHistory(sessionId, limit);
return h.response({
success: true,
data: {
sessionId,
metadata: context.metadata,
messageCount: context.messages.length,
tokenCount: context.tokenCount,
history
}
});
} catch (error) {
logger.error('Failed to get context:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Clear context
{
method: 'DELETE',
path: '/api/v1/context/{sessionId}',
options: {
validate: {
params: Joi.object({
sessionId: Joi.string().required()
})
},
handler: async (request, h) => {
try {
const { sessionId } = request.params;
await contextManager.clearContext(sessionId);
return h.response({
success: true,
message: 'Context cleared successfully'
});
} catch (error) {
logger.error('Failed to clear context:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Get available functions
{
method: 'GET',
path: '/api/v1/functions',
options: {
handler: async (request, h) => {
try {
const functionRegistry = require('../services/FunctionRegistry.js').FunctionRegistry.getInstance();
const functions = functionRegistry.getAllFunctions();
const schemas = functions.map(name => functionRegistry.getFunctionSchema(name));
return h.response({
success: true,
data: schemas
});
} catch (error) {
logger.error('Failed to get functions:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Get usage statistics
{
method: 'GET',
path: '/api/v1/stats/usage',
options: {
validate: {
query: Joi.object({
timeRange: Joi.string().valid('1h', '24h', '7d', '30d').default('24h')
})
},
handler: async (request, h) => {
try {
const { timeRange } = request.query;
const stats = await claudeService.getUsageStats(timeRange);
return h.response({
success: true,
data: stats
});
} catch (error) {
logger.error('Failed to get usage stats:', error);
return h.response({
success: false,
error: error.message
}).code(500);
}
}
}
},
// Include all intelligence routes
...intelligenceRoutes
];

View File

@@ -0,0 +1,360 @@
import express from 'express';
import { marketingIntelligence } from '../services/MarketingIntelligence.js';
import { logger } from '../utils/logger.js';
import { validateRequest } from '../middleware/validation.js';
import { body, param } from 'express-validator';
const router = express.Router();
/**
* @route POST /api/intelligence/analyze-campaign
* @desc Analyze campaign and provide AI-powered suggestions
* @access Private
*/
router.post('/analyze-campaign',
validateRequest([
body('campaignId').notEmpty().isString(),
body('campaignData').isObject(),
body('campaignData.name').notEmpty().isString(),
body('campaignData.targetAudience').isObject(),
body('campaignData.messageContent').notEmpty().isString(),
body('campaignData.metrics').isObject(),
body('includeHistorical').optional().isBoolean()
]),
async (req, res) => {
try {
const { campaignId, campaignData, includeHistorical } = req.body;
// Check cache first
const cached = await marketingIntelligence.getCachedAnalysis(campaignId);
if (cached && !includeHistorical) {
logger.info('Returning cached campaign analysis', { campaignId });
return res.json({
success: true,
data: cached.analysis,
cached: true,
timestamp: cached.timestamp
});
}
// Perform AI analysis
const analysis = await marketingIntelligence.analyzeCampaign(campaignData);
res.json({
success: true,
data: analysis,
cached: false,
timestamp: new Date().toISOString()
});
logger.info('Campaign analysis completed', {
campaignId,
score: analysis.summary.score
});
} catch (error) {
logger.error('Campaign analysis failed:', error);
res.status(500).json({
success: false,
error: 'Failed to analyze campaign',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/generate-message
* @desc Generate optimized marketing messages
* @access Private
*/
router.post('/generate-message',
validateRequest([
body('product').notEmpty().isString(),
body('targetAudience').notEmpty().isString(),
body('goal').notEmpty().isString(),
body('tone').optional().isString(),
body('features').optional().isArray(),
body('cta').optional().isString(),
body('characterLimit').optional().isInt({ min: 50, max: 4096 })
]),
async (req, res) => {
try {
const context = {
product: req.body.product,
targetAudience: req.body.targetAudience,
goal: req.body.goal,
tone: req.body.tone || 'professional',
features: req.body.features || [],
cta: req.body.cta || 'Learn more',
characterLimit: req.body.characterLimit,
additionalInfo: req.body.additionalInfo
};
const suggestions = await marketingIntelligence.generateMessageContent(context);
res.json({
success: true,
data: {
variations: suggestions,
context,
generatedAt: new Date().toISOString()
}
});
logger.info('Messages generated', {
count: suggestions.length,
product: context.product
});
} catch (error) {
logger.error('Message generation failed:', error);
res.status(500).json({
success: false,
error: 'Failed to generate messages',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/segment-audience
* @desc Suggest audience segments based on user data
* @access Private
*/
router.post('/segment-audience',
validateRequest([
body('totalUsers').isInt({ min: 1 }),
body('demographics').isObject(),
body('behavioral').isObject(),
body('engagementHistory').optional().isObject()
]),
async (req, res) => {
try {
const userData = {
totalUsers: req.body.totalUsers,
demographics: req.body.demographics,
behavioral: req.body.behavioral,
engagementHistory: req.body.engagementHistory || {}
};
const segments = await marketingIntelligence.suggestAudienceSegments(userData);
res.json({
success: true,
data: {
segments,
totalSegments: segments.length,
totalUsers: userData.totalUsers,
timestamp: new Date().toISOString()
}
});
logger.info('Audience segments generated', {
segmentCount: segments.length,
totalUsers: userData.totalUsers
});
} catch (error) {
logger.error('Audience segmentation failed:', error);
res.status(500).json({
success: false,
error: 'Failed to generate audience segments',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/optimize-timing
* @desc Get optimal timing recommendations
* @access Private
*/
router.post('/optimize-timing',
validateRequest([
body('sendTimes').isArray(),
body('activityPatterns').isObject(),
body('timeZones').isObject()
]),
async (req, res) => {
try {
const historicalData = {
sendTimes: req.body.sendTimes,
activityPatterns: req.body.activityPatterns,
timeZones: req.body.timeZones
};
const recommendations = await marketingIntelligence.optimizeTiming(historicalData);
res.json({
success: true,
data: recommendations
});
logger.info('Timing optimization completed', {
optimalWindows: recommendations.optimalWindows.length
});
} catch (error) {
logger.error('Timing optimization failed:', error);
res.status(500).json({
success: false,
error: 'Failed to optimize timing',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/ab-test-recommendations
* @desc Generate A/B test recommendations
* @access Private
*/
router.post('/ab-test-recommendations',
validateRequest([
body('type').notEmpty().isString(),
body('currentMessage').notEmpty().isString(),
body('targetMetrics').isArray(),
body('audienceSize').isInt({ min: 100 }),
body('duration').notEmpty().isString()
]),
async (req, res) => {
try {
const campaignContext = {
type: req.body.type,
currentMessage: req.body.currentMessage,
targetMetrics: req.body.targetMetrics,
audienceSize: req.body.audienceSize,
duration: req.body.duration
};
const recommendations = await marketingIntelligence.generateABTestRecommendations(campaignContext);
res.json({
success: true,
data: recommendations
});
logger.info('A/B test recommendations generated', {
testCount: recommendations.tests.length,
audienceSize: campaignContext.audienceSize
});
} catch (error) {
logger.error('A/B test generation failed:', error);
res.status(500).json({
success: false,
error: 'Failed to generate A/B test recommendations',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/check-compliance
* @desc Check message compliance with regulations
* @access Private
*/
router.post('/check-compliance',
validateRequest([
body('messageContent').notEmpty().isString(),
body('targetRegions').isArray().notEmpty()
]),
async (req, res) => {
try {
const { messageContent, targetRegions } = req.body;
const complianceCheck = await marketingIntelligence.checkCompliance(
messageContent,
targetRegions
);
res.json({
success: true,
data: complianceCheck
});
logger.info('Compliance check completed', {
score: complianceCheck.score,
regions: targetRegions.length
});
} catch (error) {
logger.error('Compliance check failed:', error);
res.status(500).json({
success: false,
error: 'Failed to check compliance',
message: error.message
});
}
}
);
/**
* @route POST /api/intelligence/engagement-strategies
* @desc Get engagement improvement strategies
* @access Private
*/
router.post('/engagement-strategies',
validateRequest([
body('current').isObject(),
body('trends').isObject(),
body('feedback').optional().isObject()
]),
async (req, res) => {
try {
const campaignMetrics = {
current: req.body.current,
trends: req.body.trends,
feedback: req.body.feedback || {}
};
const strategies = await marketingIntelligence.suggestEngagementStrategies(campaignMetrics);
res.json({
success: true,
data: strategies
});
logger.info('Engagement strategies generated', {
quickWins: strategies.quickWins.length
});
} catch (error) {
logger.error('Engagement strategy generation failed:', error);
res.status(500).json({
success: false,
error: 'Failed to generate engagement strategies',
message: error.message
});
}
}
);
/**
* @route GET /api/intelligence/health
* @desc Check AI service health
* @access Public
*/
router.get('/health', async (req, res) => {
try {
// Simple health check
const health = {
status: 'healthy',
service: 'marketing-intelligence',
timestamp: new Date().toISOString(),
apiKeyConfigured: !!process.env.ANTHROPIC_API_KEY
};
res.json({
success: true,
data: health
});
} catch (error) {
logger.error('Health check failed:', error);
res.status(503).json({
success: false,
error: 'Service unhealthy',
message: error.message
});
}
});
export default router;

View File

@@ -0,0 +1,374 @@
import Joi from '@hapi/joi';
import { marketingIntelligence } from '../services/MarketingIntelligence.js';
import { logger } from '../utils/logger.js';
export const intelligenceRoutes = [
// Analyze campaign
{
method: 'POST',
path: '/api/intelligence/analyze-campaign',
options: {
validate: {
payload: Joi.object({
campaignId: Joi.string().required(),
campaignData: Joi.object({
name: Joi.string().required(),
targetAudience: Joi.object().required(),
messageContent: Joi.string().required(),
metrics: Joi.object().required(),
historicalMetrics: Joi.object().optional()
}).required(),
includeHistorical: Joi.boolean().optional()
})
},
handler: async (request, h) => {
try {
const { campaignId, campaignData, includeHistorical } = request.payload;
// Check cache first
const cached = await marketingIntelligence.getCachedAnalysis(campaignId);
if (cached && !includeHistorical) {
logger.info('Returning cached campaign analysis', { campaignId });
return h.response({
success: true,
data: cached.analysis,
cached: true,
timestamp: cached.timestamp
});
}
// Perform AI analysis
const analysis = await marketingIntelligence.analyzeCampaign(campaignData);
logger.info('Campaign analysis completed', {
campaignId,
score: analysis.summary.score
});
return h.response({
success: true,
data: analysis,
cached: false,
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('Campaign analysis failed:', error);
return h.response({
success: false,
error: 'Failed to analyze campaign',
message: error.message
}).code(500);
}
}
}
},
// Generate message
{
method: 'POST',
path: '/api/intelligence/generate-message',
options: {
validate: {
payload: Joi.object({
product: Joi.string().required(),
targetAudience: Joi.string().required(),
goal: Joi.string().required(),
tone: Joi.string().optional(),
features: Joi.array().items(Joi.string()).optional(),
cta: Joi.string().optional(),
characterLimit: Joi.number().min(50).max(4096).optional(),
additionalInfo: Joi.string().optional()
})
},
handler: async (request, h) => {
try {
const context = {
product: request.payload.product,
targetAudience: request.payload.targetAudience,
goal: request.payload.goal,
tone: request.payload.tone || 'professional',
features: request.payload.features || [],
cta: request.payload.cta || 'Learn more',
characterLimit: request.payload.characterLimit,
additionalInfo: request.payload.additionalInfo
};
const suggestions = await marketingIntelligence.generateMessageContent(context);
logger.info('Messages generated', {
count: suggestions.length,
product: context.product
});
return h.response({
success: true,
data: {
variations: suggestions,
context,
generatedAt: new Date().toISOString()
}
});
} catch (error) {
logger.error('Message generation failed:', error);
return h.response({
success: false,
error: 'Failed to generate messages',
message: error.message
}).code(500);
}
}
}
},
// Segment audience
{
method: 'POST',
path: '/api/intelligence/segment-audience',
options: {
validate: {
payload: Joi.object({
totalUsers: Joi.number().integer().min(1).required(),
demographics: Joi.object().required(),
behavioral: Joi.object().required(),
engagementHistory: Joi.object().optional()
})
},
handler: async (request, h) => {
try {
const userData = {
totalUsers: request.payload.totalUsers,
demographics: request.payload.demographics,
behavioral: request.payload.behavioral,
engagementHistory: request.payload.engagementHistory || {}
};
const segments = await marketingIntelligence.suggestAudienceSegments(userData);
logger.info('Audience segments generated', {
segmentCount: segments.length,
totalUsers: userData.totalUsers
});
return h.response({
success: true,
data: {
segments,
totalSegments: segments.length,
totalUsers: userData.totalUsers,
timestamp: new Date().toISOString()
}
});
} catch (error) {
logger.error('Audience segmentation failed:', error);
return h.response({
success: false,
error: 'Failed to generate audience segments',
message: error.message
}).code(500);
}
}
}
},
// Optimize timing
{
method: 'POST',
path: '/api/intelligence/optimize-timing',
options: {
validate: {
payload: Joi.object({
sendTimes: Joi.array().required(),
activityPatterns: Joi.object().required(),
timeZones: Joi.object().required()
})
},
handler: async (request, h) => {
try {
const historicalData = {
sendTimes: request.payload.sendTimes,
activityPatterns: request.payload.activityPatterns,
timeZones: request.payload.timeZones
};
const recommendations = await marketingIntelligence.optimizeTiming(historicalData);
logger.info('Timing optimization completed', {
optimalWindows: recommendations.optimalWindows.length
});
return h.response({
success: true,
data: recommendations
});
} catch (error) {
logger.error('Timing optimization failed:', error);
return h.response({
success: false,
error: 'Failed to optimize timing',
message: error.message
}).code(500);
}
}
}
},
// A/B test recommendations
{
method: 'POST',
path: '/api/intelligence/ab-test-recommendations',
options: {
validate: {
payload: Joi.object({
type: Joi.string().required(),
currentMessage: Joi.string().required(),
targetMetrics: Joi.array().items(Joi.string()).required(),
audienceSize: Joi.number().integer().min(100).required(),
duration: Joi.string().required()
})
},
handler: async (request, h) => {
try {
const campaignContext = {
type: request.payload.type,
currentMessage: request.payload.currentMessage,
targetMetrics: request.payload.targetMetrics,
audienceSize: request.payload.audienceSize,
duration: request.payload.duration
};
const recommendations = await marketingIntelligence.generateABTestRecommendations(campaignContext);
logger.info('A/B test recommendations generated', {
testCount: recommendations.tests.length,
audienceSize: campaignContext.audienceSize
});
return h.response({
success: true,
data: recommendations
});
} catch (error) {
logger.error('A/B test generation failed:', error);
return h.response({
success: false,
error: 'Failed to generate A/B test recommendations',
message: error.message
}).code(500);
}
}
}
},
// Check compliance
{
method: 'POST',
path: '/api/intelligence/check-compliance',
options: {
validate: {
payload: Joi.object({
messageContent: Joi.string().required(),
targetRegions: Joi.array().items(Joi.string()).min(1).required()
})
},
handler: async (request, h) => {
try {
const { messageContent, targetRegions } = request.payload;
const complianceCheck = await marketingIntelligence.checkCompliance(
messageContent,
targetRegions
);
logger.info('Compliance check completed', {
score: complianceCheck.score,
regions: targetRegions.length
});
return h.response({
success: true,
data: complianceCheck
});
} catch (error) {
logger.error('Compliance check failed:', error);
return h.response({
success: false,
error: 'Failed to check compliance',
message: error.message
}).code(500);
}
}
}
},
// Engagement strategies
{
method: 'POST',
path: '/api/intelligence/engagement-strategies',
options: {
validate: {
payload: Joi.object({
current: Joi.object().required(),
trends: Joi.object().required(),
feedback: Joi.object().optional()
})
},
handler: async (request, h) => {
try {
const campaignMetrics = {
current: request.payload.current,
trends: request.payload.trends,
feedback: request.payload.feedback || {}
};
const strategies = await marketingIntelligence.suggestEngagementStrategies(campaignMetrics);
logger.info('Engagement strategies generated', {
quickWins: strategies.quickWins.length
});
return h.response({
success: true,
data: strategies
});
} catch (error) {
logger.error('Engagement strategy generation failed:', error);
return h.response({
success: false,
error: 'Failed to generate engagement strategies',
message: error.message
}).code(500);
}
}
}
},
// Health check
{
method: 'GET',
path: '/api/intelligence/health',
options: {
handler: async (request, h) => {
try {
const health = {
status: 'healthy',
service: 'marketing-intelligence',
timestamp: new Date().toISOString(),
apiKeyConfigured: !!process.env.ANTHROPIC_API_KEY
};
return h.response({
success: true,
data: health
});
} catch (error) {
logger.error('Health check failed:', error);
return h.response({
success: false,
error: 'Service unhealthy',
message: error.message
}).code(503);
}
}
}
}
];

View File

@@ -0,0 +1,296 @@
import Anthropic from '@anthropic-ai/sdk';
import { logger } from '../utils/logger.js';
import { RedisClient } from '../config/redis.js';
import { PromptManager } from './PromptManager.js';
import { FunctionRegistry } from './FunctionRegistry.js';
import * as promClient from 'prom-client';
export class ClaudeService {
constructor() {
this.client = null;
this.redis = null;
this.promptManager = null;
this.functionRegistry = null;
// Initialize metrics
this.metrics = {
apiCalls: new promClient.Counter({
name: 'claude_api_calls_total',
help: 'Total number of Claude API calls',
labelNames: ['status', 'function']
}),
apiLatency: new promClient.Histogram({
name: 'claude_api_latency_seconds',
help: 'Claude API call latency',
buckets: [0.1, 0.5, 1, 2, 5, 10]
}),
tokenUsage: new promClient.Counter({
name: 'claude_token_usage_total',
help: 'Total tokens used',
labelNames: ['type'] // input, output
}),
cacheHits: new promClient.Counter({
name: 'claude_cache_hits_total',
help: 'Number of cache hits'
})
};
}
static getInstance() {
if (!ClaudeService.instance) {
ClaudeService.instance = new ClaudeService();
ClaudeService.instance.initialize();
}
return ClaudeService.instance;
}
initialize() {
this.client = new Anthropic({
apiKey: process.env.CLAUDE_API_KEY
});
this.redis = RedisClient.getInstance();
this.promptManager = PromptManager.getInstance();
this.functionRegistry = FunctionRegistry.getInstance();
logger.info('Claude service initialized');
}
async generateCampaignStrategy(campaignData) {
const { goals, targetAudience, budget, duration } = campaignData;
try {
const prompt = await this.promptManager.getPrompt('campaign_strategy', {
goals,
targetAudience,
budget,
duration
});
const response = await this.callClaude({
messages: [
{
role: 'user',
content: prompt
}
],
functions: [
'analyze_audience',
'generate_content_ideas',
'plan_schedule',
'estimate_results'
]
});
return {
strategy: response.content,
functions: response.function_calls || []
};
} catch (error) {
logger.error('Failed to generate campaign strategy:', error);
throw error;
}
}
async analyzeMessage(messageData) {
const { content, context, intent } = messageData;
try {
const prompt = await this.promptManager.getPrompt('message_analysis', {
content,
context,
intent
});
const response = await this.callClaude({
messages: [
{
role: 'user',
content: prompt
}
],
functions: [
'check_content_safety',
'analyze_sentiment',
'extract_entities'
]
});
return response;
} catch (error) {
logger.error('Failed to analyze message:', error);
throw error;
}
}
async optimizeContent(content, criteria) {
try {
const prompt = await this.promptManager.getPrompt('content_optimization', {
content,
criteria
});
const response = await this.callClaude({
messages: [
{
role: 'user',
content: prompt
}
]
});
return response.content;
} catch (error) {
logger.error('Failed to optimize content:', error);
throw error;
}
}
async callClaude(options) {
const { messages, functions = [], maxTokens = 4000, temperature = 0.7 } = options;
// Check cache
const cacheKey = this.generateCacheKey(messages, functions);
const cached = await this.redis.get(`claude:cache:${cacheKey}`);
if (cached) {
this.metrics.cacheHits.inc();
logger.debug('Cache hit for Claude call');
return cached;
}
const startTime = Date.now();
try {
// Prepare function schemas if any
let tools = undefined;
if (functions.length > 0) {
tools = functions.map(funcName =>
this.functionRegistry.getFunctionSchema(funcName)
).filter(Boolean);
}
// Make API call
const response = await this.client.messages.create({
model: process.env.CLAUDE_MODEL || 'claude-3-opus-20240229',
max_tokens: maxTokens,
temperature: temperature,
messages: messages,
tools: tools
});
// Track metrics
const latency = (Date.now() - startTime) / 1000;
this.metrics.apiCalls.inc({ status: 'success', function: functions[0] || 'none' });
this.metrics.apiLatency.observe(latency);
this.metrics.tokenUsage.inc({ type: 'input' }, response.usage.input_tokens);
this.metrics.tokenUsage.inc({ type: 'output' }, response.usage.output_tokens);
// Process response
const result = {
content: response.content[0].text,
usage: response.usage,
function_calls: []
};
// Handle function calls if any
if (response.content.some(c => c.type === 'tool_use')) {
for (const content of response.content) {
if (content.type === 'tool_use') {
const functionResult = await this.executeFunctionCall(
content.name,
content.input
);
result.function_calls.push({
name: content.name,
input: content.input,
result: functionResult
});
}
}
}
// Cache the result
await this.redis.setWithExpiry(
`claude:cache:${cacheKey}`,
result,
300 // 5 minutes cache
);
return result;
} catch (error) {
this.metrics.apiCalls.inc({ status: 'error', function: functions[0] || 'none' });
logger.error('Claude API call failed:', error);
throw error;
}
}
async executeFunctionCall(functionName, parameters) {
try {
const func = this.functionRegistry.getFunction(functionName);
if (!func) {
throw new Error(`Function not found: ${functionName}`);
}
const result = await func.execute(parameters);
logger.info(`Function executed: ${functionName}`, { result });
return result;
} catch (error) {
logger.error(`Function execution failed: ${functionName}`, error);
throw error;
}
}
generateCacheKey(messages, functions) {
const content = JSON.stringify({ messages, functions });
// Simple hash function
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash.toString(16);
}
async checkHealth() {
try {
// Simple health check - verify API key is set
return !!process.env.CLAUDE_API_KEY;
} catch (error) {
logger.error('Claude health check failed:', error);
return false;
}
}
async getMetrics() {
return promClient.register.metrics();
}
async getUsageStats(timeRange = '24h') {
const stats = await this.redis.hgetall('claude:usage:stats');
return {
totalCalls: parseInt(stats.totalCalls || 0),
totalTokens: parseInt(stats.totalTokens || 0),
averageLatency: parseFloat(stats.averageLatency || 0),
cacheHitRate: parseFloat(stats.cacheHitRate || 0),
timeRange
};
}
async updateUsageStats(usage, latency) {
await this.redis.hincrby('claude:usage:stats', 'totalCalls', 1);
await this.redis.hincrby('claude:usage:stats', 'totalTokens',
usage.input_tokens + usage.output_tokens);
// Update average latency (simplified calculation)
const currentStats = await this.redis.hgetall('claude:usage:stats');
const totalCalls = parseInt(currentStats.totalCalls || 0);
const currentAvg = parseFloat(currentStats.averageLatency || 0);
const newAvg = (currentAvg * (totalCalls - 1) + latency) / totalCalls;
await this.redis.hset('claude:usage:stats', 'averageLatency', newAvg.toFixed(3));
}
}

View File

@@ -0,0 +1,259 @@
import { logger } from '../utils/logger.js';
import { RedisClient } from '../config/redis.js';
export class ContextManager {
constructor() {
this.redis = null;
this.maxContextSize = 100000; // Maximum context size in characters
this.contextTTL = 3600; // 1 hour TTL for context
}
static getInstance() {
if (!ContextManager.instance) {
ContextManager.instance = new ContextManager();
ContextManager.instance.initialize();
}
return ContextManager.instance;
}
initialize() {
this.redis = RedisClient.getInstance();
logger.info('Context manager initialized');
}
async createContext(sessionId, initialData = {}) {
const context = {
sessionId,
createdAt: new Date().toISOString(),
messages: [],
metadata: initialData,
tokenCount: 0
};
await this.saveContext(sessionId, context);
return context;
}
async getContext(sessionId) {
const key = `context:${sessionId}`;
const context = await this.redis.get(key);
if (!context) {
return await this.createContext(sessionId);
}
return context;
}
async saveContext(sessionId, context) {
const key = `context:${sessionId}`;
await this.redis.setWithExpiry(key, context, this.contextTTL);
}
async addMessage(sessionId, role, content, metadata = {}) {
const context = await this.getContext(sessionId);
const message = {
role,
content,
timestamp: new Date().toISOString(),
metadata
};
context.messages.push(message);
// Update token count (approximate)
context.tokenCount += this.estimateTokens(content);
// Trim context if too large
if (context.tokenCount > this.maxContextSize) {
context.messages = this.trimMessages(context.messages);
context.tokenCount = this.calculateTotalTokens(context.messages);
}
await this.saveContext(sessionId, context);
return context;
}
async updateMetadata(sessionId, metadata) {
const context = await this.getContext(sessionId);
context.metadata = {
...context.metadata,
...metadata
};
await this.saveContext(sessionId, context);
return context;
}
async getConversationHistory(sessionId, limit = 10) {
const context = await this.getContext(sessionId);
const messages = context.messages.slice(-limit);
return messages;
}
async summarizeContext(sessionId) {
const context = await this.getContext(sessionId);
if (context.messages.length < 10) {
return null; // No need to summarize short conversations
}
// Group messages by topic/time periods
const summary = {
sessionId,
messageCount: context.messages.length,
duration: this.calculateDuration(context),
topics: this.extractTopics(context.messages),
keyDecisions: this.extractKeyDecisions(context.messages),
actionItems: this.extractActionItems(context.messages)
};
return summary;
}
async archiveContext(sessionId) {
const context = await this.getContext(sessionId);
// Save to long-term storage (MongoDB in production)
const archiveKey = `context:archive:${sessionId}`;
await this.redis.setWithExpiry(archiveKey, context, 86400 * 7); // 7 days
// Clear active context
await this.clearContext(sessionId);
logger.info(`Context archived: ${sessionId}`);
return true;
}
async clearContext(sessionId) {
const key = `context:${sessionId}`;
await this.redis.del(key);
logger.info(`Context cleared: ${sessionId}`);
}
async mergeContexts(primarySessionId, secondarySessionId) {
const primaryContext = await this.getContext(primarySessionId);
const secondaryContext = await this.getContext(secondarySessionId);
// Merge messages chronologically
const allMessages = [...primaryContext.messages, ...secondaryContext.messages]
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
primaryContext.messages = allMessages;
primaryContext.metadata = {
...secondaryContext.metadata,
...primaryContext.metadata,
mergedFrom: secondarySessionId,
mergedAt: new Date().toISOString()
};
await this.saveContext(primarySessionId, primaryContext);
await this.clearContext(secondarySessionId);
return primaryContext;
}
// Helper methods
estimateTokens(text) {
// Rough estimation: 1 token ≈ 4 characters
return Math.ceil(text.length / 4);
}
calculateTotalTokens(messages) {
return messages.reduce((total, msg) =>
total + this.estimateTokens(msg.content), 0
);
}
trimMessages(messages) {
// Keep system messages and recent messages
const systemMessages = messages.filter(m => m.role === 'system');
const recentMessages = messages.slice(-20);
// Combine, removing duplicates
const trimmed = [...systemMessages];
recentMessages.forEach(msg => {
if (!trimmed.find(m => m.timestamp === msg.timestamp)) {
trimmed.push(msg);
}
});
return trimmed.sort((a, b) =>
new Date(a.timestamp) - new Date(b.timestamp)
);
}
calculateDuration(context) {
if (context.messages.length === 0) return 0;
const firstMessage = context.messages[0];
const lastMessage = context.messages[context.messages.length - 1];
const duration = new Date(lastMessage.timestamp) - new Date(firstMessage.timestamp);
return Math.round(duration / 1000 / 60); // Duration in minutes
}
extractTopics(messages) {
// Simple topic extraction based on keywords
const topics = new Map();
const keywords = [
'campaign', 'audience', 'content', 'strategy',
'budget', 'performance', 'engagement', 'conversion'
];
messages.forEach(msg => {
const content = msg.content.toLowerCase();
keywords.forEach(keyword => {
if (content.includes(keyword)) {
topics.set(keyword, (topics.get(keyword) || 0) + 1);
}
});
});
// Return top 5 topics
return Array.from(topics.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([topic]) => topic);
}
extractKeyDecisions(messages) {
// Extract messages that contain decision-making language
const decisionKeywords = [
'decided', 'approve', 'confirm', 'agree', 'select',
'choose', 'proceed', 'implement', 'launch'
];
return messages
.filter(msg => {
const content = msg.content.toLowerCase();
return decisionKeywords.some(keyword => content.includes(keyword));
})
.slice(-5) // Last 5 decisions
.map(msg => ({
timestamp: msg.timestamp,
summary: msg.content.substring(0, 100) + '...'
}));
}
extractActionItems(messages) {
// Extract messages that contain action-oriented language
const actionKeywords = [
'will', 'todo', 'task', 'action', 'next step',
'follow up', 'implement', 'create', 'build'
];
return messages
.filter(msg => {
const content = msg.content.toLowerCase();
return actionKeywords.some(keyword => content.includes(keyword));
})
.slice(-10) // Last 10 action items
.map(msg => ({
timestamp: msg.timestamp,
summary: msg.content.substring(0, 100) + '...'
}));
}
}

View File

@@ -0,0 +1,360 @@
import { z } from 'zod';
import { logger } from '../utils/logger.js';
import axios from 'axios';
export class FunctionRegistry {
constructor() {
this.functions = new Map();
this.schemas = new Map();
}
static getInstance() {
if (!FunctionRegistry.instance) {
FunctionRegistry.instance = new FunctionRegistry();
}
return FunctionRegistry.instance;
}
async initialize() {
// Register all available functions
this.registerAnalysisFunctions();
this.registerContentFunctions();
this.registerCampaignFunctions();
this.registerDataFunctions();
logger.info(`Function registry initialized with ${this.functions.size} functions`);
}
registerFunction(name, schema, handler) {
this.functions.set(name, {
name,
handler,
schema
});
this.schemas.set(name, {
name,
description: schema.description,
input_schema: this.zodToJsonSchema(schema.parameters)
});
logger.debug(`Function registered: ${name}`);
}
registerAnalysisFunctions() {
// Analyze audience function
this.registerFunction('analyze_audience', {
description: 'Analyze target audience characteristics and behaviors',
parameters: z.object({
audienceData: z.object({
demographics: z.array(z.string()).optional(),
interests: z.array(z.string()).optional(),
behaviors: z.array(z.string()).optional(),
locations: z.array(z.string()).optional()
})
})
}, async (params) => {
// Simulate audience analysis
return {
segments: [
{
name: 'Primary',
size: 10000,
characteristics: ['tech-savvy', 'early-adopters'],
engagement_rate: 0.15
},
{
name: 'Secondary',
size: 25000,
characteristics: ['mainstream', 'price-conscious'],
engagement_rate: 0.08
}
],
insights: [
'High concentration in urban areas',
'Peak activity between 7-9 PM',
'Prefer visual content over text'
]
};
});
// Check content safety
this.registerFunction('check_content_safety', {
description: 'Check if content violates safety guidelines',
parameters: z.object({
content: z.string(),
platform: z.enum(['telegram', 'whatsapp', 'facebook']).optional()
})
}, async (params) => {
// Call safety service
try {
const response = await axios.post(
`${process.env.SAFETY_GUARD_URL}/api/v1/check`,
params
);
return response.data;
} catch (error) {
logger.error('Safety check failed:', error);
return {
safe: false,
violations: ['service_unavailable'],
confidence: 0
};
}
});
// Analyze sentiment
this.registerFunction('analyze_sentiment', {
description: 'Analyze sentiment of text content',
parameters: z.object({
text: z.string(),
language: z.string().optional()
})
}, async (params) => {
// Simple sentiment analysis (in production, use a proper NLP service)
const positiveWords = ['good', 'great', 'excellent', 'love', 'amazing'];
const negativeWords = ['bad', 'terrible', 'hate', 'awful', 'poor'];
const text = params.text.toLowerCase();
let score = 0;
positiveWords.forEach(word => {
if (text.includes(word)) score += 1;
});
negativeWords.forEach(word => {
if (text.includes(word)) score -= 1;
});
return {
sentiment: score > 0 ? 'positive' : score < 0 ? 'negative' : 'neutral',
score: Math.max(-1, Math.min(1, score / 5)),
confidence: 0.7
};
});
}
registerContentFunctions() {
// Generate content ideas
this.registerFunction('generate_content_ideas', {
description: 'Generate content ideas for marketing campaign',
parameters: z.object({
topic: z.string(),
audience: z.string(),
count: z.number().min(1).max(10).default(5),
contentType: z.enum(['text', 'image', 'video', 'mixed']).optional()
})
}, async (params) => {
// Generate content ideas based on parameters
const ideas = [];
for (let i = 0; i < params.count; i++) {
ideas.push({
title: `${params.topic} Content Idea ${i + 1}`,
description: `Engaging content about ${params.topic} for ${params.audience}`,
type: params.contentType || 'text',
estimatedEngagement: Math.random() * 0.2 + 0.05
});
}
return { ideas };
});
// Extract entities
this.registerFunction('extract_entities', {
description: 'Extract named entities from text',
parameters: z.object({
text: z.string(),
entityTypes: z.array(z.enum(['person', 'organization', 'location', 'date', 'url', 'email']))
.optional()
})
}, async (params) => {
// Simple entity extraction (in production, use NLP service)
const entities = [];
// Extract URLs
const urlRegex = /https?:\/\/[^\s]+/g;
const urls = params.text.match(urlRegex) || [];
urls.forEach(url => {
entities.push({ type: 'url', value: url });
});
// Extract emails
const emailRegex = /[^\s]+@[^\s]+\.[^\s]+/g;
const emails = params.text.match(emailRegex) || [];
emails.forEach(email => {
entities.push({ type: 'email', value: email });
});
return { entities };
});
}
registerCampaignFunctions() {
// Plan schedule
this.registerFunction('plan_schedule', {
description: 'Plan optimal posting schedule for campaign',
parameters: z.object({
duration: z.object({
start: z.string(),
end: z.string()
}),
frequency: z.enum(['hourly', 'daily', 'weekly']),
timezone: z.string().default('UTC')
})
}, async (params) => {
// Generate schedule based on best practices
const schedule = [];
const startDate = new Date(params.duration.start);
const endDate = new Date(params.duration.end);
let currentDate = new Date(startDate);
while (currentDate <= endDate) {
// Best posting times (simplified)
const postTime = new Date(currentDate);
postTime.setHours(14, 0, 0, 0); // 2 PM
schedule.push({
datetime: postTime.toISOString(),
type: 'post',
priority: 'normal'
});
// Increment based on frequency
switch (params.frequency) {
case 'hourly':
currentDate.setHours(currentDate.getHours() + 1);
break;
case 'daily':
currentDate.setDate(currentDate.getDate() + 1);
break;
case 'weekly':
currentDate.setDate(currentDate.getDate() + 7);
break;
}
}
return { schedule };
});
// Estimate results
this.registerFunction('estimate_results', {
description: 'Estimate campaign results based on parameters',
parameters: z.object({
audienceSize: z.number(),
budget: z.number(),
duration: z.number(), // days
contentQuality: z.enum(['low', 'medium', 'high']).default('medium')
})
}, async (params) => {
// Simple estimation model
const baseEngagementRate = {
low: 0.02,
medium: 0.05,
high: 0.10
};
const engagementRate = baseEngagementRate[params.contentQuality];
const reach = Math.min(params.audienceSize, params.budget * 100);
const impressions = reach * (params.duration / 7); // Weekly multiplier
const engagements = impressions * engagementRate;
const conversions = engagements * 0.1; // 10% conversion rate
return {
estimatedReach: Math.round(reach),
estimatedImpressions: Math.round(impressions),
estimatedEngagements: Math.round(engagements),
estimatedConversions: Math.round(conversions),
estimatedROI: (conversions * 50 - params.budget) / params.budget,
confidence: 0.6
};
});
}
registerDataFunctions() {
// Fetch analytics data
this.registerFunction('fetch_analytics', {
description: 'Fetch analytics data for a specific period',
parameters: z.object({
metric: z.enum(['impressions', 'clicks', 'conversions', 'engagement']),
period: z.object({
start: z.string(),
end: z.string()
}),
groupBy: z.enum(['hour', 'day', 'week']).optional()
})
}, async (params) => {
try {
const response = await axios.get(
`${process.env.ANALYTICS_URL}/api/v1/metrics`,
{ params }
);
return response.data;
} catch (error) {
logger.error('Failed to fetch analytics:', error);
return { error: 'Failed to fetch analytics data' };
}
});
}
getFunction(name) {
return this.functions.get(name);
}
getFunctionSchema(name) {
return this.schemas.get(name);
}
getAllFunctions() {
return Array.from(this.functions.keys());
}
zodToJsonSchema(zodSchema) {
// Convert Zod schema to JSON Schema format expected by Claude
// This is a simplified version - in production, use a proper converter
if (zodSchema instanceof z.ZodObject) {
const shape = zodSchema.shape;
const properties = {};
const required = [];
for (const [key, value] of Object.entries(shape)) {
properties[key] = this.zodFieldToJsonSchema(value);
if (!value.isOptional()) {
required.push(key);
}
}
return {
type: 'object',
properties,
required: required.length > 0 ? required : undefined
};
}
return this.zodFieldToJsonSchema(zodSchema);
}
zodFieldToJsonSchema(field) {
if (field instanceof z.ZodString) {
return { type: 'string' };
} else if (field instanceof z.ZodNumber) {
return { type: 'number' };
} else if (field instanceof z.ZodBoolean) {
return { type: 'boolean' };
} else if (field instanceof z.ZodArray) {
return {
type: 'array',
items: this.zodFieldToJsonSchema(field.element)
};
} else if (field instanceof z.ZodObject) {
return this.zodToJsonSchema(field);
} else if (field instanceof z.ZodEnum) {
return {
type: 'string',
enum: field.options
};
} else if (field instanceof z.ZodOptional) {
return this.zodFieldToJsonSchema(field.unwrap());
}
return { type: 'string' }; // Default fallback
}
}

View File

@@ -0,0 +1,661 @@
import Anthropic from '@anthropic-ai/sdk';
import { logger } from '../utils/logger.js';
import { cache } from '../utils/cache.js';
/**
* Marketing Intelligence Service powered by Claude AI
*/
export class MarketingIntelligence {
constructor() {
this.anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || 'your-api-key'
});
this.systemPrompt = `You are an expert marketing strategist specializing in Telegram marketing campaigns.
Your role is to provide intelligent, data-driven marketing suggestions that are ethical, effective, and compliant with regulations.
Key responsibilities:
1. Analyze campaign performance and suggest improvements
2. Generate creative and engaging message content
3. Identify optimal timing and audience segments
4. Provide A/B testing recommendations
5. Ensure compliance with anti-spam regulations
6. Suggest personalization strategies
7. Recommend engagement optimization tactics
Always prioritize user value, ethical practices, and long-term relationship building over short-term gains.`;
}
/**
* Analyze campaign and provide suggestions
*/
async analyzeCampaign(campaignData) {
try {
const prompt = this.buildCampaignAnalysisPrompt(campaignData);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 2000,
temperature: 0.7,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const analysis = this.parseAnalysisResponse(response.content[0].text);
// Cache the analysis
await this.cacheAnalysis(campaignData.id, analysis);
return analysis;
} catch (error) {
logger.error('Campaign analysis failed:', error);
throw error;
}
}
/**
* Generate optimized message content
*/
async generateMessageContent(context) {
try {
const prompt = this.buildMessageGenerationPrompt(context);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1000,
temperature: 0.8,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const suggestions = this.parseMessageSuggestions(response.content[0].text);
return suggestions;
} catch (error) {
logger.error('Message generation failed:', error);
throw error;
}
}
/**
* Suggest audience segments
*/
async suggestAudienceSegments(userData) {
try {
const prompt = this.buildSegmentationPrompt(userData);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1500,
temperature: 0.6,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const segments = this.parseSegmentationResponse(response.content[0].text);
return segments;
} catch (error) {
logger.error('Audience segmentation failed:', error);
throw error;
}
}
/**
* Optimize campaign timing
*/
async optimizeTiming(historicalData) {
try {
const prompt = this.buildTimingOptimizationPrompt(historicalData);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1000,
temperature: 0.5,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const timingRecommendations = this.parseTimingResponse(response.content[0].text);
return timingRecommendations;
} catch (error) {
logger.error('Timing optimization failed:', error);
throw error;
}
}
/**
* Generate A/B test recommendations
*/
async generateABTestRecommendations(campaignContext) {
try {
const prompt = this.buildABTestPrompt(campaignContext);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1500,
temperature: 0.7,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const recommendations = this.parseABTestResponse(response.content[0].text);
return recommendations;
} catch (error) {
logger.error('A/B test generation failed:', error);
throw error;
}
}
/**
* Provide compliance check
*/
async checkCompliance(messageContent, targetRegions) {
try {
const prompt = this.buildCompliancePrompt(messageContent, targetRegions);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1000,
temperature: 0.3,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const complianceCheck = this.parseComplianceResponse(response.content[0].text);
return complianceCheck;
} catch (error) {
logger.error('Compliance check failed:', error);
throw error;
}
}
/**
* Suggest engagement strategies
*/
async suggestEngagementStrategies(campaignMetrics) {
try {
const prompt = this.buildEngagementPrompt(campaignMetrics);
const response = await this.anthropic.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1500,
temperature: 0.7,
system: this.systemPrompt,
messages: [{ role: 'user', content: prompt }]
});
const strategies = this.parseEngagementResponse(response.content[0].text);
return strategies;
} catch (error) {
logger.error('Engagement strategy generation failed:', error);
throw error;
}
}
/**
* Build prompts for different analyses
*/
buildCampaignAnalysisPrompt(campaignData) {
return `Analyze the following Telegram marketing campaign and provide detailed suggestions for improvement:
Campaign Name: ${campaignData.name}
Target Audience: ${JSON.stringify(campaignData.targetAudience)}
Message Content: ${campaignData.messageContent}
Current Metrics:
- Messages Sent: ${campaignData.metrics.sent}
- Open Rate: ${campaignData.metrics.openRate}%
- Click Rate: ${campaignData.metrics.clickRate}%
- Conversion Rate: ${campaignData.metrics.conversionRate}%
- Unsubscribe Rate: ${campaignData.metrics.unsubscribeRate}%
Historical Performance: ${JSON.stringify(campaignData.historicalMetrics)}
Please provide:
1. Overall campaign assessment
2. Specific improvements for message content
3. Audience targeting suggestions
4. Timing optimization recommendations
5. Engagement improvement tactics
6. Risk factors and mitigation strategies`;
}
buildMessageGenerationPrompt(context) {
return `Generate 3 variations of marketing messages for the following context:
Product/Service: ${context.product}
Target Audience: ${context.targetAudience}
Campaign Goal: ${context.goal}
Tone: ${context.tone}
Key Features: ${context.features.join(', ')}
Call to Action: ${context.cta}
Character Limit: ${context.characterLimit || 'None'}
Additional Context: ${context.additionalInfo || 'None'}
For each variation, provide:
1. The message text
2. Key psychological triggers used
3. Expected engagement level (1-10)
4. Best time to send
5. Personalization opportunities`;
}
buildSegmentationPrompt(userData) {
return `Based on the following user data, suggest optimal audience segments for targeted marketing:
Total Users: ${userData.totalUsers}
Demographics:
${JSON.stringify(userData.demographics, null, 2)}
Behavioral Data:
${JSON.stringify(userData.behavioral, null, 2)}
Engagement History:
${JSON.stringify(userData.engagementHistory, null, 2)}
Please provide:
1. 5-7 distinct audience segments
2. Key characteristics of each segment
3. Recommended messaging approach for each
4. Expected response rates
5. Segment size estimates`;
}
buildTimingOptimizationPrompt(historicalData) {
return `Analyze the following historical engagement data and recommend optimal timing for message delivery:
Historical Send Times and Results:
${JSON.stringify(historicalData.sendTimes, null, 2)}
User Activity Patterns:
${JSON.stringify(historicalData.activityPatterns, null, 2)}
Time Zone Distribution:
${JSON.stringify(historicalData.timeZones, null, 2)}
Please provide:
1. Top 3 optimal time windows for sending
2. Day of week recommendations
3. Time zone-specific strategies
4. Avoid times (low engagement periods)
5. Special timing for different message types`;
}
buildABTestPrompt(campaignContext) {
return `Design A/B test recommendations for the following campaign:
Campaign Type: ${campaignContext.type}
Current Message: ${campaignContext.currentMessage}
Target Metrics: ${campaignContext.targetMetrics.join(', ')}
Audience Size: ${campaignContext.audienceSize}
Test Duration Available: ${campaignContext.duration}
Please provide:
1. 3-5 test variations with hypotheses
2. Variables to test (one per test)
3. Sample size recommendations
4. Success metrics and thresholds
5. Statistical significance requirements
6. Test sequence if multiple tests needed`;
}
buildCompliancePrompt(messageContent, targetRegions) {
return `Review the following message for compliance with marketing regulations:
Message Content:
"${messageContent}"
Target Regions: ${targetRegions.join(', ')}
Please check for:
1. GDPR compliance (if EU targeted)
2. CAN-SPAM compliance
3. TCPA compliance (if US targeted)
4. Unsubscribe mechanism requirements
5. Consent verification needs
6. Data protection considerations
7. Age restriction requirements
8. Prohibited content or claims
Provide a compliance score (0-100) and specific recommendations for each region.`;
}
buildEngagementPrompt(campaignMetrics) {
return `Based on the following campaign metrics, suggest strategies to improve engagement:
Current Metrics:
${JSON.stringify(campaignMetrics.current, null, 2)}
Historical Trends:
${JSON.stringify(campaignMetrics.trends, null, 2)}
Audience Feedback:
${JSON.stringify(campaignMetrics.feedback, null, 2)}
Please provide:
1. Quick wins (implementable immediately)
2. Medium-term strategies (1-4 weeks)
3. Long-term engagement building (1-3 months)
4. Interactive element suggestions
5. Community building tactics
6. Retention improvement methods`;
}
/**
* Parse response methods
*/
parseAnalysisResponse(response) {
// Parse the structured response from Claude
// This is a simplified version - in production, use more robust parsing
const sections = response.split('\n\n');
return {
overallAssessment: sections[0] || '',
messageImprovements: sections[1] || '',
audienceSuggestions: sections[2] || '',
timingRecommendations: sections[3] || '',
engagementTactics: sections[4] || '',
riskFactors: sections[5] || '',
summary: {
score: this.extractScore(response),
priority: this.extractPriority(response),
estimatedImprovement: this.extractImprovement(response)
}
};
}
parseMessageSuggestions(response) {
const variations = [];
const parts = response.split(/Variation \d+:/i);
for (let i = 1; i < parts.length; i++) {
const variation = this.parseMessageVariation(parts[i]);
if (variation) {
variations.push(variation);
}
}
return variations;
}
parseMessageVariation(text) {
// Extract message components
const messageMatch = text.match(/Message Text:(.*?)Key Psychological/is);
const triggersMatch = text.match(/Key Psychological Triggers:(.*?)Expected Engagement/is);
const engagementMatch = text.match(/Expected Engagement Level: (\d+)/i);
const timeMatch = text.match(/Best Time to Send:(.*?)Personalization/is);
const personalizationMatch = text.match(/Personalization Opportunities:(.*?)$/is);
return {
message: messageMatch ? messageMatch[1].trim() : '',
psychologicalTriggers: triggersMatch ? triggersMatch[1].trim().split(',').map(t => t.trim()) : [],
engagementScore: engagementMatch ? parseInt(engagementMatch[1]) : 5,
bestTime: timeMatch ? timeMatch[1].trim() : '',
personalizationOptions: personalizationMatch ? personalizationMatch[1].trim().split(',').map(t => t.trim()) : []
};
}
parseSegmentationResponse(response) {
const segments = [];
const parts = response.split(/Segment \d+:/i);
for (let i = 1; i < parts.length; i++) {
const segment = this.parseSegment(parts[i]);
if (segment) {
segments.push(segment);
}
}
return segments;
}
parseSegment(text) {
// Extract segment details
const nameMatch = text.match(/Name:(.*?)\n/i);
const characteristicsMatch = text.match(/Characteristics:(.*?)Messaging Approach:/is);
const messagingMatch = text.match(/Messaging Approach:(.*?)Expected Response Rate:/is);
const responseMatch = text.match(/Expected Response Rate: ([\d.]+)%/i);
const sizeMatch = text.match(/Segment Size: ([\d,]+)/i);
return {
name: nameMatch ? nameMatch[1].trim() : '',
characteristics: characteristicsMatch ? characteristicsMatch[1].trim().split('\n').filter(c => c.trim()) : [],
messagingApproach: messagingMatch ? messagingMatch[1].trim() : '',
expectedResponseRate: responseMatch ? parseFloat(responseMatch[1]) : 0,
estimatedSize: sizeMatch ? parseInt(sizeMatch[1].replace(/,/g, '')) : 0
};
}
parseTimingResponse(response) {
return {
optimalWindows: this.extractTimeWindows(response),
dayRecommendations: this.extractDayRecommendations(response),
timeZoneStrategies: this.extractTimeZoneStrategies(response),
avoidPeriods: this.extractAvoidPeriods(response),
messageTypeTimings: this.extractMessageTypeTimings(response)
};
}
parseABTestResponse(response) {
const tests = [];
const parts = response.split(/Test \d+:/i);
for (let i = 1; i < parts.length; i++) {
const test = this.parseABTest(parts[i]);
if (test) {
tests.push(test);
}
}
return {
tests,
sequenceRecommendation: this.extractTestSequence(response),
overallStrategy: this.extractTestStrategy(response)
};
}
parseComplianceResponse(response) {
const scoreMatch = response.match(/Compliance Score: (\d+)/i);
return {
score: scoreMatch ? parseInt(scoreMatch[1]) : 0,
gdprCompliant: response.includes('GDPR: Compliant') || response.includes('GDPR compliant'),
canSpamCompliant: response.includes('CAN-SPAM: Compliant') || response.includes('CAN-SPAM compliant'),
regionalIssues: this.extractRegionalIssues(response),
recommendations: this.extractComplianceRecommendations(response),
requiredChanges: this.extractRequiredChanges(response)
};
}
parseEngagementResponse(response) {
return {
quickWins: this.extractQuickWins(response),
mediumTermStrategies: this.extractMediumTermStrategies(response),
longTermPlan: this.extractLongTermPlan(response),
interactiveElements: this.extractInteractiveElements(response),
communityBuilding: this.extractCommunityBuilding(response),
retentionMethods: this.extractRetentionMethods(response)
};
}
/**
* Helper extraction methods
*/
extractScore(text) {
const match = text.match(/Score: (\d+)/i);
return match ? parseInt(match[1]) : null;
}
extractPriority(text) {
const match = text.match(/Priority: (High|Medium|Low)/i);
return match ? match[1].toLowerCase() : 'medium';
}
extractImprovement(text) {
const match = text.match(/Estimated Improvement: ([\d.]+)%/i);
return match ? parseFloat(match[1]) : null;
}
extractTimeWindows(text) {
const windows = [];
const matches = text.matchAll(/(\d{1,2}:\d{2}\s*[AP]M)\s*-\s*(\d{1,2}:\d{2}\s*[AP]M)/gi);
for (const match of matches) {
windows.push({
start: match[1],
end: match[2]
});
}
return windows;
}
extractDayRecommendations(text) {
const days = [];
const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
dayNames.forEach(day => {
if (text.includes(day)) {
days.push(day);
}
});
return days;
}
extractTimeZoneStrategies(text) {
// Extract time zone specific strategies
const strategies = {};
const matches = text.matchAll(/(UTC[+-]\d+|EST|PST|GMT).*?:(.*?)(?=UTC|EST|PST|GMT|$)/gis);
for (const match of matches) {
strategies[match[1]] = match[2].trim();
}
return strategies;
}
extractAvoidPeriods(text) {
const avoidMatch = text.match(/Avoid.*?:(.*?)(?=\n\n|$)/is);
return avoidMatch ? avoidMatch[1].trim().split(',').map(p => p.trim()) : [];
}
extractMessageTypeTimings(text) {
const timings = {};
const types = ['promotional', 'transactional', 'educational', 'reminder'];
types.forEach(type => {
const match = text.match(new RegExp(`${type}.*?:\\s*([^\\n]+)`, 'i'));
if (match) {
timings[type] = match[1].trim();
}
});
return timings;
}
extractTestSequence(text) {
const sequenceMatch = text.match(/Test Sequence:(.*?)(?=\n\n|$)/is);
return sequenceMatch ? sequenceMatch[1].trim().split(/\d+\./).filter(s => s.trim()) : [];
}
extractTestStrategy(text) {
const strategyMatch = text.match(/Overall Strategy:(.*?)(?=\n\n|$)/is);
return strategyMatch ? strategyMatch[1].trim() : '';
}
extractRegionalIssues(text) {
const issues = {};
const regions = ['EU', 'US', 'UK', 'Canada', 'Australia'];
regions.forEach(region => {
const match = text.match(new RegExp(`${region}.*?:([^\\n]+)`, 'i'));
if (match && match[1].toLowerCase().includes('issue')) {
issues[region] = match[1].trim();
}
});
return issues;
}
extractComplianceRecommendations(text) {
const recommendationsMatch = text.match(/Recommendations:(.*?)(?=Required Changes:|$)/is);
return recommendationsMatch ?
recommendationsMatch[1].trim().split(/\d+\./).filter(r => r.trim()).map(r => r.trim()) : [];
}
extractRequiredChanges(text) {
const changesMatch = text.match(/Required Changes:(.*?)$/is);
return changesMatch ?
changesMatch[1].trim().split(/\d+\./).filter(c => c.trim()).map(c => c.trim()) : [];
}
extractQuickWins(text) {
const quickWinsMatch = text.match(/Quick Wins:(.*?)(?=Medium-term|$)/is);
return quickWinsMatch ?
quickWinsMatch[1].trim().split(/\d+\./).filter(w => w.trim()).map(w => w.trim()) : [];
}
extractMediumTermStrategies(text) {
const mediumMatch = text.match(/Medium-term.*?:(.*?)(?=Long-term|$)/is);
return mediumMatch ?
mediumMatch[1].trim().split(/\d+\./).filter(s => s.trim()).map(s => s.trim()) : [];
}
extractLongTermPlan(text) {
const longTermMatch = text.match(/Long-term.*?:(.*?)(?=Interactive|$)/is);
return longTermMatch ?
longTermMatch[1].trim().split(/\d+\./).filter(p => p.trim()).map(p => p.trim()) : [];
}
extractInteractiveElements(text) {
const interactiveMatch = text.match(/Interactive.*?:(.*?)(?=Community|$)/is);
return interactiveMatch ?
interactiveMatch[1].trim().split(/\d+\./).filter(e => e.trim()).map(e => e.trim()) : [];
}
extractCommunityBuilding(text) {
const communityMatch = text.match(/Community.*?:(.*?)(?=Retention|$)/is);
return communityMatch ?
communityMatch[1].trim().split(/\d+\./).filter(c => c.trim()).map(c => c.trim()) : [];
}
extractRetentionMethods(text) {
const retentionMatch = text.match(/Retention.*?:(.*?)$/is);
return retentionMatch ?
retentionMatch[1].trim().split(/\d+\./).filter(m => m.trim()).map(m => m.trim()) : [];
}
/**
* Cache analysis results
*/
async cacheAnalysis(campaignId, analysis) {
const key = `ai:analysis:${campaignId}`;
await cache.set(key, JSON.stringify({
analysis,
timestamp: new Date().toISOString()
}), 'EX', 3600); // Cache for 1 hour
}
/**
* Get cached analysis
*/
async getCachedAnalysis(campaignId) {
const key = `ai:analysis:${campaignId}`;
const cached = await cache.get(key);
if (cached) {
return JSON.parse(cached);
}
return null;
}
}
// Export singleton instance
export const marketingIntelligence = new MarketingIntelligence();

View File

@@ -0,0 +1,276 @@
import { logger } from '../utils/logger.js';
import fs from 'fs/promises';
import path from 'path';
export class PromptManager {
constructor() {
this.prompts = new Map();
this.templates = new Map();
}
static getInstance() {
if (!PromptManager.instance) {
PromptManager.instance = new PromptManager();
PromptManager.instance.initialize();
}
return PromptManager.instance;
}
async initialize() {
// Load built-in prompts
this.loadBuiltInPrompts();
// Load custom prompts from files if exist
try {
await this.loadCustomPrompts();
} catch (error) {
logger.warn('No custom prompts found, using built-in prompts only');
}
logger.info(`Prompt manager initialized with ${this.prompts.size} prompts`);
}
loadBuiltInPrompts() {
// Campaign strategy prompt
this.prompts.set('campaign_strategy', {
template: `You are an expert marketing strategist specializing in Telegram campaigns.
Given the following campaign parameters:
- Goals: {{goals}}
- Target Audience: {{targetAudience}}
- Budget: ${{budget}}
- Duration: {{duration}}
Please create a comprehensive campaign strategy that includes:
1. **Executive Summary**: Brief overview of the strategy
2. **Audience Analysis**: Deep dive into target audience segments
3. **Content Strategy**: Types of content, messaging themes, and creative direction
4. **Channel Strategy**: How to leverage Telegram features (groups, channels, bots)
5. **Engagement Tactics**: Specific tactics to drive engagement
6. **Budget Allocation**: How to distribute the budget across activities
7. **Timeline**: Phased approach with milestones
8. **KPIs and Success Metrics**: How to measure success
9. **Risk Mitigation**: Potential challenges and solutions
Use the available functions to:
- analyze_audience: Get detailed audience insights
- generate_content_ideas: Create specific content recommendations
- plan_schedule: Develop optimal posting schedule
- estimate_results: Project campaign outcomes
Format your response as a structured JSON object.`,
variables: ['goals', 'targetAudience', 'budget', 'duration']
});
// Message analysis prompt
this.prompts.set('message_analysis', {
template: `Analyze the following message for marketing campaign use:
Content: {{content}}
Context: {{context}}
Intended Purpose: {{intent}}
Please provide:
1. Safety assessment (use check_content_safety function)
2. Sentiment analysis (use analyze_sentiment function)
3. Key entities and topics (use extract_entities function)
4. Engagement potential (score 1-10)
5. Improvement suggestions
6. Platform-specific considerations
Return a structured analysis with actionable insights.`,
variables: ['content', 'context', 'intent']
});
// Content optimization prompt
this.prompts.set('content_optimization', {
template: `Optimize the following content for maximum engagement:
Original Content: {{content}}
Optimization Criteria:
{{criteria}}
Please provide:
1. Optimized version of the content
2. Key changes made and rationale
3. Expected improvement in engagement
4. A/B testing recommendations
5. Platform-specific variations
Ensure the optimized content:
- Maintains the core message
- Follows platform best practices
- Is culturally appropriate
- Maximizes engagement potential`,
variables: ['content', 'criteria']
});
// A/B test design prompt
this.prompts.set('ab_test_design', {
template: `Design an A/B test for the following campaign element:
Element Type: {{elementType}}
Current Version: {{currentVersion}}
Hypothesis: {{hypothesis}}
Success Metric: {{successMetric}}
Create:
1. Test variations (2-3 alternatives)
2. Statistical parameters (sample size, duration, confidence level)
3. Implementation plan
4. Analysis framework
5. Decision criteria
Ensure the test is:
- Statistically valid
- Practically implementable
- Aligned with campaign goals`,
variables: ['elementType', 'currentVersion', 'hypothesis', 'successMetric']
});
// Audience segmentation prompt
this.prompts.set('audience_segmentation', {
template: `Create detailed audience segments for targeting:
Available Data:
{{audienceData}}
Campaign Objectives:
{{objectives}}
Please provide:
1. Segment definitions (3-5 segments)
2. Size estimates for each segment
3. Behavioral characteristics
4. Messaging preferences
5. Optimal engagement times
6. Content preferences
7. Conversion likelihood
Format as actionable targeting criteria.`,
variables: ['audienceData', 'objectives']
});
// Performance analysis prompt
this.prompts.set('performance_analysis', {
template: `Analyze campaign performance and provide optimization recommendations:
Performance Data:
{{performanceData}}
Campaign Goals:
{{goals}}
Time Period: {{timePeriod}}
Provide:
1. Performance summary against goals
2. Key insights and trends
3. Bottleneck identification
4. Optimization opportunities (ranked by impact)
5. Specific action items
6. Resource reallocation recommendations
Use data-driven insights to support all recommendations.`,
variables: ['performanceData', 'goals', 'timePeriod']
});
}
async loadCustomPrompts() {
const promptsDir = path.join(process.cwd(), 'prompts');
const files = await fs.readdir(promptsDir);
for (const file of files) {
if (file.endsWith('.json')) {
const content = await fs.readFile(path.join(promptsDir, file), 'utf-8');
const promptData = JSON.parse(content);
const promptName = path.basename(file, '.json');
this.prompts.set(promptName, promptData);
logger.info(`Loaded custom prompt: ${promptName}`);
}
}
}
async getPrompt(name, variables = {}) {
const promptData = this.prompts.get(name);
if (!promptData) {
throw new Error(`Prompt not found: ${name}`);
}
// Replace variables in template
let prompt = promptData.template;
for (const [key, value] of Object.entries(variables)) {
const placeholder = `{{${key}}}`;
const replacement = typeof value === 'object' ?
JSON.stringify(value, null, 2) :
String(value);
prompt = prompt.replace(new RegExp(placeholder, 'g'), replacement);
}
// Check for missing variables
const missingVars = promptData.variables.filter(
varName => !Object.keys(variables).includes(varName)
);
if (missingVars.length > 0) {
logger.warn(`Missing variables for prompt ${name}: ${missingVars.join(', ')}`);
}
return prompt;
}
registerPrompt(name, template, variables = []) {
this.prompts.set(name, { template, variables });
logger.info(`Prompt registered: ${name}`);
}
getAllPrompts() {
return Array.from(this.prompts.keys());
}
async savePrompt(name, promptData) {
const promptsDir = path.join(process.cwd(), 'prompts');
// Ensure directory exists
await fs.mkdir(promptsDir, { recursive: true });
// Save prompt to file
const filePath = path.join(promptsDir, `${name}.json`);
await fs.writeFile(filePath, JSON.stringify(promptData, null, 2));
// Update in-memory storage
this.prompts.set(name, promptData);
logger.info(`Prompt saved: ${name}`);
}
// Prompt versioning support
async getPromptVersion(name, version) {
const versionedName = `${name}_v${version}`;
return await this.getPrompt(versionedName);
}
// Dynamic prompt composition
composePrompt(components) {
const composed = components.map(comp => {
if (typeof comp === 'string') {
return comp;
} else if (comp.type === 'prompt') {
const prompt = this.prompts.get(comp.name);
return prompt ? prompt.template : '';
} else if (comp.type === 'conditional') {
return comp.condition ? comp.ifTrue : comp.ifFalse;
}
return '';
}).join('\n\n');
return composed;
}
}

View File

@@ -0,0 +1,56 @@
import winston from 'winston';
const { combine, timestamp, printf, colorize, errors } = winston.format;
// Custom log format
const logFormat = printf(({ level, message, timestamp, stack, ...metadata }) => {
let msg = `${timestamp} [${level}] ${message}`;
if (stack) {
msg += `\n${stack}`;
}
if (Object.keys(metadata).length > 0) {
msg += ` ${JSON.stringify(metadata)}`;
}
return msg;
});
// Create logger instance
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: combine(
errors({ stack: true }),
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
logFormat
),
transports: [
// Console transport
new winston.transports.Console({
format: combine(
colorize(),
logFormat
)
}),
// File transport for errors
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 10485760, // 10MB
maxFiles: 5
}),
// File transport for all logs
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 10485760, // 10MB
maxFiles: 10
})
],
exceptionHandlers: [
new winston.transports.File({ filename: 'logs/exceptions.log' })
],
rejectionHandlers: [
new winston.transports.File({ filename: 'logs/rejections.log' })
]
});