Merge pull request #83 from micz/import_export_prompts

Import export prompts. Ready for version 1.2.0.
This commit is contained in:
Mic 2024-07-17 23:08:23 +02:00 committed by GitHub
commit 3371d80861
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 583 additions and 236 deletions

View file

@ -3,7 +3,12 @@
<h2>Version 1.2.0 - ??/07/2024</h2>
<ul>
<li>If no default language is set in the options, the language present in the text sent to ChatGPT will be used [<a href="https://github.com/micz/ThunderAI/issues/53">#53</a>].</li>
<li>Added the functionality to import and export custom prompts [<a href="https://github.com/micz/ThunderAI/issues/65">#65</a>].</li>
<li>Showing the currently used prompt name in the ChatGPT window [<a href="https://github.com/micz/ThunderAI/issues/20">#20</a>].</li>
</ul>
<h2>Version 1.1.4 - 28/06/2024</h2>
<ul>
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>

View file

@ -55,6 +55,14 @@
"message": "Die Standard-Eingabeaufforderungen sind nicht bearbeitbar. Sie können sie deaktivieren, dann den Text der Eingabeaufforderung kopieren und in eine neue einfügen, um eine modifizierte Version zu erstellen.",
"description": ""
},
"customPrompts_managePrompts_info_default_2": {
"message": "Sie können die Prompts importieren und exportieren. Bestehende Prompts mit derselben ID werden überschrieben. Prompts mit neuen IDs werden hinzugefügt.",
"description": ""
},
"customPrompts_managePrompts_info_default_3": {
"message": "Wenn alles korrekt ist, klicken Sie nach dem Import auf die Schaltfläche 'Alle speichern'.",
"description": ""
},
"customPrompts_start_saving": {
"message": "Eingabeaufforderungen werden gespeichert...",
"description": ""
@ -342,5 +350,37 @@
"TranslateLink": {
"message": "Finde es heraus!",
"description": ""
},
"customPrompts_ExportAll": {
"message": "Alle Prompts exportieren",
"description": ""
},
"customPrompts_Import": {
"message": "Neue Prompts importieren",
"description": ""
},
"importPrompts_confirmText": {
"message": "Sie sind dabei, neue Prompts zu importieren.",
"description": ""
},
"customPrompts_start_import": {
"message": "Import von benutzerdefinierten Prompts startet...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Import von benutzerdefinierten Prompts abgeschlossen! Sie müssen auf die Schaltfläche 'Alle speichern' klicken, um Ihre Änderungen zu speichern.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "Die Datei, die Sie importieren möchten, ist keine gültige Datei für benutzerdefinierte Prompts.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "Die Datei, die Sie importieren möchten, enthält keine gültigen Prompts.",
"description": ""
},
"currently_used_prompt": {
"message": "Derzeit verwendeter Prompt-Name",
"description": ""
}
}

View file

@ -55,6 +55,14 @@
"message": "The default prompts are not editable. You can disable them, then copy the prompt text and paste it into a new one to create a modified version.",
"description": ""
},
"customPrompts_managePrompts_info_default_2": {
"message": "You can import and export the prompts. Existing prompts with the same ID will be overwritten. Prompts with new IDs will be added.",
"description": ""
},
"customPrompts_managePrompts_info_default_3": {
"message": "If everything is correct, after the import click on the 'Save All' button.",
"description": ""
},
"customPrompts_start_saving": {
"message": "Saving prompts...",
"description": ""
@ -342,5 +350,37 @@
"TranslateLink": {
"message": "Find out how!",
"description": ""
},
"customPrompts_ExportAll": {
"message": "Export all prompts",
"description": ""
},
"customPrompts_Import": {
"message": "Import new prompts",
"description": ""
},
"importPrompts_confirmText": {
"message": "You are about to import new prompts.",
"description": ""
},
"customPrompts_start_import": {
"message": "Starting custom prompts import...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Custom prompts import completed! You need to click on the 'Save All' button to save your changes.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "The file you're trying to import is not a valid custom prompts file.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "The file you're trying to import does not contain any valid prompts.",
"description": ""
},
"currently_used_prompt": {
"message": "Currently used prompt name",
"description": ""
}
}

View file

@ -1,7 +1,7 @@
{
"extensionDescription": {
"message": "Utilisez ChatGPT pour améliorer vos emails !",
"description": "Description de l'extension."
"description": "Description de l'extension."
},
"menu_title": {
"message": "IA",
@ -55,6 +55,14 @@
"message": "Les invites par défaut ne sont pas modifiables. Vous pouvez les désactiver, puis copier le texte de l'invite et le coller dans une nouvelle pour créer une version modifiée.",
"description": ""
},
"customPrompts_managePrompts_info_default_2": {
"message": "Vous pouvez importer et exporter les prompts. Les prompts existantes avec le même ID seront écrasées. Les prompts avec de nouveaux ID seront ajoutées.",
"description": ""
},
"customPrompts_managePrompts_info_default_3": {
"message": "Si tout est correct, après l'importation, cliquez sur le bouton 'Tout enregistrer'.",
"description": ""
},
"customPrompts_start_saving": {
"message": "Enregistrement des prompts...",
"description": ""
@ -342,5 +350,37 @@
"TranslateLink": {
"message": "Découvrez comment !",
"description": ""
},
"customPrompts_ExportAll": {
"message": "Exporter toutes les prompts",
"description": ""
},
"customPrompts_Import": {
"message": "Importer de nouvelles prompts",
"description": ""
},
"importPrompts_confirmText": {
"message": "Vous êtes sur le point d'importer de nouvelles prompts.",
"description": ""
},
"customPrompts_start_import": {
"message": "Début de l'importation des prompts personnalisées...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Importation des prompts personnalisées terminée ! Vous devez cliquer sur le bouton 'Enregistrer tout' pour enregistrer vos modifications.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "Le fichier que vous essayez d'importer n'est pas un fichier de prompts personnalisées valide.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "Le fichier que vous essayez d'importer ne contient aucune prompt valide.",
"description": ""
},
"currently_used_prompt": {
"message": "Nom du prompt actuellement utilisé",
"description": ""
}
}

View file

@ -51,6 +51,18 @@
"message": "Informazioni",
"description": ""
},
"customPrompts_managePrompts_info_default": {
"message": "I prompt predefiniti non sono modificabili. Puoi disattivarli, quindi copiare il testo del prompt e incollarlo in uno nuovo per crearne una versione modificata.",
"description": ""
},
"customPrompts_managePrompts_info_default_2": {
"message": "Puoi importare ed esportare i prompt. I prompt esistenti con lo stesso ID verranno sovrascritti. I prompt con nuovi ID verranno aggiunti.",
"description": ""
},
"customPrompts_managePrompts_info_default_3": {
"message": "Se tutto è corretto, dopo l'importazione fai clic sul pulsante 'Salva Tutto'.",
"description": ""
},
"customPrompts_start_saving": {
"message": "Salvataggio prompt in corso...",
"description": ""
@ -67,10 +79,6 @@
"message": "Salvataggio prompt di base...",
"description": ""
},
"customPrompts_managePrompts_info_default": {
"message": "I prompt predefiniti non sono modificabili. Puoi disattivarli, quindi copiare il testo del prompt e incollarlo in uno nuovo per crearne una versione modificata.",
"description": ""
},
"customPrompts_saving_custom_prompts": {
"message": "Salvataggio prompt personalizzati...",
"description": ""
@ -207,6 +215,14 @@
"message": "Chiudi",
"description": ""
},
"chatgpt_textarea_not_found_error": {
"message": "C'è stato un errore nell'invio del prompt a ChatGPT, per favore riprova. Se il problema persiste, per favore <a href=\"https://micz.it/thunderbird-addon-thunderai/status/\">controlla lo stato del servizio</a>.",
"description": ""
},
"chatgpt_sendbutton_not_found_error": {
"message": "Premi il pulsante per inviare il prompt a ChatGPT.",
"description": ""
},
"chatgpt_user_not_logged_in": {
"message": "Non sei connesso a ChatGPT. Effettua l'accesso con le tue credenziali, chiudi la finestra di ChatGPT e poi ripeti l'azione che avevi tentato. Rimarrai connesso in seguito.",
"description": ""
@ -259,14 +275,6 @@
"message": "Larghezza",
"description": ""
},
"chatgpt_textarea_not_found_error": {
"message": "C'è stato un errore nell'invio del prompt a ChatGPT, per favore riprova. Se il problema persiste, per favore <a href=\"https://micz.it/thunderbird-addon-thunderai/status/\">controlla lo stato del servizio</a>.",
"description": ""
},
"chatgpt_sendbutton_not_found_error": {
"message": "Premi il pulsante per inviare il prompt a ChatGPT.",
"description": ""
},
"prefs_OptionText_default_sign_name": {
"message": "Nome da utilizzare per la firma",
"description": ""
@ -342,5 +350,37 @@
"TranslateLink": {
"message": "Scopri come!",
"description": ""
},
"customPrompts_ExportAll": {
"message": "Esporta tutti i prompt",
"description": ""
},
"customPrompts_Import": {
"message": "Importa i nuovi prompt",
"description": ""
},
"importPrompts_confirmText": {
"message": "Stai per importare nuovi prompt.",
"description": ""
},
"customPrompts_start_import": {
"message": "Inizio importazione dei prompt personalizzati...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Importazione dei prompt personalizzati completata! È necessario fare clic sul pulsante 'Salva Tutto' per salvare le modifiche.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "Il file che stai cercando di importare non è un file valido di prompt personalizzati.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "Il file che stai cercando di importare non contiene alcun prompt valido.",
"description": ""
},
"currently_used_prompt": {
"message": "Nome del prompt attualmente utilizzato",
"description": ""
}
}

View file

@ -169,4 +169,15 @@ table .sort.desc {
#txtIdNew, .id_output{
text-transform: lowercase;
}
#import_export{
position: absolute;
right:10px;
top:10px;
text-align: right;
}
#btnExportAll{
margin-bottom: 3px;
}

