#language = "v8"
#title = ""
#tooltip = "AI"
#icon = "AI.ico"
// Do not edit this file.  If you want to edit the file, please copy it into another folder.
// This file will be replaced with a newer version every time EmEditor is upgraded.
//
// This macro requires an OpenAI API key and EmEditor Professional version 23.1.907 or later.
// OpenAI API                         https://openai.com/blog/openai-api
//
// To find an OpenAI API key, please see:
// Where do I find my OpenAI API Key? https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
//
// To save an OpenAI key as an environment variable "OPENAI_API_KEY", please see:
// Best Practices for API Key Safety  https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
//
// Choose from "gpt-3.5-turbo", "gpt-4", and "gpt-4-turbo-preview"
// OpenAI Models                      https://platform.openai.com/docs/models/overview
//const sModel = "gpt-4o";

const MaxRecent = 30;
const nMaxUserQuestionLen = 65530;
const sRegEntry = "AI.jsee";
var sModel = "";

/**
 * Sends prompt to OpenAI and returns the response.
 * Docs: https://platform.openai.com/docs/guides/text-generation/chat-completions-api?lang=curl
 * @param {string} endpoint URL for request
 * @param {string} apiKey API key
 * @param {string} messageContent The prompt
 * @returns {string} The text content of the response
 */
async function callOpenAI(endpoint, apiKey, messageContent, systemContent) {
	const response = await fetch(
		endpoint,
		{
			method: "POST",
			headers: {
				"Authorization": `Bearer ${apiKey}`,
				"content-type": "application/json",
			},
			body: JSON.stringify({
				"model": sModel,
				"messages": [
					{
						"role": "system",
						"content": systemContent,
					},
					{
						"role": "user",
						"content": messageContent,
					}
				],
			}),
		}
	);

	if (!response.ok) {
		alert(await response.text());
		Quit();
	}

	const responseObj = await response.json();
	if (responseObj.choices.length === 0) {
		alert("choices length is 0");
		Quit();
	}

	// Get content of first choice
	return responseObj.choices.pop().message.content;
}

/**
 * Generates image using Dall-E
 * @param {string} endpoint URL for request
 * @param {string} apiKey API key
 * @param {string} promptStr Image prompt
 * @returns {string} URL of generated image
 */
async function callDalle(endpoint, apiKey, promptStr) {
	const payload = {
		method: "POST",
		headers: {
			"Authorization": `Bearer ${apiKey}`,
			"content-type": "application/json",
		},
		body: JSON.stringify({
			"model": "dall-e-3",
			"prompt": promptStr,
			"style": "natural",
		}),
	};

	const response = await fetch(endpoint, payload);

	if (!response.ok) {
		alert(await response.text());
		Quit();
	}

	const responseObj = await response.json();
	if (responseObj.data.length === 0) {
		alert("data array length is 0");
		Quit();
	}

	return responseObj.data[0].url;
}

function WriteRecent(asRecent) {
	const nMax = asRecent.length;
	if( nMax > MaxRecent ) {
		nMax = MaxRecent;
	}
	editor.WriteProfileInt(eeRegEmEditorUsers, sRegEntry, "NumOfRecent", nMax);
	for (var i = 0; i < nMax; ++i) {
		editor.WriteProfileString(eeRegEmEditorUsers, sRegEntry, "Recent" + i, asRecent[i]);
	}
}

function detectNewlineType(str) {
    if (str.indexOf('\r\n') >= 0) {
        return '\r\n'; // Windows style
    } else if (str.indexOf('\r') >= 0) {
        return '\r'; // Old Mac style
    } else if (str.indexOf('\n') >= 0) {
        return '\n'; // Unix/Linux style
    } else {
        return '\r\n'; // No newline characters found
    }
}

function makeNewlinesConsistent(sText, sNL) {
    // Replace all variations of newlines with the specified newline type
    const cleanedText = sText.replace(/\r\n|\r|\n/g, sNL);
    return cleanedText;
}

