@agentdao/core
Version:
Core functionality, skills, and ready-made UI components for AgentDAO - Web3 subscriptions, content generation, social media, help support, live chat, RSS fetching, web search, and agent pricing integration
423 lines (421 loc) • 17.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HelpSupportSkill = void 0;
const axios_1 = __importDefault(require("axios"));
class HelpSupportSkill {
constructor(config) {
this.conversations = new Map();
if (!config.ai?.apiKey) {
throw new Error('No OpenAI API key provided. Please add your key in Settings > API Keys.');
}
this.config = config;
}
async handleMessage(message, context) {
const conversationId = context.userId || this.generateConversationId();
// Get conversation history
const conversation = this.conversations.get(conversationId) || [];
conversation.push({ role: 'user', content: message });
// Check if we should escalate to human
const shouldEscalate = this.shouldEscalateToHuman(conversation);
if (shouldEscalate) {
return {
response: this.getEscalationMessage(),
confidence: 0,
escalateToHuman: true,
conversationId
};
}
// Generate AI response
const aiResponse = await this.generateAIResponse(message, conversation, context);
// Add AI response to conversation
conversation.push({ role: 'assistant', content: aiResponse.response });
this.conversations.set(conversationId, conversation);
return {
...aiResponse,
conversationId
};
}
async startConversation(userId) {
const conversationId = this.generateConversationId();
this.conversations.set(conversationId, []);
console.log(`Started conversation ${conversationId} for user ${userId}`);
return conversationId;
}
async endConversation(conversationId) {
const conversation = this.conversations.get(conversationId) || [];
const startTime = new Date(); // This should be stored when conversation starts
const endTime = new Date();
const summary = {
conversationId,
userId: '', // This should be stored with the conversation
startTime,
endTime,
messageCount: conversation.length,
resolution: 'resolved',
satisfaction: undefined
};
// Clean up conversation
this.conversations.delete(conversationId);
return summary;
}
async addToKnowledgeBase(content, category) {
if (!this.config.database || !this.config.database.endpoint) {
throw new Error('No database endpoint configured for knowledge base. Please configure a database endpoint to use this feature.');
}
const knowledgeId = this.generateKnowledgeId();
try {
await fetch(this.config.database.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.config.database.apiKey ? { 'Authorization': `Bearer ${this.config.database.apiKey}` } : {})
},
body: JSON.stringify({
type: 'add_knowledge',
data: {
id: knowledgeId,
content,
category,
agentId: this.config.agentId,
agentName: this.config.agentName,
domain: this.config.domain,
timestamp: new Date().toISOString()
}
})
});
}
catch (error) {
throw new Error(`Failed to add to knowledge base: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
return knowledgeId;
}
async updateKnowledgeBase(itemId, content) {
if (!this.config.database || !this.config.database.endpoint) {
throw new Error('No database endpoint configured for knowledge base. Please configure a database endpoint to use this feature.');
}
try {
const response = await fetch(this.config.database.endpoint, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(this.config.database.apiKey ? { 'Authorization': `Bearer ${this.config.database.apiKey}` } : {})
},
body: JSON.stringify({
type: 'update_knowledge',
itemId,
content,
agentId: this.config.agentId,
agentName: this.config.agentName,
domain: this.config.domain,
timestamp: new Date().toISOString()
})
});
return response.ok;
}
catch (error) {
throw new Error(`Failed to update knowledge base: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async createTicket(userId, issue, priority) {
if (!this.config.database || !this.config.database.endpoint) {
throw new Error('No database endpoint configured for ticket management. Please configure a database endpoint to use this feature.');
}
const ticket = {
id: this.generateTicketId(),
userId,
issue,
priority,
status: 'open',
createdAt: new Date(),
updatedAt: new Date(),
notes: []
};
try {
await fetch(this.config.database.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.config.database.apiKey ? { 'Authorization': `Bearer ${this.config.database.apiKey}` } : {})
},
body: JSON.stringify({
type: 'create_ticket',
data: ticket,
agentId: this.config.agentId,
agentName: this.config.agentName,
domain: this.config.domain,
timestamp: new Date().toISOString()
})
});
}
catch (error) {
throw new Error(`Failed to create ticket: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
return ticket;
}
async updateTicket(ticketId, status, notes) {
if (!this.config.database || !this.config.database.endpoint) {
throw new Error('No database endpoint configured for ticket management. Please configure a database endpoint to use this feature.');
}
try {
const response = await fetch(this.config.database.endpoint, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(this.config.database.apiKey ? { 'Authorization': `Bearer ${this.config.database.apiKey}` } : {})
},
body: JSON.stringify({
type: 'update_ticket',
ticketId,
status,
notes,
agentId: this.config.agentId,
agentName: this.config.agentName,
domain: this.config.domain,
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
throw new Error(`Failed to update ticket: ${response.statusText}`);
}
const data = await response.json();
return data.ticket;
}
catch (error) {
throw new Error(`Failed to update ticket: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async getTicketHistory(userId) {
if (!this.config.database || !this.config.database.endpoint) {
throw new Error('No database endpoint configured for ticket management. Please configure a database endpoint to use this feature.');
}
try {
const response = await fetch(`${this.config.database.endpoint}?type=tickets&userId=${userId}`, {
headers: {
...(this.config.database.apiKey ? { 'Authorization': `Bearer ${this.config.database.apiKey}` } : {})
}
});
if (!response.ok) {
throw new Error(`Failed to fetch ticket history: ${response.statusText}`);
}
const data = await response.json();
return data.tickets || [];
}
catch (error) {
throw new Error(`Failed to get ticket history: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Private helper methods
async generateAIResponse(message, conversation, context) {
const systemPrompt = this.buildSystemPrompt(context);
const userPrompt = this.buildUserPrompt(message, conversation);
try {
if (this.config.ai.provider === 'openai') {
return await this.callOpenAI(systemPrompt, userPrompt);
}
else {
throw new Error(`AI provider ${this.config.ai.provider} not implemented`);
}
}
catch (error) {
console.error('AI response generation failed:', error);
return {
response: 'I apologize, but I\'m having trouble processing your request. Please try again or contact human support.',
confidence: 0,
escalateToHuman: true,
conversationId: ''
};
}
}
async callOpenAI(systemPrompt, userPrompt) {
const response = await axios_1.default.post('https://api.openai.com/v1/chat/completions', {
model: this.config.ai.model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
max_tokens: this.config.ai.maxTokens,
temperature: this.config.ai.temperature
}, {
headers: {
'Authorization': `Bearer ${this.config.ai.apiKey}`,
'Content-Type': 'application/json'
}
});
const aiResponse = response.data.choices[0]?.message?.content || '';
// Analyze response for confidence and escalation
const confidence = this.calculateConfidence(aiResponse);
const escalateToHuman = confidence < 0.7 || this.shouldEscalateToHuman([{ role: 'assistant', content: aiResponse }]);
return {
response: aiResponse,
confidence,
sources: await this.findRelevantSources(aiResponse),
suggestedActions: this.extractSuggestedActions(aiResponse),
escalateToHuman,
conversationId: ''
};
}
buildSystemPrompt(context) {
let prompt = `You are a helpful customer support agent for ${this.config.agentName}.
Your goal is to help users with their questions and issues.
Guidelines:
- Be polite, professional, and helpful
- Provide accurate information based on your knowledge
- If you're not sure about something, say so and suggest contacting human support
- Keep responses concise but informative
- Use a friendly and approachable tone`;
if (context.subscription) {
prompt += `\n\nUser subscription: ${context.subscription}`;
}
if (this.config.knowledgeBase.sources.length > 0) {
prompt += `\n\nKnowledge base sources: ${this.config.knowledgeBase.sources.join(', ')}`;
}
return prompt;
}
buildUserPrompt(message, conversation) {
let prompt = `User message: ${message}`;
if (conversation.length > 1) {
prompt += '\n\nConversation history:\n';
conversation.slice(-5).forEach(msg => {
prompt += `${msg.role}: ${msg.content}\n`;
});
}
return prompt;
}
shouldEscalateToHuman(conversation) {
// Check conversation length
if (conversation.length > this.config.escalationThreshold) {
return true;
}
// Check for escalation keywords
const escalationKeywords = [
'speak to human', 'talk to someone', 'real person', 'agent',
'escalate', 'supervisor', 'manager', 'complaint'
];
const lastMessage = conversation[conversation.length - 1]?.content?.toLowerCase() || '';
return escalationKeywords.some(keyword => lastMessage.includes(keyword));
}
getEscalationMessage() {
if (this.config.humanSupport.enabled) {
return `I understand you'd like to speak with a human. You can reach our support team at ${this.config.humanSupport.email} or call ${this.config.humanSupport.phone}. They typically respond within ${this.config.humanSupport.responseTime}.`;
}
else {
return 'I apologize, but I\'m unable to connect you with a human agent at this time. Please try rephrasing your question or check our knowledge base for more information.';
}
}
calculateConfidence(response) {
// Simple confidence calculation based on response characteristics
let confidence = 0.8; // Base confidence
// Reduce confidence for uncertain language
const uncertainPhrases = ['i think', 'maybe', 'possibly', 'not sure', 'might'];
uncertainPhrases.forEach(phrase => {
if (response.toLowerCase().includes(phrase)) {
confidence -= 0.1;
}
});
// Reduce confidence for short responses
if (response.length < 50) {
confidence -= 0.2;
}
// Increase confidence for specific, detailed responses
if (response.includes('step') || response.includes('click') || response.includes('go to')) {
confidence += 0.1;
}
return Math.max(0, Math.min(1, confidence));
}
extractSuggestedActions(response) {
const actions = [];
// Extract action items from response
const actionPatterns = [
/click on (.+)/gi,
/go to (.+)/gi,
/try (.+)/gi,
/check (.+)/gi
];
actionPatterns.forEach(pattern => {
const matches = response.match(pattern);
if (matches) {
actions.push(...matches);
}
});
return actions;
}
generateConversationId() {
return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
generateKnowledgeId() {
return `kb_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
generateTicketId() {
return `ticket_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async findRelevantSources(response) {
try {
const sources = [];
// Search through configured knowledge base sources
for (const source of this.config.knowledgeBase.sources) {
try {
if (source.startsWith('http')) {
// For web-based knowledge bases, search for relevant content
const relevantContent = await this.searchWebKnowledgeBase(source, response);
if (relevantContent) {
sources.push(relevantContent);
}
}
else {
// For local knowledge bases, search local content
const relevantContent = await this.searchLocalKnowledgeBase(source, response);
if (relevantContent) {
sources.push(relevantContent);
}
}
}
catch (error) {
console.warn(`Failed to search knowledge base source: ${source}`, error);
}
}
return sources;
}
catch (error) {
console.error('Error finding relevant sources:', error);
return [];
}
}
async searchWebKnowledgeBase(sourceUrl, query) {
try {
// This would integrate with a search service or API
// For now, we'll use a simple keyword matching approach
const response = await fetch(`${sourceUrl}/search?q=${encodeURIComponent(query)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
return data.url || sourceUrl;
}
}
catch (error) {
console.warn('Web knowledge base search failed:', error);
}
return null;
}
async searchLocalKnowledgeBase(sourcePath, query) {
try {
// This would search through local documentation or files
// For now, return the source path if it contains relevant keywords
const keywords = query.toLowerCase().split(' ');
const sourceLower = sourcePath.toLowerCase();
const hasRelevantKeywords = keywords.some(keyword => sourceLower.includes(keyword) && keyword.length > 3);
return hasRelevantKeywords ? sourcePath : null;
}
catch (error) {
console.warn('Local knowledge base search failed:', error);
}
return null;
}
}
exports.HelpSupportSkill = HelpSupportSkill;