/**
* class Context
* Handles the display of context-related information.
*/
class Context {
/**
* Initializes a new instance of the Context class.
* @param storage
* @param app_callbacks
*/
constructor(storage, app_callbacks) {
this.storage = storage;
this.app_callbacks = app_callbacks;
}
/**
* Renders the context view.
* @returns {string} HTML string for the context view.
*/
render() {
let html = '<div class="div-scroll-container-inner">'
html+='<h4>Topics</h4><hr><div id="topics-display-area">';
html += this._generateTopicChipsHtml();
html += '</div>';
html += `
<div class="add-topic-section" style="width: 100%;">
<input type="text" id="new-topic-input" placeholder="Add new topic" style="width: 100%;box-sizing: border-box;margin: 4pt 0;"/>
<div style="text-align: right;">
<button id="add-topic-button" style="min-width: 6em;">Add Topic</button>
</div>
</div>
`;
html += '<h4>Considerations</h4><hr><div id="considerations-display-area">';
html += this._generateConsiderationsHtml();
html += '</div>';
html += `
<div class="add-consideration-section" style="width: 100%;">
<textarea id="new-consideration-input" class="prompt-style-input" style="margin-bottom: 0;" placeholder="Add new consideration"></textarea>
<div style="text-align: right;">
<button id="add-consideration-button" style="min-width: 6em;">Add Consideration</button>
</div>
</div>
`;
html += '</div>';
return html;
}
/**
* Generates the HTML for the topic chips or "No Topics" message.
* @returns {string} HTML string.
* @private
*/
_generateTopicChipsHtml() {
const key_topics = this.storage.KEY_CONVERSATION_TOPICS;
const conversation = this.storage.get_selected_conversation();
if (conversation && conversation[key_topics] && conversation[key_topics].length > 0) {
let chipsHtml = '<div id="topic-chips" class="div-options-chips">';
conversation[key_topics].forEach(topic => {
chipsHtml += `
<span class="span-option-chip" data-topic="${topic}">
${topic}
<button class="delete-topic-button" data-topic="${topic}" style="padding: 0;height: 1.2em;line-height: 0;">×</button>
</span>`;
});
chipsHtml += '</div>';
return chipsHtml;
} else {
return '<p id="no-topics-message">No Topics</p>';
}
}
_generateConsiderationsHtml() {
const key_considerations = this.storage.KEY_CONVERSATION_CONSIDERATIONS;
const conversation = this.storage.get_selected_conversation();
if (conversation && conversation[key_considerations] && conversation[key_considerations].length > 0) {
let considerationsHtml = '<div id="considerations-list">';
conversation[key_considerations].forEach(consideration => {
considerationsHtml += `
<div class="consideration-item" data-consideration="${consideration}" style="display: flex; justify-content: space-between; align-items: center;">
<span style="flex-grow: 1;">${consideration}</span>
<button class="delete-consideration-button" data-consideration="${consideration}" style="min-width: 2em; text-align: center;">×</button>
</div>`;
});
considerationsHtml += '</div>';
return considerationsHtml;
} else {
return '<p id="no-considerations-message">No Considerations</p>';
}
}
/**
* Dynamically updates the topic chips display in the DOM.
* @private
*/
_updateTopicChipsDisplay() {
const topicsDisplayArea = document.getElementById('topics-display-area');
if (topicsDisplayArea) {
topicsDisplayArea.innerHTML = this._generateTopicChipsHtml();
this._attachTopicChipListeners();
}
}
_updateConsiderationsDisplay() {
const considerationsDisplayArea = document.getElementById('considerations-display-area');
if (considerationsDisplayArea) {
considerationsDisplayArea.innerHTML = this._generateConsiderationsHtml();
this._attachConsiderationClickListeners();
}
}
_addTopicAction() {
const newTopicInput = document.getElementById('new-topic-input');
let newTopic = newTopicInput.value.trim();
if (newTopic) {
const wordCount = newTopic.split(' ').filter(word => word !== '').length;
if (wordCount > 5) {
alert('Topic can contain up to five words.');
return;
}
const topicRegex = /^[a-zA-Z0-9\s'-]+$/;
if (!topicRegex.test(newTopic)) {
alert('Topic can only contain letters, numbers, spaces, hyphens, and apostrophes.');
return;
}
this.addTopic(newTopic);
newTopicInput.value = '';
this._updateTopicChipsDisplay()
}
}
_addConsiderationAction() {
const newConsiderationInput = document.getElementById('new-consideration-input');
let newConsideration = newConsiderationInput.value.trim();
if (newConsideration) {
if (newConsideration.includes('\n')) {
alert('Considerations cannot contain new lines.');
return;
}
const wordCount = newConsideration.split(' ').filter(word => word !== '').length;
if (wordCount > 200) {
alert('Consideration can be a maximum of 200 words.');
return;
}
this.addConsideration(newConsideration);
newConsiderationInput.value = '';
this._updateConsiderationsDisplay();
}
}
/**
* Attaches event listeners for the topic chips and add topic button.
*/
attachEventListeners() {
this._attachTopicChipListeners();
this._attachConsiderationClickListeners();
const addTopicButton = document.getElementById('add-topic-button');
if (addTopicButton) {
addTopicButton.addEventListener('click', () => this._addTopicAction());
}
const newTopicInput = document.getElementById('new-topic-input');
if (newTopicInput) {
newTopicInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this._addTopicAction();
}
});
}
const addConsiderationButton = document.getElementById('add-consideration-button');
if (addConsiderationButton) {
addConsiderationButton.addEventListener('click', () => this._addConsiderationAction());
}
const newConsiderationInput = document.getElementById('new-consideration-input');
if (newConsiderationInput) {
newConsiderationInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this._addConsiderationAction();
}
});
}
}
/**
* Attaches click listeners to the topic chips.
* @private
*/
_attachTopicChipListeners() {
const topicChipsContainer = document.getElementById('topic-chips');
if (topicChipsContainer) {
topicChipsContainer.addEventListener('click', (event) => {
if (event.target.classList.contains('delete-topic-button')) {
const topicToDelete = event.target.dataset.topic;
if (confirm(`Do you want to delete the topic "${topicToDelete}"?`)) {
this.deleteTopic(topicToDelete);
}
}
});
}
}
_attachConsiderationClickListeners() {
const considerationsContainer = document.getElementById('considerations-list');
if (considerationsContainer) {
considerationsContainer.addEventListener('click', (event) => {
if (event.target.classList.contains('delete-consideration-button')) {
const considerationToDelete = event.target.dataset.consideration;
if (confirm(`Do you want to delete the consideration "${considerationToDelete}"?`)) {
this.deleteConsideration(considerationToDelete);
}
}
});
}
}
/**
* Adds a new topic to the conversation.
* @param {string} topic The topic to add.
*/
addTopic(topic) {
const config = this.storage.get_app_config();
const guid = config[this.storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (guid) {
this.storage.addTopicToConversation(guid, topic);
this._updateTopicChipsDisplay();
}
}
addConsideration(consideration) {
const config = this.storage.get_app_config();
const guid = config[this.storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (guid) {
this.storage.addConsiderationToConversation(guid, consideration);
this._updateConsiderationsDisplay();
}
}
/**
* Deletes a topic from the conversation.
* @param {string} topic The topic to delete.
*/
deleteTopic(topic) {
const config = this.storage.get_app_config();
const guid = config[this.storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (guid) {
this.storage.deleteTopicFromConversation(guid, topic);
this._updateTopicChipsDisplay();
}
}
deleteConsideration(consideration) {
const config = this.storage.get_app_config();
const guid = config[this.storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (guid) {
this.storage.deleteConsiderationFromConversation(guid, consideration);
this._updateConsiderationsDisplay();
}
}
}
export default Context;