@ingridsandev/chatbot-widget
Version:
A framework-agnostic chatbot widget using Web Components
167 lines (143 loc) • 4.38 kB
JavaScript
import EventEmitter from '../utils/event-emitter.js';
import Storage from '../utils/storage.js';
const STORAGE_KEY = 'selectedAgent';
const CSS_CLASSES = {
CONTAINER: 'chatbot-agent-selector',
SELECT: 'chatbot-agent-select'
};
const DEFAULT_AVATAR = '🤖';
/**
* Agent selector component for managing chatbot agents
*/
class AgentSelector extends EventEmitter {
/**
* @param {Array} agents - Array of agent objects
*/
constructor(agents = []) {
super();
this.agents = Array.isArray(agents) ? agents : [];
this.currentAgent = this.agents.length > 0 ? this.agents[0] : null;
this.storage = new Storage();
this._boundHandleAgentChange = this._handleAgentChange.bind(this);
this._loadSavedAgent();
}
/**
* Load previously saved agent preference
* @private
*/
_loadSavedAgent() {
try {
const savedAgent = this.storage.get(STORAGE_KEY);
if (savedAgent) {
const agent = this.agents.find(a => a.id === savedAgent);
if (agent) {
this.currentAgent = agent;
}
}
} catch (error) {
console.warn('Failed to load saved agent preference:', error);
}
}
render() {
const container = document.createElement('div');
container.className = CSS_CLASSES.CONTAINER;
if (this.agents.length <= 1) {
container.style.display = 'none';
return container;
}
const select = document.createElement('select');
select.className = CSS_CLASSES.SELECT;
select.setAttribute('aria-label', 'Select chatbot agent');
this.agents.forEach(agent => {
if (!agent || !agent.id) return;
const option = document.createElement('option');
option.value = agent.id;
option.textContent = `${agent.avatar || DEFAULT_AVATAR} ${agent.name || 'Unnamed Agent'}`;
option.selected = this.currentAgent && this.currentAgent.id === agent.id;
select.appendChild(option);
});
select.addEventListener('change', this._boundHandleAgentChange);
this._selectElement = select;
container.appendChild(select);
return container;
}
/**
* Handle agent selection change
* @private
*/
_handleAgentChange(e) {
const selectedAgent = this.agents.find(agent => agent.id === e.target.value);
if (selectedAgent) {
this.setAgent(selectedAgent);
}
}
/**
* Set the current agent
* @param {Object} agent - Agent object to set as current
*/
setAgent(agent) {
if (!agent || !agent.id) {
console.warn('Invalid agent provided to setAgent');
return;
}
const previousAgent = this.currentAgent;
this.currentAgent = agent;
try {
this.storage.set(STORAGE_KEY, agent.id);
} catch (error) {
console.warn('Failed to save agent preference:', error);
}
this.emit('agentChange', {
agent,
previousAgent
});
}
/**
* Get the currently selected agent
* @returns {Object|null} Current agent object or null
*/
getCurrentAgent() {
return this.currentAgent;
}
/**
* Update the available agents list
* @param {Array} agents - New array of agent objects
*/
updateAgents(agents) {
if (!Array.isArray(agents)) {
console.warn('updateAgents expects an array, received:', typeof agents);
return;
}
this.agents = agents;
if (this.currentAgent && !this.agents.find(a => a?.id === this.currentAgent.id)) {
const previousAgent = this.currentAgent;
const newAgent = this.agents.length > 0 ? this.agents[0] : null;
if (newAgent && newAgent !== this.currentAgent) {
this.setAgent(newAgent);
} else if (!newAgent) {
// Handle case where no agents are available
this.currentAgent = null;
try {
this.storage.remove?.(STORAGE_KEY);
} catch (error) {
console.warn('Failed to remove saved agent preference:', error);
}
this.emit('agentChange', {
agent: null,
previousAgent
});
}
}
}
/**
* Clean up event listeners and references
*/
destroy() {
if (this._selectElement && this._boundHandleAgentChange) {
this._selectElement.removeEventListener('change', this._boundHandleAgentChange);
this._selectElement = null;
}
this.removeAllListeners();
}
}
export default AgentSelector;