Merge pull request #140 from micz/shortcut_try2

Merging the new Dynamic Menu
This commit is contained in:
Mic 2024-09-24 22:40:04 +02:00 committed by GitHub
commit a35dd23401
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 710 additions and 8 deletions

View file

@ -11,6 +11,8 @@
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
<li>Custom Prompts storage space incremented to 5MB, using <i>storage.local</i>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
<li>Added an option to order alphabetically the prompts in the menu.</li>
<li>...</li>
</ul>
<h2>Version 2.1.4 - 11/09/2024</h2>

View file

@ -56,5 +56,6 @@ Are you using this addon in your Thunderbird?
<ul><li><a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGTP frontend.</li>
<li><a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration.</li>
<li><a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface.</li>
<li><a href="https://loading.io">loading.io</a> for the dynamic menu loading SVG.</li>
</ul>
<i>The specific references are described in the corresponding source files.</i>

View file

@ -327,6 +327,14 @@
"message": "To use this integration, you need to set up a local server compatible with the OpenAI API, like LM Studio. Once the server is running, enter its address in the designated field within the application. To ensure proper communication between ThunderAI and the local server remember to set the CORS settings correctly.",
"description": ""
},
"prefsInfoDesc_5": {
"message": "Remember that you can open the ThunderAI menu using the keyboard shortcut CTRL+ALT+A.",
"description": ""
},
"prefsInfoDesc_6": {
"message": "You can change the shortcut clicking on the cogwheel icon at the top right of this page and choosing \"Manage Extension Shortcut\".",
"description": ""
},
"prefsDonation_1": {
"message": "Do you like this addon?",
"description": ""
@ -542,5 +550,25 @@
"StorageSpace": {
"message": "Total storage space occupied",
"description": ""
},
"SearchPrompt": {
"message": "Search prompts",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menu: immediate prompt send",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
"message": "If checked, using the keyboard shortcut CTRL+ALT+A will automatically send the hightlighted prompt from the menu. Otherwise, the prompt name will be displayed to the user, requiring another press of the Enter key to send it.",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet": {
"message": "Menu: order alphabetically",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "If checked, the prompts in the menu will be ordered alphabetically.",
"description": ""
}
}

View file

