-
🌐 admin_panel.phpWeb, 1.63 kB⬇
-
🌐 api.jsWeb, 3.06 kB⬇
-
🌐 api.phpWeb, 6.18 kB⬇
-
🌐 app.jsWeb, 58.50 kB⬇
-
🌐 base.cssWeb, 2.23 kB⬇
-
🌐 context.jsWeb, 8.71 kB⬇
-
🌐 conversation-index.jsWeb, 11.46 kB⬇
-
🌐 conversation.jsWeb, 9.87 kB⬇
-
🌐 conversations.jsWeb, 14.49 kB⬇
-
🌐 diction.jsWeb, 384 B⬇
-
🌐 document.jsWeb, 391 B⬇
-
🌐 dynamic.phpWeb, 430 B⬇
-
📷 favicon-lg.pngImage, 9.11 kB⬇
-
📷 favicon.pngImage, 3.12 kB⬇
-
🌐 index.phpWeb, 11.54 kB⬇
-
🌐 indexjs.jsWeb, 544 B⬇
-
🌐 login.phpWeb, 619 B⬇
-
🌐 manifest.jsonWeb, 256 B⬇
-
❓ manifest.webmanifestUnknown, 262 B⬇
-
🌐 memory.jsWeb, 407 B⬇
-
🌐 models.jsonWeb, 1.70 kB⬇
-
🌐 notebook-index.jsWeb, 527 B⬇
-
⚠️ php.iniOS File, 356 B⬇
-
🌐 storage.jsWeb, 12.57 kB⬇
-
🌐 style.cssWeb, 16.16 kB⬇
-
🌐 theme_dark.jsonWeb, 266 B⬇
-
🌐 theme_light.jsonWeb, 267 B⬇
-
🌐 users.jsWeb, 12.40 kB⬇
/**
* class ConversationIndex
* Manages the secondary navigation index for a single conversation.
* Handles the display, selection, bookmarking, and deletion of individual responses
* within the active conversation's history.
*/
class ConversationIndex {
#storage;
#app_callbacks; // { get_selected_conversation, set_i_open, apply_panels_layout, on_conversation_updated_main_panel }
div_index_list;
btn_conversation_index_select;
btn_conversation_index_favorite;
btn_conversation_index_delete;
SCROLL_DELAY;
BREAKPOINT_MOBILE;
state_response_select = false;
state_responses_selected = [];
constructor(storage_instance, app_callbacks, elements, breakpoints, scroll_delay) {
this.#storage = storage_instance;
this.#app_callbacks = app_callbacks;
this.div_index_list = elements.div_index_list;
this.btn_conversation_index_select = elements.btn_conversation_index_select;
this.btn_conversation_index_favorite = elements.btn_conversation_index_favorite;
this.btn_conversation_index_delete = elements.btn_conversation_index_delete;
this.SCROLL_DELAY = scroll_delay;
this.BREAKPOINT_MOBILE = breakpoints.BREAKPOINT_MOBILE;
}
//region Response Selection and Actions
/**
* Toggles multi-selection mode for individual responses within the current conversation.
*/
toggle_response_selection_mode() {
this.state_response_select = !this.state_response_select;
if (!this.state_response_select) {
this.state_responses_selected = [];
this.btn_conversation_index_select.textContent = 'Select';
} else {
this.btn_conversation_index_select.textContent = this.state_responses_selected.length > 0 ? this.state_responses_selected.length : 'Select';
}
this.on_conversation_index_updated();
}
/**
* Toggles the bookmarked status of all responses currently selected in the response index.
*/
bookmark_selected_responses() {
const count = this.state_responses_selected.length;
if (count === 0) return;
const config = this.#storage.get_app_config();
const guid = config[this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (!guid) return;
const conversation = this.#storage.get_conversation(guid);
let history = conversation[this.#storage.KEY_CONVERSATION_HISTORY] || [];
this.state_responses_selected.forEach(index => {
if (history[index]) {
const isBookmarked = history[index].BOOKMARKED || false;
history[index].BOOKMARKED = !isBookmarked;
}
});
conversation[this.#storage.KEY_CONVERSATION_HISTORY] = history;
this.#storage.save_conversation(guid, conversation);
this.state_responses_selected = [];
this.state_response_select = false;
this.btn_conversation_index_select.textContent = 'Select';
this.on_conversation_index_updated(); // Update the index panel after bookmarking
this.#app_callbacks.on_conversation_updated_main_panel(false); // Update main panel without scrolling
}
/**
* Deletes all responses currently selected in the response index from the history after user confirmation.
*/
delete_selected_responses() {
const count = this.state_responses_selected.length;
if (count === 0) return;
const message = count === 1
? "Are you sure you want to delete this response from the conversation?"
: `Are you sure you want to delete ${count} selected responses from the conversation?`;
if (confirm(message)) {
const config = this.#storage.get_app_config();
const guid = config[this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
if (!guid) return;
const conversation = this.#storage.get_conversation(guid);
let history = conversation[this.#storage.KEY_CONVERSATION_HISTORY] || [];
// Filter out items whose index is in the selection array
history = history.filter((_, index) => !this.state_responses_selected.includes(index));
conversation[this.#storage.KEY_CONVERSATION_HISTORY] = history;
this.#storage.save_conversation(guid, conversation);
this.state_responses_selected = [];
this.state_response_select = false;
this.btn_conversation_index_select.textContent = 'Select';
this.on_conversation_index_updated(); // Update the index panel after deleting
this.#app_callbacks.on_conversation_updated_main_panel(false); // Update main panel without scrolling
}
}
//endregion
//region HTML Generation
/**
* Creates and returns a DOM element for a single response entry in the conversation index.
* @param {Object} data The response data object.
* @param {number} index The numerical index of the response in history.
* @returns {HTMLElement} The created index item element.
*/
create_response_index_item(data, index) {
const isChecked = this.state_responses_selected.includes(index);
const html = this._create_response_item_html(data, index, this.state_response_select, isChecked);
const temp = document.createElement('div');
temp.innerHTML = html.trim();
const index_item = temp.firstElementChild;
const chk = index_item.querySelector('.response-item-checkbox');
if (chk) {
chk.onclick = (e) => {
e.stopPropagation();
if (chk.checked) {
this.state_responses_selected.push(index);
} else {
this.state_responses_selected = this.state_responses_selected.filter(i => i !== index);
}
if (this.state_responses_selected.length > 0) {
this.btn_conversation_index_select.textContent = this.state_responses_selected.length;
} else {
this.btn_conversation_index_select.textContent = 'Select';
}
this.btn_conversation_index_favorite.disabled = this.state_responses_selected.length === 0;
this.btn_conversation_index_delete.disabled = this.state_responses_selected.length === 0;
};
}
index_item.onclick = () => {
const el = document.getElementById('chat-item-' + index);
if (el) {
if (window.innerWidth <= this.BREAKPOINT_MOBILE) {
setTimeout(() => {
el.scrollIntoView({ behavior: 'smooth' });
}, this.SCROLL_DELAY);
} else {
el.scrollIntoView({ behavior: 'smooth' });
}
}
if (window.innerWidth <= this.BREAKPOINT_MOBILE) {
this.#app_callbacks.set_i_open(false);
this.#app_callbacks.apply_panels_layout();
}
};
return index_item;
}
/**
* Generates the HTML string for a single response index item.
* @param {Object} data The response data object.
* @param {number} index The numerical index of the response in history.
* @param {boolean} state_response_select Whether response selection mode is active.
* @param {boolean} isChecked Whether the checkbox for this item should be checked.
* @returns {string} The HTML string for the response index item.
*/
_create_response_item_html(data, index, state_response_select, isChecked) {
return `
<div class="div-index-item div-index-item-flex ${data.BOOKMARKED ? 'div-index-item-bookmarked' : ''}"
data-index="${index}">
${state_response_select ? `<input type="checkbox" ${isChecked ? 'checked' : ''} class="response-item-checkbox index-item-checkbox-margin">` : ''}
<span class="index-item-text-grow">${data.title}</span>
</div>
`;
}
//endregion
//region UI Updates
/**
* Highlights the item in the response index that corresponds to the chat response currently in the user's viewport.
*/
highlight_active_index_item() {
const anchors = document.querySelectorAll('[id^="chat-item-"]');
const scrollContainer = document.querySelector('.div-chat-container-scroll');
if (!scrollContainer || anchors.length === 0) return;
const containerRect = scrollContainer.getBoundingClientRect();
let activeIndex = 0;
// Check if the last anchor is visible
const lastAnchor = anchors[anchors.length - 1];
const lastRect = lastAnchor.getBoundingClientRect();
const isLastVisible = (lastRect.top < containerRect.bottom && lastRect.bottom > containerRect.top);
if (isLastVisible) {
activeIndex = anchors.length - 1;
} else {
// Find anchor closest to the top of the screen/container
let minDistance = Infinity;
anchors.forEach((anchor, i) => {
const rect = anchor.getBoundingClientRect();
const distance = Math.abs(rect.top - containerRect.top);
if (distance < minDistance) {
minDistance = distance;
activeIndex = i;
}
});
}
const indexItems = this.div_index_list.querySelectorAll('.div-index-item');
indexItems.forEach((item) => {
if (parseInt(item.dataset.index) === activeIndex) {
item.classList.add('div-index-item-selected');
} else {
item.classList.remove('div-index-item-selected');
}
});
}
/**
* Renders the conversation index panel based on the current conversation history.
*/
on_conversation_index_updated() {
const conversation = this.#app_callbacks.get_selected_conversation();
const history = conversation ? conversation[this.#storage.KEY_CONVERSATION_HISTORY] : null;
this.div_index_list.innerHTML = '';
if (history && history.length > 0) {
this.btn_conversation_index_select.disabled = false;
} else {
this.btn_conversation_index_select.disabled = true;
}
if (!history || history.length === 0) {
this.btn_conversation_index_favorite.disabled = true;
this.btn_conversation_index_delete.disabled = true;
return;
}
this.btn_conversation_index_favorite.disabled = this.state_responses_selected.length === 0;
this.btn_conversation_index_delete.disabled = this.state_responses_selected.length === 0;
const indexedHistory = history.map((data, index) => ({ data, index }));
const bookmarked = indexedHistory.filter(item => item.data.BOOKMARKED === true);
const others = indexedHistory.filter(item => item.data.BOOKMARKED !== true);
bookmarked.forEach(item => {
if (item.data.title) {
this.div_index_list.appendChild(this.create_response_index_item(item.data, item.index));
}
});
if (bookmarked.length > 0 && others.length > 0) {
const hr = document.createElement('hr');
hr.className = 'hr-chat-response-divider';
this.div_index_list.appendChild(hr);
}
others.forEach(item => {
if (item.data.title) {
this.div_index_list.appendChild(this.create_response_index_item(item.data, item.index));
}
});
this.highlight_active_index_item();
}
//endregion
}
export default ConversationIndex;
conversation-index.js
×
Type: Web, text/plain
11.46 Kilobytes
Last Modified 2026-04-25 02:55:54
⬇ Download File
Type: Web, text/plain
11.46 Kilobytes
Last Modified 2026-04-25 02:55:54
⬇ Download File