async function main() {
	let asCommands = [];
	let asTranslate = [];
	let sOtherLang = "";
	let sWriteQuestion = "";
	let sWriteThrough = "";
	let sGenerateImage = "";
	let sWaiting = "";
	let sAskKey = "";
	let sSettings = "";
	let sDeleteRecent = "";
	let sAll = "";
	let sWriteSystemRole = "";
	let sDeleteSystemRole = "";
	const ID_AskQuestion = 10000;
	const ID_GenerateImage = 10001;
	const ID_WriteThrough = 10002;
	const ID_SystemRole = 10003;
	const ID_DeleteRecent = 10004;

	switch (editor.LangID) {
		case 1041:  // Japanese
			asCommands = ["校正してください", "意味を調べてください", "要約してください", "間違いや問題を見つけてください", "日本語に翻訳してください", "英語に翻訳してください"];
			asTranslate = ["チェコ語に翻訳してください", "オランダ語に翻訳してください", "フランス語に翻訳してください", "ドイツ語に翻訳してください", "イタリア語に翻訳してください", "スペイン語に翻訳してください", "韓国語に翻訳してください", "簡体字中国語に翻訳してください", "繁体字中国語に翻訳してください", "ロシア語に翻訳してください"];
			sOtherLang = "その他の言語";
			sWriteQuestion = "質問を指定";
			sWriteThrough = "このテキストをそのまま送る";
			sGenerateImage = "画像を生成";
			sWaiting = "OpenAI API からのレスポンスを待っています。しばらくお待ちください...";
			sAskKey = 'OpenAI API キーを入力します。 (次回このプロンプトをスキップするには、環境変数「OPENAI_API_KEY」を設定します)';
			sSettings = '設定';
			sDeleteRecent = '最近の質問を削除';
			sAll = 'すべて';
			sWriteSystemRole = 'system role を書いてください';
			sDeleteSystemRole = 'system role を削除しますか?';
			break;

		case 1029:  // Czech
			asCommands = ["Kontrola", "Vyhledat", "Shrnout", "Najděte chyby nebo problémy", "Přeložit do japonštiny", "Přeložit do angličtiny"]
			asTranslate = ["Přeložit do češtiny", "Přeložit do holandštiny", "Přeložit do francouzštiny", "Přeložit do němčiny", "Přeložit do italštiny", "Přeložit do španělštiny", "Přeložit do korejštiny", "Přeložit do zjednodušené čínštiny", "Přeložit do tradiční čínštiny", "Přeložit do ruštiny"];
			sOtherLang = "Jiné jazyky";
			sWriteQuestion = "Napište otázku";
			sWriteThrough = "Pošlete tento text tak, jak je";
			sGenerateImage = "Vygenerujte obrázek";
			sWaiting = "Čekání na odpověď od OpenAI API. Prosím, čekejte...";
			sAskKey = 'Zadejte svůj klíč OpenAI API. (Nastavte proměnnou prostředí "OPENAI_API_KEY", abyste tuto výzvu příště přeskočili)';
			sSettings = 'Nastavení';
			sDeleteRecent = 'Smazat nedávné otázky';
			sAll = 'Všechno';
			sWriteSystemRole = 'Napište systémovou roli';
			sDeleteSystemRole = 'Smazat systémovou roli?';
			break;

		case 1031:  // German
			asCommands = ["Korrekturlesen", "Nachschlagen", "Zusammenfassen", "Finden Sie Fehler oder Probleme", "Ins Japanische übersetzen", "Ins Englische übersetzen"]
			asTranslate = ["Übersetzen ins Tschechische", "Übersetzen ins Niederländische", "Übersetzen ins Französische", "Übersetzen ins Deutsche", "Übersetzen ins Italienische", "Übersetzen ins Spanische", "Übersetzen ins Koreanische", "Übersetzen ins vereinfachte Chinesisch", "Ins traditionelle Chinesisch übersetzen", "Ins Russische übersetzen"];
			sOtherLang = "Andere Sprachen";
			sWriteQuestion = "Schreiben Sie eine Frage";
			sWriteThrough = "Senden Sie diesen Text unverändert";
			sGenerateImage = "Erzeugen Sie ein Bild";
			sWaiting = "Warten auf eine Antwort von der OpenAI-API. Bitte warten...";
			sAskKey = 'Geben Sie Ihren OpenAI-API-Schlüssel ein. (Setzen Sie die Umgebungsvariable „OPENAI_API_KEY“, um diese Eingabeaufforderung das nächste Mal zu überspringen.)';
			sSettings = 'Einstellungen';
			sDeleteRecent = 'Löschen Sie kürzlich gestellte Fragen';
			sAll = 'Alle';
			sWriteSystemRole = 'Schreiben Sie eine Systemrolle';
			sDeleteSystemRole = 'Systemrolle löschen?';
			break;

		case 1036:  // French
			asCommands = ["Relire", "Rechercher", "Résumer", "Rechercher des erreurs ou des problèmes", "Traduire en japonais", "Traduire en anglais"]
			asTranslate = ["Traduire en tchèque", "Traduire en néerlandais", "Traduire en français", "Traduire en allemand", "Traduire en italien", "Traduire en espagnol", "Traduire en coréen", "Traduire en chinois simplifié", " Traduire en chinois traditionnel", "Traduire en russe"];
			sOtherLang = "Autres langues";
			sWriteQuestion = "Écrivez une question";
			sWriteThrough = "Envoyez ce texte tel quel";
			sGenerateImage = "Générer une image";
			sWaiting = "En attente d'une réponse de l'API OpenAI. S'il vous plaît, attendez...";
			sAskKey = "Entrez votre clé API OpenAI. (Définissez la variable d'environnement \"OPENAI_API_KEY\" pour ignorer cette invite la prochaine fois)";
			sSettings = 'Paramètres';
			sDeleteRecent = 'Supprimer les questions récentes';
			sAll = 'Toute';
			sWriteSystemRole = 'Écrire un rôle système';
			sDeleteSystemRole = 'Supprimer le rôle système ?';
			break;

		case 3082:  // Spanish
		case 2058:  // Spanish
			asCommands = ["Revisar", "Buscar", "Resumir", "Encuentra errores o problemas", "Traducir al japonés", "Traducir al inglés"]
			asTranslate = ["Traducir al checo", "Traducir al holandés", "Traducir al francés", "Traducir al alemán", "Traducir al italiano", "Traducir al español", "Traducir al coreano", "Traducir al chino simplificado", " Traducir al chino tradicional", "Traducir al ruso"];
			sOtherLang = "Otros idiomas";
			sWriteQuestion = "Escribe una pregunta";
			sWriteThrough = "Envía este texto tal cual";
			sGenerateImage = "Generar una imagen";
			sWaiting = "Esperando una respuesta de la API OpenAI. Espere por favor...";
			sAskKey = 'Ingrese su clave API de OpenAI. (Establezca la variable de entorno "OPENAI_API_KEY" para omitir este mensaje la próxima vez)';
			sSettings = 'Ajustes';
			sDeleteRecent = 'Eliminar preguntas recientes';
			sAll = 'Toda';
			sWriteSystemRole = 'Escribir un rol del sistema';
			sDeleteSystemRole = '¿Eliminar el rol del sistema?';
			break;

		case 1040:  // Italian
			asCommands = ["Rileggi", "Cerca", "Riassumi", "Trova errori o problemi", "Traduci in giapponese", "Traduci in inglese"]
			asTranslate = ["Traduci in ceco", "Traduci in olandese", "Traduci in francese", "Traduci in tedesco", "Traduci in italiano", "Traduci in spagnolo", "Traduci in coreano", "Traduci in cinese semplificato", " Traduci in cinese tradizionale", "Traduci in russo"];
			sOtherLang = "Altre lingue";
			sWriteQuestion = "Scrivi una domanda";
			sWriteThrough = "Invia questo testo così com'è";
			sGenerateImage = "Genera un'immagine";
			sWaiting = "In attesa di una risposta dall'API OpenAI. Attendere prego...";
			sAskKey = 'Inserisci la tua chiave API OpenAI. (Imposta la variabile di ambiente "OPENAI_API_KEY" per saltare questo messaggio la prossima volta)';
			sSettings = 'Impostazioni';
			sDeleteRecent = 'Elimina le domande recenti';
			sAll = 'Tutto';
			sWriteSystemRole = 'Scrivi un ruolo di sistema';
			sDeleteSystemRole = 'Eliminare il ruolo di sistema?';
			break;

		case 1042:  // Korean
			asCommands = ["교정", "찾아보기", "요약", "실수나 문제점 찾기", "일본어로 번역", "영어로 번역"]
			asTranslate = ["체코어로 번역", "네덜란드어로 번역", "프랑스어로 번역", "독일어로 번역", "이탈리아어로 번역", "스페인어로 번역", "한국어로 번역", "중국어(간체)로 번역", "중국어(번체)로 번역", "러시아어로 번역"];
			sOtherLang = "다른 언어";
			sWriteQuestion = "질문을 작성하세요";
			sWriteThrough = "이 문자를 있는 그대로 보내세요";
			sGenerateImage = "이미지 생성";
			sWaiting = "OpenAI API의 응답을 기다리는 중입니다. 기다리세요...";
			sAskKey = 'OpenAI API 키를 입력하세요. (다음번에 이 프롬프트를 건너뛰려면 환경 변수 "OPENAI_API_KEY"를 설정하십시오)';
			sSettings = '환경 설정';
			sDeleteRecent = '최근 질문 삭제하기';
			sAll = '모두';
			sWriteSystemRole = '시스템 역할 작성';
			sDeleteSystemRole = '시스템 역할을 삭제하시겠습니까?';
			break;

		case 1043:  // Dutch
			asCommands = ["Proeflezen", "Opzoeken", "Samenvatten", "Vind fouten of problemen", "Vertalen naar het Japans", "Vertalen naar het Engels"]
			asTranslate = ["Vertalen naar het Tsjechisch", "Vertalen naar het Nederlands", "Vertalen naar het Frans", "Vertalen naar het Duits", "Vertalen naar het Italiaans", "Vertalen naar het Spaans", "Vertalen naar het Koreaans", "Vertalen naar Vereenvoudigd Chinees", "Vertalen in traditioneel Chinees", "Vertalen in het Russisch"];
			sOtherLang = "Andere talen";
			sWriteQuestion = "Schrijf een vraag";
			sWriteThrough = "Verzend deze tekst zoals deze is";
			sGenerateImage = "Genereer een afbeelding";
			sWaiting = "Wachten op een reactie van OpenAI API. Even geduld aub...";
			sAskKey = 'Voer uw OpenAI API-sleutel in. (Stel omgevingsvariabele "OPENAI_API_KEY" in om deze prompt de volgende keer over te slaan)';
			sSettings = 'Instellingen';
			sDeleteRecent = 'Verwijder recente vragen';
			sAll = 'Alle';
			sWriteSystemRole = 'Schrijf een systeemrol';
			sDeleteSystemRole = 'De systeemrol verwijderen?';
			break;

		case 2052:  // Simplified Chinese
			asCommands = ["校对", "查找", "总结", "查找错误或问题", "翻译成日语", "翻译成英语"]
			asTranslate = ["翻译成捷克语", "翻译成荷兰语", "翻译成法语", "翻译成德语", "翻译成意大利语", "翻译成西班牙语", "翻译成韩语", "翻译成简体中文", "翻译成繁体中文", "翻译成俄语"];
			sOtherLang = "其他语言";
			sWriteQuestion = "提出問題";
			sWriteThrough = "按原样发送此文本";
			sGenerateImage = "生成圖像";
			sWaiting = "等待 OpenAI API 的响应。请稍等...";
			sAskKey = '输入您的 OpenAI API 密钥。 （设置环境变量“OPENAI_API_KEY”下次跳过此提示）';
			sSettings = '设置';
			sDeleteRecent = '删除最近的问题';
			sAll = '全部';
			sWriteSystemRole = '编写系统角色';
			sDeleteSystemRole = '删除系统角色？';
			break;

		case 1028:  // Traditional Chinese
			asCommands = ["校對", "尋找", "總結", "尋找錯誤或問題", "翻譯成日語", "翻譯成英語"]
			asTranslate = ["翻譯成捷克語", "翻譯成荷蘭語", "翻譯成法語", "翻譯成德語", "翻譯成意大利語", "翻譯成西班牙語", "翻譯成韓語", "翻譯成簡體中文", "翻譯成繁體中文", "翻譯成俄語"];
			sOtherLang = "其他語言";
			sWriteQuestion = "提出問題";
			sWriteThrough = "按原樣發送此文本";
			sGenerateImage = "生成图像";
			sWaiting = "等待 OpenAI API 的響應。請稍等...";
			sAskKey = '輸入您的 OpenAI API 金鑰。 （設定環境變數“OPENAI_API_KEY”下次跳過此提示）';
			sSettings = '設定';
			sDeleteRecent = '刪除最近的問題';
			sAll = '全部';
			sWriteSystemRole = '編寫系統角色';
			sDeleteSystemRole = '刪除系統角色？';
			break;

		default:  // English
			asCommands = ["Proofread", "Look up", "Summarize", "Find mistakes or issues", "Translate into Japanese", "Translate into English"]
			asTranslate = ["Translate into Czech", "Translate into Dutch", "Translate into French", "Translate into German", "Translate into Italian", "Translate into Spanish", "Translate into Korean", "Translate into Simplified Chinese", "Translate into Traditional Chinese", "Translate into Russian"];
			sOtherLang = "Other languages";
			sWriteQuestion = "Write a question";
			sWriteThrough = "Send this text as is";
			sGenerateImage = "Generate an image";
			sWaiting = "Waiting for a response from OpenAI API. Please wait...";
			sAskKey = "Enter your OpenAI API key. (Set environment variable \"OPENAI_API_KEY\" to skip this prompt next time)";
			sSettings = 'Settings';
			sDeleteRecent = 'Delete recent questions';
			sAll = 'All';
			sWriteSystemRole = 'Write a system role';
			sDeleteSystemRole = 'Delete the system role?';
			break;
	}

	if( !editor.GetProfileInt( eeRegCommon, "", "EnableAI", 0 ) ) {
		editor.ExecuteCommandByID( 9071 );  // Customize AI
		Quit();
	}
	const bUseEnv = editor.GetProfileInt( eeRegCommon, "", "UseEnvOpenAI", 1 );
	let apiKey = "";
	if( bUseEnv ) {
		apiKey = shell.GetEnv("OPENAI_API_KEY");
	}
	else {
		apiKey = editor.GetProfileString( eeRegCommon, "", "OpenAIKey", "" );
	}
	sModel = editor.GetProfileString( eeRegCommon, "", "OpenAIModel", "gpt-4o" );
	if (apiKey.length === 0 || sModel.length === 0) {
		editor.ExecuteCommandByID( 9071 );  // Customize AI
		Quit();
	}

	let sText = document.selection.Text;
	if (sText.length === 0) {
		let nLines = document.GetLines();
		for (var i = 1; i <= nLines; ++i) {
			sText += document.GetLine(i, eeGetLineWithNewLines);
		}
	}

	const menu = CreatePopupMenu();
	const submenu = CreatePopupMenu();
	var nID = 0;
	for (var i = 0; i < asCommands.length; ++i) {
		menu.Add(asCommands[i], ++nID);
	}
	menu.Add("", 0, eeMenuSeparator);
	for (var i = 0; i < asTranslate.length; ++i) {
		submenu.Add(asTranslate[i], ++nID);
	}
	menu.AddPopup(sOtherLang + "...", submenu);
	menu.Add("", 0, eeMenuSeparator);
	menu.Add(sWriteThrough, ID_WriteThrough);
	menu.Add("", 0, eeMenuSeparator);
	menu.Add(sGenerateImage, ID_GenerateImage);
	menu.Add("", 0, eeMenuSeparator);
	menu.Add(sWriteQuestion + "...", 10000);

	asRecent = new Array();
	let nMax = editor.GetProfileInt(eeRegEmEditorUsers, sRegEntry, "NumOfRecent", 0);
	for (var i = 0; i < nMax; ++i) {
		let s = editor.GetProfileString(eeRegEmEditorUsers, sRegEntry, "Recent" + i, "");
		if (s.length !== 0) {
			asRecent.push(s);
		}
	}

	const nMinUserQuesitonID = nID + 1;
	if (asRecent.length !== 0) {
		menu.Add("", 0, eeMenuSeparator);
		for (var i = 0; i < asRecent.length; ++i) {
			menu.Add(asRecent[i], ++nID);
		}
	}
	const nMaxUserQuesitonID = nID + 1;

	menu.Add("", 0, eeMenuSeparator);
	const submenu2 = CreatePopupMenu();
	submenu2.Add(sWriteSystemRole, ID_SystemRole);
	submenu2.Add(sDeleteRecent, ID_DeleteRecent);
	menu.AddPopup(sSettings, submenu2);

	const result = menu.Track(0);

	if (result !== 0) {
		let sSystemRole = editor.GetProfileString(eeRegEmEditorUsers, sRegEntry, "SystemRole", "");
		if (result == ID_SystemRole) {
			let sOrg = sSystemRole;
			sSystemRole = prompt(sWriteSystemRole, sOrg, eePromptMultiline);
			if (sOrg.length !== 0 && sSystemRole.length === 0) {
				let b = confirm(sDeleteSystemRole, "");
				if (!b) {
					Quit();
				}
			}
			if (sOrg != sSystemRole) {
				editor.WriteProfileString(eeRegEmEditorUsers, sRegEntry, "SystemRole", sSystemRole);
			}
			Quit();
		}
		if (result == ID_DeleteRecent) {
			const menu = CreatePopupMenu();
			if (asRecent.length !== 0) {
				for (var i = 0; i < asRecent.length; ++i) {
					menu.Add(asRecent[i], i + 1);
				}
				menu.Add("", 0, eeMenuSeparator);
				menu.Add(sAll, MaxRecent + 1);
			}
			let n = menu.Track(0);
			if (n > 0) {
				if (n == MaxRecent + 1) {
					asRecent.splice(0, asRecent.length);
				}
				else {
					asRecent.splice(n - 1, 1);
				}
			}
			WriteRecent(asRecent);
			Quit();
		}

		let sPrompt = "";
		if (result === ID_AskQuestion) {
			sPrompt = prompt(sWriteQuestion, "", eePromptMultiline);
			if (sPrompt.length > nMaxUserQuestionLen) {
				sPrompt = sPrompt.substr(0, nMaxUserQuestionLen);
			}
		}
		else if (result !== ID_WriteThrough) {
			sPrompt = menu.GetText(result);
		}
		if (sPrompt.length !== 0 || sText.length !== 0) {
			if (result === ID_AskQuestion || (result >= nMinUserQuesitonID && result < nMaxUserQuesitonID)) {
				for (var i = 0; i < asRecent.length; ++i) {
					if (sPrompt == asRecent[i]) {
						asRecent.splice(i, 1);
						break;
					}
				}
				asRecent.unshift(sPrompt);
				WriteRecent(asRecent);
			}

			if (sPrompt.length == 0) {
				sPrompt = sText;
			}
			else if (sText.length !== 0) {
				sPrompt = sPrompt + ":\n" + sText;
			}
			shell.KeepRunning = true;
			status = sWaiting;

			if (result === ID_GenerateImage) {
				const endpoint = "https://api.openai.com/v1/images/generations";
				const url = await callDalle(endpoint, apiKey, sText);
				WebBar.Open(url);
			}
			else {
				const endpoint = "https://api.openai.com/v1/chat/completions";
				const response = await callOpenAI(endpoint, apiKey, sPrompt, sSystemRole);   // Call OpenAI, get response

				if (sText.length === 0) {   // the current document is empty. No need to split or use Output bar
					document.write(response);
				}
				else {
					let sNL = detectNewlineType( sText );   // detect original newlines
					let sCleanedResponse = makeNewlinesConsistent( response, sNL );   // make newlines consistent
					const sOrgPath = document.FullName;
					const bAlreadySplit = (editor.QueryStatusByID(4497) & eeStatusEnabled);  // "Reset to Normal Mode" command is enabled if the window is already split
					if (bAlreadySplit) {
						OutputBar.writeln(sCleanedResponse);
						OutputBar.Visible = true;
					}
					else {
						editor.NewFile();
						document.write(sCleanedResponse);
						const sNewPath = document.FullName;
						if (editor.Compare(eeCompareQuality3 | eeCompareSplitVert | eeIgnoreEncodings, sOrgPath, sNewPath) === eeMatched) {
							alert("The response is identical to the original text.");
							document.Saved = true;
							document.Close();
						}
					}
				}
			}

			status = "";
			shell.KeepRunning = false;
		}
	}
}
main();