@ -19,7 +19,7 @@
// Some original methods are derived from https://github.com/ali-raheem/Aify/blob/cfadf52f576b7be3720b5b73af7c8d3129c054da/plugin/html/actions.js
import { getPrompts } from './mzta-prompts.js';
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay } from './mzta-utils.js'
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay, i18nConditionalGet } from './mzta-utils.js'
export class mzta_Menus {
@ -33,6 +33,10 @@ export class mzta_Menus {
//{ id: 'ItemC', act: (info, tab) => { console.log('ItemC', info, tab, info.menuItemId); alert('ItemC') } },
];
shortcutMenu = [
//{ id: 'ItemD', label: 'LabelD' },
];
constructor(openChatGPT) {
this.menu_context_compose = getMenuContextCompose();
this.menu_context_display = getMenuContextDisplay();
@ -45,6 +49,7 @@ export class mzta_Menus {
async initialize() {
this.allPrompts = [];
this.rootMenu = [];
this.shortcutMenu = [];
this.menu_listeners = {};
this.allPrompts = await getPrompts(true);
this.allPrompts.sort((a, b) => a.name.localeCompare(b.name));
@ -55,7 +60,7 @@ export class mzta_Menus {
async reload(){
await browser.menus.removeAll().catch(error => {
console.error("[ThunderAI] ERROR removing the menus: ", error);
console.error("[ThunderAI] ERROR removing the menus: ", error);
});
this.removeClickListener();
this.loadMenus();
@ -140,10 +145,23 @@ export class mzta_Menus {
}
}
loadShortcutMenu() {
this.shortcutMenu = [];
this.allPrompts.forEach((prompt) => {
this.addShortcutMenu(prompt);
});
}
addShortcutMenu(prompt) {
let curr_menu_entry = {id: prompt.id, label: i18nConditionalGet(prompt.name), type: prompt.type};
this.shortcutMenu.push(curr_menu_entry);
}
async loadMenus() {
await this.initialize();
await this.addMenu(this.rootMenu);
this.addClickListener();
this.loadShortcutMenu();
}
listener(info, tab) {
@ -219,4 +237,23 @@ export class mzta_Menus {
}
}
async executeMenuAction(id) {
// Retrieve the action callback from the menu listeners using the provided ID
const action = this.menu_listeners[id];
if (action) {
try {
// Execute the action callback
await action();
} catch (error) {
// Log any errors that occur during execution
console.error(`Error executing action for menu item ${id}:`, error);
}
} else {
// Warn if no action is found for the provided ID
console.warn(`No action found for menu item ID: ${id}`);
}
}
}

32
js/mzta-store.js Normal file
View file

@ -0,0 +1,32 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const taStore = {
async setSessionData (key, value) {
let obj = {};
obj[key] = value;
await browser.storage.session.set(obj);
},
async getSessionData (key) {
let output = await browser.storage.session.get(key);
return output[key];
},
};

View file

@ -0,0 +1,65 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Firefox 115 and, consequently, Thunderbird 115 are not compatible with Intl.Segmenter.
// ChatGPT is using Intl.Segmenter, so we have to do something about it or it won't work...
console.log("[ThunderAI] Intl.Segmenter: " + Intl.Segmenter);
(function() {
if('Segmenter' in Intl){
console.log('[ThunderAI] TB128+ detected. Intl.Segmenter is already supported.');
return;
}
// Creates a <script> element to inject polyfill.js into the page context
const script = document.createElement('script');
script.src = browser.runtime.getURL('js/tb115_segmenter/tb115_polyfill.js');
script.onload = function() {
// Remove the script once loaded for cleanup
this.remove();
};
script.onerror = function() {
console.error('[ThunderAI] Error loading Intl.Segmenter polyfill.');
};
(document.head || document.documentElement).appendChild(script);
})();
async function isThunderbird128OrGreater(){
try {
const info = await browser.runtime.getBrowserInfo();
const version = info.version;
return compareThunderbirdVersions(version, '128.0') >= 0;
} catch (error) {
console.error('[ThunderAI] Error retrieving browser information:', error);
return false;
}
}
function compareThunderbirdVersions(v1, v2) {
const v1parts = v1.split('.').map(Number);
const v2parts = v2.split('.').map(Number);
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
const v1part = v1parts[i] || 0;
const v2part = v2parts[i] || 0;
if (v1part > v2part) return 1;
if (v1part < v2part) return -1;
}
return 0;
}

View file

@ -0,0 +1,48 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Firefox 115 and, consequently, Thunderbird 115 are not compatible with Intl.Segmenter.
// ChatGPT is using Intl.Segmenter, so we have to do something about it or it won't work...
(function() {
if (!('Segmenter' in Intl)) {
class Segmenter {
constructor(locale = 'en', options = { granularity: 'grapheme' }) {
this.locale = locale;
this.granularity = options.granularity || 'grapheme';
}
segment(text) {
switch (this.granularity) {
case 'word':
return text.split(/\s+/).filter(word => word.length > 0).map(word => ({ segment: word }));
case 'grapheme':
return Array.from(text).map(char => ({ segment: char }));
case 'sentence':
return text.split(/(?<=[.!?])\s+/).map(sentence => ({ segment: sentence }));
default:
return [{ segment: text }];
}
}
}
Intl.Segmenter = Segmenter;
console.log('Intl.Segmenter polyfill correctly loaded.');
} else {
console.log('Intl.Segmenter is already supported.');
}
})();

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "ThunderAI",
"description": "__MSG_extensionDescription__",
"version": "2.2.0pre2",
"version": "2.2.0pre3",
"author": "Mic (m@micz.it)",
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
"browser_specific_settings": {
@ -14,12 +14,12 @@
"message_display_action": {
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png",
"type": "menu"
"default_popup": "popup/mzta-popup.html"
},
"compose_action": {
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png",
"type":"menu"
"default_popup": "popup/mzta-popup.html"
},
"permissions": [
"compose",
@ -44,5 +44,18 @@
"default_locale": "en",
"options_ui": {
"page": "options/mzta-options.html"
}
},
"commands": {
"_thunderai__do_action": {
"description": "Opens the ThunderAI prompts menu"
}
},
"content_scripts": [
{
"matches": ["https://*.chatgpt.com/*"],
"js": ["js/tb115_segmenter/tb115_loader.js"],
"all_frames": true,
"run_at": "document_start"
}
]
}

View file

@ -20,6 +20,7 @@ import { mzta_script } from './js/mzta-chatgpt.js';
import { prefs_default } from './options/mzta-options-default.js';
import { mzta_Menus } from './js/mzta-menus.js';
import { taLogger } from './js/mzta-logger.js';
import { taStore } from './js/mzta-store.js';
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage } from './js/mzta-utils.js';
await migrateCustomPromptsStorage();
@ -65,6 +66,61 @@ browser.contentScripts.register({
runAt: "document_idle"
});
let ThunderAI_Shortcut = "Ctrl+Alt+A";
// Shortcut
messenger.commands.update({
name: "_thunderai__do_action",
shortcut: ThunderAI_Shortcut
}).then(() => {
taLog.log('Shortcut [' + ThunderAI_Shortcut + '] registered successfully!');
}).catch((error) => {
taLog.error('Error registering shortcut [' + ThunderAI_Shortcut + ']: ' + error);
});
// Listen for shortcut command
messenger.commands.onCommand.addListener((command, tab) => {
if (command === "_thunderai__do_action") {
handleShortcut(tab);
}
});
async function handleShortcut(tab) {
taLog.log("Shortcut triggered!");
if(!["mail", "messageCompose","messageDisplay"].includes(tab.type)){
return;
}
switch (tab.type) {
case "mail":
case "messageDisplay":
browser.messageDisplayAction.openPopup();
break;
case "messageCompose":
browser.composeAction.openPopup();
break;
default:
break;
}
}
async function preparePopupMenu(tab) {
await taStore.setSessionData("lastShortcutTabId", tab.id);
await taStore.setSessionData("lastShortcutTabType", tab.type);
await taStore.setSessionData("lastShortcutPromptsData", menus.shortcutMenu);
console.log(">>>>>>>> menus.shortcutMenu: " + JSON.stringify(menus.shortcutMenu));
switch (tab.type) {
case "mail":
case "messageDisplay":
taStore.setSessionData("lastShortcutFiltering", 1);
break;
case "messageCompose":
taStore.setSessionData("lastShortcutFiltering", 2);
break;
default:
break;
}
}
messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
// Check what type of message we have received and invoke the appropriate
// handler function.
@ -142,10 +198,25 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
modified_html = await getOriginalBody(message.tabId);
await setBody(message.tabId, original_html);
await setBody(message.tabId, modified_html);
return true;
break;
case 'reload_menus':
await menus.reload();
taLog.log("[ThunderAI] Reloaded menus");
return true;
break;
case 'shortcut_do_prompt':
taLog.log("Executing shortcut, promptId: " + message.promptId);
menus.executeMenuAction(message.promptId);
return true;
break;
case 'popup_menu_ready':
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
if(tabs.length == 0){
return Promise.resolve(false);
}
preparePopupMenu(tabs[0]);
return Promise.resolve(true);
break;
default:
break;

View file

@ -29,4 +29,6 @@ export const prefs_default = {
openai_comp_host: '', // For OpenAI Compatible API as LM-Studio
openai_comp_model: '',
openai_comp_chat_name: 'OpenAI Comp',
dynamic_menu_force_enter: false,
dynamic_menu_order_alphabet: true,
}

View file

@ -59,6 +59,28 @@
</label>
</td>
</tr>
<tr>
<td><label>
<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet__</span>
</label></td>
<td>
<label>
<input type="checkbox" id="dynamic_menu_order_alphabet" name="dynamic_menu_order_alphabet" class="option-input" />
&nbsp;<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet_info__</span>
</label>
</td>
</tr>
<tr>
<td><label>
<span>__MSG_prefs_OptionText_dynamic_menu_force_enter__</span>
</label></td>
<td>
<label>
<input type="checkbox" id="dynamic_menu_force_enter" name="dynamic_menu_force_enter" class="option-input" />
&nbsp;<span>__MSG_prefs_OptionText_dynamic_menu_force_enter_info__</span>
</label>
</td>
</tr>
<tr>
<td>
<label>
@ -183,10 +205,14 @@
</div>
<div id="miczDescription">
<h1>__MSG_prefsInfoTitle__</h1>
<p><span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br></span>
<p>
<span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br></span>
<span class="conntype_chatgpt_api">__MSG_prefsInfoDesc_2__<br></span>
<span class="conntype_ollama_api">__MSG_prefsInfoDesc_3__<br></span>
<span class="conntype_openai_comp_api">__MSG_prefsInfoDesc_4__</span></p>
<span class="conntype_openai_comp_api">__MSG_prefsInfoDesc_4__</span>
<br><span>__MSG_prefsInfoDesc_5__
<br>__MSG_prefsInfoDesc_6__</span>
</p>
</div>
<div id="miczTranslate">__MSG_TranslateText__ <a href="https://micz.it/thunderbird-addon-thunderai/translate/">__MSG_TranslateLink__</a></div>
<div id="miczDonation">__MSG_prefsDonation_1__<br/><a href="http://micz.it/thunderbird-addon-thunderai/donate/">__MSG_prefsDonation_2__</a></div>

View file

@ -13,6 +13,8 @@
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
<li>Custom Prompts storage space incremented to 5MB, using <pre>storage.local</pre>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
<li>Added an option to order alphabetically the prompts in the menu.</li>
<li>...</li>
</ul>
<h2>Version 2.1.4 - 11/09/2024</h2>

10
popup/mzta-loading.svg Normal file
View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="shape-rendering: auto; display: block; background: transparent;" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><g><circle r="25" fill="#408ee0" cy="50" cx="25">
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
</circle>
<circle r="25" fill="#89bff8" cy="50" cx="75">
<animate begin="0s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
</circle>
<circle r="25" fill="#408ee0" cy="50" cx="25">
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
<animate repeatCount="indefinite" dur="1s" keyTimes="0;0.499;0.5;1" calcMode="discrete" values="0;0;1;1" attributeName="fill-opacity"></animate>
</circle><g></g></g><!-- [ldio] generated by https://loading.io --></svg>

After

Width:  |  Height:  |  Size: 990 B

93
popup/mzta-popup.css Normal file
View file

@ -0,0 +1,93 @@
body {
background-color: #FAFAFA;
font-size: 14px;
padding: 0;
margin:3px;
}
#mzta_search_banner{
background-color: #FAFAFA;
color:black;
width: fit-content;
height: fit-content;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
width: 10em;
max-height: 13em;
min-height: 1em;
padding: 0;
}
#mzta_search_input{
width: 12em;
position: fixed;
top:0;
left:0;
width: -moz-available;
z-index: 1001;
font-size: 13px;
}
#_spacer_div{
width: 100%;
height: 2.1em;
}
#mzta_autocomplete-items {
border: none;
z-index: 1000;
background-color: #FAFAFA;
width: 10em;
overflow-y: auto;
padding: 0;
}
.mzta_autocomplete-item {
padding: 4px;
cursor: pointer;
}
.mzta_autocomplete-item:hover {
background-color: #e9e9e9;
}
.mzta_autocomplete-item-active {
background-color: #e9e9e9;
}
@media (prefers-color-scheme: dark) {
body{
background-color: #2c2c2c;
}
#mzta_search_input {
background-color: #2c2c2c;
color: #ffffff;
}
#mzta_search_banner{
background-color: #2c2c2c;
color:#ffffff;
}
#mzta_autocomplete-items {
background-color: #2c2c2c;
}
.mzta_autocomplete-item {
color: #ffffff;
}
.mzta_autocomplete-item:hover {
background-color: #444444;
}
.mzta_autocomplete-item-active {
background-color: #444444;
}
}