View file

@ -8,9 +8,15 @@
<body>
<div>
<h1>__MSG_customPrompts_managePrompts__</h1>
<p>__MSG_customPrompts_managePrompts_info_default__
<p>__MSG_customPrompts_managePrompts_info_default__<br>
__MSG_customPrompts_managePrompts_info_default_2__<br>
__MSG_customPrompts_managePrompts_info_default_3__
<br><a href="https://micz.it/thunderbird-addon-thunderai/custom-prompts/">__MSG_customPrompts_managePrompts_help__</a></p>
</div>
<div id="import_export">
<button id="btnExportAll">__MSG_customPrompts_ExportAll__</button><br>
<button id="btnImport">__MSG_customPrompts_Import__</button>
</div>
<div id="command_palette">
<button id="btnSaveAll" disabled>__MSG_customPrompts_btnSaveAll__</button>
<span id="msgDisplay"></span>

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts } from "../js/mzta-prompts.js";
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../js/mzta-prompts.js";
var promptsList = null;
var somethingChanged = false;
@ -26,91 +26,12 @@ var idnumMax = 0;
var msgTimeout = null;
document.addEventListener('DOMContentLoaded', async () => {
let options = {
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'} ],
item: function(values) {
let type_output = '';
switch(String(values.type)){
case "0":
type_output = `__MSG_customPrompts_add_to_menu_always__`;
break;
case "1":
type_output = `__MSG_customPrompts_add_to_menu_reading__`;
break;
case "2":
type_output = `__MSG_customPrompts_add_to_menu_composing__`;
break;
}
let action_output = '';
switch(String(values.action)){
case "0":
action_output = `__MSG_customPrompts_close_button__`;
break;
case "1":
action_output = `__MSG_customPrompts_do_reply__`;
break;
case "2":
action_output = `__MSG_customPrompts_substitute_text__`;
break;
}
//console.log('>>>>>>>>>>>>> action_output: ' + JSON.stringify(action_output));
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
<td class="w08"><span class="name name_show"></span><input type="text" class="hiddendata name_output" value="` + values.name + `" /></td>
<td class="w40"><span class="text text_show"></span><textarea class="hiddendata text_output">` + values.text + `</textarea></td>
<td class="w08"><span class="type_show">` + type_output + `</span>
<select class="type_output hiddendata">
<option value="0"` + ((values.type == "0") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_always__</option>
<option value="1"` + ((values.type == "1") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_reading__</option>
<option value="2"` + ((values.type == "2") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_composing__</option>
</select>` +
`<span class="type hiddendata"></span>
</td>
<td class="w17">
Action: <span class="action_show">` + action_output + `</span>
<select class="action_output hiddendata">
<option value="0"` + ((values.action == "0") ? ' selected':'') + `>__MSG_customPrompts_close_button__</option>
<option value="1"` + ((values.action == "1") ? ' selected':'') + `>__MSG_customPrompts_do_reply__</option>
<option value="2"` + ((values.action == "2") ? ' selected':'') + `>__MSG_customPrompts_substitute_text__</option>
</select>` +
`<span class="action hiddendata"></span>
<br>
<input type="checkbox" class="need_selected" disabled> __MSG_customPrompts_form_label_need_selected__
<br>
<input type="checkbox" class="need_signature" disabled> __MSG_customPrompts_form_label_need_signature__
<br>
<input type="checkbox" class="need_custom_text" disabled> __MSG_customPrompts_form_label_need_custom_text__
<br>
<input type="checkbox" class="enabled input_mod"> __MSG_customPrompts_form_label_enabled__
<span class="is_default hiddendata"></span>
<span class="position_compose hiddendata"></span>
<span class="position_display hiddendata"></span>
</td>
<td>
<button class="btnEditItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnEdit__</button>
<button class="btnCancelItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnCancel__</button>
<br><br>
<button class="btnConfirmItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnOK__</button>
<button class="btnDeleteItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnDelete__</button>
</td>
</tr>`;
//console.log('>>>>>>>> values.name: ' + JSON.stringify(values.name));
positionMax_compose = Math.max(positionMax_compose, values.position_compose);
positionMax_display = Math.max(positionMax_display, values.position_display);
idnumMax = Math.max(idnumMax, values.idnum);
return output;
}
};
let values = await getPrompts();
//console.log('>>>>>>>>>>>>>>>> values: ' + JSON.stringify(values));
promptsList = new List('all_prompts', options, values);
checkSelectedBoxes();
loadPromptsList(values);
const btnSaveAll = document.getElementById('btnSaveAll');
btnSaveAll.disabled = true;
@ -137,15 +58,6 @@ document.addEventListener('DOMContentLoaded', async () => {
}
btnNew.addEventListener('click', handleNewClick);
// Enable save button on input change
function handleInputChange(e) {
e.preventDefault();
setSomethingChanged();
}
document.querySelectorAll('.input_mod').forEach(element => {
element.addEventListener('change', handleInputChange);
});
// Display selected value next to select input
// function handleSelectChange(e) {
// e.preventDefault();
@ -157,123 +69,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// });
// Log data ID number from item row and prepare for edit action
function handleEditClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
e.target.style.display = 'none'; // Edit btn
tr.querySelector('.btnConfirmItem').style.display = 'inline'; // Save btn
tr.querySelector('.btnCancelItem').style.display = 'inline'; // Cancel btn
// tr.querySelector('.btnEditItem').style.display = 'none'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'none'; // Delete btn
showItemRowEditor(tr);
}
let btnEditItem_elements = document.querySelectorAll(".btnEditItem");
btnEditItem_elements.forEach(element => {
element.addEventListener('click', handleEditClick);
});
function showItemRowEditor(tr) {
tr.querySelector('.id_output').style.display = 'inline';
tr.querySelector('.id_show').style.display = 'none';
tr.querySelector('.name_output').style.display = 'inline';
tr.querySelector('.name_show').style.display = 'none';
tr.querySelector('.text_output').style.display = 'inline';
tr.querySelector('.text_show').style.display = 'none';
tr.querySelector('.type_output').style.display = 'inline';
tr.querySelector('.type_show').style.display = 'none';
tr.querySelector('.action_output').style.display = 'inline';
tr.querySelector('.action_show').style.display = 'none';
tr.querySelector('input.need_selected').disabled = false;
tr.querySelector('input.need_signature').disabled = false;
tr.querySelector('input.need_custom_text').disabled = false;
}
function hideItemRowEditor(tr) {
tr.querySelector('.id_output').style.display = 'none';
tr.querySelector('.id_show').style.display = 'inline';
tr.querySelector('.name_output').style.display = 'none';
tr.querySelector('.name_show').style.display = 'inline';
tr.querySelector('.text_output').style.display = 'none';
tr.querySelector('.text_show').style.display = 'inline';
tr.querySelector('.type_output').style.display = 'none';
tr.querySelector('.type_show').style.display = 'inline';
tr.querySelector('.action_output').style.display = 'none';
tr.querySelector('.action_show').style.display = 'inline';
tr.querySelector('input.need_selected').disabled = true;
tr.querySelector('input.need_signature').disabled = true;
tr.querySelector('input.need_custom_text').disabled = true;
}
// Confirm and log deletion action
function handleDeleteClick(e) {
e.preventDefault();
const checkConfirm = window.confirm(browser.i18n.getMessage("customPrompts_btnDelete_confirmText"));
if (!checkConfirm) {
return;
}
const tr = e.target.parentNode.parentNode;
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
promptsList.remove("id", tr.querySelector('span.id').innerText);
setSomethingChanged();
}
let btnDeleteItem_elements = document.querySelectorAll(".btnDeleteItem");
btnDeleteItem_elements.forEach(element => {
element.addEventListener('click', handleDeleteClick);
});
function handleCancelClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
e.target.style.display = 'none'; // Cancel btn
tr.querySelector('.btnConfirmItem').style.display = 'none'; // Save btn
// tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
hideItemRowEditor(tr);
}
let btnCancelItem_elements = document.querySelectorAll(".btnCancelItem");
btnCancelItem_elements.forEach(element => {
element.addEventListener('click', handleCancelClick);
});
function handleConfirmClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
e.target.style.display = 'none'; // Ok btn
// tr.querySelector('.btnConfirmItem').style.display = 'none'; // Ok btn
tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
// Update item data
tr.querySelector('.id_show').innerText = String(tr.querySelector('.id_output').value).toLocaleLowerCase();
tr.querySelector('.name_show').innerText = tr.querySelector('.name_output').value;
tr.querySelector('.text_show').innerText = tr.querySelector('.text_output').value;
tr.querySelector('.type').innerText = tr.querySelector('.type_output').value;
tr.querySelector('.type_show').innerText = tr.querySelector('.type_output').selectedOptions[0].text;
tr.querySelector('.action').innerText = tr.querySelector('.action_output').value;
tr.querySelector('.action_show').innerText = tr.querySelector('.action_output').selectedOptions[0].text;
// the checkboxes update is handled directly by themselves
hideItemRowEditor(tr);
setSomethingChanged();
}
let btnConfirmItem_elements = document.querySelectorAll(".btnConfirmItem");
btnConfirmItem_elements.forEach(element => {
element.addEventListener('click', handleConfirmClick);
});
// Handle checkbox changes and log new state
function handleCheckboxChange(e) {
e.preventDefault();
e.target.setAttribute('checked_val', e.target.checked ? '1' : '0');
//console.log('>>>>>>>> checked_val: ' + e.target.getAttribute('checked_val'));
}
let checkbox_elements = document.querySelectorAll("input[type='checkbox']");
checkbox_elements.forEach(element => {
element.addEventListener('change', handleCheckboxChange);
});
// Handle "type" select changes and log new state
// function handleTypeSelectChange(e) {
// e.preventDefault();
@ -367,8 +163,306 @@ document.addEventListener('DOMContentLoaded', async () => {
});
});
//Import Export
const btnExportAll = document.getElementById('btnExportAll');
btnExportAll.addEventListener('click', (e) => {
e.preventDefault();
exportPrompts();
});
async function exportPrompts() {
const manifest = browser.runtime.getManifest();
const addonVersion = manifest.version;
const outputPrompts = preparePromptsForExport(await getPrompts());
let outputObj = {id: 'thunderai-prompts', addon_version: addonVersion, prompts: outputPrompts};
const blob = new Blob([JSON.stringify(outputObj, null, 2)], {
type: "application/json",
});
const currentDate = new Date();
const time_stamp = `${currentDate.getFullYear()}${String(currentDate.getMonth() + 1).padStart(2, '0')}${String(currentDate.getDate()).padStart(2, '0')}${String(currentDate.getHours()).padStart(2, '0')}${String(currentDate.getMinutes()).padStart(2, '0')}${String(currentDate.getSeconds()).padStart(2, '0')}`;
messenger.downloads.download({
url: URL.createObjectURL(blob),
filename: `thunderai-prompts-${time_stamp}.json`,
saveAs: true,
});
}
const btnImport = document.getElementById('btnImport');
btnImport.addEventListener('click', (e) => {
e.preventDefault();
importPrompts();
});
function importPrompts() {
if(confirm(browser.i18n.getMessage("importPrompts_confirmText") + '\n' + browser.i18n.getMessage("customPrompts_managePrompts_info_default_2") + '\n' + browser.i18n.getMessage("customPrompts_managePrompts_info_default_3"))) {
//ask the user to choose a JSON file, and then read it, check if the serialized JSON is valid as generated from exportPrompts(), and if so, add it to the list
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.click();
input.onchange = async () => {
setMessage(browser.i18n.getMessage('customPrompts_start_import'));
const file = input.files[0];
const reader = new FileReader();
reader.onload = async () => {
const json = reader.result;
try {
const obj = JSON.parse(json);
if(obj.id !== 'thunderai-prompts') {
alert(browser.i18n.getMessage("importPrompts_invalidFile"));
setMessage(browser.i18n.getMessage('importPrompts_invalidFile'),'red');
return;
}
// if(obj.addon_version !== manifest.version) {
// alert(browser.i18n.getMessage("importPrompts_invalidVersion"));
// return;
// }
if(!Array.isArray(obj.prompts)) {
alert(browser.i18n.getMessage("importPrompts_invalidPrompts"));
setMessage(browser.i18n.getMessage('customPrompts_invalidPrompts'),'red');
return;
}
//setCustomPrompts(obj.prompts);
promptsList.clear();
loadPromptsList(await preparePromptsForImport(obj.prompts));
setSomethingChanged();
i18n.updateDocument();
// browser.runtime.sendMessage({command: "reload_menus"});
setMessage(browser.i18n.getMessage('customPrompts_import_completed'), 'orange');
// msgTimeout = setTimeout(() => {
// clearMessage();
// }, 10000);
} catch(err) {
alert(browser.i18n.getMessage("importPrompts_invalidFile") + ' ' + err);
setMessage(browser.i18n.getMessage('importPrompts_invalidFile'),'red');
return;
}
};
reader.readAsText(file);
};
};
}
}, { once: true });
//========= handling an item in a row
function handleEditClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
e.target.style.display = 'none'; // Edit btn
tr.querySelector('.btnConfirmItem').style.display = 'inline'; // Save btn
tr.querySelector('.btnCancelItem').style.display = 'inline'; // Cancel btn
// tr.querySelector('.btnEditItem').style.display = 'none'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'none'; // Delete btn
showItemRowEditor(tr);
}
function showItemRowEditor(tr) {
tr.querySelector('.id_output').style.display = 'inline';
tr.querySelector('.id_show').style.display = 'none';
tr.querySelector('.name_output').style.display = 'inline';
tr.querySelector('.name_show').style.display = 'none';
tr.querySelector('.text_output').style.display = 'inline';
tr.querySelector('.text_show').style.display = 'none';
tr.querySelector('.type_output').style.display = 'inline';
tr.querySelector('.type_show').style.display = 'none';
tr.querySelector('.action_output').style.display = 'inline';
tr.querySelector('.action_show').style.display = 'none';
tr.querySelector('input.need_selected').disabled = false;
tr.querySelector('input.need_signature').disabled = false;
tr.querySelector('input.need_custom_text').disabled = false;
}
function hideItemRowEditor(tr) {
tr.querySelector('.id_output').style.display = 'none';
tr.querySelector('.id_show').style.display = 'inline';
tr.querySelector('.name_output').style.display = 'none';
tr.querySelector('.name_show').style.display = 'inline';
tr.querySelector('.text_output').style.display = 'none';
tr.querySelector('.text_show').style.display = 'inline';
tr.querySelector('.type_output').style.display = 'none';
tr.querySelector('.type_show').style.display = 'inline';
tr.querySelector('.action_output').style.display = 'none';
tr.querySelector('.action_show').style.display = 'inline';
tr.querySelector('input.need_selected').disabled = true;
tr.querySelector('input.need_signature').disabled = true;
tr.querySelector('input.need_custom_text').disabled = true;
}
// Confirm and log deletion action
function handleDeleteClick(e) {
e.preventDefault();
const checkConfirm = window.confirm(browser.i18n.getMessage("customPrompts_btnDelete_confirmText"));
if (!checkConfirm) {
return;
}
const tr = e.target.parentNode.parentNode;
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
promptsList.remove("id", tr.querySelector('span.id').innerText);
setSomethingChanged();
}
function handleCancelClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
e.target.style.display = 'none'; // Cancel btn
tr.querySelector('.btnConfirmItem').style.display = 'none'; // Save btn
// tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
hideItemRowEditor(tr);
}
function handleConfirmClick(e) {
e.preventDefault();
const tr = e.target.parentNode.parentNode;
e.target.style.display = 'none'; // Ok btn
// tr.querySelector('.btnConfirmItem').style.display = 'none'; // Ok btn
tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
// Update item data
tr.querySelector('.id_show').innerText = String(tr.querySelector('.id_output').value).toLocaleLowerCase();
tr.querySelector('.name_show').innerText = tr.querySelector('.name_output').value;
tr.querySelector('.text_show').innerText = tr.querySelector('.text_output').value;
tr.querySelector('.type').innerText = tr.querySelector('.type_output').value;
tr.querySelector('.type_show').innerText = tr.querySelector('.type_output').selectedOptions[0].text;
tr.querySelector('.action').innerText = tr.querySelector('.action_output').value;
tr.querySelector('.action_show').innerText = tr.querySelector('.action_output').selectedOptions[0].text;
// the checkboxes update is handled directly by themselves
hideItemRowEditor(tr);
setSomethingChanged();
}
// Handle checkbox changes and log new state
function handleCheckboxChange(e) {
e.preventDefault();
e.target.setAttribute('checked_val', e.target.checked ? '1' : '0');
//console.log('>>>>>>>> checked_val: ' + e.target.getAttribute('checked_val'));
}
// Enable save button on input change
function handleInputChange(e) {
e.preventDefault();
setSomethingChanged();
}
//========= handling an item in a row - END
function loadPromptsList(values){
let options = {
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'} ],
item: function(values) {
let type_output = '';
switch(String(values.type)){
case "0":
type_output = `__MSG_customPrompts_add_to_menu_always__`;
break;
case "1":
type_output = `__MSG_customPrompts_add_to_menu_reading__`;
break;
case "2":
type_output = `__MSG_customPrompts_add_to_menu_composing__`;
break;
}
let action_output = '';
switch(String(values.action)){
case "0":
action_output = `__MSG_customPrompts_close_button__`;
break;
case "1":
action_output = `__MSG_customPrompts_do_reply__`;
break;
case "2":
action_output = `__MSG_customPrompts_substitute_text__`;
break;
}
//console.log('>>>>>>>>>>>>> action_output: ' + JSON.stringify(action_output));
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
<td class="w08"><span class="name name_show"></span><input type="text" class="hiddendata name_output" value="` + values.name + `" /></td>
<td class="w40"><span class="text text_show"></span><textarea class="hiddendata text_output">` + values.text + `</textarea></td>
<td class="w08"><span class="type_show">` + type_output + `</span>
<select class="type_output hiddendata">
<option value="0"` + ((values.type == "0") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_always__</option>
<option value="1"` + ((values.type == "1") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_reading__</option>
<option value="2"` + ((values.type == "2") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_composing__</option>
</select>` +
`<span class="type hiddendata"></span>
</td>
<td class="w17">
Action: <span class="action_show">` + action_output + `</span>
<select class="action_output hiddendata">
<option value="0"` + ((values.action == "0") ? ' selected':'') + `>__MSG_customPrompts_close_button__</option>
<option value="1"` + ((values.action == "1") ? ' selected':'') + `>__MSG_customPrompts_do_reply__</option>
<option value="2"` + ((values.action == "2") ? ' selected':'') + `>__MSG_customPrompts_substitute_text__</option>
</select>` +
`<span class="action hiddendata"></span>
<br>
<input type="checkbox" class="need_selected" disabled> __MSG_customPrompts_form_label_need_selected__
<br>
<input type="checkbox" class="need_signature" disabled> __MSG_customPrompts_form_label_need_signature__
<br>
<input type="checkbox" class="need_custom_text" disabled> __MSG_customPrompts_form_label_need_custom_text__
<br>
<input type="checkbox" class="enabled input_mod"> __MSG_customPrompts_form_label_enabled__
<span class="is_default hiddendata"></span>
<span class="position_compose hiddendata"></span>
<span class="position_display hiddendata"></span>
</td>
<td>
<button class="btnEditItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnEdit__</button>
<button class="btnCancelItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnCancel__</button>
<br><br>
<button class="btnConfirmItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnOK__</button>
<button class="btnDeleteItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnDelete__</button>
</td>
</tr>`;
//console.log('>>>>>>>> values.name: ' + JSON.stringify(values.name));
positionMax_compose = Math.max(positionMax_compose, values.position_compose);
positionMax_display = Math.max(positionMax_display, values.position_display);
idnumMax = Math.max(idnumMax, values.idnum);
return output;
}
};
promptsList = new List('all_prompts', options, values);
checkSelectedBoxes();
let btnEditItem_elements = document.querySelectorAll(".btnEditItem");
btnEditItem_elements.forEach(element => {
element.addEventListener('click', handleEditClick);
});
let btnDeleteItem_elements = document.querySelectorAll(".btnDeleteItem");
btnDeleteItem_elements.forEach(element => {
element.addEventListener('click', handleDeleteClick);
});
let btnCancelItem_elements = document.querySelectorAll(".btnCancelItem");
btnCancelItem_elements.forEach(element => {
element.addEventListener('click', handleCancelClick);
});
let btnConfirmItem_elements = document.querySelectorAll(".btnConfirmItem");
btnConfirmItem_elements.forEach(element => {
element.addEventListener('click', handleConfirmClick);
});
let checkbox_elements = document.querySelectorAll("input[type='checkbox']");
checkbox_elements.forEach(element => {
element.addEventListener('change', handleCheckboxChange);
});
document.querySelectorAll('.input_mod').forEach(element => {
element.addEventListener('change', handleInputChange);
});
}
function checkFields() {
//console.log('>>>>>>>>>>>>> typeof promptsList: ' + typeof promptsList);

View file

@ -155,6 +155,7 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
style.textContent += "#mzta-custom_loading{height:50px;display:none;}";
style.textContent += "#mzta-custom_textarea{color:black;padding:1px;font-size:15px;width:100%;}";
style.textContent += "#mzta-custom_info{text-align:center;width:100%;padding-bottom:10px;font-size:15px;}";
style.textContent += "#mzta-prompt-name{font-size:13px;font-style:italic;color:#919191;position:fixed;bottom:75px;;left:0;padding-left:5px;}";
// Add <style> to the page's <head>
document.head.appendChild(style);
@ -179,6 +180,13 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
force_go = true;
};
modelWarnDiv.appendChild(btn_gpt35);
//prompt name
var prompt_name_div = document.createElement('div');
prompt_name_div.id = 'mzta-prompt-name';
prompt_name_div.title= browser.i18n.getMessage("currently_used_prompt");
prompt_name_div.textContent = mztaPromptName;
fixedDiv.appendChild(prompt_name_div);
//status page
var status_page_div = document.createElement('div');

View file

@ -93,14 +93,25 @@ export class mzta_Menus {
}
//open chatgpt window
//console.log("Click menu item...");
let prefs = await browser.storage.sync.get({default_chatgpt_lang: getLanguageDisplayName(browser.i18n.getUILanguage())});
let prefs = await browser.storage.sync.get({default_chatgpt_lang: ''});
let chatgpt_lang = prefs.default_chatgpt_lang;
//console.log(" >>>>>>>>>>>> chatgpt_lang: " + chatgpt_lang);
if(chatgpt_lang === ''){
chatgpt_lang = 'Reply in the language it is written.';
}else{
chatgpt_lang = browser.i18n.getMessage("prompt_lang") + " " + chatgpt_lang + ".";
}
var fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await this.getDefaultSignature():"") + " " + browser.i18n.getMessage("prompt_lang") + chatgpt_lang + ". \"" + body_text + "\" ";
var fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await this.getDefaultSignature():"") + " " + chatgpt_lang + " \"" + body_text + "\" ";
switch(curr_prompt.id){
case 'prompt_translate_this':
fullPrompt = curr_prompt.text + chatgpt_lang + ". \"" + body_text + "\" ";
let prefs2 = await browser.storage.sync.get({default_chatgpt_lang: getLanguageDisplayName(browser.i18n.getUILanguage())});
let chatgpt_lang2 = prefs2.default_chatgpt_lang;
if(chatgpt_lang2 === ''){
chatgpt_lang2 = getLanguageDisplayName(browser.i18n.getUILanguage());
}
fullPrompt = curr_prompt.text + chatgpt_lang2 + ". \"" + body_text + "\" ";
break;
case 'prompt_reply':
fullPrompt += "Do not add the subject line to the response."
@ -112,7 +123,7 @@ export class mzta_Menus {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
// add custom text if needed
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.need_custom_text);
this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.name, curr_prompt.need_custom_text);
};
this.rootMenu.push(curr_menu_entry);
};