20
popup/mzta-popup.html Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="mzta-popup.css">
<title>ThunderAI Prompts</title>
</head>
<body>
<div id="mzta_search_banner">
<div id="_spacer_div">&nbsp;</div>
<input type="text" id="mzta_search_input" placeholder="__MSG_SearchPrompt__">
<div id="mzta_autocomplete-items-loading"><img src="mzta-loading.svg" title="__MSG_Loading__"></div>
<div id="mzta_autocomplete-items" style="display: none;"></div>
</div>
<script src="mzta-popup.js" type="module"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>

252
popup/mzta-popup.js Normal file
View file

@ -0,0 +1,252 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { taStore } from "../js/mzta-store.js";
import { taLogger } from "../js/mzta-logger.js";
let menuSendImmediately = false;
let taLog = console;
document.addEventListener('DOMContentLoaded', async () => {
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false});
taLog = new taLogger("mzta-popup",prefs.do_debug);
let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"});
taLog.log("Preparing data to load the popup menu: " + JSON.stringify(reponse));
let tabId = await taStore.getSessionData("lastShortcutTabId");
let tabType = await taStore.getSessionData("lastShortcutTabType");
let filtering = await taStore.getSessionData("lastShortcutFiltering");
let _prompts_data = await taStore.getSessionData("lastShortcutPromptsData");
taLog.log("_prompts_data: " + JSON.stringify(_prompts_data));
let active_prompts = filterPromptsForTab(_prompts_data, filtering);
taLog.log("active_prompts: " + JSON.stringify(active_prompts));
menuSendImmediately = prefs.dynamic_menu_force_enter;
searchPrompt(active_prompts, tabId, tabType);
i18n.updateDocument();
}, { once: true });
async function searchPrompt(allPrompts, tabId, tabType){
taLog.log("tabType: " + tabType);
let prefs_order = await browser.storage.sync.get({dynamic_menu_order_alphabet: true});
if(prefs_order.dynamic_menu_order_alphabet){
allPrompts.sort((a, b) => a.label.localeCompare(b.label));
}
// console.log(">>>>>>>>> allPrompts: " + JSON.stringify(allPrompts));
let input = document.getElementById('mzta_search_input');
let autocompleteList = document.getElementById('mzta_autocomplete-items');
let autocompleteListLoading = document.getElementById('mzta_autocomplete-items-loading');
let _spacer_div = document.getElementById('_spacer_div');
let banner = document.getElementById('mzta_search_banner');
// Initialize variables to track focus and selection
let currentFocus = -1; // Tracks the currently highlighted item
let selectedId = null; // Tracks the ID of the selected item
// Function to filter and display autocomplete suggestions
input.addEventListener('input', function() {
const query = this.value.trim().toLowerCase();
// console.log(">>>>>>>>>>>> query: " + query);
autocompleteList.innerHTML = ''; // Clear previous suggestions
currentFocus = -1; // Reset the highlighted index
selectedId = null; // Reset the selected ID since input has changed
// Uncomment the following lines if you want to hide suggestions when input is empty
/*
if (query === '') {
autocompleteList.style.display = 'none';
return;
}
*/
// Filter data based on the query
const filteredData = allPrompts.filter(item =>
item.label.toLowerCase().includes(query)
);
taLog.log("filteredData: " + JSON.stringify(filteredData));
if (filteredData.length === 0) {
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
return;
}
// Prepend numbers to the first 10 items
Array.from(filteredData).slice(0, 10).forEach((item, index) => {
const number = (index < 9) ? (index + 1).toString() : '0';
// Check if the number is already prepended to avoid duplication
if (!item.numberPrepended) {
item.label = `${number}. ${item.label}`;
item.numberPrepended = 'true'; // Mark as prepended
}
});
// console.log(">>>>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
// Create a div for each filtered result
filteredData.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.classList.add('mzta_autocomplete-item');
itemDiv.textContent = item.label;
itemDiv.setAttribute('data-id', item.id);
// Add a mousedown event to select the item
itemDiv.addEventListener('mousedown', function(e) { // Use mousedown instead of click
e.preventDefault(); // Prevents the input from losing focus
input.value = item.label;
selectedId = item.id; // Store the selected item's ID
taLog.log('mousedown selectedId:', selectedId);
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
sendPrompt(selectedId, tabId);
});
// Add a select_prompt event to select the item
itemDiv.addEventListener('select_prompt', function(e) { // Use select_prompt instead of click
e.preventDefault(); // Prevents the input from losing focus
input.value = item.label;
selectedId = item.id; // Store the selected item's ID
// console.log('>>>>>>>>>>>>> select_prompt selectedId:', selectedId);
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
if(menuSendImmediately){
sendPrompt(selectedId, tabId);
}
});
autocompleteList.appendChild(itemDiv);
});
autocompleteListLoading.style.display = 'none';
autocompleteList.style.display = 'block';
_spacer_div.style.display = 'block';
});
// Add a keydown event listener to handle arrow navigation and selection
input.addEventListener('keydown', function (e) {
const items = autocompleteList.getElementsByClassName('mzta_autocomplete-item');
if ((autocompleteList.style.display === 'none' || items.length === 0)
&& (e.key !== 'Enter')
&& !['1','2','3','4','5','6','7','8','9','0'].includes(e.key))
{
return; // Do nothing if the autocomplete list is not visible
}
// Handle number key presses (1-9,0) to select the corresponding item directly
if (['1','2','3','4','5','6','7','8','9','0'].includes(e.key)) {
// Map '1' to index 0, '2' to 1, ..., '9' to 8, '0' to 9
const numIndex = (e.key === '0') ? 9 : parseInt(e.key, 10) - 1;
if (items[numIndex]) {
e.preventDefault(); // Prevent any default behavior
// Dispatch a select_prompt event to simulate a click/select action
items[numIndex].dispatchEvent(new Event('select_prompt'));
return; // Exit after handling the number key
}
}
if (e.key === 'ArrowDown') {
// Navigate down the list
currentFocus++;
if (currentFocus >= items.length) currentFocus = 0; // Wrap to the first item
addActive(items);
e.preventDefault(); // Prevent cursor from moving to the end
} else if (e.key === 'ArrowUp') {
// Navigate up the list
currentFocus--;
if (currentFocus < 0) currentFocus = items.length - 1; // Wrap to the last item
addActive(items);
e.preventDefault(); // Prevent cursor from moving to the start
} else if (e.key === 'Enter') {
// console.log(">>>>>>>>>>>>>> keydown == enter selectedId: " + selectedId);
if (selectedId) {
// If an item is already selected, call sendPrompt with the selected ID
e.preventDefault();
sendPrompt(selectedId, tabId); // Call your sendPrompt function
//banner.remove(); // Remove the banner after sending the prompt
} else {
// If no item is selected yet, select the highlighted item
// Select the highlighted item, or the first item if none is highlighted
e.preventDefault(); // Prevent form submission if inside a form
if (currentFocus > -1) {
if (items[currentFocus]) {
items[currentFocus].dispatchEvent(new Event('select_prompt')); // Trigger the select_prompt event
}
} else if (items.length > 0) {
// If no item is highlighted, select the first item
items[0].dispatchEvent(new Event('select_prompt'));
}
}
}
});
// Function to add the "active" class to the current item
function addActive(items) {
removeActive(items); // Remove the "active" class from all items
if (currentFocus >= items.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = items.length - 1;
items[currentFocus].classList.add('mzta_autocomplete-item-active'); // Add "active" class to the current item
// Ensure the active item is visible within the scrollable list
items[currentFocus].scrollIntoView({
behavior: 'auto', // You can change to 'smooth' if you prefer smooth scrolling
block: 'nearest', // Align the item to the nearest edge of the visible area
});
}
// Function to remove the "active" class from all items
function removeActive(items) {
for (let i = 0; i < items.length; i++) {
items[i].classList.remove('mzta_autocomplete-item-active');
}
}
document.body.insertBefore(banner, document.body.firstChild);
setTimeout(() => {
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
input.focus();
}, 100);
}
function sendPrompt(prompt_id, tabId){
taLog.log("sendPrompt: " + prompt_id);
browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
}
function filterPromptsForTab(prompts_data, filtering){
// If filtering is 0, return the original array without any filters (btw it should not happen)
if (filtering === 0) {
return prompts_data;
}
// Define the types to include based on the value of filtering
let allowedTypes;
if (filtering === 1) {
allowedTypes = ["0", "1"];
} else if (filtering === 2) {
allowedTypes = ["0", "2"];
} else {
// If filtering has an unexpected value, return the original data
return prompts_data;
}
// Filter the array based on the allowed types
return prompts_data.filter(prompt => allowedTypes.includes(prompt.type));
}