View file

@ -153,6 +153,37 @@ export async function getPrompts(onlyEnabled = false){
return output;
}
export function preparePromptsForExport(prompts){
let output = [...prompts];
output.forEach(prompt => {
if(prompt.is_default == 1){
Object.keys(prompt).forEach(key => {
if(!['id', 'enabled', 'position_compose', 'position_display'].includes(key)){
delete prompt[key];
}
})
}else{
delete prompt['idnum'];
}
});
return output;
}
export async function preparePromptsForImport(prompts){
const output = await getPrompts();
prompts.forEach(prompt => {
if(output.some(p => p.id == prompt.id)){
Object.keys(prompt).forEach(key => {
output.find(p => p.id == prompt.id)[key] = prompt[key];
})
}else{
output.push(prompt);
}
});
output.sort((a, b) => a.id.localeCompare(b.id));
//console.log(">>>>>>>>>>> preparePromptsForImport: " + JSON.stringify(output));
return output;
}
async function getDefaultPrompts_withProps() {
let prefs = await browser.storage.sync.get({_default_prompts_properties: null}); //production

View file

@ -134,4 +134,16 @@ const insertHtml = function (replyHtml, fullBody_string) {
});
fullBody.body.insertBefore(fragment, fullBody.body.firstChild);
return fullBody.body.innerHTML;
}
export function i18nConditionalGet(str) {
// if we are getting a string that starts with '__MSG_' and ends with '__' we return the translated string
// using the browser.i18n API
// else we return the original string
// Check if the string starts with '__MSG_' and ends with '__'
if (str.startsWith('__MSG_') && str.endsWith('__')) {
// Remove '__MSG_' from the beginning and '__' from the end
return browser.i18n.getMessage(str.substring(6, str.length - 2));
}
return str; // Return the original string if the conditions are not met
}

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "ThunderAI",
"description": "__MSG_extensionDescription__",
"version": "1.1.4",
"version": "1.2.0",
"author": "Mic (m@micz.it)",
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
"browser_specific_settings": {
@ -30,6 +30,7 @@
"tabs",
"activeTab",
"accountsRead",
"downloads",
"<all_urls>"
],
"background": {

View file

@ -19,7 +19,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 { getCurrentIdentity, getOriginalBody, replaceBody, setBody } from './js/mzta-utils.js';
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet } from './js/mzta-utils.js';
var createdWindowID = null;
var original_html = '';
@ -50,9 +50,9 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
// handler function.
if (message && message.hasOwnProperty("command")){
switch (message.command) {
case 'chatgpt_open':
openChatGPT(message.prompt,message.action,message.tabId);
return true;
// case 'chatgpt_open':
// openChatGPT(message.prompt,message.action,message.tabId);
// return true;
case 'chatgpt_close':
browser.windows.remove(createdWindowID).then(() => {
console.log("[ThunderAI] ChatGPT window closed successfully.");
@ -136,7 +136,7 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
});
async function openChatGPT(promptText, action, curr_tabId, do_custom_text = 0) {
async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_custom_text = 0) {
let prefs = await browser.storage.sync.get(prefs_default);
prefs = checkScreenDimensions(prefs);
//console.log(">>>>>>>>>>>>>>>> prefs: " + JSON.stringify(prefs));
@ -181,6 +181,7 @@ async function openChatGPT(promptText, action, curr_tabId, do_custom_text = 0) {
let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`";
let mztaDoCustomText=`+ do_custom_text +`;
let mztaKeepFormatting=`+ prefs.chatgpt_keep_formatting +`;
let mztaPromptName="[`+ i18nConditionalGet(prompt_name) +`]";
`;
browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false })

View file

@ -21,4 +21,5 @@ export const prefs_default = {
chatgpt_win_width: 700,
chatpgt_use_gpt4: false,
chatgpt_keep_formatting: false,
default_chatgpt_lang: '',
}

View file

@ -61,7 +61,7 @@ function restoreOptions() {
break;
case 'text':
let default_text_value = '';
if(element.id == 'default_chatgpt_lang') default_text_value = getLanguageDisplayName(browser.i18n.getUILanguage());
if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang;
element.value = result[element.id] || default_text_value;
break;
default:

View file

@ -7,11 +7,17 @@
<body>
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
<h2>Version 1.2.0 - ??/07/2024</h2>
<ul>
<li>If no default language is set in the options, the language present in the text sent to ChatGPT will be used [<a href="https://github.com/micz/ThunderAI/issues/53">#53</a>].</li>
<li>Added the functionality to import and export custom prompts [<a href="https://github.com/micz/ThunderAI/issues/65">#65</a>].</li>
<li>Showing the currently used prompt name in the ChatGPT window [<a href="https://github.com/micz/ThunderAI/issues/20">#20</a>].</li>
</ul>
<h2>Version 1.1.4 - 28/06/2024</h2>
<ul>
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>
<li>Improve the message warning to choose the right model [<a href="https://github.com/micz/ThunderAI/issues/76">#76</a>].</li>
<li>Added a description in the Custom Prompts page about default prompts [<a href="https://github.com/micz/ThunderAI/issues/74">#74</a>].</li>
<li>Improve the message warning to choose the right model [<a href="https://github.com/micz/ThunderAI/issues/76">#76</a>].</li>
<li>Added a description in the Custom Prompts page about default prompts [<a href="https://github.com/micz/ThunderAI/issues/74">#74</a>].</li>
<li>Added a link to the <a href="https://micz.it/thunderbird-addon-thunderai/translate/">translate page</a> in the options window [<a href="https://github.com/micz/ThunderAI/issues/68">#68</a>].</li>
</ul>
<h2>Version 1.1.3 - 18/06/2024</h2>